next/script rendered the theme script with a nonce, and browsers hide a nonce attribute from the DOM once they have parsed it, so the server sent nonce="" and the client read undefined. Every page load logged a hydration mismatch. The cookie is the fix rather than a workaround for it. The server reads it and stamps data-theme on the html element, so the first frame is already the right colour and there is no inline script at all. Chasing that turned up worse. /offline sat outside [locale], which made it a sibling of the root layout, so Next gave it a generated one: no stylesheet, no font, no theme, and a second html element. The phase 7 check asserted the text and a button and passed while the page was plainly broken. It is a normal screen now. The service worker keeps one offline copy per language the reader actually visits, learned from their own successful navigations, so no list of locales lives in the worker and a third language stays a catalog file. The e2e warm-up also asks for the dynamic routes, which cost a compile of their own and were being paid for by whichever test reached one first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
52 lines
1.5 KiB
TypeScript
52 lines
1.5 KiB
TypeScript
import { request } from '@playwright/test';
|
|
|
|
/**
|
|
* Asks for every screen once before the suite starts.
|
|
*
|
|
* Against `pnpm dev` a route is compiled the first time it is requested, and whichever test
|
|
* happens to be first pays for it: a sign in navigation runs past the five seconds
|
|
* Playwright waits by default and fails for a reason that has nothing to do with the
|
|
* product. Against a built stack these are cheap 200s and 307s.
|
|
*/
|
|
const ROUTES = [
|
|
'/es',
|
|
'/en',
|
|
'/es/login',
|
|
'/es/registro',
|
|
'/es/verificar',
|
|
'/es/inicio',
|
|
'/es/bandeja',
|
|
'/es/comprobantes',
|
|
'/es/comprobantes/nuevo',
|
|
'/es/declaraciones',
|
|
'/es/vencimientos',
|
|
'/es/escanear',
|
|
'/es/perfil',
|
|
'/es/configuracion',
|
|
'/es/consentimiento',
|
|
'/es/usuarios',
|
|
'/es/errores',
|
|
'/es/auditoria',
|
|
'/en/login',
|
|
// The dynamic routes cost a compile too. The id does not have to exist: reaching the
|
|
// route is what compiles it, and what the first test would otherwise pay for.
|
|
'/es/comprobantes/warmup',
|
|
'/es/declaraciones/warmup',
|
|
'/es/usuarios/warmup',
|
|
];
|
|
|
|
export default async function globalSetup(): Promise<void> {
|
|
const baseURL = process.env['E2E_BASE_URL'] ?? 'http://localhost:3005';
|
|
const context = await request.newContext({ baseURL });
|
|
try {
|
|
for (const route of ROUTES) {
|
|
// A redirect to sign in is a compiled route, which is all this is after.
|
|
await context
|
|
.get(route, { failOnStatusCode: false, timeout: 120_000 })
|
|
.catch(() => undefined);
|
|
}
|
|
} finally {
|
|
await context.dispose();
|
|
}
|
|
}
|