playwright-qa · git:20261004.8dfccd1 · 2026-10-04 · sha256 ed74c73b980b5118

playwright-qa git:20261004.8dfccd1A

Immutable. This exact content never changes and is served at /api/v1/blob/ed74c73b980b5118.

---
name: playwright-qa
description: Browser-based QA contract for frontend tasks — resolution chain CLI→MCP→env-browser, headed/headless modes, artifact layout under datarim/qa/.
current_aal: 1
target_aal: 2
---

# Playwright QA — Browser-Based Frontend Verification

When a task changes files that affect the rendered UI, `/dr-qa` runs a real
browser pass against the project's local dev surface (or a static fixture)
and captures evidence. This skill defines the contract: when the pass
fires, which tool gets used, how the run is isolated from concurrent
agents, and where artifacts land.

The skill is **opt-in by detection**: tasks that do not touch frontend
files never trigger a Playwright pass. Missing tooling is a finding, not
a block — the operator's pipeline keeps moving.

## Frontend touch detection

A task is considered «frontend-touching» when the changed-files set
contains at least one entry matching:

- `*.html`, `*.htm`
- `*.css`, `*.scss`, `*.sass`, `*.less`
- `*.tsx`, `*.jsx`, `*.vue`, `*.svelte`
- `*.php` whose body matches an HTML-markup heuristic
  (`grep -qE '<(html|body|div|section|form|main|nav|header|footer|button|input)\b' <file>`)

Detection runs on `git diff --name-only <base>...HEAD` against the
canonical base (`main` for most repos, configurable via
`DATARIM_FRONTEND_BASE` env var, default `origin/main`). Renamed and
deleted entries are ignored — Playwright runs only against present
markup.

## Resolution chain

`dev-tools/detect-playwright-tooling.sh` resolves the tool to use:

1. **Override** — `DATARIM_PLAYWRIGHT` env var, one of
   `playwright-cli | playwright-mcp | env-browser | none`. Operator escape
   hatch; bypasses probing entirely.
2. **CLI** — `playwright` binary on `PATH`, probed with `--version`. First
   preference for determinism and version pinning.
3. **MCP** — `DATARIM_PLAYWRIGHT_MCP_AVAILABLE=1` env var, or a
   `playwright-mcp` binary on `PATH`. Used when the runtime exposes
   Playwright as a Claude/Codex MCP server.
4. **env-browser** — `BROWSER`, `PLAYWRIGHT_BROWSER_PATH`, or
   `CHROME_PATH` env var points at an executable file. Each candidate is
   path-traversal guarded (no `..` segments) and tested with a
   3-second `--version` probe.
5. **none** — chain exhausted. `/dr-qa` records a finding
   («playwright-tooling-missing») and continues to the next layer; the
   step does not fail the QA verdict on its own.

Exit codes: `0` resolved (including `none`), `1` only when invoked with
`--require` and the result is `none`, `2` on usage error / invalid
override / strict-headed without a display.

## Headed mode

Headed mode is orthogonal to detection and is requested by:

- CLI flag: `/dr-qa --headed` or `/dr-qa --headed-strict`
- Init-task frontmatter key: `qa_browser_mode: headed` (lenient) or
  `qa_browser_mode: headed-strict` (fail-fast)

Three states:

| Request | `$DISPLAY` present | Result |
|---------|--------------------|--------|
| default (neither flag nor key) | n/a | headless |
| `headed` (lenient) | yes | headed |
| `headed` (lenient) | no | headless + finding `headed-requested-but-no-display` |
| `headed-strict` | yes | headed |
| `headed-strict` | no | **exit 2** — pass aborts, BLOCKED finding |

The lenient mode is the right default for CI/server contexts; strict mode
is for operator-driven local runs where a visible browser is a hard
requirement.

## Artifact layout

```
datarim/qa/playwright-{ID}/
├── run-<ISO-timestamp>/    # one directory per pass, UTC YYYYMMDDTHHMMSSZ
│   ├── screenshot.png      # final viewport, full page when supported
│   ├── trace.zip           # Playwright trace bundle (CLI/MCP only)
│   ├── run.log             # combined stdout + stderr, one line per event
│   └── summary.md          # short report: tool, headed mode, exit code,
│                           # findings, target URL, viewport
├── latest -> run-<ISO>/    # symlink to most recent run; falls back to
│                           # a regular copy on filesystems without symlinks
└── .lock                   # flock target — see § Concurrency below
```

`{ID}` is the task ID. The per-run subdirectory keeps history bounded by
the operator (manual purge or scheduled cleanup outside this skill's
scope); `latest` provides a stable path for downstream readers.

`summary.md` follows the shape:

```markdown
# Playwright run — <ISO-timestamp>

- Task: <ID>
- Tool: <playwright-cli | playwright-mcp | env-browser | none>
- Headed mode: <headless | headed>
- Display available: <true | false>
- Target URL: <url or "static fixture: <path>">
- Viewport: <WxH>
- Exit code: <int>
- Findings:
  - <one bullet per finding, or "None">
```

## Concurrency

Concurrent agents running `/dr-qa` on the same task MUST serialise on a
per-task lock to avoid trampling each other's runs. The runner acquires
`datarim/qa/playwright-{ID}/.lock` via `flock --timeout 30` (or a
`mkdir`-based atomic fallback on systems without `flock`). The lock
covers the entire pass — resolution, browser invocation, artifact write,
`latest` swap, lock release.

A lock-acquire timeout is treated as a finding
(«playwright-lock-timeout»), not a block — the operator may inspect the
in-flight run via `latest/`.

## Outputs consumed by `/dr-qa`

The QA report (`datarim/qa/qa-report-{ID}.md`) cites the resolved tool,
headed mode, exit code, and the path to the run directory. Failure or
absent tooling appears as a finding under the Code Quality layer
(playwright sub-step), never as a separate FAIL routing branch — the
existence of a finding is the signal; the operator decides whether to
treat it as blocking.

## Cross-references

- `skills/frontend-ui/SKILL.md` § Visual Verification — pre-existing UI checks
  that complement the browser pass.
- `skills/cta-format/SKILL.md` — when the operator wants a re-run, the CTA
  points at the per-task lock file path so re-entry is unambiguous.
- `"${DATARIM_RUNTIME:?}/dev-tools/detect-playwright-tooling.sh"` — single source of truth for
  the resolution chain; this skill is its operator-facing contract.

## When NOT to apply

- Backend-only tasks (API, infra, DB) — no frontend touch detected.
- Tasks that only modify content text inside HTML files without changing
  structure — detection still triggers, but the run is a no-op smoke pass
  by design; operators may suppress with frontmatter
  `qa_browser_mode: skip`.
- Research / docs / archive-only tasks — no executable surface.