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
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.
40 lines
1.8 KiB
TypeScript
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;
|