From 3cf9a262ca8fa71d05e96d0bb15ec4e1adcdd054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 18 Jun 2026 17:48:57 +0200 Subject: [PATCH] test(e2e): keep route mocks authoritative against the v1.18.6 service worker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The production build registers a service worker whose v1.18.6 data branch serves allowlisted GET /api/* reads network-first. A worker-originated fetch is not subject to Playwright's page.route interception, so the worker re-fetched the real (empty) backend and bypassed every per-spec route.fulfill — a just-created row never surfaced and read-after-write assertions failed. Block the worker in the test context so the route mocks are authoritative; the shipped offline cache is untouched for real users. Separately, the dashboard now batches chart series into /api/measurements/series-batch, a sub-path the **/api/measurements* glob does not match, so it fell through to the empty backend and each chart painted an empty preloaded slice; stub the endpoint with the same populated set. --- e2e/dashboard.spec.ts | 43 +++++++++++++++++++++++++++++++++++++++++++ playwright.config.ts | 11 +++++++++++ 2 files changed, 54 insertions(+) diff --git a/e2e/dashboard.spec.ts b/e2e/dashboard.spec.ts index 2a86bc52d..0a4891df2 100644 --- a/e2e/dashboard.spec.ts +++ b/e2e/dashboard.spec.ts @@ -98,10 +98,53 @@ test.describe("authenticated dashboard render", () => { }), ); + // v1.18.6 — the dashboard now batches every visible chart's daily + // series into ONE `/api/measurements/series-batch` round-trip and + // hands each chart its slice via `preloadedSeries`. The route is a + // sub-path of `/api/measurements`, which the `**/api/measurements*` + // glob below does NOT match (Playwright's `*` does not cross `/`), so + // without an explicit stub the request fell through to the real + // backend. The seed user has no measurements, so the route returned + // every requested type as an EMPTY array — and an empty-but-present + // slice satisfies the chart's preloaded-coverage check, so each chart + // rendered the batched empty slice instead of self-fetching. Mock the + // endpoint with the same populated set so the charts paint. + await page.route(/\/api\/measurements\/series-batch(\?|$)/, (route) => { + const url = new URL(route.request().url()); + const types = (url.searchParams.get("types") ?? "WEIGHT").split(","); + const series: Record< + string, + Array<{ type: string; value: number; measuredAt: string; count: number }> + > = {}; + for (const type of types) { + const baseValue = + type === "BLOOD_PRESSURE_SYS" + ? 124 + : type === "BLOOD_PRESSURE_DIA" + ? 80 + : type === "PULSE" + ? 70 + : 78.5; + series[type] = Array.from({ length: 10 }, (_, i) => ({ + type, + value: baseValue + (i % 3) - 1, + measuredAt: new Date(Date.now() - i * 86_400_000).toISOString(), + count: 1, + })); + } + return route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ data: { series }, error: null }), + }); + }); + // The chart's per-type fetch reads `json.data.measurements` and // expects a `meta.total`. Return a small dataset so the chart has // points to plot — otherwise Recharts mounts a placeholder instead // of the recharts-wrapper SVG and the assertion below times out. + // (Self-fetch fallback for any chart not covered by the batch above, + // e.g. a range-tab change that drops batched coverage.) await page.route("**/api/measurements*", (route) => { const url = new URL(route.request().url()); const type = url.searchParams.get("type") ?? "WEIGHT"; diff --git a/playwright.config.ts b/playwright.config.ts index 1ee341f56..1c9773571 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -33,6 +33,17 @@ export default defineConfig({ trace: "retain-on-failure", screenshot: "only-on-failure", video: "retain-on-failure", + // v1.18.6 — block the service worker in the test context. The + // production build registers `/sw.js`, whose v1.18.6 data branch + // serves allowlisted `/api/*` GET reads network-first. A worker- + // originated `fetch` is NOT subject to Playwright's `page.route` + // interception, so the SW would re-fetch the real (empty) backend + // and serve that — bypassing every per-spec `route.fulfill` mock and + // breaking read-after-write assertions (a just-created row never + // surfaced). Blocking the worker keeps the route mocks authoritative + // without weakening the shipped SW behaviour. Real users keep the + // offline data cache; only the test harness opts out. + serviceWorkers: "block", // HealthLog ships dark mode as the default (Dracula theme) — `globals.css` // sets `color-scheme: dark` on the root and the `` defaults // to "system". Playwright's stock context is `colorScheme: "light"`, which