Vigil
A field report for any public URL.
Vigil is a website health auditor. You give it a public address. It opens the site in a real headless browser, discovers controls, clicks or taps the safe ones, measures what actually changed, and writes PASS / FAIL / WARN / SKIP from those measurements.
It does not invent passes. If it cannot operate a control, that is SKIP, not FAIL. If a click lands and nothing measurable happens, that is FAIL.
Reusable on any public http / https site. One job per run. History keeps prior reports. Export Markdown, HTML, or JSON.
What you give it
| Input | Required | What it does |
|---|---|---|
| URL | Yes | Site to audit. google.com becomes https://google.com. |
| Depth | Yes (default Quick) | How far to crawl. |
| Viewport | Yes (default Both) | Desktop 1280×800, Mobile 390×844, or both. |
| Login / auth notes | No | Recorded on the report. Does not log in for you. Auth-looking controls stay skipped. |
| Priority pages | No | Extra paths to visit (/pricing, /watch). On Quick they are visited after home. On Full they go first in the crawl queue. |
Blocked: localhost, private IPs, .local / .internal hosts. Only public http/https.
How a scan runs
- Normalize the URL and refuse private targets.
- Launch a real browser (Chrome). If the browser cannot start, fall back to HTTP-only: fetch HTML, list links, no clicks, no play, no layout. The report says which engine ran.
- Open the URL. Record HTTP status. Record the final URL after redirects (
google.com→www.google.comis the same site; a jump to a different registrable domain is flagged). - Dismiss obvious overlays (cookie / “got it” / “accept”) when a matching button is visible.
- Discover visible nav, tabs, buttons, links, media, forms, headings, and page text.
- Interact in this order: tabs A–Z (including nested), then nav, then safe buttons, then media play, then search smoke, then link probes, then layout at the chosen widths.
- Full: crawl every same-origin page found (priority list first, then links discovered on each page), repeat discovery + interaction, until the queue is empty or the safety ceiling.
- Assemble the report: counts, failed-assertion cards, tab coverage tree, inventory, suggested fix order, exports.
Typical Quick run: ~30–40s, one page. Full: walks the whole same-origin site (safety ceiling 80 pages).
If a step stops making progress (e.g. play() on a live stream), a watchdog skips it, records the reason, and continues. After ~55s still frozen, remaining work is aborted and a partial report is written.
Depth and budgets
Hard caps so a huge public site cannot run forever. Full keeps discovering same-origin pages as it walks; each page gets its own nav/button budget.
| Limit | Quick | Full |
|---|---|---|
| Pages | 1 (+ optional priority paths) | 80 (entire same-origin site, safety ceiling) |
| Nav clicks | 12 (home) | 16 per page |
| Button clicks | 12 (home) | 16 per page |
| Link probes | 40 | 400 |
| Media play attempts | 4 | 8 per page |
| Forms (search only auto-submitted) | 4 | 8 per page |
| Tabs activated | 80 | 200 |
| Nested tab depth | 5 | 8 |
| Navigation timeout | 18s | 20s |
| Click timeout | 7s | 8s |
| Link timeout | 7s | 8s |
Over-budget leftovers are INFO (“N links not probed”), not failures.
What it inspects
1. Document load
After each page load it reads visible text. It prefers main / [role=main], but falls back to `document.body` if main is a stub — some site builders put the nav in main.
| Assertion | PASS | FAIL |
|---|---|---|
| HTTP | status < 400 | 4xx/5xx or no response |
| Content | title + enough body text | title/body match an error regex, or visible text < 40 characters |
2. Navigation
Finds links in nav, [role=navigation], and header. Clicks each (budget permitting).
- PASS if the destination’s URL, title, headings, or text contain tokens from the label (e.g. “Equities” →
/equitiesor the word “equities”). - FAIL if the result looks like an error page or is nearly blank.
- WARN if the URL changed but the label cannot be confirmed, or the click did nothing measurable and the label is too generic (
Homeis a common WARN — “home” is ignored as a token). - SKIP if the item is covered / not visible, looks like login, or matches safe-mode (logout, checkout, delete, …).
3. Tabs (A–Z, including tabs inside tabs)
Finds [role=tablist] / [role=tab], sorts names A–Z, activates each, then walks child tablists inside the selected panel. Paths are recorded as Parent > Child.
A tab PASSes if after click:
aria-selected=trueordata-state=active, or- URL changed, or
- main/body text changed enough to count.
| Coverage | Status |
|---|---|
| Tabs found and all activated | PASS |
| Some discovered, none activated | FAIL |
| Partial | WARN |
No role=tab on the page | INFO (not a site bug) |
The report’s Tab coverage A–Z list is the tree of what was found vs activated.
4. Buttons
Visible button, [role=button], input[type=button|submit]. Disabled → INFO. Destructive / payment / auth labels → SKIP.
After a successful click it measures:
- URL change
[role=dialog]/<dialog>count[role=menu|listbox|grid]- iframe count
- high z-index overlay count
aria-expanded- body/main text delta
- popup window
PASS if any of those fire (this is how Google’s apps waffle passes: aria-expanded false→true + iframe + overlay).
FAIL only if the click landed and all of those stayed false.
Then it hits Escape / returns to the page so the next control is not blocked by a menu.
5. Links
Collects visible a[href] on the page (and inventory hrefs). Probes each URL:
- HEAD, follow redirects
- If HEAD is 404 / 403 / 405 / 501 → GET (some hosts 404 on HEAD and 200 on GET)
| Result | Status |
|---|---|
| 2xx | PASS |
| 401 / 403 | SKIP (auth wall, not scored broken) |
| 0 / timeout | FAIL |
| Other 4xx/5xx after GET | FAIL |
External links are labeled with host + path so support.google.com/websearch/… is not shown as a homepage 404.
javascript: hrefs are WARN.
6. Media
Finds video / audio and play-looking controls. Mutes, calls play(), waits ~700ms.
| Outcome | Status |
|---|---|
currentTime > 0 and not paused | PASS |
| Autoplay policy / NotAllowedError | SKIP (browser, not the site) |
| Player error | FAIL |
| Stayed paused | WARN |
| Loose “Play” button click, cannot prove decode | WARN |
7. Forms / search
- Search (
input[type=search], search-named/placeholder fields): types a nonsense probe (zzzz-vigil-probe), presses Enter. PASS if URL or results text change, or an honest empty-state string appears. FAIL if nothing changes. SKIP if the field cannot be driven. - Other forms: SKIP (safe mode). Payment-like field names are called out in the note. Nothing is purchased or submitted.
8. Layout
Sets the viewport and compares documentElement.scrollWidth vs clientWidth.
| Overflow | Status |
|---|---|
| ≤ 8px | PASS |
| 9–23px | WARN (usually 100vw / scrollbar slop) |
| ≥ 24px | FAIL (user must side-scroll) |
Desktop 1280×800 and/or mobile 390×844, depending on the viewport option.
9. Dead app on a 200 page
After load, Vigil records console pageerror, failed same-origin script/css/xhr, mixed http:// assets on HTTPS, and empty #app / #__next / #root shells.
10. Action destinations off-site
Follows book / order / schedule / donate / tickets / maps-looking links (max 6) in a fresh tab. FAIL if that URL is blank or 4xx/5xx. tel: / mailto: are checked for form only.
11. Contact / zip / newsletter forms
Safe probe values (vigil-probe@example.com, zip 10001). Never payment or password. PASS if thanks, validation, or URL change. FAIL if Submit does nothing.
12. Widgets, images, jumps, overlays, phone chrome
- iframes (maps, chat, booking, video): must have src and non-zero size
- visible
<img>:naturalWidth > 0 #sectionlinks: target exists and scrolls into viewelementFromPointon buttons: FAIL if another layer is on top- 390px: hamburger opens, tap targets, sticky header cover, zero-size iframes
Scoring
Read this first.
| Status | Meaning |
|---|---|
| PASS | The assertion ran and the expected measurement happened. |
| FAIL | The assertion ran and the expected measurement did not happen. |
| WARN | Something changed, but we could not confirm the label — or the issue is small (e.g. 10px overflow). |
| SKIP | Not run: hidden, timeout, auth, captcha, safe-mode, autoplay block. Not a failure. |
| INFO | Present but not operable (disabled), or leftover budget. |
The report opens on failed assertions (FAIL + WARN). Passing link checks are hidden until you open the full log. Skips live under Locator not run.
Copy list dumps the failures as a checklist.
Safe mode (always on)
Vigil will not click or submit anything whose label/href looks like:
delete, remove, destroy, cancel subscription, logout / sign out, purchase, buy now, checkout, pay now, add to cart, transfer funds, and similar.
Login / sign up / captcha / password controls are skipped. It does not type into real accounts. It will not scan a LAN or localhost.
What the report contains
- Identity — host you entered, document title, final URL, job id, engine (browser vs HTTP fallback), depth, viewport, duration, pages visited
- Counts — fail / warn / skip / pass
- Tab coverage A–Z
- Failed assertion cards — grouped (blank document, no post-click mutation, HTTP not 2xx, media, form, layout, unconfirmed labels). Each card: control, page, type, measured deltas, expected formula
- Fix order — page load → nav/tabs → links → media → buttons → forms → layout
- Locator not run — deduped skips
- Full finding log — filterable table: Page / Control / Type / Expected / Actual / Status / Notes
- Inventory — every major control and what it *appeared* to do from the DOM (href, submit, play, etc.) — observed, not assumed
- Exports — Markdown, HTML, JSON
Start another URL from the bar on every report so jobs do not get mixed up.
What it does not do
- Log in, solve CAPTCHAs, or use your cookies
- Buy, delete, or change account data
- Run as a real iPhone / Safari / WebKit device farm (Mobile = a 390×844 Chrome viewport)
- Prove visual design, brand, SEO, performance, or accessibility beyond the checks above
- Test authenticated dashboards without a session
- Guarantee that a FAIL is the site’s fault — some FAILs have been our assertions (nav marked as
main, HEAD-only 404s, menus that are notrole=dialog). Those were fixed as we found them; new site patterns can still fool a check - Cover every control on a huge site (see budgets)
- Watch streaming video quality — only “did
play()advancecurrentTime?”
How to use it well
- Quick + Both on the homepage first.
- Treat FAIL as “we clicked/loaded and the measurement failed.” Open the card and read the deltas.
- Treat SKIP as untested.
- If many pages FAIL with the same tiny
mainlength, suspect a builder shell (Vigil now falls back tobodywhenmainis a stub). - Full when you want the whole same-origin site (priority paths first, then every internal page found).
- Re-run after a deploy. Compare job ids — each scan is a new report.
Product in one line
A bounded, safe, browser-driven pass over nav, tabs, buttons, links, media, search, layout, plus deeper work checks (dead shells, outbound actions, forms, widgets, images, jumps, phone chrome) — with a report that shows what was measured, not a vibe.