From 0dbc77641f88f9685d8c4a3e8322d9ec3b55a381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 21 May 2026 10:30:34 +0200 Subject: [PATCH] test(e2e): migrate /api/analytics route mocks to the regex pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Playwright's literal `**/api/analytics` string glob does not match the sliced URL form `/api/analytics?slice=summaries` the v1.4.39.2 dashboard split fires alongside the thick request. The slim request fell through to the real route on every authenticated spec, and because the seeded e2e user carries no measurements, the real slim slice returned empty summaries — exactly the case the v1.4.39.3 production merge fix already guards against, but the regex form is the durable alignment with the v1.4.37 W-CI lesson that already swept `onboarding-flicker.spec.ts` and `mobile-viewport.spec.ts`. Ten specs migrate: dashboard, chart-overlay-controls, measurement-flow, charts-mobile, a11y, v1427-responsive-sheet, v1427-coach-launch, v1427-measurements-add-param, v1427-insights-empty-state, onboarding-tour-passthrough. Every spec now uses `/\/api\/analytics(\?|$)/` so the slim AND thick slice URLs intercept under one route handler. --- e2e/a11y.spec.ts | 6 +++++- e2e/chart-overlay-controls.spec.ts | 8 +++++++- e2e/charts-mobile.spec.ts | 8 +++++++- e2e/dashboard.spec.ts | 13 ++++++++++++- e2e/measurement-flow.spec.ts | 9 ++++++++- e2e/onboarding-tour-passthrough.spec.ts | 4 +++- e2e/v1427-coach-launch.spec.ts | 4 +++- e2e/v1427-insights-empty-state.spec.ts | 4 +++- e2e/v1427-measurements-add-param.spec.ts | 4 +++- e2e/v1427-responsive-sheet.spec.ts | 4 +++- 10 files changed, 54 insertions(+), 10 deletions(-) diff --git a/e2e/a11y.spec.ts b/e2e/a11y.spec.ts index 64787e12a..048c116fd 100644 --- a/e2e/a11y.spec.ts +++ b/e2e/a11y.spec.ts @@ -70,7 +70,11 @@ test.describe("axe-core authenticated surfaces", () => { // need fully-painted DOM, and a flaky API would otherwise stall the // analyse() call until the 30s test timeout. test.beforeEach(async ({ page }) => { - await page.route("**/api/analytics", (route) => + // v1.4.39.3 — match `/api/analytics` AND any sliced variant + // (`?slice=summaries`); the v1.4.39.2 dashboard split fires both + // in parallel and the literal string glob misses the query- + // string form. + await page.route(/\/api\/analytics(\?|$)/, (route) => route.fulfill({ status: 200, contentType: "application/json", diff --git a/e2e/chart-overlay-controls.spec.ts b/e2e/chart-overlay-controls.spec.ts index dec64dd30..c3bf48ca6 100644 --- a/e2e/chart-overlay-controls.spec.ts +++ b/e2e/chart-overlay-controls.spec.ts @@ -21,7 +21,13 @@ test.describe("chart overlay controls", () => { test.beforeEach(async ({ page }) => { // Mock analytics + measurements so the dashboard paints at least // one chart card. Same seed shape the dashboard.spec.ts uses. - await page.route("**/api/analytics", (route) => + // + // v1.4.39.3 — match `/api/analytics` AND any sliced variant + // (`?slice=summaries`). See the comment on dashboard.spec.ts for + // the underlying minimatch-vs-query-string gap; the regex form + // catches both the thick and slim slice URLs the dashboard now + // mounts in parallel. + await page.route(/\/api\/analytics(\?|$)/, (route) => route.fulfill({ status: 200, contentType: "application/json", diff --git a/e2e/charts-mobile.spec.ts b/e2e/charts-mobile.spec.ts index 7c890bcc6..b21fca257 100644 --- a/e2e/charts-mobile.spec.ts +++ b/e2e/charts-mobile.spec.ts @@ -33,7 +33,13 @@ test.describe("charts — mobile-viewport regression", () => { // Mock analytics + measurements so the chart wrappers all clear // their data-floor gates. Same pattern as `dashboard.spec.ts`. - await page.route("**/api/analytics", (route) => + // + // v1.4.39.3 — match `/api/analytics` AND any sliced variant + // (`?slice=summaries`). The v1.4.39.2 dashboard split mounts the + // slim slice in parallel; the literal `**/api/analytics` glob + // misses the query-string form and dropped the request through to + // the real route, blanking the tile strip + chart cards. + await page.route(/\/api\/analytics(\?|$)/, (route) => route.fulfill({ status: 200, contentType: "application/json", diff --git a/e2e/dashboard.spec.ts b/e2e/dashboard.spec.ts index 944b6ac39..60e54b87c 100644 --- a/e2e/dashboard.spec.ts +++ b/e2e/dashboard.spec.ts @@ -23,7 +23,18 @@ test.describe("authenticated dashboard render", () => { // Mock analytics with a populated weight summary so the weight tile // and chart render. `count` must be >0 for the tile to clear the // data-floor gate in `src/app/page.tsx`. - await page.route("**/api/analytics", (route) => + // + // v1.4.39.3 — match `/api/analytics` AND any sliced variant + // (`?slice=summaries`). The v1.4.39.2 dashboard split mounts two + // parallel `useAnalyticsQuery` calls; the slim variant carries a + // `?slice=summaries` query string that the literal string glob + // `**/api/analytics` (Playwright minimatch) refuses to match, + // dropping the request to the real route. The seeded test user has + // no measurements so the real route returns empty summaries, and + // the dashboard's slim-first merge then blanked the tile strip. + // The same trap bit `onboarding-flicker` and `mobile-viewport` in + // v1.4.37 W-CI; the regex form is the durable fix. + await page.route(/\/api\/analytics(\?|$)/, (route) => route.fulfill({ status: 200, contentType: "application/json", diff --git a/e2e/measurement-flow.spec.ts b/e2e/measurement-flow.spec.ts index e23e194a3..904095a1f 100644 --- a/e2e/measurement-flow.spec.ts +++ b/e2e/measurement-flow.spec.ts @@ -74,7 +74,14 @@ test.describe("add measurement flow", () => { // Dashboard's analytics fetch — empty summaries are fine; we only // need the page to render so we can click "Add". - await page.route("**/api/analytics", (route) => + // + // v1.4.39.3 — match `/api/analytics` AND any sliced variant + // (`?slice=summaries`). The v1.4.39.2 dashboard split fires both + // requests in parallel; the previous string glob missed the slim + // variant which then fell through to the real route and slowed + // the page paint enough that the post-mutation list query never + // surfaced inside the 10 s window. + await page.route(/\/api\/analytics(\?|$)/, (route) => route.fulfill({ status: 200, contentType: "application/json", diff --git a/e2e/onboarding-tour-passthrough.spec.ts b/e2e/onboarding-tour-passthrough.spec.ts index 5e94b9237..558967f53 100644 --- a/e2e/onboarding-tour-passthrough.spec.ts +++ b/e2e/onboarding-tour-passthrough.spec.ts @@ -69,7 +69,9 @@ test.describe("v1.4.33 F2 — onboarding tour passes clicks through to the page" }), ); - await page.route("**/api/analytics", (route) => + // v1.4.39.3 — regex form matches the slim slice the v1.4.39.2 + // dashboard split fires alongside the thick request. + await page.route(/\/api\/analytics(\?|$)/, (route) => route.fulfill({ status: 200, contentType: "application/json", diff --git a/e2e/v1427-coach-launch.spec.ts b/e2e/v1427-coach-launch.spec.ts index 71b667f1f..1baae5fb9 100644 --- a/e2e/v1427-coach-launch.spec.ts +++ b/e2e/v1427-coach-launch.spec.ts @@ -31,7 +31,9 @@ test.describe("v1.4.27 — Coach reachability on insights sub-pages", () => { // Populated analytics so each sub-page paints the data branch // (`hasMetricData` true) — the Coach button is mounted at the // bottom of the data tree. - await page.route("**/api/analytics", (route) => + // v1.4.39.3 — regex form matches the slim slice the v1.4.39.2 + // dashboard split fires alongside the thick request. + await page.route(/\/api\/analytics(\?|$)/, (route) => route.fulfill({ status: 200, contentType: "application/json", diff --git a/e2e/v1427-insights-empty-state.spec.ts b/e2e/v1427-insights-empty-state.spec.ts index a3f51915f..cd30003cf 100644 --- a/e2e/v1427-insights-empty-state.spec.ts +++ b/e2e/v1427-insights-empty-state.spec.ts @@ -24,7 +24,9 @@ test.describe("v1.4.27 — insights empty-state with metric gating", () => { test.beforeEach(async ({ page }) => { // Zero-count summaries — every gated sub-page short-circuits to // its EmptyState branch. - await page.route("**/api/analytics", (route) => + // v1.4.39.3 — regex form matches the slim slice the v1.4.39.2 + // dashboard split fires alongside the thick request. + await page.route(/\/api\/analytics(\?|$)/, (route) => route.fulfill({ status: 200, contentType: "application/json", diff --git a/e2e/v1427-measurements-add-param.spec.ts b/e2e/v1427-measurements-add-param.spec.ts index 41d7321c6..e208345e8 100644 --- a/e2e/v1427-measurements-add-param.spec.ts +++ b/e2e/v1427-measurements-add-param.spec.ts @@ -25,7 +25,9 @@ test.describe("v1.4.27 — /measurements?add= consumer", () => { test.use({ storageState: STORAGE_STATE_PATH }); test.beforeEach(async ({ page }) => { - await page.route("**/api/analytics", (route) => + // v1.4.39.3 — regex form matches the slim slice the v1.4.39.2 + // dashboard split fires alongside the thick request. + await page.route(/\/api\/analytics(\?|$)/, (route) => route.fulfill({ status: 200, contentType: "application/json", diff --git a/e2e/v1427-responsive-sheet.spec.ts b/e2e/v1427-responsive-sheet.spec.ts index 90a83edcc..b3e83e391 100644 --- a/e2e/v1427-responsive-sheet.spec.ts +++ b/e2e/v1427-responsive-sheet.spec.ts @@ -22,7 +22,9 @@ test.describe("v1.4.27 — ResponsiveSheet branch", () => { test.beforeEach(async ({ page }) => { // Empty analytics + list keeps the page fast and deterministic. - await page.route("**/api/analytics", (route) => + // v1.4.39.3 — regex form matches `/api/analytics` AND any sliced + // variant (`?slice=summaries`) the v1.4.39.2 dashboard split fires. + await page.route(/\/api\/analytics(\?|$)/, (route) => route.fulfill({ status: 200, contentType: "application/json",