test(e2e): align export and integrations specs with removed surfaces

Drop the obsolete doctor-report dialog spec (the interactive export-page
dialog was removed; the report now lives under the health-record export),
remove the doctor-report testid from the export smoke, and update the
integrations layout spec to three cards after the moodLog removal.
This commit is contained in:
Marc-André Bombeck committed 2026-06-06 23:40:10 +02:00
1 parent 7176afa9ef
commit ab1eea7f23
3 files changed
+15 -114

No files matched your search

-98
View File
@@ -1,98 +0,0 @@
import { expect, test } from "@playwright/test";
import { STORAGE_STATE_PATH } from "./setup/global-setup";
/**
* Doctor-report PDF flow. The real flow:
* 1. User clicks "Configure & generate" on `/settings/export`
* (relocated from `/settings/advanced` in v1.4.16 phase B7)
* 2. The dialog opens; user picks dates and submits.
* 3. Frontend POSTs `/api/doctor-report` to fetch aggregated data
* 4. Frontend dynamic-imports `src/lib/doctor-report-pdf.ts` and
* generates the PDF client-side via jsPDF; calls `doc.save(...)`
* which triggers a download.
*
* This spec mocks step 3 (so we don't need 90 days of seeded data) and
* captures step 4's download via Playwright's download API. We then
* confirm the file is non-empty and starts with `%PDF` magic bytes.
*/
test.describe("doctor report PDF generation", () => {
test.use({ storageState: STORAGE_STATE_PATH });
test("button click triggers a non-empty PDF download", async ({ page }) => {
// Mock the data endpoint with a minimal-but-shape-correct payload.
// The shape below mirrors the `ReportData` interface in
// `src/lib/doctor-report-pdf.ts` — empty stats / medications / mood
// is enough to exercise the table-rendering path without 90 days
// of seeded readings.
await page.route("**/api/doctor-report", (route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
data: {
period: {
days: 90,
since: new Date(Date.now() - 90 * 86_400_000).toISOString(),
},
patient: {
username: "e2e-tester",
dateOfBirth: "1985-06-15T00:00:00.000Z",
gender: "MALE",
heightCm: 180,
},
stats: {},
bmi: null,
compliance: {},
medications: [],
mood: null,
},
error: null,
}),
}),
);
// Capture page errors so a failed dynamic-import surfaces in the
// test output instead of timing out silently.
const pageErrors: string[] = [];
page.on("pageerror", (err) => pageErrors.push(`pageerror: ${err.message}`));
page.on("console", (msg) => {
if (msg.type() === "error")
pageErrors.push(`console.error: ${msg.text()}`);
});
await page.goto("/settings/export", { waitUntil: "domcontentloaded" });
// v1.4.37 renamed the trigger testid from `export-action-doctor-report`
// to `export-hero-doctor-report-action` when the card was lifted to a
// hero block on the export page.
const reportBtn = page.getByTestId("export-hero-doctor-report-action");
await expect(reportBtn).toBeVisible({ timeout: 10_000 });
// Click opens the dialog; the dialog's submit button triggers the PDF
// generation. Trigger the download and capture the resulting Download
// object once the user confirms.
const downloadPromise = page.waitForEvent("download", { timeout: 30_000 });
await reportBtn.click();
// Submit the dialog with the default dates (last 90 days).
const submitBtn = page.getByRole("button", {
name: /generate pdf|pdf generieren/i,
});
await expect(submitBtn).toBeVisible({ timeout: 5_000 });
await submitBtn.click();
const download = await downloadPromise.catch((err) => {
throw new Error(
`Download did not fire. Page errors:\n${pageErrors.join("\n")}\n\nOriginal: ${err}`,
);
});
const path = await download.path();
expect(path).toBeTruthy();
// Read first 4 bytes — a valid PDF starts with `%PDF`.
const fs = await import("node:fs/promises");
const buf = await fs.readFile(path!);
expect(buf.byteLength).toBeGreaterThan(1024);
expect(buf.subarray(0, 4).toString("ascii")).toBe("%PDF");
});
});
+4 -4
View File
@@ -24,16 +24,16 @@ test.describe("Settings → Export", () => {
test("renders all export surfaces with stable testids", async ({ page }) => {
await page.goto("/settings/export", { waitUntil: "domcontentloaded" });
// v1.12 — the health-record export is the page hero; the
// doctor-report card (`export-hero-*` prefix) is demoted to the
// bottom; the four CSV/backup tiles keep the `export-card-*` shape.
// The health-record export is the page hero; the four CSV/backup
// tiles keep the `export-card-*` shape. The doctor report lives
// under the health-record export, so it is no longer a separate
// surface here.
for (const id of [
"health-record-export-panel",
"export-card-measurements-csv",
"export-card-medications-csv",
"export-card-mood-csv",
"export-card-full-backup",
"export-hero-doctor-report",
]) {
await expect(page.getByTestId(id)).toBeVisible({ timeout: 10_000 });
}
+11 -12
View File
@@ -15,11 +15,10 @@ import { STORAGE_STATE_PATH } from "./setup/global-setup";
* verifies neither path leaks into a horizontal scrollbar on the
* <html> element.
*
* 2. Render exactly one pill per integration card (Withings + Mood
* Log when both are configured). Belt-and-braces guard for the
* consolidation Marc requested — the Vitest spec covers this on
* the SSR markup level, but the Pixel-5 layout is what matters
* to him in practice.
* 2. Render exactly one pill per integration card. Belt-and-braces
* guard for the card consolidation — the Vitest spec covers this
* on the SSR markup level, but the Pixel-5 layout is what matters
* in practice.
*
* Mobile-only spec — desktop project skips it.
*/
@@ -144,18 +143,18 @@ test.describe("/settings/integrations Pixel-5 layout", () => {
// Wait for the pills to mount (queries resolve after first paint).
const pills = page.locator('[data-testid="integration-status-pill"]');
// Four integration cards now (Withings, moodLog, WHOOP, Google Health) →
// four pills.
await expect(pills).toHaveCount(4, { timeout: 10_000 });
// Three integration cards now (Withings, WHOOP, Google Health) → three
// pills.
await expect(pills).toHaveCount(3, { timeout: 10_000 });
// Withings + moodLog are connected in this fixture; WHOOP is a BYO-keys
// card that stays dormant until an operator adds credentials, so assert
// the two connected pills order-independently.
// Withings is connected in this fixture; WHOOP is a BYO-keys card that
// stays dormant until an operator adds credentials, so assert the one
// connected pill.
await expect(
page.locator(
'[data-testid="integration-status-pill"][data-state="connected"]',
),
).toHaveCount(2);
).toHaveCount(1);
// The redundant v1.4.18 banner must be gone.
await expect(