Skip to content

fix(map): select capture tests with a precise grep, not a loose substring - #340

Merged
BenSheridanEdwards merged 1 commit into
mainfrom
fix/capture-grep-precision
Aug 4, 2026
Merged

fix(map): select capture tests with a precise grep, not a loose substring#340
BenSheridanEdwards merged 1 commit into
mainfrom
fix/capture-grep-precision

Conversation

@BenSheridanEdwards

@BenSheridanEdwards BenSheridanEdwards commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Why does this feature exist?

styleproof-map selected the capture with --grep 'styleproof capture'.

Playwright compiles a plain --grep string as new RegExp(pattern, 'gi')case-insensitive — and matches it against the whole grep title path: file path, every enclosing describe, the test title, and tags. From playwright/lib/util.js:

function forceRegExp(pattern) {
  const match = pattern.match(/^\/(.*)\/([gi]*)$/);
  if (match) return new RegExp(match[1], match[2]);
  return new RegExp(pattern, "gi");     // ← plain strings become case-insensitive
}

So the selector matched any consumer test whose title merely mentioned StyleProof capture in prose.

Found in a consumer whose behaviour spec was titled:

ci runners: the StyleProof capture fixture actually contains the lane panel

That spec asserts head-only UI. styleproof-ci --spec-ref overlays the head harness onto the base checkout, so once selected as a capture test it ran against the base application, failed, and took the entire base capture down with it:

✘ ci runners: the StyleProof capture fixture actually contains the lane panel
    Expected: 3   Received: 0
base capture failed (exit 1) — continuing with a bare baseline
230 new surface(s) with no baseline

The head capture passed and the run reported success. A gate that certifies nothing while looking green is the worst failure this tool can have, and the consumer needed a full investigation to find it — the log names a spec, not a selector.

What changed?

  • The selector is now the regex literal /(?:^|\s)styleproof capture(?:\s|$)/. The /.../ form makes Playwright honour it as a regex with no flags, so matching is case-sensitive, and the boundaries stop it matching a longer word. It still selects both capture blocks and the styleproof browser build test nested inside them, because a describe title is part of every descendant's grep title.
  • It lives in src/runner.ts beside the test.describe titles it has to agree with, exported as CAPTURE_TEST_GREP, and bin/styleproof-map.mjs imports it. Those were two independent string literals with nothing holding them together; either could drift silently.
  • Not exported from src/index.ts — an internal contract between the runner and the map CLI, so the public API, the README tables and the Action inputs are all unchanged.

Considered and rejected:

  • Restricting selection to --spec's file. A consumer may legitimately split captures across files, and silently dropping those would reduce the captured surface set — the one failure this tool exists to prevent. Worse than the bug.
  • Playwright tags (test.describe('…', { tag: '@styleproof' })). Collision-proof, but tags need Playwright >= 1.42 and the declared peer floor is >= 1.40.

Residual, stated plainly: a consumer title containing the exactly-lowercase phrase styleproof capture would still be selected. Case-sensitivity removes the realistic collision — prose naturally capitalises the product name, which is exactly how the consumer above hit it — without a peer bump or a silent change to what gets captured.

Behavioural Proof (with video and screenshots)

Not applicable — this changes which Playwright tests a CLI selects. There is no rendered surface, no captured map and no report output to screenshot; the observable behaviour is the argument passed to playwright test and the set of tests it matches. The deterministic evidence is the unit suite below, which asserts against Playwright's own matching semantics.

test:e2e not run: this touches neither src/capture.ts nor the browser-evaluated functions (capturePage, snapSubtree, pathsForSelector).

Verification Summary

  • Commands run:
    • npm test (builds, then node --test over test/*.test.mjs)
    • npm run typecheck
    • npm run lint
    • npm run format:check
  • Results:
    • npm test635 passed, 0 failed
    • typecheck / lint / format:check — clean
  • New suite test/capture-grep.test.mjs reproduces Playwright's forceRegExp from playwright/lib/util.js rather than asserting against a hand-written regex, so it tracks what Playwright actually does with the string we hand it. It covers:
    • all three capture title shapes are still selected (plain, crawl, and the nested browser-build test);
    • the real consumer title is not selected;
    • the old bare-string selector did match that title — proving the defect was real and that this suite would not have caught it before;
    • a longer word (restyleproof capturely) is not matched;
    • bin/styleproof-map.mjs selects with the shared constant and carries no literal of its own.
  • Two existing assertions in test/cli.test.mjs that pinned the old invocation string were updated to the new one.
  • Known risks or skipped checks:
    • build (node 22) is red on this branch and on main: the Dependency audit step fails on a fast-uri advisory (GHSA-7p8r-x3mc-p8w7) in a transitive dependency. Reproduced on a clean main checkout with npm audit. Unrelated to this change, which adds no dependencies. Note the audit step gates the Unit tests step after it, so node 22 reports no test result on any branch until that advisory is resolved.
    • The residual lowercase-collision case described above.

Generated by Claude Code

…ring

`styleproof-map` selected the capture with `--grep 'styleproof capture'`.
Playwright compiles a plain `--grep` string as `new RegExp(pattern, 'gi')` —
case-INSENSITIVE — and matches it against the whole grep title path: file path,
every enclosing describe, the test title, and tags. So the selector matched any
consumer test whose title merely MENTIONED StyleProof capture in prose.

That is not hypothetical. A consumer spec titled

    "ci runners: the StyleProof capture fixture actually contains the lane panel"

was selected as a capture test. `styleproof-ci --spec-ref` overlays the head
harness onto the BASE checkout, so it then ran against the base application,
asserted head-only UI, failed, and took the whole base capture down with it:

    base capture failed (exit 1) — continuing with a bare baseline
    230 new surface(s) with no baseline

The head capture passed, the run looked green, and the gate certified nothing.

The selector is now the regex literal `/(?:^|\s)styleproof capture(?:\s|$)/`.
The `/.../` form makes Playwright honour it as a regex with no flags, so
matching is case-SENSITIVE; the boundaries stop it matching a longer word. It
still selects both capture blocks and the `styleproof browser build` test nested
inside them, since a describe title is part of every descendant's grep title.

It also now lives in `src/runner.ts` beside the `test.describe` titles it must
agree with, and `bin/styleproof-map.mjs` imports it. The selector and the titles
were two independent string literals with nothing holding them together; a test
pins that the CLI no longer carries its own copy.

Not exported from `src/index.ts` — this is an internal contract between the
runner and the map CLI, so the public API is unchanged.

Considered and rejected:

- Restricting selection to `--spec`'s file. A consumer may legitimately split
  captures across files, and dropping those would silently reduce the captured
  surface set — the one failure this tool exists to prevent.
- Playwright tags (`{ tag: '@styleproof' }`). They need >= 1.42 and the declared
  peer floor is >= 1.40.

Residual, stated plainly: a consumer title containing the exactly-lowercase
phrase "styleproof capture" would still be selected. Case-sensitivity removes
the realistic collision — prose naturally capitalises the product name — without
a peer bump or a silent coverage change.

Verified: `npm test` 635 passed; typecheck, lint and format:check clean. The new
suite asserts against Playwright's own `forceRegExp` semantics, reproduced from
`playwright/lib/util.js`, rather than a hand-written regex — including a case
proving the OLD bare-string selector did match the consumer title.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFwLkpuAEXYKNNDNQqy4uc
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

🗺️ StyleProof report

📊 View the side-by-side visual report →


Coverage, determinism, or report/diff consistency evidence is incomplete — repair the capture or reflow source; visual approval cannot clear this failure.

@BenSheridanEdwards
BenSheridanEdwards merged commit 96c7d10 into main Aug 4, 2026
11 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants