One Issue → One PR with visual proof.
Flow: Select one issue → Write test (BEFORE screenshot) → Fix → Test passes (AFTER screenshot) → PR with screenshot comment.
Golden Rule: Study ≥2 existing PASSING tests before writing any new test. Wrong fixture/selector usage is #1 failure cause.
Choose an open issue to work on, or pick randomly:
bash
# Option A: List and manually select
gh issue list --repo <owner/repo> --state open --json number,title,body
# Option B: Pick one randomly
gh issue list --repo <owner/repo> --state open --json number,title | \
jq -r '.[] | "\(.number): \(.title)"' | shuf -n 1
Note the issue number and create a short kebab-case slug from title (e.g. "fix-discard-button")
Create isolated worktree: bash
git worktree add .worktrees/fix/<slug> -b fix/<slug>
cd .worktrees/fix/<slug>
Copy screenshot-reporter.ts into the worktree's e2e/ directory (not tracked on fix branches)
Set screenshot output: bash
export E2E_SCREENSHOT_DIR="$(pwd)/e2e-screenshots"
Read ≥2 passing E2E tests to understand:
withProject, withSession, gotoSession, sdk)waitSessionIdle, runTerminal, openSettings)apply_patch, terminal commands, SDK methods)typescript
import { test, expect } from "../fixtures"
import { createScreenshotReporter } from "../screenshot-reporter"
test("feature description", async ({ page, withProject }) => {
const screenshot = createScreenshotReporter(page, "test-name")
await withProject(async (project) => {
// ... setup using project's fixture patterns ...
await screenshot.captureBefore("initial-state")
// ... assertions ...
})
})
Playwright TS transform gotcha — rejects inline object params:
typescript
// WRONG: fs.mkdirSync(dir, { recursive: true })
// RIGHT:
const opts = { recursive: true }
fs.mkdirSync(dir, opts)
Apply to ALL object literals: fs.mkdirSync, page.screenshot, page.waitForFunction, etc.
Write minimum code to pass. Do NOT modify test assertions.
page.waitForSelector or page.waitForFunction to confirm elements existpage.pause() to inspect live DOMSymptomLikely CauseFixTimeout on selectorWrong data attribute or shadow DOMCheck DOM with page.pause(), try ARIA roleswaitMark never resolvesSeed function missing expected contentMatch seed format exactly from passing testsButton not visibleMissing hover/expand stepHover parent row, expand section firstReview panel emptyWrong changes mode (git vs session)Switch mode before assertingStale element referenceRace condition with async updatesAdd waitSessionIdle after mutations
typescript
await expect(fixedElement).toBeVisible()
await screenshot.captureAfter("feature-working")
Hard gate — ALL three must be true before PR:
BEFORE-*.png existsAFTER-*.png existsbash
# Stage, commit, push
git add -A
git commit -m "fix: <issue-description> (closes #<issue-number>)"
git push origin fix/<slug>
# Create PR referencing the issue
gh pr create \
--title "fix: <short-description>" \
--body "Closes #<issue-number>
## Summary
<Brief description of the fix>
## Screenshots
| Before | After |
|--------|-------|
|  |  |
## Verification
- [x] E2E test passes
- [x] BEFORE screenshot captured
- [x] AFTER screenshot captured" \
--repo <owner/repo>
# Push screenshots to branch for GitHub raw URLs
git add e2e-screenshots/
git commit -m "chore: add e2e screenshots"
git push origin fix/<slug>
gh pr comment has no --attach — push images to branch, use raw GitHub URLs.
For PRs without direct UI changes, find the closest UI-exercisable path:
Change TypeE2E StrategyReal ExampleConfig utilityOpen settings dialogopenSettings(page) → screenshot config panelPython resolverRun terminal commandrunTerminal(page, {cmd: "python3 --version"}) → show outputRace conditionTrigger rapid operationsCreate file externally → click refresh → verifyState managementMulti-session switchingSelect file in session A → switch to B → back to ACache bypassForce refreshAdd external file → click refresh button → verifyURL parsingCheck connection statusopenStatusPopover(page) → verify connectedFocus managementVerify focus after actionOpen file → check prompt still focused
NEVER use page.setContent() — always test through the real application.
bash
# Select an issue (manual or random)
gh issue list --repo owner/repo --state open --json number,title
# Create worktree
git worktree add .worktrees/fix/ISSUE -b fix/ISSUE
cd .worktrees/fix/ISSUE
# Set screenshot dir
export E2E_SCREENSHOT_DIR="$(pwd)/e2e-screenshots"
# Run E2E test
npm run test:e2e -- <test-file>
# Push and create PR
git push origin fix/ISSUE
gh pr create --title "fix: ..." --body "Closes #N" --repo owner/repo
E2E_SCREENSHOT_DIR, never to main repopage.setContent() — real app onlyscreenshot-reporter.ts must be copied to each worktree's e2e/ (not on fix branches)