page-as-data
Use it
1. Pick a Chrome
Pages anyone can open (a public site, or your app in CI): add --launch,
and page-as-data starts its own headless Chrome.
Pages behind a sign-in (most admin screens): start Chrome with a debugging port and its own profile, sign in once, and leave it open.
# Windows
"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222 --user-data-dir=%TEMP%\chrome-debug
# macOS
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug
# Linux
google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug
page-as-data connects to port 9222 by default. It opens its own tab, and
closes it when it is done. Your other tabs are not touched.
2. Read a screen
npx @keenskills/page-as-data read http://localhost:3000/orders --width 390
You get the screen as text: problems first, then dialogs, alerts, headings, form fields, tables, buttons and the visible text.
3. Reproduce a bug
Steps run in the order you give them. --click finds a button or link by its
name. --fill finds a field by its label. --press sends a real key press
(F9, Enter, Escape, a character…), which keyboard-shortcut handlers that
check event.isTrusted accept. --wait-for "text" waits (up to --timeout)
until that text is on screen, for long work whose progress never goes quiet —
a render, an upload. The page settles after each step, including screen
changes an app schedules on a short timer (a fade, then the next route).
npx @keenskills/page-as-data read http://localhost:3000/orders \
--click "New order" --fill "Email=a@b.co" --click "Save" \
--inspect "Save"
--inspect answers the questions you would take a screenshot for: where the
element is, whether it is visible (and if not, why), its colours, and whether
its text is readable. It takes visible text or a CSS selector.
Real output from this repository's test page (the button list and the page text are trimmed here):
Fixture: planted bugs — / @ 390px, settled in 291ms
✔ fill "Name" = "Ada" → input "Name"
✔ click "Add row" → button "Add row"
PROBLEMS
• uncaught exception: Error: Fixture uncaught error | at http://localhost:3000/:101:15
• failed request: GET http://localhost:3000/missing-image.png → 404
• failed request: GET http://localhost:3000/hero.jpg → 404
• failed request: GET http://localhost:3000/api/missing → 404
• console.error: Fixture console error
• broken image: http://localhost:3000/missing-image.png
• invalid field "Email": Enter a valid email address
• layout: Page is 616px wide in a 390px viewport, so it scrolls sideways
• layout: button "Hidden action" is cut off (15% visible) by div.clipping-bar, and nothing scrolls to it
• layout: th "Name" is sticky (top: 48px) inside div.table-box (overflow auto/auto), which never scrolls vertically, so it never sticks, and its offset pushes it 48px down over the content below
• layout (warning): button "Crowded A" is 16×16px, under WCAG 2.2's 24×24px, and too close to button "Crowded B"
• layout (warning): button "Crowded B" is 16×16px, under WCAG 2.2's 24×24px, and too close to button "Crowded A"
OPEN DIALOGS
[Confirm delete] This removes the venue.
ALERTS / STATUS MESSAGES
(alert) Saving failed: the server did not answer
HEADINGS
h1 Planted bugs
h2 Confirm delete
FORM FIELDS
Name [text] = "Ada"
Email [text] = "not-an-email" INVALID: Enter a valid email address
TABLE 0 — 2 rows
Name | Role
Grace | Admin
Ada | User
INSPECT "Faint note" — 1 found
p "Faint note" at x16 y402 358×22: visible
colour rgb(204, 204, 204) on rgb(255, 255, 255), contrast 1.61:1 (TOO LOW); font 16px 400; block, static
INSPECT "Hidden action" — 1 found
button "Hidden action" at x297 y125 140×32: NOT visible (cut off by div.clipping-bar, 15% shown)
colour rgb(0, 0, 0) on rgb(240, 240, 240), contrast 18.43:1 (readable); font 16px 400; inline-block, static
Add --json for the full result, including every table row and link.
4. Check many pages, for CI
npx @keenskills/page-as-data check http://localhost:3000/ http://localhost:3000/orders --widths 390,1440 --launch
Each page is checked at each width. The exit code is 1 when an error is
found, so a CI job fails on a real defect. Warnings (small touch targets,
console.error) do not fail the job unless you add --strict.
# GitHub Actions: after your app is running on port 3000
- run: npx @keenskills/page-as-data check http://localhost:3000/ http://localhost:3000/orders --launch
5. Take a screenshot, only when needed
npx @keenskills/page-as-data read http://localhost:3000/dashboard --screenshot dashboard.png
Use it for images, charts and overall visual polish. Everything else is already in the data.