punimtag/e2e/timing-budgets.ts
ilia c0c997f796
All checks were successful
CI / skip-ci-check (pull_request) Successful in 5s
CI / docker-ci (pull_request) Successful in 7s
CI / secret-scan (pull_request) Successful in 12s
CI / viewer-unit (pull_request) Successful in 1m29s
CI / e2e (pull_request) Successful in 4m8s
CI / admin-unit (pull_request) Successful in 4m24s
test: timing budgets + CI hardening (artifact v3 pin, npm cache retry)
Closes three items from the outstanding e2e/CI gap list:

- Timing budgets: timings.measure() only ever recorded durations for the
  (still-unwired) Pushgateway export — nothing failed CI when a step got
  slow. Add e2e/timing-budgets.ts (expectWithinBudget + shared BUDGET_MS
  buckets) and wire it into every measure() call site across the suite.
  Mail-wait steps are deliberately left unbudgeted (external mail-trap
  delivery latency, not a code performance signal).

- actions/upload-artifact@v4 doesn't work against this Gitea/act runner's
  artifact backend — pin to v3 for the e2e failure-report upload.

- Shared act_runner npm cache has corrupted platform-native tarballs before
  (@next/swc-linux-x64-musl) and reds viewer-unit/admin-unit/e2e with no
  product bug involved. All three npm ci steps now retry once after
  `npm cache clean --force` on first failure.

Verified: full local suite green against DEV (37 passed, 6 skipped, no
budget assertion failures) before wiring into CI.
2026-07-15 08:56:31 -04:00

40 lines
1.8 KiB
TypeScript

import { expect } from '@playwright/test';
import type { TimingCollector } from '@levkin/playkit';
/**
* Perf-budget gate for a `timings.measure(name, ...)` step.
*
* `timings.measure()` on its own only *records* durations (for the
* Pushgateway export, see ROADMAP "Timing -> Pushgateway") — nothing ever
* failed CI for a step getting slow. This asserts the most recently
* recorded sample for `name` against `maxMs`, so a regression shows up as a
* red test instead of only a graph nobody's watching.
*
* Budgets are intentionally generous (shared CI runner + DEV LXC under
* concurrent PR load — see docs/guides/ci-runners-and-control.md in the
* ansible repo) — this catches order-of-magnitude regressions, not
* micro-optimization. Pick the closest `BUDGET_MS` bucket rather than
* inventing a new one-off number per call site.
*/
export function expectWithinBudget(timings: TimingCollector, name: string, maxMs: number): void {
const samples = timings.getSamples().filter((sample) => sample.name === name);
const sample = samples[samples.length - 1];
expect(sample, `no timing sample recorded for "${name}" — call timings.measure() first`).toBeTruthy();
expect(
sample!.durationMs,
`"${name}" took ${Math.round(sample!.durationMs)}ms, budget is ${maxMs}ms`,
).toBeLessThanOrEqual(maxMs);
}
/** Shared budget buckets — see `expectWithinBudget` for rationale. */
export const BUDGET_MS = {
/** Direct FastAPI/NextAuth-route calls (LAN, no browser). */
api: 8_000,
/** Single UI interaction: open a page/panel, apply a filter, sign out. */
uiAction: 20_000,
/** Full login flow (navigate + submit + redirect wait). */
uiLogin: 45_000,
/** Multi-step UI CRUD (create/edit/deactivate + list refetch). */
heavyCrud: 60_000,
} as const;