diff --git a/e2e/record-csv-import.spec.ts b/e2e/record-csv-import.spec.ts index 5ad83d964..d1168700a 100644 --- a/e2e/record-csv-import.spec.ts +++ b/e2e/record-csv-import.spec.ts @@ -1,29 +1,69 @@ +import type { Page } from "@playwright/test"; + import { expect, test } from "./setup/test"; -import { STORAGE_STATE_PATH } from "./setup/global-setup"; +import { + criticalAccount, + criticalStorageState, + resetCriticalAccount, +} from "./setup/critical-fixture"; /** - * Lab results from a spreadsheet (#1215), at phone width, against the real - * server: the mapping is suggested from the headers, the check reports a - * refused row by line and writes nothing, the import writes the rest, and a - * second import of the same file only finds duplicates. + * Lab results from a spreadsheet (#1215), against the real server: the + * mapping is suggested from the headers, the check reports a refused row by + * line and writes nothing, the import writes the rest, the imported row is on + * the labs page, and a second import of the same file only finds duplicates. + * + * The account is the labs journey's own, one per project + * (`critical-fixture.ts`), emptied of results first. Before v1.46 this ran on + * the shared account in both projects, so the "only duplicates" verdict of + * one project could be the other project's first import. */ + +const ANALYTE = "E2E Marker CSV"; + function csv(): string { const day = new Date(Date.now() - 30 * 86_400_000).toISOString().slice(0, 10); return [ "Analyte;Value;Unit;Date", - `E2E Marker CSV;5,4;mmol/L;${day}`, - `E2E Marker CSV;1.234;mmol/L;${day}`, + // A decimal comma, as a German lab export writes it. + `${ANALYTE};5,4;mmol/L;${day}`, + // Either 1.234 or 1234: refused as ambiguous rather than guessed. + `${ANALYTE};1.234;mmol/L;${day}`, ].join("\n"); } +/** The account's lab results, as the list route answers them. */ +async function storedResults( + page: Page, +): Promise> { + const res = await page.request.get("/api/labs"); + expect(res.status()).toBe(200); + const { data } = (await res.json()) as { + data: { + results: Array<{ analyte: string; value: number | null; unit: string }>; + }; + }; + return data.results.map(({ analyte, value, unit }) => ({ + analyte, + value, + unit, + })); +} + test.describe("record CSV import", () => { - test.use({ - storageState: STORAGE_STATE_PATH, - viewport: { width: 390, height: 844 }, + test.use({ storageState: criticalStorageState("labs") }); + + test.beforeEach(async ({}, testInfo) => { + // Fixture hygiene: what a previous run or a retry left on the account. + await resetCriticalAccount(criticalAccount("labs", testInfo), [ + "DELETE FROM lab_results WHERE user_id = $1", + ]); }); - test("checks, imports and then only finds duplicates", async ({ page }) => { + test("checks, imports, lists the result and then only finds duplicates @critical", async ({ + page, + }) => { await page.goto("/settings/export"); await page.locator('[data-slot="record-import-open-labs"]').click(); const sheet = page.locator('[data-slot="record-import-sheet"]'); @@ -48,19 +88,42 @@ test.describe("record CSV import", () => { await expect( page.locator('[data-slot="record-import-error-row"]'), ).toHaveCount(1); + // The check is a dry run. + expect(await storedResults(page)).toEqual([]); await page.locator('[data-slot="record-import-run"]').click(); await expect( page.locator('[data-slot="record-import-done"]'), ).toBeVisible(); + expect(await storedResults(page)).toEqual([ + { analyte: ANALYTE, value: 5.4, unit: "mmol/L" }, + ]); - // Second run of the same file. - await page.keyboard.press("Escape"); - await page.locator('[data-slot="record-import-open-labs"]').click(); - await page.locator('[data-slot="record-import-file"]').setInputFiles(file); - await page.locator('[data-slot="record-import-check"]').click(); - await expect( - page.locator('[data-slot="record-import-run"]'), - ).toBeDisabled(); + await test.step("the result is on the labs page", async () => { + await page.goto("/labs"); + const analyte = page + .locator('[data-slot="lab-list-analyte"]') + .filter({ hasText: ANALYTE }) + .filter({ visible: true }); + await expect(analyte).toHaveCount(1); + // The cell the name heads carries the latest reading under it. + await expect(analyte.locator("xpath=../..")).toContainText("5.4"); + }); + + await test.step("a second run of the same file", async () => { + await page.goto("/settings/export"); + await page.locator('[data-slot="record-import-open-labs"]').click(); + await page + .locator('[data-slot="record-import-file"]') + .setInputFiles(file); + await page.locator('[data-slot="record-import-check"]').click(); + await expect( + page.locator('[data-slot="record-import-checked"]'), + ).toBeVisible(); + await expect( + page.locator('[data-slot="record-import-run"]'), + ).toBeDisabled(); + expect(await storedResults(page)).toHaveLength(1); + }); }); }); diff --git a/src/__tests__/e2e-critical-no-api-stub-guard.test.ts b/src/__tests__/e2e-critical-no-api-stub-guard.test.ts index c571d4bbd..5a51fb4a1 100644 --- a/src/__tests__/e2e-critical-no-api-stub-guard.test.ts +++ b/src/__tests__/e2e-critical-no-api-stub-guard.test.ts @@ -49,7 +49,7 @@ const E2E = join(process.cwd(), "e2e"); * the set is the release gate, so a journey that loses its tag is a change * somebody should see here. */ -const CRITICAL_TEST_FLOOR = 12; +const CRITICAL_TEST_FLOOR = 13; /** A test whose title literal carries the tag, however the call is spelled. */ const CRITICAL_TEST =