import { Page } from '@playwright/test' import { ScriptConfig } from './types' interface SharedOptions { /** unique ID that becomes part of the dynamic page URL */ testId: string /** optional path to append to the dynamic page URL */ path?: string headers?: Record } interface TemplatedResponse { /** string like `` or ScriptConfig to be set to web snippet */ scriptConfig: ScriptConfig | string /** vanilla HTML string, which can contain JS, will be set in the body of the page */ bodyContent: string } interface FullResponse { // Full html response response: string } interface DynamicPageInfo { /** the url where the page is served */ url: string } const RESPONSE_BODY_TEMPLATE = ` Plausible Playwright tests ` const PLAUSIBLE_WEB_SNIPPET = ` ` export function serializeWithFunctions(obj: Record): string { const functions: Record = {} let counter = 0 const jsonString = JSON.stringify(obj, (_key, value) => { if (typeof value === 'function') { const placeholder = `__FUNCTION_${counter++}__` functions[placeholder] = value.toString() return placeholder } return value }) // Replace placeholders with actual function strings let result = jsonString for (const [placeholder, funcString] of Object.entries(functions)) { result = result.replace(`"${placeholder}"`, funcString) } return result } export function getConfiguredPlausibleWebSnippet({ hashBasedRouting, outboundLinks, fileDownloads, formSubmissions, domain, endpoint, ...initOverrideOptions }: ScriptConfig): string { const injectedScriptConfig = { domain, endpoint, hashBasedRouting, outboundLinks, fileDownloads, formSubmissions } const snippet = PLAUSIBLE_WEB_SNIPPET.replace( '<%= plausible_script_url %>', `/tracker/js/plausible-web.js?script_config=${encodeURIComponent( JSON.stringify(injectedScriptConfig) )}` ) if ( Object.entries(initOverrideOptions).some( ([_key, value]) => value !== undefined ) ) { const serializedOptions = serializeWithFunctions(initOverrideOptions) return snippet.replace( 'plausible.init()', `plausible.init(${serializedOptions})` ) } return snippet } export async function initializePageDynamically( page: Page, options: SharedOptions & (TemplatedResponse | FullResponse) ): Promise { const url = `/dynamic/${options.testId}${options.path || ''}` await page.context().route(url, async (route) => { let responseBody: string if ('response' in options) { responseBody = options.response } else { responseBody = RESPONSE_BODY_TEMPLATE.replace( '', typeof options.scriptConfig === 'string' ? options.scriptConfig : getConfiguredPlausibleWebSnippet(options.scriptConfig) ).replace('', `${options.bodyContent}`) } await route.fulfill({ body: responseBody, contentType: 'text/html', headers: options.headers }) }) return { url } }