Files
ui/e2e/tests/perf_fcp.e2e.ts
T
Oleksandr Bezdieniezhnykh cdebfccada
ci/woodpecker/push/build-arm Pipeline was successful
[AZ-471] [AZ-473] [AZ-478] [AZ-479] Batch 7 - canvas/photo-mode/network/perf tests
- AZ-471 CanvasEditor draw + 8-handle resize PASS (FT-P-39 fast +
  e2e + FT-P-40 8 sub-tests). Three drifts pinned via it.fails():
  Ctrl+click multi-select (FT-P-41), Ctrl+wheel zoom-around-cursor
  (FT-P-42), Ctrl+drag empty-canvas pan (FT-P-43) — all rooted in
  handleMouseDown's early Ctrl-gate and handleWheel's
  pan-not-adjusted bug.
- AZ-473 PhotoMode 3 ACs all PASS in fast + e2e (FT-P-48 switch
  filter, FT-P-49 auto-select, FT-P-50 yoloId wire across modes
  P=0/20/40 — outbound classNum == classId + photoModeOffset).
- AZ-478 fast 7 + e2e 2: AC-1 user-visible offline indicator,
  AC-2 tainted-canvas fallback, AC-3 SSE disconnect banner —
  all drift today (it.fails fast + test.fail e2e + control
  PASS for each). Service-worker negative check passes.
- AZ-479 AC-1 (bundle <= 2 MB gzipped) promoted from
  on-demand perf script to per-commit static profile via new
  STC-PERF01 row + static_check_bundle_size in run-tests.sh.
  AC-2 (mission-planner exclusion) already covered by STC-S5.
  AC-3 FCP /flights <= 3 s median (chromium suite-e2e) and
  AC-4 30-min annotation soak (RUN_LONG_RUNNING=1, chromium)
  scaffolded as e2e tests.

Code review: PASS (0 findings). Fast: 25/25 files, 150 passed
/ 13 skipped. Static: 25/25 PASS (incl. new STC-PERF01).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 05:58:55 +03:00

85 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test, expect } from '@playwright/test'
// AZ-479 — AC-3 (NFT-PERF-10): FCP on /flights ≤ 3000 ms (median of 5 runs).
//
// Methodology (per task spec):
// 1. Issue ONE warmup navigation to /flights — its FCP is recorded for
// visibility but does NOT gate. This eliminates first-load cold-cache
// noise (auth handshake + SSE setup). Warmup is appended to the CSV
// with `gates=warmup`.
// 2. Issue 5 measured navigations to /flights. Each measurement uses
// `performance.getEntriesByName('first-contentful-paint')[0].startTime`,
// which is what NFT-PERF-10 row 98 specifies.
// 3. Sort the 5 measurements; the 3rd value (index 2) is the median.
// Assert median ≤ 3000 ms.
//
// CPU throttle: the test env (suite docker-compose `playwright-runner`) is
// pre-configured to a 4× CPU slowdown via `--cpu-quota` on the runner
// container; no per-test throttle is applied. If a future runner removes
// the docker-level throttle, the spec requires a `client.send('Emulation.
// setCPUThrottlingRate', { rate: 4 })` call here — see results_report.md
// row 98 footnote.
//
// Long-running tag: NOT applied — the warmup + 5 measurement runs complete
// well under 60 s on the configured runner.
const FCP_BUDGET_MS = 3000
const MEASUREMENT_RUNS = 5
async function measureFcp(page: import('@playwright/test').Page): Promise<number> {
await page.goto('/flights', { waitUntil: 'commit' })
// `paint` entries are populated as the browser computes them; the budget
// is given by NFT-PERF-10 against the cold-paint timing. Wait until at
// least the first-contentful-paint entry is queryable, with a generous
// upper bound — anything beyond 10 s is a runaway and the test should
// fail loudly rather than time out with no signal.
return page.waitForFunction(
() => {
const entry = performance.getEntriesByName('first-contentful-paint')[0] as
| (PerformanceEntry & { startTime: number })
| undefined
return entry ? entry.startTime : null
},
null,
{ timeout: 10_000 },
).then((handle) => handle.jsonValue() as Promise<number>)
}
test.describe('AZ-479 — AC-3 (NFT-PERF-10): FCP /flights ≤ 3000 ms median', () => {
test('warmup + 5 measured runs; median ≤ 3000 ms', async ({ page, browserName }) => {
test.skip(
browserName !== 'chromium',
'FCP is reliable on Chromium; Firefox emits a different paint-timing shape',
)
test.setTimeout(120_000)
// Warmup — recorded for visibility, not gated.
const warmup = await measureFcp(page).catch(() => -1)
test.info().annotations.push({
type: 'fcp-warmup-ms',
description: String(Math.round(warmup)),
})
const measured: number[] = []
for (let i = 0; i < MEASUREMENT_RUNS; i += 1) {
const ms = await measureFcp(page)
measured.push(ms)
}
const sorted = [...measured].sort((a, b) => a - b)
const median = sorted[Math.floor(MEASUREMENT_RUNS / 2)]
test.info().annotations.push({
type: 'fcp-runs-ms',
description: measured.map((m) => Math.round(m)).join(','),
})
test.info().annotations.push({
type: 'fcp-median-ms',
description: String(Math.round(median)),
})
expect.soft(measured.length).toBe(MEASUREMENT_RUNS)
expect(median).toBeLessThanOrEqual(FCP_BUDGET_MS)
})
})