Skills
QA pass
Put a page through the same checks every time: how it holds up at seven screen widths, what the markup says, whether anyone on a keyboard can get through it, and whether the words are right.
When do you reach for QA pass?
Reach for it before you publish, after a redesign, and on the page a client says looks off without being able to say why. What comes back is a list of faults with a fix attached to each one, not a folder of screenshots you still have to read yourself.
What does a QA pass check?
Six areas, in the same order every run: content, links and CTAs, layout, accessibility, technical basics, and anything that comes from a shared template. The list is fixed on purpose, so a run today and a run in a month are comparable and nothing gets checked only when somebody happens to remember it.
- Content: spelling, leftover placeholder text, stale dates, terminology that changed halfway down the page.
- Links and CTAs: dead links, links still pointing at staging, empty anchors, a primary CTA that is missing or below the fold.
- Layout: text overflow, overlapping elements, and sideways scroll at any of the seven widths.
- Accessibility: images with no alt, controls with no label, low contrast, focus that vanishes when you tab.
- Technical: title, meta description, canonical, the four social tags, console errors.
- Shared regions: the footer and global nav get checked once for the whole site, not once per page.
How does it use Playwright?
Playwright loads the page in a real browser, because half of what breaks only exists after JavaScript has run and a plain fetch never sees it. It accepts the cookie banner first, then sweeps seven widths from 330px to 1920px, screenshotting five of them and running an overflow check at all seven. Accordions get opened one at a time and screenshotted open, since content behind a collapsed panel reads as missing when it is only hidden.
How does it avoid crying wolf?
One rule above the others: verify visually before reporting. Code tells you what the DOM says, the screenshot tells you what a visitor sees, and anything you cannot see in the screenshot does not go in the report. Three known false positives are handled by name, including the big one, where a container hiding its overflow makes an element look clipped when nothing is wrong. The skill also keeps a written ignore list of patterns that are fine on your site, because a report that raises two false alarms gets skimmed the third time.
What comes back?
Only what is broken. No best-practice suggestions, nothing optional, nothing that passed. Every issue gets an ID, a priority, where it is, and a fix, plus a column holding the whole thing in under 200 characters so it can be pasted straight into a message to whoever owns the page. It closes with what is clean, which is what makes the failures believable.
What you copy
One block, ready to paste. Nothing else to install unless the block says so.
1---2name: qa-pass3description: QA a live page across seven breakpoints. Checks content, links,4 layout, accessibility and technical basics, then writes a prioritised report5 with a paste-ready line per issue. Use before launch, after a redesign, or on6 any page someone says looks off.7argument-hint: <url> [quick|deep|focus:<category>]8---910# QA pass1112One URL in, one prioritised report out. Same checks every time, so two runs a13month apart are comparable.1415**What you need:** the Playwright MCP server, and nothing else. No install into16the site you are checking, no script to place, no repo access. It works against17any URL you can load, including a site you did not build and cannot deploy to.1819## Step 1 - load the page in a real browser2021Drive Playwright, never a plain fetch. Half of what breaks only exists after22JavaScript runs, and a fetch will not see it.2324Accept the cookie banner before anything else, with a text selector rather than25a snapshot reference: snapshot refs go stale between calls and the click fails26silently.2728## Step 2 - sweep seven breakpoints2930Smallest to largest, every run, whatever device the request named:3132| Width | Represents | Screenshot |33| --- | --- | --- |34| 330px | smallest common phone | yes |35| 375px | mobile baseline | yes |36| 768px | tablet portrait | yes |37| 1024px | tablet landscape | overflow check only |38| 1280px | desktop baseline | yes, full page |39| 1440px | large desktop | overflow check only |40| 1920px | full HD desktop | yes, full page |4142At each width run this through the MCP server's evaluate call. Run it as43written rather than improvising the check, so a run today and a run in a month44are answering the same question:4546```js47() => ({48 // A false here with elements listed below does NOT mean clipping: a49 // container with overflow-x hidden suppresses the bar. Check the shot.50 hasHorizontalScroll:51 document.documentElement.scrollWidth > document.documentElement.clientWidth,52 past: [...document.querySelectorAll("*")]53 .filter((el) => el.getBoundingClientRect().right > window.innerWidth + 5)54 .slice(0, 10)55 .map((el) => ({56 tag: el.tagName,57 class: el.className?.toString().slice(0, 60),58 right: Math.round(el.getBoundingClientRect().right),59 })),60})61```6263These are representative widths, not every pixel. If someone reports a break at64850px, resize to 850px and look, rather than trusting the sweep.6566Reset to 1280px before the content checks. Capture the accessibility tree and67the console messages once, at that width.6869## Step 3 - the two rules that keep a report honest7071**Verify visually before reporting.** Code tells you what the DOM says, the72screenshot tells you what a visitor sees. If you cannot see the problem in the73screenshot, it does not go in the report.7475**Know the false positives.** Three that catch every new run:7677- No horizontal scrollbar plus overflowing elements does not mean clipping. A78 container with overflow-x hidden suppresses the bar while the element still79 extends past the viewport. Check the screenshot.80- innerText returns empty for anything hidden or mid-animation, including links81 inside a collapsed accordion. Read textContent before calling a link empty.82- Keep a written ignore list of the patterns that are fine on this site, and83 cite it in the report. A report that cries wolf twice gets skimmed the third84 time.8586Expand every accordion individually, wait for the animation to settle, and87screenshot the open state before reading what is inside.8889## Step 4 - check these, at 1280px9091**Content.** Spelling and grammar in visible copy, placeholder text left behind92(lorem, TBD, {{key}}, %s), stale dates and old product names, inconsistent93terminology, contradicting statements.9495**Links and CTAs.** Broken links, links pointing at staging or localhost, empty96anchors with no text and no image, CTA text that says only "read more", a97primary CTA that is missing or below the fold.9899**Layout.** Broken or stretched images, text overflow and truncation, misaligned100or overlapping elements, inconsistent button styles, heading sizes that do not101match the hierarchy.102103**Accessibility.** Images with no alt, links with no descriptive text, form104inputs with no label, visibly low contrast, focus that disappears on tab.105106**Technical.** One evaluate covers everything a screenshot cannot show you.107Read the console messages separately, and check for a consent banner where the108law needs one:109110```js111() => {112 const meta = (sel, name) => document.querySelector(sel)?.getAttribute(name) ?? null;113 return {114 title: document.title,115 description: meta('meta[name="description"]', "content"),116 canonical: meta('link[rel="canonical"]', "href"),117 robots: meta('meta[name="robots"]', "content"),118 social: ["og:title", "og:description", "og:image", "twitter:card"].map((p) => ({119 tag: p,120 content: meta(`meta[property="${p}"], meta[name="${p}"]`, "content"),121 })),122 headings: [...document.querySelectorAll("h1,h2,h3,h4,h5,h6")].map((h) => ({123 level: Number(h.tagName[1]),124 // textContent, not innerText: innerText is empty for anything hidden or125 // mid-animation, which turns every collapsed accordion into a false alarm.126 text: h.textContent.trim().slice(0, 90),127 })),128 imagesWithoutAlt: [...document.images]129 .filter((i) => !i.hasAttribute("alt"))130 .map((i) => i.currentSrc),131 vagueLinks: [...document.querySelectorAll("a")]132 .map((a) => a.textContent.trim())133 .filter((t) => /^(click here|read more|learn more|here|more)$/i.test(t)),134 emptyLinks: [...document.querySelectorAll("a[href]")]135 .filter((a) => !a.textContent.trim() && !a.querySelector("img"))136 .map((a) => a.href),137 offsiteLinks: [...document.querySelectorAll("a[href]")]138 .map((a) => a.href)139 .filter((h) => /staging\.|dev\.|localhost/.test(h)),140 unnamedControls: [...document.querySelectorAll("button,input,select,textarea")]141 .filter(142 (el) =>143 !el.labels?.length && !el.getAttribute("aria-label") && !el.textContent.trim(),144 )145 .map((el) => el.outerHTML.slice(0, 120)),146 jsonLd: [...document.querySelectorAll('script[type="application/ld+json"]')].map(147 (s) => s.textContent,148 ),149 };150}151```152153Title present and roughly 50-60 characters, meta description present, all four154social tags present, canonical correct, no console errors.155156**Shared regions.** Check the footer and the global nav once for the whole site,157not once per page. They come from one template, so the same three issues on158forty pages is one issue.159160## Step 5 - report only what is broken161162Nothing optional, no best-practice suggestions, no "consider adding". If it is163fine it does not appear. Give every issue an ID (EN1, EN2, and EN1M for a164mobile-only one) so a reviewer can reply "EN3 is by design" in one line.165166| ID | Category | Priority | Location | Issue | Fix | Paste |167| --- | --- | --- | --- | --- | --- | --- |168169The last column is the whole issue in under 200 characters, starting with the170ID and ending with a sentence that starts "Fix:", so it drops straight into a171message to whoever owns the page.172173Priorities: red for broken functionality, a typo in headline copy, a dead nav174link or a severe accessibility failure. Yellow for anything a visitor will175notice. Green and grey below that.176177Close with what is clean. The list of what passed is what makes the failures178believable.179180## More than one language181182Every locale gets the same full checks, not a lighter scan. Practical order:183establish the base language first, then the languages whose words run longest,184where text expansion breaks layouts at 330 and 375px. Use one small evaluate185per locale for title, H1 and a body excerpt, and pull a full snapshot only186where that flags something.187188A fault in a template is one systemic issue affecting every locale, not one189issue per locale. Say it once.190191Always state the limit: translation accuracy needs a native speaker. This pass192covers structure, layout and completeness only.
Free to use in your own work, paid work included, no attribution required. Not for repackaging into a product you sell. Full terms.
More in skills
Back to every skills entry.