qa-detect-breakpoint-boundary
Detects layout breakage at common CSS breakpoint boundaries (767px, 1023px, 1279px) — widths bugs typically hide between standard test viewports.
SKILL.mdskills/qa-detect-breakpoint-boundary/SKILL.md
GitHubHow the orchestrator runs this (resize + ONE probe call per boundary)
🚨 This skill is EXECUTABLE but partial: the detection logic is a single in-page probe, but each boundary needs a REAL viewport resize that browser_evaluate cannot do. Bugs hide at the exact pixel where CSS media queries fire; standard viewports (390/820/1440) skip these. The orchestrator resizes to each boundary and calls the probe once per boundary.
MCP steps (resize only)
Step 3 (restore) is mandatory even if any probe call errors — otherwise the page is left at a wrong size for downstream skills.
orig = browser_evaluate(() => ({ w: window.innerWidth, h: window.innerHeight }))— save{w, h}.- For each
boundaryin[767, 1023, 1279]: a.browser_resize(width=boundary, height=720)b.browser_wait_for(time=400)— media queries settle c.result = browser_evaluate(<the async function in "## Interactive Probe" below>, { width: boundary })— collectfindings[] browser_resize(width=orig.w, height=orig.h)— RESTORE original viewport.browser_wait_for(time=200).
If browser_resize is unavailable in the active MCP session, emit zero findings and set screenshotSkipReason="viewport resize unsupported". Transcribe each returned finding verbatim into the cell JSONL; add only the envelope fields (runId, cellId, route, viewport, …).
Interactive Probe (browser_evaluate, async) — args: { width }
async ({ width }) => {
const out = [];
const W = width || window.innerWidth;
const sel = el => {
const id = el.id ? `#${el.id}` : '';
const cls = (el.className && typeof el.className === 'string')
? '.' + el.className.trim().split(/\s+/).slice(0, 2).join('.') : '';
return (el.tagName.toLowerCase() + id + cls).slice(0, 120);
};
const bb = el => { const r = el.getBoundingClientRect(); return { x: Math.round(r.left), y: Math.round(r.top), w: Math.round(r.width), h: Math.round(r.height) }; };
// 1. Horizontal scroll at boundary
if (document.documentElement.scrollWidth > W + 2) {
out.push({ skill: 'qa-detect-breakpoint-boundary', issueType: 'breakpointOverflow', severity: 'high', selector: 'html',
description: `Horizontal overflow at ${W}px boundary — scrollWidth ${document.documentElement.scrollWidth}px (page-level)`,
bbox: { x: 0, y: 0, w: Math.min(W, window.innerWidth), h: 80 } });
}
// 2. Elements cut off at right edge of viewport
let cutCount = 0;
for (const el of document.querySelectorAll('header, nav, button, a, input, [role="button"], [class*="card"], [class*="menu"]')) {
if (cutCount >= 12) break;
const r = el.getBoundingClientRect();
if (r.width === 0 || r.height === 0) continue;
if (r.right > W + 2 && r.left < W) {
cutCount++;
out.push({ skill: 'qa-detect-breakpoint-boundary', issueType: 'breakpointElementCutOff', severity: 'medium', selector: sel(el),
description: `Element ${sel(el)} extends past ${W}px viewport edge at this breakpoint (right edge ${Math.round(r.right)}px)`,
bbox: bb(el) });
}
}
return out;
}
Issues
| issueType | severity | description |
|---|---|---|
| breakpointOverflow | high | "Horizontal overflow at {width}px breakpoint boundary" |
| breakpointElementCutOff | medium | "Element {selector} cut off at {width}px boundary" |
Notes on this conversion
- The old
captureOriginalSize+ per-boundaryscanBoundaryis folded so detection is ONE asyncbrowser_evaluatecall (per boundary). Same checks, same issueTypes. - Marked
executable: partialbecause each boundary requires a REALbrowser_resizethatbrowser_evaluatecannot perform — the orchestrator resizes to 767/1023/1279, calls the probe once per width, then restores. The capture step is inlined into MCP step 1.