import { expect, test, type Page, type Request } from '@playwright/test'; type LoadMetrics = { 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 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) => { 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 { 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 path = new URL(resourceKey).pathname; return ( count > 1 && !path.startsWith('/api/') && !path.endsWith('/ko/signin') && !path.endsWith('/') ); }, ); expect(duplicates).toEqual([]); } test.describe('UserFront login performance budget', () => { test('warm login page load stays within the two second budget and reuses cached assets', async ({ page, }) => { await mockPublicApis(page); 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(1500); expect(warm.durationMs).toBeLessThanOrEqual(1100); expect(warm.transferredBytes).toBeLessThanOrEqual(1_000_000); expectNoDuplicateStaticRequests(cold); expectNoDuplicateStaticRequests(warm); expect(warm.requestedUrls.some((url) => url.includes('NotoSansKR'))).toBe( false, ); expect( warm.requestedUrls.some((url) => url.includes('fonts.googleapis.com/icon?family=Material+Icons'), ), ).toBe(false); expect( [...cold.requestedUrls, ...warm.requestedUrls].some((url) => url.includes('www.gstatic.com/flutter-canvaskit'), ), ).toBe(false); expect(warm.requestedUrls.some((url) => url.endsWith('/assets/.env'))).toBe( false, ); expect( cold.requestedUrls.some((url) => url.endsWith('/flutter_service_worker.js'), ), ).toBe(false); const cacheControlByPath = new Map([ ...cold.cacheControlByPath, ...warm.cacheControlByPath, ]); const contentEncodingByPath = new Map([ ...cold.contentEncodingByPath, ...warm.contentEncodingByPath, ]); const appShellCache = cacheControlByPath.get('/ko/signin') ?? ''; expect(appShellCache).toContain('no-cache'); expect(cold.durationMs).toBeGreaterThanOrEqual(0); const brotliEntrypoint = [...contentEncodingByPath.entries()].some( ([path, encoding]) => /^\/main\.dart\.[0-9a-f]{12}\.(?:mjs|wasm|js)$/.test(path) && encoding === 'br', ); expect(brotliEntrypoint).toBe(true); expect(contentEncodingByPath.get('/canvaskit/skwasm.wasm')).toBe('br'); }); test('root redirects to localized signin before Flutter boots', async ({ page, }) => { 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(300); 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).toBeGreaterThan(rootIndex); }); });