mit einem Klick
manually-validate-fix
// Validate a bug-fix PR by reproducing the bug in production and confirming the fix locally, using browser automation.
// Validate a bug-fix PR by reproducing the bug in production and confirming the fix locally, using browser automation.
Process multiple bugs from a Fizzy board (or GitHub issues) in parallel. Uses worktree agents to reproduce, fix, test, create PRs, and comment on cards.
Bug reproduction for the Lexxy rich text editor. Core editing bugs use Playwright (Selenium fallback); system-level bugs use Capybara. All local.
| name | manually-validate-fix |
| description | Validate a bug-fix PR by reproducing the bug in production and confirming the fix locally, using browser automation. |
| disable-model-invocation | true |
| argument-hint | <pr-number> |
Validate a bug-fix PR by testing like a human — reproduce the bug in production (broken), confirm the fix works locally (working). Uses a single Selenium WebDriver script run against both environments.
PR → UNDERSTAND BUG → PLAN REPRODUCTION → WRITE SCRIPT → RUN vs PRODUCTION (bug)
→ RUN vs LOCAL (fix)
→ VERDICT → label PR
| Environment | URL | Runs | Expected |
|---|---|---|---|
| Production | https://basecamp.github.io/lexxy/try-it.html | Latest released version (CDN) | Bug reproduces |
| Local | http://lexxy.localhost:3000 | Current branch with the fix | Bug does NOT reproduce |
The try-it page loads Lexxy from jsDelivr CDN. After JS executes:
<lexxy-editor> with toolbar, emoji prompt on : triggerdata-direct-upload-url and data-blob-url-template are set but uploads go to a service worker (no real backend)http://lexxy.localhost:3000 runs the Rails dummy app:
/sandbox) — editor with @ mention and : emoji prompts, template switcher/posts) — full Action Text form, all features/posts/new control features: ?attachments_disabled=true, ?toolbar_disabled=true, etc.gh pr view $ARGUMENTS --json title,body,files,commits,headRefName
gh pr diff $ARGUMENTS
Extract:
If the PR references an issue, fetch that too.
Write a concrete step-by-step plan:
The plan must map directly to WebDriver actions.
ls /tmp/node_modules/selenium-webdriver >/dev/null 2>&1 || {
cd /tmp && npm install selenium-webdriver
}
which chromedriver >/dev/null 2>&1 || echo "ERROR: Install chromedriver"
Write a single .mjs script in /tmp/ that accepts a URL argument and runs the reproduction steps. The same script runs against both environments — only the URL changes.
Script skeleton:
import webdriver from '/tmp/node_modules/selenium-webdriver/index.js'
import chrome from '/tmp/node_modules/selenium-webdriver/chrome.js'
import { Key } from '/tmp/node_modules/selenium-webdriver/lib/input.js'
import fs from 'fs'
const BASE_URL = process.argv[2]
if (!BASE_URL) {
console.error('Usage: node script.mjs <url>')
process.exit(1)
}
const ENV = BASE_URL.includes('github.io') ? 'production' : 'local'
async function sleep(ms) { return new Promise(r => setTimeout(r, ms)) }
async function screenshot(driver, name) {
const data = await driver.takeScreenshot()
fs.writeFileSync(`/tmp/validation-${ENV}-${name}.png`, Buffer.from(data, 'base64'))
console.log(`Screenshot: /tmp/validation-${ENV}-${name}.png`)
}
const options = new chrome.Options()
options.addArguments('--headless=new', '--no-sandbox', '--window-size=1280,900')
const driver = await new webdriver.Builder()
.forBrowser('chrome').setChromeOptions(options).build()
try {
// Navigate and wait for editor
await driver.get(BASE_URL)
await driver.wait(
webdriver.until.elementLocated(webdriver.By.css('lexxy-editor[connected]')),
15000
)
const content = await driver.findElement(webdriver.By.css('.lexxy-editor__content'))
await content.click()
await sleep(300)
// ===== REPRODUCTION STEPS HERE =====
// Use real browser interactions:
// content.sendKeys('text') — type text
// content.sendKeys(Key.RETURN) — press Enter
// content.sendKeys(Key.BACK_SPACE) — press Backspace
// content.click() — click to focus
// driver.findElement(...).click() — click toolbar buttons
// driver.executeScript(...) — read DOM state (not for actions)
// ===== COLLECT EVIDENCE =====
await screenshot(driver, 'result')
const html = await driver.executeScript(
'return document.querySelector("lexxy-editor").value'
)
console.log(`[${ENV}] Editor value:`, html)
// ===== ASSERT =====
// Check whether the bug manifests or not.
// Print a clear PASS/FAIL line:
// PASS = behavior is correct (expected for local)
// FAIL = bug manifests (expected for production)
// Example:
// const bugPresent = html.includes('<some-broken-markup>')
// console.log(`[${ENV}] Bug present: ${bugPresent}`)
} finally {
await driver.quit()
}
Key rules for the script:
sendKeys, click). No programmatic shortcuts.executeScript is for reading state only — never for producing the action under test.curl -s -o /dev/null -w "%{http_code}" http://lexxy.localhost:3000/ 2>/dev/null | grep -q 200 || {
echo "Starting local server..."
cd ~/Work/basecamp/lexxy && bin/dev &
for i in $(seq 1 30); do
curl -s -o /dev/null -w "%{http_code}" http://lexxy.localhost:3000/ 2>/dev/null | grep -q 200 && break
sleep 1
done
}
Verify the right branch is checked out:
cd ~/Work/basecamp/lexxy && git branch --show-current
# Must match the PR's head branch
# Production — expect the bug
node /tmp/validate-fix-<pr>.mjs "https://basecamp.github.io/lexxy/try-it.html"
# Local — expect the fix
node /tmp/validate-fix-<pr>.mjs "http://lexxy.localhost:3000"
Review screenshots and console output from both runs.
| Production | Local | Verdict |
|---|---|---|
| Bug reproduces | Bug does NOT reproduce | Validated |
| Bug reproduces | Bug STILL reproduces | Not Fixed |
| Bug does NOT reproduce | N/A | Cannot Validate |
Verdict format:
**Validation Verdict: [Validated / Not Fixed / Cannot Validate]**
**PR:** #<number> — <title>
**Bug:** <one-line description>
**Production** (https://basecamp.github.io/lexxy/try-it.html):
- <what happened>
- Evidence: <screenshot paths>
**Local** (http://lexxy.localhost:3000):
- <what happened>
- Evidence: <screenshot paths>
**Steps executed:**
1. <step> — <result>
2. ...
If Validated:
gh pr edit <number> --add-label "fix-validated" -R basecamp/lexxy
gh pr comment <number> --body "$(cat <<'EOF'
## Manual Validation: Validated
<Brief description of what was tested — written for a human reviewer. Describe the steps you performed and what you observed, e.g. "Typed a bulleted list, selected all items, applied heading format from the toolbar. Confirmed the list converted to a heading without leaving orphaned list markers.">
EOF
)"
If Not Fixed — comment on the PR:
gh pr comment <number> --body "$(cat <<'EOF'
## Manual Validation: Not Fixed
<Same human-readable description of the steps tested and what was observed, explaining why validation failed.>
EOF
)"