Skip to content

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.