qa-detect-console-errors
Captures and CLASSIFIES runtime errors during load AND interaction: uncaught exceptions (TypeError/ReferenceError/…), unhandled promise rejections, CSP violations, and console.error — scored by first-
SKILL.mdskills/qa-detect-console-errors/SKILL.md
GitHubWhat it checks
Runtime JavaScript errors, captured from FOUR channels (not just console.error):
- Uncaught exceptions —
window.onerror/pageerror(TypeError, ReferenceError, SyntaxError, …) - Unhandled promise rejections —
unhandledrejection(failedawait, rejectedfetch/observable — the most common SPA error, invisible to plain console capture) - CSP violations —
securitypolicyviolation(blocked scripts/styles) console.error()— explicit error logs
Each is classified (by error kind) and attributed (first-party vs third-party origin), then scored — so a real TypeError in your bundle is high while a third-party analytics error is low, instead of one flat consoleError.
Two execution paths (identical output)
- Deterministic runner (
run-passive-probes.cjs) — owns this skill in the passive batch via real Playwright listeners (page.on('console'|'pageerror'|'requestfailed')) PLUS an in-pageaddInitScriptcollector for rejections + CSP. No model in the loop. This is the authoritative path. - MCP / older path (this flow) — used when the orchestrator drives a cell directly. Same classification, via
browser_console_messages+ an injected collector.
Orchestrator flow (MCP path — uses Playwright MCP)
- Install the collector BEFORE navigation so rejections/CSP during load are caught. Inject via
browser_evaluatethe snippet below (idempotent — safe to inject every cell):(function(){if(window.__argusErr)return;var s=window.__argusErr=[];function p(o){if(s.length<200)s.push(o);} window.addEventListener('unhandledrejection',function(e){var r=e&&e.reason; p({source:'',kind:(r&&r.name)||'PromiseRejection',text:'Uncaught (in promise) '+((r&&r.message)||(typeof r==='string'?r:''))});}); window.addEventListener('error',function(e){if(e&&e.error)p({source:e.filename||'',kind:(e.error&&e.error.name)||'Error',text:e.message||String(e.error)});}); document.addEventListener('securitypolicyviolation',function(e){ p({source:e.sourceFile||'',kind:'CSPViolation',text:e.violatedDirective+' blocked '+(e.blockedURI||'inline')});});})() - Navigate to the cell route; wait for settle.
- Also drive the interactive phase first where applicable (CRUD/sort/search) — errors fire on user action, not just load. The collector keeps accumulating across interactions.
- Read BOTH sources:
browser_console_messages(onlyErrors=true)ANDbrowser_evaluate(() => window.__argusErr || []). Merge them.
Rules (both paths)
Skip noise: messages matching
favicon,ResizeObserver loop,Non-passive event listener,Permissions policy,Download the React DevTools.Classify each message → kind:
Uncaught (in promise)⇒PromiseRejection; else first\b(TypeError|ReferenceError|SyntaxError|RangeError|URIError|EvalError)\bmatch; elseConsoleError.securitypolicyviolation⇒CSPViolation.Attribute: if the message
sourceURL ishttp(s)and NOT the app origin ⇒ third-party.Map to issueType + severity:
condition issueType severity third-party origin thirdPartyErrorlow CSPViolation cspViolationmedium PromiseRejection (first-party) unhandledRejectionhigh TypeError/ReferenceError/… (first-party) uncaughtExceptionhigh other console.error (first-party) consoleErrorhigh Dedup by
kind + text.slice(0,100).Bucket by issueType → emit ONE finding per issueType (count + up to 3 samples in the description), never one ticket per duplicate. Report the real total even if samples are capped.
Issue format
{ "issueType":"uncaughtException", "severity":"high", "selector":null, "evidenceType":"console",
"description":"2 uncaughtException on /admin/...: [TypeError] Cannot read properties of undefined ..." }
Issues
| issueType | severity | description |
|---|---|---|
| uncaughtException | high | "{n} uncaughtException on {route}: [{kind}] {msg}" — first-party uncaught JS exception |
| unhandledRejection | high | "{n} unhandledRejection on {route}: {msg}" — first-party unhandled promise rejection |
| consoleError | high | "{n} consoleError on {route}: {msg}" — first-party console.error not matching above |
| cspViolation | medium | "{n} cspViolation on {route}: {directive} blocked {uri}" |
| thirdPartyError | low | "{n} thirdPartyError on {route}: {msg}" — error originating from a third-party script |