|
|
|
|
@ -3,15 +3,17 @@
|
|
|
|
|
*
|
|
|
|
|
* `build/` is served by a real Node HTTP server, never `route.fulfill`: a fulfilled body
|
|
|
|
|
* is not the bytes a browser decodes from the wire, and WebKit was measured blaming the CSP
|
|
|
|
|
* hash for it. A static check, then two passes for each visitor, one line of figures, exit 1
|
|
|
|
|
* on any failure:
|
|
|
|
|
* hash for it. A static check, a styled first-paint pass, then two passes for each visitor;
|
|
|
|
|
* one line of figures, exit 1 on any failure:
|
|
|
|
|
*
|
|
|
|
|
* 0. `index.html` — the boot tag comes AFTER the CSP `<meta>`. A `<meta>` policy governs
|
|
|
|
|
* only what follows it: a boot above it runs under no policy, and every browser pass
|
|
|
|
|
* below would stay green with a wrong hash (measured);
|
|
|
|
|
* 1. modules aborted (`/_app/**`) — the boot alone must stamp its nine attributes on
|
|
|
|
|
* 1. client JS aborted, linked CSS kept — foundation tokens must style the prerendered
|
|
|
|
|
* page before ActiveEidos can inject its runtime copy;
|
|
|
|
|
* 2. modules aborted (`/_app/**`) — the boot alone must stamp its nine attributes on
|
|
|
|
|
* `<html>`, mark the `dir` as its own, and give the visitor the values it asks for;
|
|
|
|
|
* 2. full load — 0 pageerror, 0 console.error, 0 CSP violations, and the nine attributes
|
|
|
|
|
* 3. full load — 0 pageerror, 0 console.error, 0 CSP violations, and the nine attributes
|
|
|
|
|
* unchanged once the app has hydrated.
|
|
|
|
|
*
|
|
|
|
|
* The visitors: `es-ES`; `ar-EG`, which must get `dir="rtl" lang="ar"`; and `es-ES` asking for
|
|
|
|
|
@ -155,6 +157,26 @@ async function bootOnly(browser, url, visitor) {
|
|
|
|
|
return { attrs, csp };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Keep linked CSS but block client JS: the prerendered first paint must be styled. */
|
|
|
|
|
async function foundationBeforeHydration(browser, url, visitor) {
|
|
|
|
|
const { context, page } = await open(browser, visitor, { abortModules: false });
|
|
|
|
|
await page.route('**/_app/**', (route) =>
|
|
|
|
|
route.request().resourceType() === 'script' ? route.abort() : route.continue()
|
|
|
|
|
);
|
|
|
|
|
await page.goto(url, { waitUntil: 'networkidle' });
|
|
|
|
|
const foundation = await page.evaluate(() => {
|
|
|
|
|
const style = getComputedStyle(document.documentElement);
|
|
|
|
|
return {
|
|
|
|
|
linked: document.querySelectorAll('link[rel="stylesheet"]').length > 0,
|
|
|
|
|
injected: document.getElementById('uix-eidos-static') !== null,
|
|
|
|
|
color: style.getPropertyValue('--color-primary-element').trim(),
|
|
|
|
|
dialogWidth: style.getPropertyValue('--dialog-content-width-md').trim()
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
await context.close();
|
|
|
|
|
return foundation;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Pass 2: the whole app, hydrated, watched for a late write. */
|
|
|
|
|
async function hydrated(browser, url, visitor) {
|
|
|
|
|
const { context, page, log } = await open(browser, visitor, { abortModules: false });
|
|
|
|
|
@ -211,6 +233,14 @@ try {
|
|
|
|
|
` boot tag at ${bootAt}, CSP <meta> at ${policyAt}: the boot is outside the policy`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
const foundation = await foundationBeforeHydration(browser, url, VISITORS[0]);
|
|
|
|
|
const foundationReady =
|
|
|
|
|
foundation.linked && !foundation.injected && !!foundation.color && !!foundation.dialogWidth;
|
|
|
|
|
if (!foundationReady) {
|
|
|
|
|
failures.push(
|
|
|
|
|
` prerendered foundation missing before hydration: ${JSON.stringify(foundation)}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const visitor of VISITORS) {
|
|
|
|
|
const boot = await bootOnly(browser, url, visitor);
|
|
|
|
|
@ -264,7 +294,7 @@ try {
|
|
|
|
|
);
|
|
|
|
|
const seconds = ((performance.now() - started) / 1000).toFixed(1);
|
|
|
|
|
console.log(
|
|
|
|
|
`smoke apps/base · boot ${stamped}/${BOOT_ATTRS.length * VISITORS.length} · ` +
|
|
|
|
|
`smoke apps/base · foundation ${Number(foundationReady)}/1 · boot ${stamped}/${BOOT_ATTRS.length * VISITORS.length} · ` +
|
|
|
|
|
Object.entries(figures)
|
|
|
|
|
.map(([name, value]) => `${name} ${value}`)
|
|
|
|
|
.join(' · ') +
|
|
|
|
|
|