Debug test
Automatically diagnose a failing Playwright test β reads files directly, applies QA heuristics, routes to the Playwright healer or diagnosing-bugs, and classifies what the healer changed before calling it fixed, flagging π when the same test keeps getting healed for the same reason (read from git history, nothing stored). Also has a flake mode (detects, quarantines, routes flaky tests β Playwright or Cypress) and a drift mode (classifies an already-red test as external drift vs local regression and surfaces the mismatch for a human to dispose).From its SKILL.md
npx -y skills add TzolkinB/skills --skill debug-testAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
14.7 KB, as published. Nobody here has run it
When a Playwright test fails, don't describe the problem β let the skill read it. This skill runs the test, reads the file, applies fast QA heuristics, and routes to the right tool. Steps 0β5 below assume Playwright β Flake Mode is the one exception, since measuring a flake rate doesn't need the rest of this skill's Playwright-specific diagnosis machinery, and also supports Cypress (via @cypress/grep --burn). For a plain, non-flaky Cypress failure, or any other non-Playwright failure (Jest/Vitest/pytest), this skill stops rather than running its Playwright-specific steps against the wrong target β Step 0 checks for this and names diagnosing-bugs as the tool to reach for instead of proceeding.
Two special cases branch off the normal flow, each with its own procedure file β load it only when its trigger fires:
- Flake Mode β a non-deterministic failure (mixed pass/fail). Most teams
.skip()or delete it; this skill instead detects, quarantines, and routes the cause, consuming the framework's own burn rather than rebuilding a runner. Triggered by--flake(Step 0) or a mixed-result Step 1. β follow reference/flake-mode.md. - Drift Mode β a deterministic red with no relevant local change (an external service moved a contract underneath a long-green test). This skill classifies drift vs local-regression, quarantines, and surfaces the mismatch for a human β never green-locks, never runs the suite. Triggered by
--drift(Step 0) or a Step-1 N/N with an empty/irrelevant diff. β follow reference/drift-mode.md.
Steps
0. Locate the test
Get the test file path or test name from $ARGUMENTS.
- If
--flakeis present β load and follow reference/flake-mode.md. Use this when the complaint is "it's flaky," not "it's failing." (Flake mode does its own Playwright/Cypress detection.) - If
--driftis present β load and follow reference/drift-mode.md. Use this when a long-green test went red and nothing in its own repo changed. (Framework-agnostic β drift mode never runs the suite.) - Otherwise (the main diagnosis flow, Steps 1β5): confirm the target is Playwright first β a
playwright.config.*in the project, or a@playwright/testimport in the file. If it isn't, stop here β do not proceed to Step 1. Report that this skill's diagnosis is Playwright-scoped and namediagnosing-bugsas the tool to reach for instead (see Output Format). Don't self-invoke it β that's a decision for the user to make, not a silent handoff. - If a file path: read it directly; pass it positionally to the run commands below (
playwright test path/to/file.spec.ts). - If a test name:
grep -r "$ARGUMENTS" tests/ --include="*.spec.*" -l, then select it in every run command below with-g "$TEST_NAME"β Playwright's title filter. A bare positional arg is a filename regex, not a title: passing a test name positionally matches zero files, runs 0 tests, and reports 0 failures β which reads as a false "not flaky." Always-gfor a name.
1. Flakiness check first
Before single-run analysis, rule out non-determinism using the framework's own burn mechanism β never a hand-rolled loop. With Playwright, --repeat-each runs the same test N times in one invocation:
npx playwright test -g "$TEST_NAME" --repeat-each=5 --reporter=line 2>&1
- Mixed pass/fail across the repeats β confirmed flaky. Follow reference/flake-mode.md β do not run single-run diagnosis on a non-deterministic failure.
- Fails every repeat (N/N) β a real, deterministic failure, not flake. Before assuming a local cause, run the drift signal: if the working/PR diff doesn't touch any source this test exercises (empty or drift-irrelevant diff under a previously-green test) β follow reference/drift-mode.md. Otherwise continue below β the cause is local.
Otherwise (a plain failing test) run once and capture full output:
npx playwright test -g "$TEST_NAME" --reporter=line 2>&1
This is Phase 1 of diagnosing-bugs β the feedback loop is already built.
2. Quick QA heuristics (automated β no asking)
Read the test file. Check three angles:
Setup
- Is the function/action under test actually called?
- Are mocks or fixtures set up but never triggered?
- Is there a missing
awaiton an async action or assertion? - Does
beforeEach/ setup run before the assertion fires?
Assertion
- Is the assertion reachable? (Not inside an early return or dead branch)
- Is it too loose? (
toBeDefined(),toBeTruthy(), emptyexpect()) - Does it check the right value β not an incidental side effect?
Code logic
- Does the failure message point to an unexpected return value (not a selector/timeout error)?
- Does the stack trace land in production code rather than the test itself?
β Found: report root cause + fix. Done (see Output Format). β Not found: proceed to Step 3.
3. Route based on failure output
Read the failure output from Step 1:
| Failure pattern | Route |
|---|---|
locator / selector / No element found / strict mode violation | β Step 4 (Healer) |
Timeout / waiting for / exceeded | β Step 4 (Healer) |
Expected X, received Y / TypeError / ReferenceError / value mismatch | β Step 5 (diagnosing-bugs) |
| Unknown / ambiguous | β Step 5 (diagnosing-bugs) |
4. Playwright Healer
Invoke the Playwright healer subagent (via the Task tool) with the failing test name. This reaches execution across the seam β the healer owns the browser run; debug-test only orchestrates the handoff (ADR-0010).
Requires
npx playwright init-agentsto have been run in the repo. If not initialized, note it and proceed directly to Step 5.
Before invoking it, record git status --porcelain -- <test file> β Step 4.5 needs to know whether the file was already dirty to attribute the healer's edit.
Healer input: [failing test name]
- Healer passes β Step 4.5 β a pass is a change, not yet a result
- Healer skips (outputs "functionality broken") β Step 5
4.5 Classify the heal
A healer pass says the test is green; it doesn't say what the healer changed to get there. A locator touch-up and an expected value rewritten to match a regression both end in green, and only the second green-locks a bug β the assertion stays live, keeps killing mutations, and now enforces the broken behavior (ADR-0017). So don't call it done on the pass alone: git diff the test file, bucket what changed, and take that bucket's disposition. β follow reference/heal-classification.md.
The three buckets and where they land: selector / timeout / wait only β done, no mutation spent on the low-risk common case; expected-value literal changed β /audit-test's baseline-lock check via the Skill tool β the invocation must carry the assertion co-change itself, since the healer's edit is uncommitted and audit-test can't resolve it β verdict reported inline before "done"; setup / fixture / flow changed β not auto-cleared, diff shown, human review required. All three also propose a Healed-by: / Heal-bucket: commit-trailer block β the durable record of the classification, for a human or a commit template to apply. This skill never creates or amends a commit (ADR-0047 Β§2).
4.6 Check repeat-heal history
A single heal is a data point; a pattern across heals is the thing worth a human's attention. Immediately after Step 4.5 classifies this heal, read the test file's history for prior heals of the same kind: git log --follow, filtered on the Heal-bucket trailer #190 writes and reading the union of the shared trunk (origin/<default-branch>, resolved dynamically, never hardcoded as main) and the current branch β so a teammate's merged heal isn't invisible on an unrebased branch, and a heal already committed on this branch isn't invisible either. Falls back to plain file churn when trailers are absent, and flags a shallow clone rather than reading a truncated history as clean. Three or more heals of the same bucket within the window (this one included) β π repeat-heal, naming the count, bucket, and element/behavior each occurrence touched. β follow reference/repeat-heal.md.
Nothing is stored β this reads git fresh every time (#194, ADR-0047 Β§2). It always reports what history was available; an empty or trailer-less read is never reported as "no repeats."
5. diagnosing-bugs
Invoke Matt Pocock's diagnosing-bugs skill via the Skill tool.
Phase 1 is already complete β the failing Playwright test is the tight, deterministic feedback loop. Pass this context and begin at Phase 2:
- Test file: [path]
- Failing test: [name]
- Failure output: [captured in Step 1]
- QA-enriched hypothesis candidates for Phase 3 (pre-ranked):
- Mock/stub returns wrong shape or is never triggered
- Missing
awaiton async step or assertion - Test sets up state but never invokes the code under test (false positive)
- Code logic regression β behavior changed, test not updated
Output Format
Not a Playwright test (Step 0)
## debug-test: [Test Name / file]
### Not Playwright
[file] has no `playwright.config.*` / `@playwright/test` import β debug-test's diagnosis (Steps 1β5) is Playwright-scoped.
### Route
Invoke diagnosing-bugs directly for this one. (If the actual complaint is flakiness, `--flake` also covers Cypress.)
Heuristics caught it (Step 2)
## debug-test: [Test Name]
### Failure
[One-line summary from test output]
### Root Cause
**[Setup | Assertion | Code Logic]** β [specific finding]
[code snippet: the problem]
β
[code snippet: the fix]
### Confidence
π’ High β [reason]
Routing to healer (Step 4)
## debug-test: [Test Name]
### Failure
[One-line: locator/timeout error]
### Routing β Playwright Healer
Failure type: [locator | wait | data]
Invoking healer: [test name]
Heal classification (Step 4.5) + repeat-heal check (Step 4.6)
Emitted after a healer pass that changed the test file β see reference/heal-classification.md for the per-bucket dispositions, the trailer rules, and the cases that produce no classification at all (empty diff, pre-dirty tree, edits outside the test file); see reference/repeat-heal.md for the read-side rules (window, trailer-coverage tiers, churn fallback) behind the last section.
## debug-test (heal): [Test Name]
### Healer β passed
[what the healer reported, one line]
### Heal classification β [Selector / timeout / wait only | Expected-value literal | Setup / fixture / flow]
`git diff -- [test file]`: [what changed β old β new]
[bucket disposition: cleared Β· /audit-test baseline-lock verdict Β· β not auto-cleared, human review]
### Healing justification
[what changed] Β· [which check ran] Β· [verdict]
### Proposed commit trailers (proposed, not applied β debug-test does not own the commit)
Healed-by: debug-test
Heal-bucket: [locator | assertion-value | flow-data]
### Repeat-heal check (Step 4.6)
History read: [window] from [origin/<default-branch> βͺ HEAD | HEAD only β no origin remote found][ Β· β οΈ shallow clone, history may be truncated] Β· [bucket-accurate | churn-only (missing/no Heal-bucket trailers found) | no prior history]
[π Repeat-heal β N heals of `[bucket]` in [window], this one included: [what each occurrence touched] | N heals of `[bucket]` in [window], below the 3-heal threshold | π N total edits to this file in [window] (churn fallback, no bucket data) | first heal on record for this file]
Routing to diagnosing-bugs (Step 5)
## debug-test: [Test Name]
### Failure
[One-line summary]
### Heuristics: Nothing Found
Checked: setup (β), assertion (β), code logic (β)
### Routing β diagnosing-bugs
Feedback loop: `npx playwright test [name]`
Phase 1: complete
Top hypotheses for Phase 3:
1. [highest ranked]
2. [second]
3. [third]
Proceeding with diagnosing-bugs Phase 2...
Flake Mode and Drift Mode carry their own output formats β see reference/flake-mode.md and reference/drift-mode.md.
Notes
- Self-invoking orchestrator. debug-test drives its own handoffs across the ADR-0010 seam β the Playwright healer via
Task, sibling skills viaSkillβ but never owns execution itself. A routed cause is a lead to confirm, not a verdict. - A green from a healer is a change, not a result. Step 4.5 classifies every healer pass from the diff β never from the healer's account of what it did β because the two greens that matter (a re-synced selector, an expected value rewritten to bless a regression) are indistinguishable from the pass alone.
- Never commits. Step 4.5 proposes the
Healed-by:/Heal-bucket:trailer block; this skill does not create or amend a commit, and writes no ledger of its own (ADR-0047). - Repeat-heal is read, not stored. Step 4.6 computes π repeat-heal from
git log --follow+ theHeal-buckettrailer at read time, every time β never a new store, and it says plainly when trailers are missing and the weaker churn fallback is doing the work instead (#194). - Scoped to Playwright (flake mode also supports Cypress
@cypress/grep --burn). For Jest/Vitest/pytest failures, invoke diagnosing-bugs directly. --explainis not supported β this skill is procedural, not pedagogical.