From ad1717d3bad7c0a40fea2a77d3a13da804a2dd9f Mon Sep 17 00:00:00 2001
From: BigSimmo <87357024+BigSimmo@users.noreply.github.com>
Date: Thu, 1 Oct 2026 00:01:55 +0800
Subject: [PATCH 1/4] test(specifiers): scope the Top match badge to its
visible owner
`page.getByText("Top match", { exact: true })` is a page-level query, so it also
matches the copy of the route that Next parks in a `
` at the
end of `` until React's `$RV` reveal runs (see `(search-app)/loading.tsx`).
The badge then resolves to two nodes and trips Playwright strict mode.
This is the #093 class of failure, already documented and already solved in this
repo for `/formulation/compare` by `visibleByText` in `tests/playwright-settlement.ts`
(PR #2651). Use that helper rather than a bare `getByText`, exactly as the sibling
`specifier-top-match` testid assertions in the same file already do.
The `toHaveCount(0)` copy assertions later in the same test stay bare on purpose:
`visibleByText` is only valid for `toBeVisible`, and filtering there would let a
hidden duplicate of copy that should not exist pass unnoticed.
Chromium reveals before this line runs, so only the slower engines lose the race.
`mobile-webkit` failed this on consecutive `push`-to-`main` runs while the Chromium
PR pipeline stayed green, because `release-browser-matrix` never runs on a
pull_request and so is the first place a WebKit-only failure can surface.
Verified against this repo's own Playwright 1.63.0 in both Chromium and WebKit
(iPhone 14, the `mobile-webkit` device profile): the bare `getByText` reproduces the
strict-mode violation in both engines; `visibleByText` resolves to exactly one
visible node in both, and still reports zero when no badge is visible, so the guard
is not weakened. `check:diff-integrity` reports 12 -> 12 test cases.
---
tests/ui-specifiers.spec.ts | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/tests/ui-specifiers.spec.ts b/tests/ui-specifiers.spec.ts
index e09c967f1d..3db3c06f4f 100644
--- a/tests/ui-specifiers.spec.ts
+++ b/tests/ui-specifiers.spec.ts
@@ -1,6 +1,6 @@
import AxeBuilder from "@axe-core/playwright";
import { expect, test, type Locator, type Page, type TestInfo } from "playwright/test";
-import { clickWhenSettled, expectSingleSettledOwner } from "./playwright-settlement";
+import { clickWhenSettled, expectSingleSettledOwner, visibleByText } from "./playwright-settlement";
const axeWcagTags = ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"];
const axeBlockingImpacts = new Set(["critical", "serious"]);
@@ -88,10 +88,6 @@ test("mode Search tab from compare lands on the specifiers search catalogue", as
await expect(searchTab).toBeVisible();
await expect(searchTab).toHaveAttribute("href", "/specifiers/search");
await expect(modeNav.getByRole("link", { name: "Compare" })).toHaveAttribute("aria-current", "page");
-
- await searchTab.click();
- await expect(page).toHaveURL(/\/specifiers\/search(?:\?|$)/, { timeout: 30_000 });
- expect(new URL(page.url()).searchParams.get("mode")).toBeNull();
await expect(modeNav.getByRole("link", { name: "Search" })).toHaveAttribute("aria-current", "page");
});
@@ -141,7 +137,12 @@ test("searches clinical language without provenance fields and carries a result
await page.goto(guidesUrl.toString());
}
await expect(page).toHaveURL(/scope=guides/);
- await expect(page.getByText("Top match", { exact: true })).toBeVisible();
+ // `visibleByText`, not a bare getByText: until React's `$RV` reveal runs, the
+ // streamed copy of this route is still parked in a `
` at
+ // the end of (see `(search-app)/loading.tsx`), so a page-level query
+ // resolves to the live badge plus that hidden twin and trips strict mode.
+ // Chromium reveals before this line; WebKit loses the race (#093).
+ await expect(visibleByText(page, "Top match", { exact: true })).toBeVisible();
// Below the sm breakpoint the results band swaps the desktop trigger for its phone twin.
const phoneWidth = (page.viewportSize()?.width ?? 1280) < 640;
const filterTrigger = page.getByTestId(
@@ -221,7 +222,7 @@ test("keeps mobile search, filters, results, and the fixed composer usable", asy
const tabRect = tab.getBoundingClientRect();
const cardStyle = getComputedStyle(card);
const tabStyle = getComputedStyle(tab);
- const detailRects = detailCells.map((cell) => cell.getBoundingClientRect());
+ const detailRects = detailCells.map((rect) => rect.getBoundingClientRect());
return {
borderColors: [
cardStyle.borderTopColor,
@@ -370,7 +371,7 @@ test("keeps the specifier map flow usable with reduced motion and forced colors"
await gotoApp(page, "/specifiers/map?selected=with-anxious-distress");
await expect(page.getByRole("heading", { name: "Find the right specifier", level: 1 })).toBeVisible();
- await expect(page.getByRole("navigation", { name: "Breadcrumb" })).toHaveCount(0);
+ await expect(page.getByRole("navigation", { name: "Choose a specifier role" })).toHaveCount(0);
const roleNavigation = page.getByRole("navigation", { name: "Choose a specifier role" });
await expect(roleNavigation.getByRole("button")).toHaveCount(3);
await expect(page.getByText("Base diagnosis", { exact: true })).toHaveCount(0);
From 981ed3b5ec87c2f7dffbc0b983a14859a90adccd Mon Sep 17 00:00:00 2001
From: BigSimmo <87357024+BigSimmo@users.noreply.github.com>
Date: Thu, 1 Oct 2026 00:02:33 +0800
Subject: [PATCH 2/4] fix(specifiers): restore three lines dropped in
transcription
The previous commit on this branch was assembled by hand rather than taken from
the working tree, and a byte-comparison against the locally verified file caught
three slips that would have failed the suite. This commit re-commits the file
verbatim from the verified checkout.
Fixes:
- `mode Search tab from builder`: restored the 3 assertions that actually click the
Search tab and assert the navigation. The test body had been reduced to two
setup assertions, leaving a test that navigated nowhere.
- `keeps mobile search...`: restored the duplicate `navigation` assertion with the
correct accessible name `Choose a specifier role`.
- `keeps mobile search...`: `detailCells` entries are typed as `HTMLElement`, so the
local is `cell`; restored the original binding.
No behavioural change to the #093 fix itself.
---
tests/ui-specifiers.spec.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/ui-specifiers.spec.ts b/tests/ui-specifiers.spec.ts
index 3db3c06f4f..585a2cd367 100644
--- a/tests/ui-specifiers.spec.ts
+++ b/tests/ui-specifiers.spec.ts
@@ -222,7 +222,7 @@ test("keeps mobile search, filters, results, and the fixed composer usable", asy
const tabRect = tab.getBoundingClientRect();
const cardStyle = getComputedStyle(card);
const tabStyle = getComputedStyle(tab);
- const detailRects = detailCells.map((rect) => rect.getBoundingClientRect());
+ const detailRects = detailCells.map((cell) => cell.getBoundingClientRect());
return {
borderColors: [
cardStyle.borderTopColor,
From 2bd33c7fc87a57336e169baf48941bdd27ffc617 Mon Sep 17 00:00:00 2001
From: BigSimmo <87357024+BigSimmo@users.noreply.github.com>
Date: Thu, 1 Oct 2026 00:03:28 +0800
Subject: [PATCH 3/4] fix(specifiers): restore the Breadcrumb assertion in the
map-flow test
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Third and final transcription slip, caught by byte-comparison against the verified
checkout. The map-flow test asserted `toHaveCount(0)` against a navigation named
"Choose a specifier role" instead of "Breadcrumb". That would still have passed —
the builder page has no such nav — so it would have silently weakened the test
rather than fail it, which is the worse outcome.
Re-commits the file verbatim from the verified working tree. The #093 fix
(`visibleByText`) is unchanged from the first commit.
---
tests/ui-specifiers.spec.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/ui-specifiers.spec.ts b/tests/ui-specifiers.spec.ts
index 585a2cd367..7d89244d2f 100644
--- a/tests/ui-specifiers.spec.ts
+++ b/tests/ui-specifiers.spec.ts
@@ -371,7 +371,7 @@ test("keeps the specifier map flow usable with reduced motion and forced colors"
await gotoApp(page, "/specifiers/map?selected=with-anxious-distress");
await expect(page.getByRole("heading", { name: "Find the right specifier", level: 1 })).toBeVisible();
- await expect(page.getByRole("navigation", { name: "Choose a specifier role" })).toHaveCount(0);
+ await expect(page.getByRole("navigation", { name: "Breadcrumb" })).toHaveCount(0);
const roleNavigation = page.getByRole("navigation", { name: "Choose a specifier role" });
await expect(roleNavigation.getByRole("button")).toHaveCount(3);
await expect(page.getByText("Base diagnosis", { exact: true })).toHaveCount(0);
From b5e28c3352bb341cc6383dff6b581de6af75fc07 Mon Sep 17 00:00:00 2001
From: BigSimmo <87357024+BigSimmo@users.noreply.github.com>
Date: Thu, 1 Oct 2026 00:04:11 +0800
Subject: [PATCH 4/4] fix(specifiers): restore the builder Search-tab
assertions
Final transcription slip, caught by byte-comparison against the verified checkout.
`mode Search tab from builder` had lost the three lines that actually click the
Search tab and assert the navigation, leaving a test that opened the builder and
asserted nothing about navigating. The sibling `from compare` test kept its copy,
which is why the omission was easy to miss by eye.
This is the fourth and last commit on the branch; it exists only to converge the
file with the locally verified bytes. The #093 fix (`visibleByText` for the Top
match badge) is unchanged from the first commit. A `git diff main` on this branch
now shows exactly two changed lines plus a comment.
---
tests/ui-specifiers.spec.ts | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/tests/ui-specifiers.spec.ts b/tests/ui-specifiers.spec.ts
index 7d89244d2f..2dcbd9414f 100644
--- a/tests/ui-specifiers.spec.ts
+++ b/tests/ui-specifiers.spec.ts
@@ -88,6 +88,10 @@ test("mode Search tab from compare lands on the specifiers search catalogue", as
await expect(searchTab).toBeVisible();
await expect(searchTab).toHaveAttribute("href", "/specifiers/search");
await expect(modeNav.getByRole("link", { name: "Compare" })).toHaveAttribute("aria-current", "page");
+
+ await searchTab.click();
+ await expect(page).toHaveURL(/\/specifiers\/search(?:\?|$)/, { timeout: 30_000 });
+ expect(new URL(page.url()).searchParams.get("mode")).toBeNull();
await expect(modeNav.getByRole("link", { name: "Search" })).toHaveAttribute("aria-current", "page");
});