File size: 3,786 Bytes
6778ee0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | 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<string, string>
}
interface TemplatedResponse {
/** string like `<script async id="plausible" src="/plausible.compat.local.js"></script>` 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 = `
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Plausible Playwright tests</title>
<script>// Plausible script</script>
</head>
<body></body>
</html>
`
const PLAUSIBLE_WEB_SNIPPET = `
<script async src="<%= plausible_script_url %>"></script>
<script>
window.plausible=window.plausible||function(){(plausible.q=plausible.q||[]).push(arguments)},plausible.init=plausible.init||function(i){plausible.o=i||{}};
plausible.init()
</script>
`
export function serializeWithFunctions(obj: Record<string, unknown>): string {
const functions: Record<string, string> = {}
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<DynamicPageInfo> {
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(
'<script>// Plausible script</script>',
typeof options.scriptConfig === 'string'
? options.scriptConfig
: getConfiguredPlausibleWebSnippet(options.scriptConfig)
).replace('<body></body>', `<body>${options.bodyContent}</body>`)
}
await route.fulfill({
body: responseBody,
contentType: 'text/html',
headers: options.headers
})
})
return { url }
}
|