Skip to main content

PagePatrol

Open-source, local-first QA agent for web pages.

PagePatrol opens your page in a real browser at phone and desktop sizes and looks for the problems people actually run into: text that overlaps or gets cut off, text that is too small or too faint to read, layouts that scroll sideways on a phone, broken images and links, JavaScript errors and failed requests. Every finding comes with a screenshot that shows where it is.

Everything runs on your machine. The checks need no account and no API key. If you want, connect the AI model of your choice and it will look over the screenshots too, the way a human tester would.

Status: early development. Expect rough edges and breaking changes.

Quick start

uv tool install pagepatrol            # or: pipx install pagepatrol
pagepatrol install                    # one-time: download Chromium
pagepatrol check https://example.com
pagepatrol check ./site/index.html --open
pagepatrol check localhost:3000 -v mobile -v tablet -v 1440x900

check prints a summary and writes a report to .pagepatrol/runs/<time>/:

  • report.html with marked-up screenshots per viewport
  • report.json with every finding, for scripts and CI
  • screenshots/ with clean and annotated full-page captures

It exits with code 1 when it finds errors, so it can gate a CI job. Use --fail-on warning to be stricter or --fail-on never to only report.

Browsers

Chromium is the default. Firefox and WebKit, the engine behind Safari, work too, with every check, scenario and explore:

pagepatrol install --browser all          # one-time: download Firefox and WebKit as well
pagepatrol check localhost:3000 --browser firefox --browser safari
pagepatrol run ./my-app --browser all

The report shows each browser side by side. Plain-language steps are worked out by the AI once, in the first browser, and replayed in the others, so extra browsers cost nothing there.

Pages don't look exactly alike in every browser, but each screenshot the AI reviews adds to the cost, so by default it looks at the first browser only. Pick others with --review-browser firefox (repeat it for more) or --review-browser all. Firefox can't pretend to be a phone browser, so at phone size it shows the page in a narrow desktop window; layout problems still show up the same way.

AI review (optional)

pagepatrol init        # pick a provider and model, paste your key
pagepatrol check https://example.com --depth thorough

With a model set up, every check also sends the screenshots and the list of findings to it. The model:

  • confirms each finding, or marks it as intentional or a false alarm (those drop to notes, with the reason shown in the report)
  • points out visible problems the checks can't measure: text over busy images, broken alignment, placeholder text, error messages on the page

Supported: Anthropic, OpenAI, Google Gemini, OpenRouter, Ollama and any OpenAI-compatible server (LM Studio, vLLM and others). The model needs to accept images.

--depth Screens per viewport Effort
quick 2 low
balanced (default) 4 medium
thorough 12 high

What it costs

You pay your provider directly; PagePatrol itself is free. Every check prints how long it took, the tokens used and, when the model's price is known, an estimate of the cost. The report shows the same.

Measured on real pages with the default balanced depth at phone and desktop sizes:

Page Claude Sonnet 5.5 Claude Opus 5.5 Claude Haiku 5.5
Small page (one screen) ~$0.015 ~$0.03 <$0.001
Long page (Hacker News, MDN) ~$0.05 ~$0.10 ~$0.003

Opus and Haiku figures are scaled from the Sonnet runs by list price, so treat them as rough. --depth quick looks at fewer screens and costs about half; thorough looks at up to 12 screens per size and can cost three times as much on long pages. Prices for Claude models ship with PagePatrol (list prices as of 2026-10); for other providers init asks for them, and local models through Ollama are free.

Screenshots of the page go to the provider you picked. Use --no-llm for pages that shouldn't leave your machine, or a local model through Ollama.

Your API key

PagePatrol has no server. Your key stays on your machine and is only ever sent to the provider you chose, by that provider's official SDK.

It looks for a key in this order:

  1. PAGEPATROL_API_KEY
  2. the provider's usual variable: ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY or OPENROUTER_API_KEY
  3. a key you pasted into pagepatrol init, kept in your system keyring (Windows Credential Manager, macOS Keychain, Secret Service on Linux)

If init finds the key in an environment variable it doesn't store it. On systems without a keyring, such as headless Linux, a pasted key goes to ~/.pagepatrol/credentials.toml, readable only by your user. pagepatrol config shows which source is in use without printing the key.

The key is never written to reports, screenshots or the terminal; a test checks this on every change. Pages under test run inside Chromium and can't reach it, and the model never sees it either.

What this doesn't protect against: anything that already runs as your user can read your keyring and environment, and so could a compromised dependency of any tool you run. PagePatrol keeps its dependencies few and is published from GitHub Actions through PyPI trusted publishing, with no long-lived token that could be stolen. If you find a security problem, please report it privately through GitHub's security advisories instead of a public issue.

Scenarios

check looks at one page. A scenario walks through a flow the way a visitor would, and runs the same checks after every step, so a problem shows up at the step that caused it.

# .pagepatrol/scenarios/contact.yaml
name: Contact form sends a message
url: /contact
steps:
  - fill: { target: Email, value: test@example.com }
  - fill: { target: Message, value: Hello }
  - click: Send
  - expect: Thanks for your message
pagepatrol run ./my-app                       # starts the app, runs its scenarios, stops it
pagepatrol run --base-url localhost:3000      # runs everything in .pagepatrol/scenarios
pagepatrol run checkout.yaml -b https://staging.example.com --open

Targets are what people see on the page: a button's text, a field's label or placeholder, a link. PagePatrol finds them the way assistive technology does. When that isn't enough, be explicit: { role: button, name: Send }, { label: Email }, { css: "#send" }, { testid: send-button }, adding nth: 1 to pick one of several. Quote values that contain ?, : or #, as usual in YAML.

Step Example
goto goto: /pricing
click, hover, check, uncheck click: Add to cart
fill, select fill: { target: Email, value: a@b.co }
press press: Enter or press: { key: Enter, target: Search }
wait wait: 500 or wait: { text: Loaded }
expect expect: Saved, expect: { url: /thanks }, { title: ... }, { visible: ... }, { hidden: ... }, { no_text: Error }

A file can hold one scenario or a list under scenarios:, and each scenario can set viewport: mobile. A failed step stops its scenario; the rest are marked skipped. run exits with code 1 if any scenario failed.

On sites other than localhost and local files, PagePatrol asks before clicking anything that looks permanent, such as Delete, Pay or Place order. Pass --yes to skip the question. Without a terminal to ask in (CI), it stops the scenario instead.

For pages behind a login, run pagepatrol login https://example.com/login once. A browser opens, you log in, and the session is saved to .pagepatrol/auth.json, which run then uses. That file holds your login cookies, so add it to .gitignore. The scenarios next to it are worth committing.

Plain-language steps

A step can also be a sentence. The AI model from pagepatrol init works out the clicks and typing, one action at a time:

name: Buy the cheapest mug
url: /shop
steps:
  - Add the cheapest mug to the cart
  - Go to checkout and fill in a test address
  - expect: Order summary

When it gets there, the concrete actions it took are saved to .pagepatrol/recordings.yaml. Later runs replay those without calling the AI, so they cost nothing and give the same result every time. If the page changes and a recording stops working, the AI redoes the step and updates it. Use --fresh to redo all of them, or --no-llm to only replay. The recordings are plain YAML; you can copy them into the scenario if you'd rather keep explicit steps.

A run with AI steps prints how many calls it made and what they cost. Each action is one call with a screenshot, about half a cent with Claude Sonnet 5.5.

Scenarios from your code

Don't want to write them yourself? Let the AI read the project:

pagepatrol plan ./my-app

It works with any language or framework. It reads the source files (respecting .gitignore), tells you how many files it will send and roughly what that costs, and asks before sending. Files like .env, keys and credentials are never sent, and values that look like passwords or tokens are masked. It then lists the scenarios it suggests; you pick which ones to save to the project's .pagepatrol/scenarios/. They use plain-language steps, so the first pagepatrol run ./my-app lets the AI do them and records the result.

On the demo app in this repo, planning cost about $0.01 and the first run of the five planned scenarios about $0.08; runs after that replay for free.

Exploring without scenarios

pagepatrol explore localhost:3000
pagepatrol explore ./my-app        # starts the dev server first, see below

No AI and nothing to write. PagePatrol follows the links on the site, and on each page presses the buttons and fills in the forms with test data (test@example.com and the like), running the usual checks after every action. A button that throws a JavaScript error or a form that breaks on submit shows up as a finding on the action that caused it. It stays on the same site, never follows logout links, and never presses anything that looks permanent, like Delete or Pay.

Pressing buttons and sending forms happens only on localhost and local files unless you pass --interact. On other sites it just follows links. --max-pages (default 15) keeps the walk short.

Starting your app

Point run or explore at a project folder and PagePatrol starts its dev server, waits until it answers, tests it and stops it again, including any processes the server started. It recognizes:

Project Started with
package.json with a dev, start or serve script npm run dev (or pnpm, yarn, bun, going by the lockfile)
Django (manage.py) manage.py runserver
FastAPI or Flask uvicorn or flask run, from the project's .venv if it has one
ASP.NET Core (.csproj using the Web SDK) dotnet run
A folder with index.html a small built-in file server

Dev servers that print their address (Vite, Next.js and most others) are found that way; for the rest PagePatrol picks the port.

If the project's packages aren't installed yet, PagePatrol shows what it would run (npm ci, uv sync, or a fresh .venv with pip install -r requirements.txt) and asks first, since installing runs code from those packages. Say no and it prints the commands so you can run them yourself. --install skips the question, --no-install never installs. Node.js and the .NET SDK themselves aren't installed for you; it tells you where to get them.

If it guesses wrong, tell it once:

pagepatrol run ./my-app --start "npm run dev:web" --base-url localhost:8080

The command and address are saved to .pagepatrol/config.toml in the project, so after that pagepatrol run ./my-app is enough. Scenarios, recordings, logins and reports live in that project's .pagepatrol/ folder too.

What it checks

Check Level
overlap error / warning Text on top of text, or text and controls covering each other
horizontal-overflow error Elements that make the page scroll sideways
viewport-meta error No <meta name=viewport>, so phones show the desktop layout zoomed out
broken-image, broken-link error Images that don't load, links that return 404/410/5xx
js-exception, console-error error Uncaught exceptions and console errors
http-error, failed-request error Resources that fail or return 4xx/5xx
clipped-text warning / info Text cut off by its container
small-text warning Text below 12px
axe/* warning / info WCAG 2.2 A/AA rules from axe-core, including color contrast
perf/* warning / info Slow loading, layout that jumps, a busy main thread, heavy pages and images far larger than shown (see below)

Errors mean something is broken. Warnings are real quality problems on a page that still works. Notes are worth a look but may be intentional.

Performance

check also measures how fast the page loads and shows the numbers at the top of each size in the report. run and explore do the same for every step that loads a page, opening one or following a link, and print the slowest page at the end.

What it means Good Poor
LCP when the main content shows up under 2.5s over 4s
FCP when anything shows up under 1.8s over 3s
CLS how much the layout jumps around while loading under 0.1 over 0.25
TBT how long the page is too busy to respond to a tap under 200ms over 600ms
TTFB how long the server takes to start answering under 0.8s over 1.8s

The limits are Google's, the same ones Lighthouse and PageSpeed Insights use. "Poor" becomes a warning, in between a note. Layout jumps point at the element that moved, and images that are much larger than they're shown are marked on the screenshot.

At phone size in Chromium, the page is loaded the way a mid-range phone on 4G would load it (slower network, 4x slower processor), like Lighthouse does. That's what makes a fast developer machine show realistic numbers. Turn it off with --no-throttle, or skip measuring with --no-perf. Firefox and WebKit can't slow themselves down and can't measure CLS and TBT, so there those are shown as "not measured".

On localhost, sizes come from a dev server that sends unminified code, so a heavy page is only a note there.

Development

Requires uv.

uv sync
uv run playwright install chromium
uv run pytest
uv run ruff check . && uv run ruff format --check .

tests/fixtures/ holds small pages that are broken on purpose, one per check. If you add a check, add a fixture that proves it fires and, where it makes sense, one that proves it stays quiet.

License

MIT. Bundles axe-core (MPL-2.0), see src/pagepatrol/checks/vendor/axe-core.LICENSE.

Metadata

Release files for pagepatrol 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pagepatrol 0.5.0
File Size Uploaded Provenance Actions
pagepatrol-0.5.0.tar.gz 282.2 kB Without provenance Details for pagepatrol-0.5.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pagepatrol 0.5.0
File Interpreter ABI Platform Actions
pagepatrol-0.5.0-py3-none-any.whl Python 3 none any Details for pagepatrol-0.5.0-py3-none-any.whl

Total release size: 563.5 kB

Release files / pagepatrol-0.5.0.tar.gz

Download URL pagepatrol-0.5.0.tar.gz
Size 282.2 kB
Tags Source
SHA-256 checksum
How to use checksums
299f3335c05cfae02c7e7fd9f6ff0636af46577f9537288ca2ab05b64c7d8b03
BLAKE2b-256 checksum
How to use checksums
a004ea9b3e59c98f4f076749c043945ed092065d48284a12f12f7b1a8320856b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / pagepatrol-0.5.0-py3-none-any.whl

Download URL pagepatrol-0.5.0-py3-none-any.whl
Size 281.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
570fee029802bf2ca4f0161c07c9a5f06137d0207d10d71deb638c2138a2d5d2
BLAKE2b-256 checksum
How to use checksums
c14c4044c5104db39866c09753a86c59db90d3a2632ef88868613499a7dbc427
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for pagepatrol 0.5.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page