1
16import { test, expect } from "../fixtures";
17import { createPage } from "../utils/api";
18
19const DEV_FCP_BUDGET_MS = 3_000;
20const DEV_TTI_BUDGET_MS = 4_000;
21const KEYSROKE_PAINT_BUDGET_MS = 100;
22const SEARCH_RESULT_BUDGET_MS = 1_000;
23
24test.describe("Performance — Phase 15 (E2E budgets)", () => {
25 test("workspace page first contentful paint under budget", async ({
26 authPage,
27 freshWorkspace,
28 }) => {
29 const ws = await freshWorkspace("Perf Space");
30 const metrics = await authPage.goto(`/w/${ws.id}`, { waitUntil: "networkidle" });
31 void metrics;
32
33 const fcp = await authPage.evaluate(async () => {
34
35 return new Promise<number>((resolve) => {
36 const observer = new PerformanceObserver((list) => {
37 const entry = list.getEntries().find((e) => e.name === "first-contentful-paint");
38 if (entry) {
39 observer.disconnect();
40 resolve(entry.startTime);
41 }
42 });
43 observer.observe({ type: "paint", buffered: true });
44
45 setTimeout(() => {
46 const nav = performance.getEntriesByType("navigation")[0] as PerformanceNavigationTiming;
47 observer.disconnect();
48 resolve(nav ? nav.loadEventEnd : performance.now());
49 }, 5_000);
50 });
51 });
52 console.log(`[perf] FCP = ${Math.round(fcp)}ms (budget ${DEV_FCP_BUDGET_MS}ms)`);
53 expect(fcp, `FCP ${fcp}ms exceeded budget ${DEV_FCP_BUDGET_MS}ms`).toBeLessThan(
54 DEV_FCP_BUDGET_MS * 1.5,
55 );
56 });
57
58 test("editor becomes interactive under budget", async ({
59 authPage,
60 api,
61 freshWorkspace,
62 }) => {
63 const ws = await freshWorkspace("Perf TTI Space");
64 const page = await createPage(api, ws.id, { title: "TTI Page" });
65 const started = Date.now();
66 await authPage.goto(`/w/${ws.id}/page/${page.id}`, { waitUntil: "networkidle" });
67
68 const firstBlock = authPage.locator('[data-testid^="block-"]').first();
69 await expect(firstBlock).toBeVisible({ timeout: DEV_TTI_BUDGET_MS });
70 const tti = Date.now() - started;
71 console.log(`[perf] TTI (editor ready) = ${tti}ms (budget ${DEV_TTI_BUDGET_MS}ms)`);
72 expect(tti).toBeLessThan(DEV_TTI_BUDGET_MS * 1.5);
73 });
74
75 test("keystroke-to-paint under budget in the editor", async ({
76 authPage,
77 api,
78 freshWorkspace,
79 }) => {
80 const ws = await freshWorkspace("Perf Typing Space");
81 const page = await createPage(api, ws.id, { title: "Typing Page" });
82 await authPage.goto(`/w/${ws.id}/page/${page.id}`, { waitUntil: "networkidle" });
83 const block = authPage.locator('[data-testid^="block-"]').first();
84 await block.click();
85
86
87 const t0 = Date.now();
88 await authPage.keyboard.type("x");
89 await expect(block).toContainText("x");
90 const dt = Date.now() - t0;
91 console.log(`[perf] keystroke→paint = ${dt}ms (budget ${KEYSROKE_PAINT_BUDGET_MS}ms)`);
92
93 expect(dt, `keystroke→paint ${dt}ms`).toBeLessThan(KEYSROKE_PAINT_BUDGET_MS * 3);
94 });
95
96 test("search first result under budget", async ({
97 authPage,
98 api,
99 freshWorkspace,
100 }) => {
101 const ws = await freshWorkspace("Perf Search Space");
102 const token = `PERF-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
103 await createPage(api, ws.id, { title: `${token} fast lookup` });
104
105 await authPage.goto(`/w/${ws.id}`, { waitUntil: "networkidle" });
106 await authPage.getByTestId("search-button").click();
107 const input = authPage.getByTestId("search-input");
108
109 const t0 = Date.now();
110 await input.fill(token);
111 await expect(authPage.getByTestId("search-result").first()).toBeVisible({
112 timeout: 5_000,
113 });
114 const dt = Date.now() - t0;
115 console.log(`[perf] search→result = ${dt}ms (budget ${SEARCH_RESULT_BUDGET_MS}ms)`);
116
117 expect(dt, `search→result ${dt}ms`).toBeLessThan(SEARCH_RESULT_BUDGET_MS * 2);
118 });
119});
120