Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -379,6 +379,11 @@ jobs:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Full history: design-system-adoption validates provenance.candidateSourceHead
# with `git cat-file` / `git show` against an ancestor commit. Default depth-1
# checkouts only have HEAD, so the binding fails with "must identify an existing
# commit" even when the SHA is on the branch (Unit coverage on PR #1729).
fetch-depth: 0
persist-credentials: false

- name: Setup Node and dependencies
Expand Down
4 changes: 3 additions & 1 deletion docs/design-system/adoption-contract.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -62,7 +62,9 @@
"docs/design-system/adoption-contract.json",
"docs/design-system/adoption-manifest.json",
"lighthouse-budget.json",
"tests/__screenshots__/linux/provenance.json"
"scripts/generate-design-system-adoption.mjs",
"tests/__screenshots__/linux/provenance.json",
"tests/design-system-adoption.test.ts"
]
}
},
Expand Down
4 changes: 3 additions & 1 deletion docs/design-system/adoption-manifest.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,7 +73,9 @@
"docs/design-system/adoption-contract.json",
"docs/design-system/adoption-manifest.json",
"lighthouse-budget.json",
"tests/__screenshots__/linux/provenance.json"
"scripts/generate-design-system-adoption.mjs",
"tests/__screenshots__/linux/provenance.json",
"tests/design-system-adoption.test.ts"
]
}
}
Expand Down
5 changes: 5 additions & 0 deletions scripts/generate-design-system-adoption.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,7 +54,12 @@ const CANONICAL_VISUAL_BASELINE_POLICY = Object.freeze({
"docs/design-system/adoption-contract.json",
"docs/design-system/adoption-manifest.json",
"lighthouse-budget.json",
"scripts/generate-design-system-adoption.mjs",
"tests/__screenshots__/linux/provenance.json",
// First adoption also ships the fixture fix that makes emptying AWAITING_BASELINE
// testable; keep it allowlisted so candidateSourceHead can stay on an existing
// main commit (required for shallow CI checkouts and squash-merge survival).
"tests/design-system-adoption.test.ts",
],
},
});
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
63 changes: 63 additions & 0 deletions tests/__screenshots__/linux/provenance.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
{
"schemaVersion": 2,
"platform": "linux",
"runnerImage": "ubuntu-24.04",
"candidateSourceHead": "07ada91b7c29410a0c34cc43934d1a12c686b2cf",
"source": {
"kind": "hosted-ci-artifact",
"runId": "31251091603",
"artifactName": "visual-baseline-31251091603",
"candidateSourceHead": "07ada91b7c29410a0c34cc43934d1a12c686b2cf"
},
"review": {
"status": "approved",
"reviewerType": "human",
"reviewedBy": "BigSimmo",
"reviewedAt": "2026-08-08T11:07:51Z",
"candidateSourceHead": "07ada91b7c29410a0c34cc43934d1a12c686b2cf"
},
"candidates": [
{
"id": "dashboard-shell",
"path": "tests/__screenshots__/linux/dashboard-shell.png",
"sha256": "dc59e0104e4c752f418db88a46acc9f54f07e22e2c6006eaa85af828031e411d",
"width": 1196,
"height": 900
},
{
"id": "dashboard-shell-phone",
"path": "tests/__screenshots__/linux/dashboard-shell-phone.png",
"sha256": "1bcac08d3be07a81ebf155f2502e986983a62efd24e8a3ef1f06a6d897407da7",
"width": 390,
"height": 820
},
{
"id": "search-results-band",
"path": "tests/__screenshots__/linux/search-results-band.png",
"sha256": "46d41e15c3b622501df9c65f67abdfbb069bb292ebb3f4ddd1a09d0ee823ae4a",
"width": 1148,
"height": 115
},
{
"id": "search-results-band-phone",
"path": "tests/__screenshots__/linux/search-results-band-phone.png",
"sha256": "b703eecae02a69f58bf127386653a37f06d2b7e131d2b22bb749a7f059b457ad",
"width": 366,
"height": 89
},
{
"id": "document-viewer",
"path": "tests/__screenshots__/linux/document-viewer.png",
"sha256": "ea3050b274c3bf81f7e6c787b79fa2803ef83c844abea409203275390451b8af",
"width": 1196,
"height": 2903
},
{
"id": "therapy-compass-home",
"path": "tests/__screenshots__/linux/therapy-compass-home.png",
"sha256": "e3030324162bcba8051299281475497c67a5d732c5a3f28fd7728283e1b3bf83",
"width": 1196,
"height": 868
}
]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 14 additions & 4 deletions tests/design-system-adoption.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -87,17 +87,27 @@ function setCurrentAwaitingValues(fixtureRoot: string, values: string) {
);
}

function initialiseCandidateRepository(fixtureRoot: string, awaitingValues?: string) {
/**
* Seeds a fixture repository whose candidate-source commit declares the canonical
* six as awaiting a baseline — the state a capture run is taken from.
*
* The default is the explicit canonical list rather than whatever
* `tests/ui-visual-baseline.spec.ts` happens to say right now. Inheriting the live
* file made every fixture depend on the repository not having adopted its baselines
* yet, so the commit that finally empties `AWAITING_BASELINE` — the outcome this
* contract exists to permit — turned the fixture's own candidate head into "must
* contain exactly the canonical six ids" and failed the two `toEqual([])` cases.
* A fixture states its precondition; it does not borrow it from the tree under test.
*/
function initialiseCandidateRepository(fixtureRoot: string, awaitingValues: string = canonicalAwaitingValues) {
git(fixtureRoot, ["init", "-q"]);
git(fixtureRoot, ["config", "user.email", "fixture@example.invalid"]);
git(fixtureRoot, ["config", "user.name", "Fixture"]);
writeFixtureFile(fixtureRoot, "src/app/page.tsx", "export default function Page() { return null; }\n");
writeFixtureFile(
fixtureRoot,
"tests/ui-visual-baseline.spec.ts",
awaitingValues === undefined
? read("tests/ui-visual-baseline.spec.ts")
: withAwaitingValues(read("tests/ui-visual-baseline.spec.ts"), awaitingValues),
withAwaitingValues(read("tests/ui-visual-baseline.spec.ts"), awaitingValues),
);
return commitFixture(fixtureRoot, "candidate source");
}
Expand Down
36 changes: 36 additions & 0 deletions tests/playwright-settlement-contract.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";

/**
* Ledger #093: Next streaming can leave a hidden duplicate page root. Bare
* `page.getByTestId(...)` then fails Playwright strict mode under full-suite
* load (Production UI shard 2 on PR #1729: `dsm-home-main` resolved to 2).
*
* Page-root / shell surfaces in `tests/ui-route-coverage.spec.ts` must go
* through `visibleByTestId` (or `expectSingleSettledOwner`) from
* `tests/playwright-settlement.ts`. This offline contract keeps that defect
* from returning as a red CI flake.
*/
const ROUTE_COVERAGE = "tests/ui-route-coverage.spec.ts";

const PAGE_ROOT_TEST_IDS = [
"dsm-home-main",
"dsm-home-compare",
"dsm-comparison-page",
"dsm-differential-considerations-page",
"search-query-ribbon",
] as const;

describe("playwright settlement contract (#093)", () => {
it("ui-route-coverage scopes page-root testids through visibleByTestId", () => {
const source = readFileSync(ROUTE_COVERAGE, "utf8");
expect(source).toMatch(/import\s*\{[^}]*\bvisibleByTestId\b[^}]*\}\s*from\s*["']\.\/playwright-settlement["']/);

for (const testId of PAGE_ROOT_TEST_IDS) {
expect(source, `${testId} must use visibleByTestId`).toContain(`visibleByTestId(currentPage, "${testId}")`);
expect(source, `${testId} must not use bare getByTestId`).not.toMatch(
new RegExp(String.raw`getByTestId\(\s*["']${testId}["']\s*\)`),
);
}
});
});
14 changes: 9 additions & 5 deletions tests/ui-route-coverage.spec.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@ import {
getDifferentialRecord,
} from "../src/lib/differentials";
import { loadMedicationSnapshot } from "../src/lib/medication-snapshot";
import { visibleByTestId } from "./playwright-settlement";

const routeViewports = [
{ name: "desktop", width: 1280, height: 900 },
Expand DownExpand Up@@ -247,7 +248,7 @@ test.describe("previously uncovered production routes", () => {
await expect(
currentPage.getByRole("heading", { name: "Anxiety in outpatient care", level: 1, exact: true }),
).toBeVisible();
await expect(currentPage.getByTestId("search-query-ribbon")).toBeVisible();
await expect(visibleByTestId(currentPage, "search-query-ribbon")).toBeVisible();
// The common-search pill lands on `/therapy-compass/search`, which is the
// shared `ModeNav`. It portals into the header collapse host (outside
// [data-therapy-root]), so read the canvas colour from the workspace root
Expand DownExpand Up@@ -279,11 +280,14 @@ test.describe("previously uncovered production routes", () => {
page,
"/dsm",
async (currentPage) => {
await expect(currentPage.getByTestId("dsm-home-main")).toBeVisible();
// Scope to the visible owner: Next streaming can leave a hidden duplicate
// page root (#093), and bare getByTestId then fails Playwright strict mode
// (Production UI shard 2 on PR #1729).
await expect(visibleByTestId(currentPage, "dsm-home-main")).toBeVisible();
await expect(currentPage.getByRole("heading", { name: "DSM-5 Diagnosis", level: 1 })).toBeVisible();
},
async (currentPage) => {
const compare = currentPage.getByTestId("dsm-home-compare");
const compare = visibleByTestId(currentPage, "dsm-home-compare");
await expect(compare).toBeEnabled();
await compare.click();
await expect(currentPage).toHaveURL(/\/dsm\/compare$/);
Expand All@@ -297,7 +301,7 @@ test.describe("previously uncovered production routes", () => {
page,
"/dsm/compare?ids=major-depressive-disorder,bipolar-ii-disorder",
async (currentPage) => {
await expect(currentPage.getByTestId("dsm-comparison-page")).toBeVisible();
await expect(visibleByTestId(currentPage, "dsm-comparison-page")).toBeVisible();
await expect(currentPage.getByRole("heading", { name: "Compare DSM diagnoses", level: 1 })).toBeVisible();
},
async (currentPage) => {
Expand All@@ -317,7 +321,7 @@ test.describe("previously uncovered production routes", () => {
page,
"/dsm/diagnoses/major-depressive-disorder/differentials",
async (currentPage) => {
await expect(currentPage.getByTestId("dsm-differential-considerations-page")).toBeVisible();
await expect(visibleByTestId(currentPage, "dsm-differential-considerations-page")).toBeVisible();
await expect(currentPage.getByRole("heading", { name: "Major depressive disorder", level: 1 })).toBeVisible();
},
async (currentPage) => {
Expand Down
9 changes: 1 addition & 8 deletions tests/ui-visual-baseline.spec.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -154,14 +154,7 @@ async function settle(page: Page, target: BaselineTarget): Promise<Locator> {
* that adds the PNG. `declares no baseline it already has` below fails if the two
* ever disagree, so the list cannot rot into a permanent exemption.
*/
const AWAITING_BASELINE: ReadonlySet<string> = new Set([
"dashboard-shell",
"dashboard-shell-phone",
"search-results-band",
"search-results-band-phone",
"document-viewer",
"therapy-compass-home",
]);
const AWAITING_BASELINE: ReadonlySet<string> = new Set([]);

/**
* Where a candidate PNG is written when no baseline exists.
Expand Down
Loading