Skip to content

Commit 26e8b51

Browse files
dodsonmgclaude
andcommitted
Add project verifier-gui skill for browser-based verification
Playwright is added as a devDependency (not wired into npm test/CI) so the skill's one-time browser-driving recipe is available without an ad-hoc install/cleanup dance. The docs-check PR hook now also nudges to keep the skill's selector reference in sync when a change touches UI selectors/ labels/aria-labels. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 3787b6a commit 26e8b51

4 files changed

Lines changed: 141 additions & 1 deletion

File tree

.claude/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"hooks": [
77
{
88
"type": "command",
9-
"command": "grep -q 'gh pr create' && printf '{\"systemMessage\":\"Docs check: confirm SPEC.md, README.md and CLAUDE.md reflect this change before merging.\",\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"additionalContext\":\"Docs check: before this PR is merged, confirm SPEC.md, README.md and CLAUDE.md reflect the change; update any that drifted and push (the PR auto-updates).\"}}' || true"
9+
"command": "grep -q 'gh pr create' && printf '{\"systemMessage\":\"Docs check: confirm SPEC.md, README.md, CLAUDE.md, and .claude/skills/verifier-gui/SKILL.md (if UI selectors/labels changed) reflect this change before merging.\",\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"additionalContext\":\"Docs check: before this PR is merged, confirm SPEC.md, README.md and CLAUDE.md reflect the change, and update .claude/skills/verifier-gui/SKILL.md if this PR touched UI selectors, labels, or aria-labels the skill references; update any that drifted and push (the PR auto-updates).\"}}' || true"
1010
}
1111
]
1212
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
---
2+
name: verifier-gui
3+
description: This project's GUI verifier for the /verify skill — drives the DTS Expense Tracker in a real browser (Playwright) to visually confirm a UI change, beyond what vitest/RTL asserts.
4+
---
5+
6+
The generic `/verify` skill looks for a `verifier-*` skill matching the
7+
surface before cold-starting; this is that skill for this repo's surface
8+
(browser/GUI — it's a PWA with no CLI or server API). It's a **manual
9+
verification recipe**, not a test suite — it isn't wired into `npm test` or
10+
CI. Reach for it when a change touches rendering/styling that component tests
11+
(jsdom) can assert structurally (class names, text) but can't show you
12+
visually — e.g. color precedence between two highlight states, layout, or the
13+
real download flow for CSV/`.xlsx` exports.
14+
15+
## One-time setup
16+
17+
```bash
18+
npx playwright install chromium # ~100MB, cached per-machine, only needed once
19+
```
20+
21+
`playwright` is a devDependency (added for this purpose only — not imported
22+
by any app or test code), so `npm ci` already pulls the package; only the
23+
browser binary needs the explicit install above.
24+
25+
## Drive it
26+
27+
```bash
28+
npm run dev -- --port 5183 # note the printed base path, e.g. /dts-expense-tracker/
29+
```
30+
31+
Write a throwaway script at the **repo root** (not `/tmp` — it needs
32+
`node_modules` resolution) and run it with plain `node`, e.g.
33+
`node verify-scratch.mjs`. Delete it before committing anything — it's
34+
scratch, not a fixture.
35+
36+
```js
37+
import { chromium } from 'playwright';
38+
39+
const BASE = 'http://localhost:5183/dts-expense-tracker/'; // match the dev server's base path
40+
41+
const browser = await chromium.launch();
42+
const page = await browser.newPage({ viewport: { width: 420, height: 900 } }); // iPhone-ish width; this is a mobile PWA
43+
await page.goto(BASE);
44+
await page.evaluate(() => localStorage.clear()); // fresh trip, no leftover IndexedDB/localForage state
45+
await page.reload();
46+
```
47+
48+
Key selectors (all accessible-name based; the tab bar's icon glyphs are
49+
`aria-hidden`, so the button's name is just the label):
50+
51+
- Tabs: `page.getByRole('button', { name: 'Entry', exact: true })` — same
52+
pattern for `List`, `M&IE`, `Totals`, `Export`.
53+
- Entry form: `getByLabel('GBP (receipt)')`, `getByLabel('USD (DTS)')`,
54+
category via `page.locator('select')` + `.selectOption('TRANSPORT')`,
55+
payment via `page.locator('.toggle__opt', { hasText: 'Personal' }).click()`.
56+
Save with `getByRole('button', { name: 'Save & view list' })`.
57+
- Totals inputs: `getByLabel('DTS USD total for LODGING')`,
58+
`getByLabel('DTS USD reimbursement for GTCC')` (also `Personal`).
59+
- Recon row state: `page.$$eval('.recon__row', els => els.map(el =>
60+
({ text: el.textContent, className: el.className })))` — cheapest way to
61+
confirm which highlight class (`recon__row--mismatch`,
62+
`recon__row--incomplete`, etc.) actually won, and read off the rendered
63+
text in one shot.
64+
- Screenshot: `page.screenshot({ path: '/tmp/whatever.png', fullPage: true })`,
65+
then `Read` the PNG to eyeball it.
66+
67+
## Exports
68+
69+
`ExportView`'s download buttons use `<a download>` + an object URL — capture
70+
them with Playwright's download event, not by inspecting the DOM:
71+
72+
```js
73+
const [download] = await Promise.all([
74+
page.waitForEvent('download'),
75+
page.getByRole('button', { name: 'Download CSV' }).click(), // or 'Download .xlsx'
76+
]);
77+
const buf = fs.readFileSync(await download.path());
78+
```
79+
80+
CSV is readable directly (`buf.toString('utf8')`); for `.xlsx`, load it with
81+
`exceljs` (already a project dependency) the same way `xlsx.test.ts`'s
82+
`readBack` helper does.
83+
84+
## Cleanup
85+
86+
- Kill the dev server (`pkill -f "vite --port 5183"` or just Ctrl-C the
87+
foreground job).
88+
- Delete the scratch script — it's not a fixture, don't commit it.
89+
- Fill colors are intentionally not asserted in vitest (see `xlsx.test.ts`
90+
comment) — this recipe is the only way those actually get eyeballed, so
91+
don't skip it for changes that touch `pendingFill`/`mismatchFill`/CSS.

package-lock.json

Lines changed: 48 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"eslint-plugin-react-refresh": "^0.5.3",
3737
"globals": "^17.7.0",
3838
"jsdom": "^29.1.1",
39+
"playwright": "^1.61.1",
3940
"typescript": "^5.7.3",
4041
"typescript-eslint": "^8.62.1",
4142
"vite": "^6.0.7",

0 commit comments

Comments
 (0)