import { devices, expect, type Page, type Request, type Response, test, } from "@playwright/test"; type LoadMetrics = { appOrigin: string; durationMs: number; transferredBytes: number; requestedUrls: string[]; requestedPathCounts: Map; cacheControlByPath: Map; contentEncodingByPath: Map; }; async function mockPublicApis(page: Page): Promise { await page.route("**/api/v1/**", async (route) => { const requestUrl = new URL(route.request().url()); if (requestUrl.pathname.endsWith("/api/v1/user/me")) { await route.fulfill({ status: 401, contentType: "application/json", body: JSON.stringify({ error: "unauthorized" }), }); return; } await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({}), }); }); } async function measureSigninLoad(page: Page): Promise { const appOrigin = new URL( process.env.BASE_URL ?? `http://127.0.0.1:${process.env.PORT ?? "4173"}`, ).origin; const requestedUrls: string[] = []; const requestedPathCounts = new Map(); const cacheControlByPath = new Map(); const contentEncodingByPath = new Map(); let transferredBytes = 0; const onRequest = (request: Request) => { const requestUrl = new URL(request.url()); requestedUrls.push(request.url()); if (requestUrl.protocol === "http:" || requestUrl.protocol === "https:") { const resourceKey = `${requestUrl.origin}${requestUrl.pathname}`; requestedPathCounts.set( resourceKey, (requestedPathCounts.get(resourceKey) ?? 0) + 1, ); } }; const onResponse = async (response: Response) => { const url = new URL(response.url()); const cacheControl = response.headers()["cache-control"]; if (cacheControl) { cacheControlByPath.set(url.pathname, cacheControl); } const contentEncoding = response.headers()["content-encoding"]; if (contentEncoding) { contentEncodingByPath.set(url.pathname, contentEncoding); } const timing = response.request().timing(); if (timing.responseEnd >= 0) { const sizes = await response .request() .sizes() .catch(() => null); transferredBytes += sizes?.responseBodySize ?? 0; } }; page.on("request", onRequest); page.on("response", onResponse); try { const start = performance.now(); await page.goto("/ko/signin", { waitUntil: "networkidle" }); await expect(page).toHaveURL(/\/ko\/signin(?:\?.*)?$/); const durationMs = Math.round(performance.now() - start); return { appOrigin, durationMs, transferredBytes, requestedUrls, requestedPathCounts, cacheControlByPath, contentEncodingByPath, }; } finally { page.off("request", onRequest); page.off("response", onResponse); } } function expectNoDuplicateStaticRequests(metrics: LoadMetrics): void { const duplicates = [...metrics.requestedPathCounts.entries()].filter( ([resourceKey, count]) => { const resourceUrl = new URL(resourceKey); const path = resourceUrl.pathname; return ( count > 1 && resourceUrl.origin === metrics.appOrigin && !path.startsWith("/api/") && !path.endsWith("/ko/signin") && !path.endsWith("/") && !path.endsWith("/main.dart.wasm") && !path.endsWith("/main.dart.mjs") && !path.endsWith("/assets/AssetManifest.bin.json") && !path.endsWith("/skwasm.js") && !path.endsWith("/skwasm.wasm") ); }, ); expect(duplicates).toEqual([]); } function resolvePerformanceBudget(projectName: string): { coldMs: number; warmMs: number; } { if (projectName === "webkit-mobile-webapp") { return { coldMs: 10_000, warmMs: 4000 }; } if (projectName.includes("webkit")) { return { coldMs: 4000, warmMs: 4000 }; } if (projectName.includes("firefox")) { return { coldMs: 3000, warmMs: 2800 }; } if (projectName.includes("mobile")) { return { coldMs: 3000, warmMs: 2300 }; } return { coldMs: 2300, warmMs: 1500 }; } function resolveRootRedirectBudget(projectName: string): number { if (projectName.includes("webkit")) { return 700; } if (projectName.includes("firefox")) { return 600; } return 300; } test.describe("UserFront login performance budget", () => { test("mobile Chrome service worker install does not fetch unused CanvasKit variants", async ({ browser, }, testInfo) => { test.skip( testInfo.project.name !== "chromium-mobile-webapp", "service worker install race is covered once in the mobile Chromium project", ); const context = await browser.newContext({ ...devices["Pixel 7"], locale: "ko-KR", serviceWorkers: "allow", }); const page = await context.newPage(); await mockPublicApis(page); try { const serviceWorkerResponse = await context.request.get( new URL( "/flutter_service_worker.js", process.env.BASE_URL ?? `http://127.0.0.1:${process.env.PORT ?? "4173"}`, ).toString(), ); const serviceWorkerBody = await serviceWorkerResponse.text(); expect(serviceWorkerBody).not.toContain('"/canvaskit/'); expect(serviceWorkerBody).not.toContain('"/main.dart.'); await page.goto("/ko/signin", { waitUntil: "domcontentloaded" }); await page.waitForTimeout(3_000); } finally { await context.close(); } }); test("warm login page load stays within the platform budget and reuses cached assets", async ({ page, }, testInfo) => { await mockPublicApis(page); const budget = resolvePerformanceBudget(testInfo.project.name); const cold = await measureSigninLoad(page); const warm = await measureSigninLoad(page); console.log( `[userfront-perf] cold=${cold.durationMs}ms/${cold.transferredBytes}B warm=${warm.durationMs}ms/${warm.transferredBytes}B`, ); expect(cold.durationMs).toBeLessThanOrEqual(budget.coldMs); expect(warm.durationMs).toBeLessThanOrEqual(budget.warmMs); expect(warm.transferredBytes).toBeLessThanOrEqual(1_000_000); expectNoDuplicateStaticRequests(cold); expectNoDuplicateStaticRequests(warm); const cacheControlByPath = new Map([ ...cold.cacheControlByPath, ...warm.cacheControlByPath, ]); const appShellCache = cacheControlByPath.get("/ko/signin") ?? ""; expect(appShellCache).toContain("no-cache"); const serviceWorkerState = await page.evaluate(async () => { if (!("serviceWorker" in navigator)) { return { available: false, secure: window.isSecureContext, scriptUrl: "", }; } const registrations = await navigator.serviceWorker.getRegistrations(); const registration = registrations[0]; return { available: true, secure: window.isSecureContext, count: registrations.length, controller: navigator.serviceWorker.controller?.scriptURL ?? "", scriptUrl: registration?.active?.scriptURL ?? registration?.waiting?.scriptURL ?? registration?.installing?.scriptURL ?? "", }; }); if ( testInfo.project.name.includes("mobile") && serviceWorkerState.scriptUrl ) { expect(new URL(serviceWorkerState.scriptUrl).pathname).toBe( "/flutter_service_worker.js", ); const serviceWorkerResponse = await page .context() .request.get( new URL("/flutter_service_worker.js", page.url()).toString(), ); expect(serviceWorkerResponse.headers()["cache-control"] ?? "").toContain( "no-cache", ); } else { expect(serviceWorkerState.scriptUrl).toBe(""); } expect(cold.durationMs).toBeGreaterThanOrEqual(0); }); test("root redirects to localized signin before Flutter boots", async ({ page, }, testInfo) => { await mockPublicApis(page); const requestedUrls: string[] = []; page.on("request", (request) => { requestedUrls.push(request.url()); }); const start = performance.now(); await page.goto("/", { waitUntil: "domcontentloaded" }); await expect(page).toHaveURL(/\/ko\/signin(?:\?.*)?$/); const durationMs = Math.round(performance.now() - start); expect(durationMs).toBeLessThanOrEqual( resolveRootRedirectBudget(testInfo.project.name), ); const rootIndex = requestedUrls.findIndex( (url) => new URL(url).pathname === "/", ); const bootstrapIndex = requestedUrls.findIndex((url) => new URL(url).pathname.endsWith("/flutter_bootstrap.js"), ); expect(rootIndex).toBeGreaterThanOrEqual(0); expect(bootstrapIndex).toBe(-1); }); });