A Claude skill for Playwright is three things, not one. Most people only install one of them.
An MCP server gives the agent tools. That is necessary, not sufficient. A skill that actually changes behavior also needs a hook, so something nudges the agent at the right moment, and a CLAUDE.md preamble, so the agent has a standing rule across sessions. This page shows you the literal three pieces, the 5-line bash hook included, and what `npx assrt setup` writes to your disk.
The anatomy of a real Playwright skill
A skill is a contract between the agent and the surrounding system. The contract has to cover three things: what the agent can do, when the agent should remember to do it, and what the agent has been told once and is expected to remember forever. Map those onto Claude Code primitives and you get MCP servers, hooks, and CLAUDE.md.
Three sources, one runtime, four artifacts
The hook is 5 lines of bash. Here it is.
The piece that most posts on this topic skip entirely is the PostToolUse hook. It is a shell script saved to ~/.claude/hooks/assrt-qa-reminder.sh with mode 0755, registered against the Bash matcher in settings.json. It runs after every Bash tool call, exits silently on commands it does not care about, and on a git commit or git push it prints a single line of JSON that Claude Code treats as additional context.
The full source is at /Users/matthewdi/assrt-mcp/src/cli.ts lines 203-212, defined as the constant QA_REMINDER_HOOK. Every byte of it:
Two things make this work. The first is jq -r '.tool_input.command // empty' which extracts the command string from the structured tool-call JSON Claude Code pipes to stdin. The second is the regex git (commit|push) which is intentionally permissive, it catches git commit, git commit -am, git push origin main, and any chained variant the agent might write. On a hit, stdout becomes structured input to the model on the next turn.
And here is how it gets wired into settings.json
The setup function reads your existing settings.json, merges this entry under hooks.PostToolUse, and writes the file back. Other hooks you have, other matchers, other event types are left alone.
What happens when the agent commits code
Walk through one full cycle so the role of each piece is concrete. The interesting moment is between step 3 and step 4, where a 5-line bash file successfully changes how a large language model behaves on its next turn.
Lifecycle of a single test reminder
You commit code that touches a route or a form
Claude runs the Bash tool with `git commit -m "..."` or `git push`. Nothing about the agent's plan changes; it would normally move on to the next file.
PostToolUse fires on the Bash matcher
Claude Code reads ~/.claude/settings.json, finds the entry registered at cli.ts:273-276, and pipes the tool call's JSON to the script at ~/.claude/hooks/assrt-qa-reminder.sh.
The script greps for git commit or git push
It is a five-line bash file. jq -r '.tool_input.command // empty' pulls the command, grep -qE 'git (commit|push)' decides whether to act, everything else exits silently. Defined verbatim at cli.ts:203-212.
On a match, it prints a JSON envelope to stdout
The shape is hookSpecificOutput.additionalContext — Claude Code's contract for hook-injected system text. The reminder tells Claude to run assrt_test if the change was user-facing, and to use assrt_plan first if it needs test cases.
Claude reads the reminder mid-turn and acts on it
The reminder lands as additional context for the model on the next inference step. Combined with the CLAUDE.md preamble appended at cli.ts:297, the model has both the standing rule (CLAUDE.md) and the just-in-time prompt (hook). It is far more likely to actually call assrt_test now.
Compare what people install vs what actually changes behavior
The left tab is the most common shape of a "Claude skill for Playwright". The right tab is what `npx assrt setup` writes. The left teaches Claude how to drive a browser. The right teaches Claude how to drive a browser and when to bother.
The 80 percent install vs the full install
// What people install when they ship a "Claude skill for Playwright".
// Just an MCP server registration. The agent now has tools.
// The agent does not necessarily know WHEN to use them.
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["-y", "@playwright/mcp"]
}
}
}
// Result: agent will navigate, click, screenshot if you ask.
// Will it test after a route change? Only if you remember to ask.The skill in numbers
All of these come from the source files at /Users/matthewdi/assrt-mcp/src/cli.ts and /Users/matthewdi/assrt-mcp/src/mcp/server.ts. Run wc -l on the files yourself.
cli.ts:203-212
0 lines
The PostToolUse hook script. Reads the command from stdin, greps for git activity, prints JSON on a hit.
cli.ts:214-308
0 lines
The whole setupAssrt() function. Three labeled steps: register MCP, install hook, append CLAUDE.md.
server.ts:276-326
0 lines
The SERVER_INSTRUCTIONS string handed to Claude when the MCP server boots. The agent sees this verbatim.
cli.ts:297
0 CLAUDE.md preamble
A single inline string appended to ~/.claude/CLAUDE.md if not already present. Loads on every session.
One command, three writes to disk
The setup function logs each touchpoint as it goes, so you can see exactly which files it touches. There is no daemon, no background process, no account. Three files end up on your machine, and Claude Code picks them up the next time it starts.
Skill-shaped install vs Assrt's three-piece install
What "Claude skill for Playwright" usually means in a blog post versus what `npx assrt setup` actually puts on your machine.
| Feature | Common installs | Assrt |
|---|---|---|
| What gets installed when you call it a skill | An MCP server. That is usually it. | MCP server + PostToolUse hook + CLAUDE.md, all three. |
| Where the timing instruction lives | Nowhere. You hope the agent remembers to test. | A 5-line bash hook fires on every Bash call and injects a reminder when the command is git commit or git push. |
| Standing rule for the agent | Hand-edited CLAUDE.md, if you remembered. | Auto-appended to ~/.claude/CLAUDE.md (cli.ts:297) so every session sees it. |
| What the hook script looks like | No hook ships. | Plain bash you can read, fork, or replace. Saved at ~/.claude/hooks/assrt-qa-reminder.sh, mode 0755. |
| Browser engine under the hood | Often a wrapper around Chromium with a custom DSL. | Real @playwright/mcp driving real Chromium with the user's persistent profile at ~/.assrt/browser-profile. |
| Test artifact format | Vendor-specific YAML or platform-internal records. | Plain Markdown #Case blocks at /tmp/assrt/scenario.md. Editable, diffable, gittable. |
| Cost at single-developer scale | $7,500 / month per seat for closed AI QA platforms. | $0 plus Anthropic tokens. Open source, self-hosted. |
| Test ownership when you walk away | Tests live in vendor cloud. Migration means re-authoring. | scenario.md is on your disk and optionally in your git repo. |
Want the three-piece skill on your team's Claude Code, but with custom hooks?
We can fork the setup script for you (project-scoped MCP, custom hook regex, your own CLAUDE.md preamble) on a 30-minute call.
Frequently asked questions
What does "Claude skill" actually mean for Playwright test automation?
In Claude Code's vocabulary, anything that extends the agent's behavior is a skill in the loose sense — an MCP server, a hook, a CLAUDE.md preamble, a markdown card under .claude/skills/, or a combination. For Playwright specifically, the cheapest version is one MCP server registration and you stop there. The version that actually changes how the agent behaves is three things together: tools (so Claude can drive a real browser), a hook (so something nudges the agent to test at the right moment), and a CLAUDE.md preamble (so the agent has a standing rule). Most posts you find treat this as MCP-only. Assrt's setup script is one of the few that wires up all three with one command.
Where exactly does the PostToolUse hook live and what does it do?
After running `npx @assrt-ai/assrt setup` it is written to ~/.claude/hooks/assrt-qa-reminder.sh with mode 0755 (cli.ts:243-246). The script reads the tool-call JSON from stdin, extracts the command via jq, and uses grep -qE 'git (commit|push)' to decide whether to act. On a match, it prints a single line of JSON containing hookSpecificOutput.additionalContext — Claude Code's documented hook contract — which the agent treats as system context for the next inference step. On a miss, it exits silently and Claude continues whatever it was doing. Total length: 5 lines of bash. Source at /Users/matthewdi/assrt-mcp/src/cli.ts:203-212.
Why a hook on Bash and not a hook on git? Won't this fire on every shell command?
It does fire on every shell command, by design. Claude Code only exposes a few hook event types (PreToolUse, PostToolUse, Stop, UserPromptSubmit, etc.) and those are scoped by tool matcher, not by command pattern. The matcher in settings.json is "Bash" (cli.ts:274). The script does the command-level filtering itself with grep, which is the right place: shell text matching is what bash is good at. The cost of firing on every Bash call is negligible (one process spawn, one jq, one grep, exit). The benefit is that you get a single hook that catches every commit or push regardless of how the agent phrased it.
What does the CLAUDE.md preamble look like and where does it come from?
The setup script appends a section titled "## QA Testing (Assrt)" to ~/.claude/CLAUDE.md if neither the heading nor the string "assrt_test" already exists in the file (cli.ts:296-302). The text is the constant defined inline at cli.ts:297: it tells Claude that running assrt_test is mandatory after any user-facing change, that assrt_plan should be used to draft cases for new features, that bug fixes should get a targeted #Case, and that assrt_diagnose should run before guessing at a fix. Because CLAUDE.md is auto-loaded into every session, this is the standing rule that the just-in-time hook reminder reinforces.
How is this different from a markdown card in .claude/skills/?
The .claude/skills/ folder hosts SKILL.md files that Claude Code loads on demand, gated by a description that the model uses to decide whether to invoke. They are excellent for capability discovery but they do not run code, do not watch for events, and do not persistently change behavior between sessions. The Assrt setup uses none of that surface; it relies on three older, more durable mechanisms: MCP, settings.json hooks, and CLAUDE.md. The result is that Assrt's behavior does not depend on Claude noticing a SKILL.md or matching its description — the hook runs whether or not the model thinks of QA, and the CLAUDE.md preamble is loaded whether or not the model retrieves a skill.
Does Assrt write real Playwright code or its own DSL?
It runs real Playwright via @playwright/mcp. The plan format (the thing the model reads and the thing you can edit by hand) is plain Markdown with #Case blocks, but at execution time, Assrt drives a real Chromium through Playwright's own MCP server. The implementation lives at /Users/matthewdi/assrt-mcp/src/core/browser.ts; it uses Playwright's persistent context, supports headed and headless mode, supports extension mode (cli.ts:85-88) where it attaches to your real Chrome, and records videos to .webm. The tests are not bound to selectors at write time — every step rediscovers elements from the live accessibility tree — but the runtime is unmodified Playwright. There is no proprietary engine to lock you in.
Can I read the system instructions the MCP server hands to Claude?
Yes. The Assrt MCP server is registered with an instructions string at /Users/matthewdi/assrt-mcp/src/mcp/server.ts:276-326, passed to the McpServer constructor at server.ts:328-331. It tells Claude when to use assrt_test (after every user-facing change), how to use assrt_plan (when test cases do not yet exist), and how to use assrt_diagnose (after a failed test). Compare this to a closed AI QA platform where you cannot see what the agent has been told. With Assrt, the system message, the tool descriptions, the hook script, and the CLAUDE.md preamble are all plain text in a public repo. You can read them, fork them, or rewrite them.
Does the hook overwrite my existing settings.json or break other hooks?
It merges. The setup function reads ~/.claude/settings.json (cli.ts:254-258), pulls hooks.PostToolUse (cli.ts:261), checks whether an entry referencing assrt-qa-reminder is already in there (cli.ts:269-271), and only pushes a new entry if it is missing. There is also a migration path that drops a legacy hook named assrt-post-commit if it exists from an earlier version (cli.ts:264-267, 248-251). After the merge it writes the file back with two-space indentation and a trailing newline. Other hooks under PostToolUse, other matchers, and other event types are untouched.
I run multiple Claude Code projects. Does the skill apply globally or per-project?
Globally, by default. The MCP server is added with --scope user (cli.ts:228), the hook is written under ~/.claude/hooks (cli.ts:242-243) and registered in the global ~/.claude/settings.json (cli.ts:254), and the preamble is appended to ~/.claude/CLAUDE.md (cli.ts:291). That means every Claude Code session in every project picks up assrt_test, assrt_plan, and assrt_diagnose, every Bash call across every project triggers the hook, and every CLAUDE.md load includes the QA Testing section. If you want project-scoped behavior instead, install per-project: register the MCP server with --scope project, write the hook to .claude/hooks/, and patch .claude/settings.json. The setup script does not do that today; the source is small enough to fork.
How do I uninstall the skill cleanly if I want to swap in something else?
Three reverse steps. (1) Run `claude mcp remove assrt` to drop the MCP server. (2) Delete ~/.claude/hooks/assrt-qa-reminder.sh and edit ~/.claude/settings.json to remove the entry under hooks.PostToolUse where the command path ends in assrt-qa-reminder.sh. (3) Open ~/.claude/CLAUDE.md and remove the "## QA Testing (Assrt)" section that the setup script appended. Because all three live in plain files on your disk, there is nothing to talk to a vendor about and no account to cancel. The opposite of "Generates real Playwright code, not proprietary YAML. Open-source and free vs $7.5K/mo competitors." is also the opposite of "easy to leave" — by being open and self-hosted, Assrt makes leaving as cheap as installing.
How did this page land for you?
React to reveal totals
Comments (••)
Leave a comment to see what others are saying.Public and anonymous. No signup.