Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Run QA testing on a page, feature, or full site at one of three depth tiers (smoke, standard, full). Use this skill whenever the user asks to test a page, audit a site, check for bugs, verify a deploy, run a QA sweep, or review accessibility, performance, or SEO basics. Triggers on test, QA, audit, verify, check, is it working, does it look right, broken, 404, image not loading, post-deploy check, regression test. Also triggers proactively after any significant code change or new page launch whe
.claude/skills/rampstackco-qa-testing/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 97% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 59% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 220% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 202% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 170% | 0% |
Verify that a page, feature, or site is working before declaring it shipped. Stack-agnostic. Console-snippet driven for speed.
This skill is faster than accessibility-audit (which goes deeper on WCAG) and performance-optimization (which goes deeper on Core Web Vitals). Use this skill for general QA. Use the specialists for deep audits.
accessibility-audit)performance-optimization)code-review-web)seo-technical)QA scales with the stakes. Pick the tier that matches the context.
| Tier | When to run | Time | Coverage | |---|---|---|---| | Smoke | After every deploy | 2 minutes | Critical signals only | | Standard | New page or feature | 10 minutes | On-page basics, accessibility, structure | | Full | Major release, pre-launch | 30+ minutes | Comprehensive across all dimensions |
The 2-minute "did the deploy break anything obvious?" check. Run after every deploy.
Console snippet (paste in browser dev tools):
javascriptconst smoke = { title: document.title, titleLen: document.title.length, canonical: document.querySelector('link[rel="canonical"]')?.href, h1Count: document.querySelectorAll('h1').length, missingAlts: [...document.querySelectorAll('img')].filter(i => !i.hasAttribute('alt')).length, schema: [...document.querySelectorAll('script[type="application/ld+json"]')] .map(s => { try { return JSON.parse(s.innerText)['@type'] } catch(e) { return 'invalid' } }), brokenImages: [...document.querySelectorAll('img')].filter(i => !i.complete || i.naturalWidth === 0).length, }; console.log(JSON.stringify(smoke, null, 2));
Pass criteria:
alt attribute. An empty alt="" on a decorative image is correct markup and passes; only an absent attribute fails.invalid entries in the snippet output). Whether the types are the right ones for the page is a Full-tier check, against the Rich Results Test.If any of these fail, do not proceed with deeper testing until the smoke issue is fixed.
The 10-minute new-page-or-feature audit. Covers the on-page basics plus accessibility and structure.
Console snippet:
javascriptconst audit = { title: document.title, titleLen: document.title.length, canonical: document.querySelector('link[rel="canonical"]')?.href, metaDesc: document.querySelector('meta[name="description"]')?.content, metaDescLen: document.querySelector('meta[name="description"]')?.content?.length, ogImage: document.querySelector('meta[property="og:image"]')?.content, ogTitle: document.querySelector('meta[property="og:title"]')?.content, twitterCard: document.querySelector('meta[name="twitter:card"]')?.content, h1Count: document.querySelectorAll('h1').length, h1Text: document.querySelector('h1')?.innerText, h2Count: document.querySelectorAll('h2').length, h2s: [...document.querySelectorAll('h2')].map(h => h.innerText.trim().slice(0, 60)), totalImages: document.querySelectorAll('img').length, missingAlts: [...document.querySelectorAll('img')].filter(i => !i.hasAttribute('alt')).length, brokenImages: [...document.querySelectorAll('img')].filter(i => !i.complete || i.naturalWidth === 0).length, externalLinksWithoutNoopener: [...document.querySelectorAll('a[target="_blank"]')] .filter(a => !a.rel?.includes('noopener')).length, schema: [...document.querySelectorAll('script[type="application/ld+json"]')] .map(s => { try { const d = JSON.parse(s.innerText); return d['@graph'] ? d['@graph'].map(x => x['@type']) : d['@type']; } catch(e) { return 'invalid' } }), hasSkipLink: [...document.querySelectorAll('a[href^="#"]')].slice(0, 3) .some(a => /skip/i.test(a.textContent) && !!document.getElementById(a.getAttribute('href').slice(1))), pageLanguage: document.documentElement.lang || 'NOT SET', hasFavicon: !!document.querySelector('link[rel*="icon"]'), }; console.log(JSON.stringify(audit, null, 2));
Pass criteria (in addition to smoke):
target="_blank" have rel="noopener"lang attribute on <html>)The 30-minute pre-launch check. Cover all dimensions.
| Dimension | Pass criteria | |---|---| | Smoke and standard | All pass | | Accessibility (basic) | Run browser audit tool (e.g., Lighthouse), score above 90 | | Performance (basic) | Every threshold in the report template's Performance checklist, INP included | | Mobile responsiveness | Every viewport in the report template's responsiveness checklist | | Cross-browser | Tested in Chrome, Safari, Firefox (and Edge if relevant audience) | | Forms | All forms submit successfully and validate correctly | | Internal links | No broken internal links (sample 20 random) | | External links | All return 200 (sample 10) | | Sitemap | Returns 200, lists canonical URLs only | | robots.txt | Allows production crawlers, blocks staging if applicable | | Security headers | HSTS, X-Frame-Options, X-Content-Type-Options present | | HTTPS | All resources load over HTTPS, no mixed content | | 404 handling | 404 pages return HTTP 404 (not soft 200) | | Schema validation | All schema validates in Rich Results Test | | Analytics | Events fire as expected on key user actions | | Cache behavior | Cache headers appropriate for page type |
For headers, run:
javascriptfetch(window.location.origin, { method: 'HEAD' }) .then(r => { const headers = {}; for (const [k, v] of r.headers.entries()) headers[k] = v; console.log(JSON.stringify(headers, null, 2)); });
Look for: strict-transport-security, x-frame-options, x-content-type-options.
javascriptconst imgs = [...document.querySelectorAll('img')].map(i => ({ src: i.src.split('/').pop().split('?')[0].slice(0, 60), alt: i.hasAttribute('alt') ? (i.alt === '' ? 'DECORATIVE (empty alt)' : i.alt) : 'MISSING', width: i.naturalWidth, height: i.naturalHeight, loaded: i.complete && i.naturalWidth > 0, })); console.table(imgs); console.log({ total: imgs.length, broken: imgs.filter(i => !i.loaded).length, noAlt: imgs.filter(i => i.alt === 'MISSING').length, decorative: imgs.filter(i => i.alt.startsWith('DECORATIVE')).length, });
javascriptconst headings = [...document.querySelectorAll('h1, h2, h3, h4, h5, h6')].map(h => ({ level: parseInt(h.tagName[1]), text: h.innerText.trim().slice(0, 80), })); console.table(headings); // Check for skipped levels const levels = headings.map(h => h.level); let skipped = false; for (let i = 1; i < levels.length; i++) { if (levels[i] > levels[i-1] + 1) { console.warn(`Skipped from H${levels[i-1]} to H${levels[i]}: "${headings[i].text}"`); skipped = true; } } if (!skipped) console.log('No skipped heading levels');
javascriptfunction contrast(bg, fg) { function lum(hex) { return [hex.slice(1,3), hex.slice(3,5), hex.slice(5,7)] .map(h => parseInt(h, 16) / 255) .map(v => v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4)) .reduce((a, v, i) => a + v * [0.2126, 0.7152, 0.0722][i], 0); } const [l1, l2] = [lum(bg), lum(fg)]; const r = ((Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05)).toFixed(2); return r + ':1 ' + (parseFloat(r) >= 4.5 ? 'PASS body' : parseFloat(r) >= 3 ? 'PASS large only' : 'FAIL'); } // Examples contrast('#FFFFFF', '#4B5563'); // body color check contrast('#FFFFFF', '#9CA3AF'); // verify gray choices
javascript[...document.querySelectorAll('form')].forEach((form, i) => { const fields = [...form.querySelectorAll('input, select, textarea')].map(field => ({ type: field.type || field.tagName.toLowerCase(), name: field.name, hasLabel: !!form.querySelector(`label[for="${field.id}"]`) || !!field.closest('label'), required: field.required, })); console.log(`Form ${i + 1}:`); console.table(fields); });
javascriptconst externalLinks = [...document.querySelectorAll('a[href^="http"]')] .filter(a => !a.href.includes(window.location.host)); const issues = externalLinks.filter(a => a.target === '_blank' && (!a.rel?.includes('noopener') || !a.rel?.includes('noreferrer')) ); if (issues.length) { console.warn(`${issues.length} external links missing noopener/noreferrer:`); issues.forEach(a => console.warn(a.href)); } else { console.log(`All ${externalLinks.length} external links properly attributed`); }
references/qa-report-template.md for full audits.For smoke tests: console output is the report.
For standard and full audits: a markdown report at qa-report-[date].md. Use the template in references/qa-report-template.md.
This skill's output depends on data, measurements, or tool results it cannot generate on its own. When a required input, tool, or data source is unavailable or unverifiable, the sanctioned output is the deliverable with the gap stated: what was needed, what was actually obtained or verified, and which parts of the output are affected. Fabricating, estimating, or interpolating a required number to complete the deliverable is never sanctioned. A stated gap is a complete answer.
references/qa-report-template.md - Markdown report template for standard and full audits.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 11,719 | 7,629 | -35% | 1 | 1 | 0% | 2,512 | 4,950 | +97% | 0 | 0 | — |
case-02 | fail→fail | 22,311 | 23,959 | +7% | 1 | 1 | 0% | 4,782 | 6,106 | +28% | 0 | 0 | — |
case-03 | fail→fail | 10,138 | 20,157 | +99% | 1 | 1 | 0% | 875 | 7,499 | +757% | 0 | 0 | — |
case-04 | pass→pass | 12,894 | 11,870 | -8% | 1 | 1 | 0% | 2,328 | 5,580 | +140% | 0 | 0 | — |
case-05 | fail→pass | 25,558 | 19,017 | -26% | 1 | 1 | 0% | 4,338 | 6,907 | +59% | 0 | 0 | — |
case-06 | fail→fail | 5,780 | 4,409 | -24% | 1 | 1 | 0% | 955 | 4,068 | +326% | 0 | 0 | — |
case-07 | pass→pass | 9,007 | 5,653 | -37% | 1 | 1 | 0% | 1,557 | 4,343 | +179% | 0 | 0 | — |
case-08 | fail→pass | 7,792 | 10,187 | +31% | 1 | 1 | 0% | 1,242 | 3,973 | +220% | 0 | 0 | — |
case-09 | fail→pass | 7,952 | 2,387 | -70% | 1 | 1 | 0% | 1,262 | 3,813 | +202% | 0 | 0 | — |
case-10 | pass→pass | 9,456 | 6,093 | -36% | 1 | 1 | 0% | 1,642 | 4,478 | +173% | 0 | 0 | — |
case-11 | pass→pass | 8,954 | 6,532 | -27% | 1 | 1 | 0% | 1,809 | 4,670 | +158% | 0 | 0 | — |
case-12 | pass→pass | 9,833 | 11,820 | +20% | 1 | 1 | 0% | 1,920 | 5,815 | +203% | 0 | 0 | — |
case-13 | pass→pass | 10,202 | 4,422 | -57% | 1 | 1 | 0% | 2,022 | 4,227 | +109% | 0 | 0 | — |
case-14 | pass→pass | 6,426 | 4,685 | -27% | 1 | 1 | 0% | 1,190 | 4,224 | +255% | 0 | 0 | — |
case-15 | pass→pass | 5,328 | 2,508 | -53% | 1 | 1 | 0% | 965 | 3,856 | +300% | 0 | 0 | — |
case-16 | pass→pass | 13,364 | 9,572 | -28% | 1 | 1 | 0% | 2,726 | 5,269 | +93% | 0 | 0 | — |
case-17 | fail→pass | 8,205 | 1,950 | -76% | 1 | 1 | 0% | 1,390 | 3,749 | +170% | 0 | 0 | — |
case-18 | pass→pass | 11,541 | 1,723 | -85% | 1 | 1 | 0% | 1,785 | 3,641 | +104% | 0 | 0 | — |
case-19 | fail→pass | 11,874 | 1,721 | -86% | 1 | 1 | 0% | 1,912 | 3,640 | +90% | 0 | 0 | — |
case-20 | pass→pass | 12,240 | 4,534 | -63% | 1 | 1 | 0% | 2,053 | 4,179 | +104% | 0 | 0 | — |
case-21 | fail→pass | 7,514 | 2,528 | -66% | 1 | 1 | 0% | 1,401 | 3,868 | +176% | 0 | 0 | — |
case-22 | pass→pass | 8,211 | 6,375 | -22% | 1 | 1 | 0% | 1,636 | 4,778 | +192% | 0 | 0 | — |
case-23 | pass→pass | 10,091 | 4,757 | -53% | 1 | 1 | 0% | 1,704 | 4,270 | +151% | 0 | 0 | — |
case-24 | pass→pass | 9,278 | 5,854 | -37% | 1 | 1 | 0% | 1,459 | 4,311 | +195% | 0 | 0 | — |
case-25 | fail→pass | 13,278 | 2,210 | -83% | 1 | 1 | 0% | 2,230 | 3,722 | +67% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 25 cases were attempted. The headline lift of +32 percentage points is the difference between those two pass rates over the 25 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.