audit-ui-e2e · git:20260911.22df896 · 2026-09-11 · sha256 a1407b8dac506a4a

audit-ui-e2e git:20260911.22df896A

Immutable. This exact content is served forever at /api/v1/blob/a1407b8dac506a4a.

---
name: audit-ui-e2e
description: >-
  Runs a beginner-mind end-to-end UI audit of any running app — local dev
  server, staging, production, or a specific URL. Drives Chrome through every
  interactive element on the target surface, collects structured findings
  (severity, category, where, symptom, impact, repro, triage), and hands the
  result off to `/triage-board` which produces the Desktop folder (schema +
  JSON + Markdown + single-file HTML viewer with MD/CSV/JSON exports and a
  per-finding Copy as Markdown button). Use when you want fresh-eyes
  verification of a feature, page, modal, flow, branch, or whole app — before
  shipping, before review, before a demo, or any time the UI deserves a
  careful poke.
argument-hint: "[<url-or-path>] [--depth quick|standard|thorough] [--scope <what-to-audit>] [--include-destructive] | help | config | reset"
disable-model-invocation: true
allowed-tools:
  - AskUserQuestion
  - Read
  - Write
  - Edit
  - Bash
  - Glob
  - Grep
  - ToolSearch
metadata:
  trigger: "Before shipping a feature, before review, before a demo, or any time the UI deserves a careful poke with fresh eyes. Works on localhost, staging, prod, PR previews \u2014 anywhere a browser reaches."
  tags: "audit, qa, ui, e2e, browser-automation, review, beginner-mind"
---

# Audit UI End-to-End

Walk a running UI with beginner's mind. Drive the browser through every interactive surface inside the target scope, log each rough edge as a structured finding, and ship a triage-board the user can hand to a teammate or paste into a ticket system.

The target can be **anything reachable in a browser** — a local dev server, staging, production, a specific route, a single modal, an entire flow, a deployed PR preview. Branch context is detected and used as enrichment when the URL is on `localhost`, but isn't required.

## Preferences

_On startup, use the `Read` tool to load `~/.claude/skills/audit-ui-e2e/preferences.md`. If missing, use defaults._

Defaults:
- `default-depth`: `standard`
- `auto-open-triage`: `true` (open the resulting viewer in Chrome at the end)
- `destructive-default`: `skip` (Delete / Purge / Drop / Wipe buttons skipped unless `--include-destructive`)
- `localhost-port-hint`: `3000` (used only to ask "should I start the dev server" when localhost is the target and the port is dead)

## Context

_On startup, use Bash to detect (best-effort, skip on failure):_
- _Whether the user is currently in a git repo (`git rev-parse --is-inside-work-tree`)_
- _If so: current branch, recent commits ahead of main, files changed_
- _A Linear/Jira ticket key from the branch name if pattern matches (e.g. `feat/ais-2060-…`)_
- _If the target URL is on `localhost`: whether the port is listening_

_These are used to seed the audit's metadata + plan, not as preconditions. The skill works fine on any URL with no git context._

## Command routing

Check `$ARGUMENTS`:

- `help` → show help, then stop
- `config` → interactive setup, then stop
- `reset` → delete preferences, confirm, then stop
- bare URL or path as first arg → target (e.g. `/audit-ui-e2e https://app.example.com/dashboard` or `/audit-ui-e2e /w/30/meetings`)
- `--scope <description>` → free-text scope hint (e.g. `--scope "the new export buttons in the header"`)
- `--depth quick|standard|thorough` → override default depth
- `--include-destructive` → don't skip destructive actions; clicks Delete/Purge/etc. but honors confirmation dialogs (missing confirms become findings)
- anything else (including empty) → run the audit, ask for target

### Help

```
audit-ui-e2e — Beginner-mind end-to-end UI audit of any running app

Usage:
  /audit-ui-e2e                                Ask for target, then audit
  /audit-ui-e2e <url-or-path>                  Audit a specific URL (or localhost path)
  /audit-ui-e2e <url> --scope "checkout flow"  Narrow the audit to a named surface
  /audit-ui-e2e <url> --depth quick            Hit the headline surfaces only (~5 min)
  /audit-ui-e2e <url> --depth thorough         Walk every reachable interactive (~30+ min)
  /audit-ui-e2e <url> --include-destructive    Test Delete/Drop/Purge (confirms honored)
  /audit-ui-e2e config                         Set defaults
  /audit-ui-e2e reset                          Clear preferences
  /audit-ui-e2e help                           This help

Target accepts:
  - Full URL:    https://app.example.com/dashboard, https://staging.acme.io
  - Local path:  /w/30/meetings  (resolves against localhost:<port-hint>)
  - Just /:      /  (the app root on localhost)

Output: Hands findings to /triage-board, which produces
  ~/Desktop/triage-boards/<scope-or-host>-<YYYY-MM-DD>/
    ├── report.json      — schema-conformant findings
    ├── report.md        — prose mirror
    └── viewer.html      — single-file viewer (MD/CSV/JSON exports + per-finding Copy as Markdown)
```

## First-time experience

If `preferences.md` is missing, show:
> First time using /audit-ui-e2e — quick context: I drive Chrome (via the
> claude-in-chrome MCP) through any URL you point me at. I won't click anything
> destructive unless you pass `--include-destructive`. Output lands in
> `~/Desktop/triage-boards/` via the `/triage-board` skill — open the viewer,
> click any card's `Copy as Markdown` to paste into Linear/GitHub/Notion.
>
> Run `/audit-ui-e2e config` for setup, or continue with defaults (standard
> depth, destructive skipped).

Then proceed.

---

## Workflow

### Step 1 — Confirm target + scope

Inputs determine what's asked:

- **URL/path was provided** as first arg → use it. Resolve `/relative-path` against `http://localhost:<port-hint>` only if a server is listening; otherwise ask.
- **`--scope`** was provided → use it as the audit's narrative goal (e.g. "the new export buttons in the header").
- **Otherwise** → ask via `AskUserQuestion`:
  1. Target — a URL, path, or short description (e.g. "the meeting selector dropdown on /w/30/meetings").
  2. Scope hint — what specifically to focus on, or "broad walkthrough".
  3. Depth — quick / standard / thorough.
  4. Destructive — skip (default) / include.

Show a concise plan derived from the target + scope + (optional) branch context:

```
Target:  http://localhost:3000/w/30/meetings
Scope:   delete-meeting modal flow (recent PR work)
Depth:   standard
Skipping: Upload Transcript (destructive opted-out)

Plan:
  1. Baseline render at the target
  2. Three-dot menu → Delete Meeting → modal open / X / Esc / Cancel
  3. Bulk select + bulk-action header
  4. Cross-check: insight drawer behavior, filter pills, tab counts

Go?
```

If the target is `localhost` and the port is dead, ask: should we start the dev server? (Detect command from `package.json` — `pnpm run dev` / `npm run dev` / `yarn dev`.)

### Step 2 — Load browser tools, attach to a tab

Use `ToolSearch` to load the Chrome MCP tools that aren't already in context:

```
ToolSearch(query: "select:mcp__claude-in-chrome__tabs_context_mcp,mcp__claude-in-chrome__navigate,mcp__claude-in-chrome__computer,mcp__claude-in-chrome__find,mcp__claude-in-chrome__read_console_messages,mcp__claude-in-chrome__read_network_requests,mcp__claude-in-chrome__browser_batch")
```

Then `tabs_context_mcp` to find or create a tab. Prefer reusing an existing tab on the target host; create new only if no match.

### Step 3 — Drive the browser

For each plan bullet:

1. `navigate` to the relevant URL
2. `screenshot` baseline (in-memory — used by the agent to read state, never persisted)
3. Drive the interaction via `computer` / `browser_batch` (click, type, key)
4. `read_console_messages` if anomalies are suspected (filter to `error|warn|TypeError|Hydration`)
5. `read_network_requests` if a fetch is expected and might be silently absent or 4xx/5xx
6. `screenshot` after the interaction (in-memory)
7. On every observed rough edge → record a finding (see Step 4) with a precise `where` + `repro` so the developer can reproduce it without the screenshot

**Beginner-mind heuristics** (what to look for):
- Empty states with "yet" copy on filtered views (implies "no data ever", not "no results for this filter")
- Sticky toasts (not auto-dismissing) — accumulate as wallpaper
- Buttons that look disabled but have no tooltip explaining why
- Mutations that fire success toasts but don't update the visible state
- Destructive actions without confirmation modals
- Stat badges that disappear instead of showing `0`
- Console warnings: a11y, deprecation, missing aria-describedby, React hydration mismatches
- Dropdowns/filters with no clear "active" indicator or no Clear/All option
- Same UI used for semantically-different concepts (Owner field on Decisions vs Action Items)
- URL state inconsistencies (some filters in URL, others not — breaks deep-linking)
- Inputs that clip long values without scroll/wrap
- Buttons placed dangerously close to destructive ones
- Form fields with no required-indicator + no validation feedback
- Modals that don't auto-close on success
- Mismatched terminology across surfaces (button says X, modal title says Y)

**Safety rules:**
- Default: do NOT click anything labeled Delete / Drop / Purge / Wipe / Reset / Disconnect / Sign out / Cancel subscription. Skip and log "destructive — not tested".
- If `--include-destructive`, click them but honor confirmation dialogs. **If a confirmation dialog is missing, that itself is a finding** (this is how PMI's bulk-delete bug was caught).
- Never enter real credentials, real card numbers, real personal data.
- Never click through third-party OAuth/SSO "Approve" / "Authorize" buttons.
- Never submit forms that send real notifications (Slack/email/SMS) unless the user explicitly approved.

### Step 4 — Capture findings

Maintain an in-memory `findings[]` array as the audit progresses. Each finding:

```json
{
  "id": "B1",
  "severity": "critical | high | medium | low | info",
  "category": "bug | ux | accessibility | content | performance | console",
  "title": "One-line summary",
  "where": "specific surface / route / component",
  "symptom": "what you saw (be concrete — quote labels, name elements)",
  "impact": "why it matters to a user",
  "repro": "step-by-step — precise enough that a developer can reproduce in their own browser without the agent's screenshot",
  "triage": "suggested fix direction (not prescriptive)"
}
```

ID convention: `B#` for bugs, `U#` for UX gaps, `C#` for console/perf/a11y. Number sequentially within each prefix as findings are discovered.

Severity calibration:
- **critical** — data loss, security, breaks a primary user journey, or correctness bug on a destructive action
- **high** — silent failure (toast lies, state doesn't update), schema mismatch, blocking ambiguity
- **medium** — cosmetic but real (sticky toast, contradictory labels, badge disappears)
- **low** — accumulated rough edges (missing tooltips, weak active state, copy nits)
- **info** — console noise from 3rd-party, low-priority observations

Findings are text-only. The `repro` field is the contract — a developer reading the report should be able to reproduce the issue in their own browser without ever seeing a screenshot. If you can't write a clean repro, the finding isn't well-scoped yet.

### Step 5 — Synthesize positives + notes

Before handing off to `/triage-board`, collect:
- `positives[]` — things that worked well (deep-linking, drawer behavior, focus rings, etc.). Keep short — 3-8 lines.
- `notes[]` — side effects of testing if any (data destroyed when `--include-destructive`, accounts created, test uploads, etc.). Be specific — name what was modified.

### Step 6 — Hand off to /triage-board

Build the `report.json` payload conforming to the triage-board schema:

```json
{
  "schemaVersion": "1",
  "meta": {
    "title": "<derived from --scope or target host/path>",
    "date": "<today>",
    "tester": "<git user.name or 'agent'>",
    "surface": "<entry URL + brief description>",
    "branch": "<git branch IF localhost AND inside a repo>",
    "scopeIncluded": [...plan bullets that ran...],
    "scopeExcluded": [...skipped items (destructive, unrelated)...]
  },
  "findings": [ ... ],
  "positives": [ ... ],
  "notes": [ ... ]
}
```

Topic slug for the folder, in priority order:
1. `--scope` value (kebab-cased)
2. Linear/Jira ticket key from branch (if detected)
3. Hostname + path slug (e.g. `app-example-com-dashboard`)
4. `ui-audit` as final fallback

Invoke `/triage-board --findings <path-to-report.json> --topic <slug>` (or write the folder directly using triage-board's template at `~/.claude/skills/triage-board/templates/`).

### Step 7 — Open + report

If `auto-open-triage` is on, `open <triage-folder>/viewer.html`. Print a tight summary:

```
Audited {target} — {N} findings, {scope} scope

  Critical: {n}  High: {n}  Medium: {n}  Low: {n}  Info: {n}

Folder:  ~/Desktop/triage-boards/{slug}-{date}/
Viewer:  ~/Desktop/triage-boards/{slug}-{date}/viewer.html

Hand off: open the viewer, click any card's "Copy as Markdown" to paste into Linear/GitHub/Notion.
```

---

## Principles

1. **Beginner's mind, not test plan** — the value is fresh eyes, not exhaustive coverage. Don't pretend to test what you can't; call out what you skipped (`scopeExcluded`).
2. **Capture, don't fix** — this skill audits; it does NOT touch code. Findings go to a triage doc; fixes are the user's decision (or another skill's).
3. **Default safe** — destructive actions are skipped unless explicitly opted in. The user can `--include-destructive` for a more thorough pass when they're on test data.
4. **Any URL, any time** — works on localhost, staging, prod, PR previews, anywhere a browser can reach. Branch + ticket context is enrichment, not a precondition.
5. **Composable with triage-board** — this skill produces structured findings; `/triage-board` produces the artifact. Keep the boundary clean.
6. **Repro is the contract** — text-only findings. A developer reading the report should be able to reproduce the issue in their own browser using just the `where` + `repro` fields, without ever seeing what the agent saw.
7. **No real credentials, no real data** — credentials, personal info, payment methods, and side-effecting third-party flows are off-limits. Always.

## Learned

_Auto-managed. The skill silently adds preferences here when the user corrects a default (e.g. always include destructive, always start at a specific route on this host). Surface once when adding: "Noted: <pattern>. Saved for next time."_