import { request as playwrightRequest } from '@playwright/test' import type { APIRequestContext } from '@playwright/test' const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '[::1]']) const parseHttpUrl = (raw: string, label: string): URL => { let url: URL try { url = new URL(raw) } catch { throw new Error(`${label} must be an absolute HTTP(S) URL`) } if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) { throw new Error(`${label} must be an HTTP(S) URL without embedded credentials`) } return url } const isLoopback = (url: URL): boolean => LOOPBACK_HOSTS.has(url.hostname.toLowerCase()) export const resolveContentControlPlaneBaseUrl = ( environment: NodeJS.ProcessEnv = process.env, ): string => { const applicationUrl = parseHttpUrl( environment.BASE_URL?.trim() || 'http://127.0.0.1:6180', 'BASE_URL', ) const controlPlaneUrl = parseHttpUrl( environment.E2E_API_BASE_URL?.trim() || 'http://127.0.0.1:6100', 'E2E_API_BASE_URL', ) if ( !isLoopback(applicationUrl) && applicationUrl.protocol !== 'https:' && environment.E2E_ALLOW_INSECURE_REMOTE !== '1' ) { throw new Error('Remote BASE_URL must use HTTPS unless E2E_ALLOW_INSECURE_REMOTE=1 explicitly accepts the risk') } if (!isLoopback(controlPlaneUrl) && controlPlaneUrl.protocol !== 'https:') { throw new Error('E2E_API_BASE_URL must use HTTPS unless it targets a loopback host') } if ( isLoopback(applicationUrl) !== isLoopback(controlPlaneUrl) && environment.E2E_ALLOW_CROSS_ENV_CONTROL_PLANE !== '1' ) { throw new Error( 'BASE_URL and E2E_API_BASE_URL cross the local/remote boundary; ' + 'set E2E_ALLOW_CROSS_ENV_CONTROL_PLANE=1 only for an intentional protected tunnel', ) } return controlPlaneUrl.toString().replace(/\/$/, '') } export const openContentControlPlane = async ( baseURL = resolveContentControlPlaneBaseUrl(), ): Promise => playwrightRequest.newContext({ baseURL, timeout: 30_000, // The gateway control plane is expected to be direct. Following a 307/308 // could forward a login body or a manually supplied bearer token elsewhere. maxRedirects: 0, })