Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Catch UI regressions with Percy visual testing, snapshot strategy, Playwright and Cypress integration, responsive widths, dynamic-content stabilization, review workflow discipline, and CI gating without approval fatigue.
.claude/skills/pramoddutta-percy-visual-regression-testing/SKILL.md| Model | Eval pass | Runs |
|---|---|---|
| gemini-3.6-flash | 94% | 19 |
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-10 | ✗→✓ | ▲ Improved | 49% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 53% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 53% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 78% | 0% |
| case-20 | ✓→✗ | ▼ Worse | 71% | 0% |
You are an expert front-end QA engineer specializing in Percy (BrowserStack). When the user asks you to add visual testing, integrate Percy with an E2E suite, or fix noisy visual diffs, follow these instructions.
bashnpm i -D @percy/cli @percy/playwright export PERCY_TOKEN=... # project token from percy.io
typescriptimport { test } from '@playwright/test'; import percySnapshot from '@percy/playwright'; test('checkout states', async ({ page }) => { await page.goto('/checkout'); await page.waitForLoadState('networkidle'); await percySnapshot(page, 'Checkout - empty cart'); await addItemsViaApi(page, 3); await page.reload(); await page.getByTestId('cart-total').waitFor(); await percySnapshot(page, 'Checkout - 3 items', { widths: [375, 768, 1280] }); await page.getByRole('button', { name: 'Apply coupon' }).click(); await page.getByRole('dialog').waitFor(); await percySnapshot(page, 'Checkout - coupon modal'); });
bashnpx percy exec -- npx playwright test e2e/visual.spec.ts
Cypress equivalent: @percy/cypress, cy.percySnapshot('name', { widths: [...] }). Storybook: @percy/storybook snapshots every story, the highest-leverage entry point for design systems.
| Layer | What to snapshot | Cadence | |---|---|---| | Design system | Every component story (via Storybook) | Every PR | | Money-path pages | Each state of checkout, auth, pricing | Every PR | | Marketing/content pages | Above-the-fold at 3 widths | Nightly | | Full-page long-tail | Top 20 templates, not every URL | Nightly |
Name snapshots as stable identifiers ("Checkout - coupon modal"), never with timestamps or data values; renamed snapshots orphan their baselines. Standard widths: 375 (mobile), 768 (tablet), 1280 (desktop); add 1920 only where layout actually changes.
css/* percy.css (applied only in Percy's rendering) */ .timestamp, .relative-time { visibility: hidden !important; } .avatar-random { background: #ccc !important; } * { animation: none !important; transition: none !important; caret-color: transparent !important; }
yaml# .percy.yml version: 2 snapshot: percy-css: | @import url("./percy.css"); widths: [375, 1280] discovery: network-idle-timeout: 500
Additional stabilizers: seed test data (fixed names, fixed prices); freeze the clock in the app under test where dates render; mock third-party embeds (maps, ads, chat widgets) at the network layer; wait for fonts (document.fonts.ready) before snapshotting; scope flaky regions with per-snapshot percyCSS rather than growing the global file forever.
yaml# .github/workflows/visual.yml - name: Percy visual tests env: PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }} run: npx percy exec -- npx playwright test e2e/visual/ # parallel shards: run percy exec on each shard with PERCY_PARALLEL_TOTAL set, # so Percy assembles one build from all shards
Budget note: Percy prices by screenshots (snapshots x widths x browsers); the strategy table above exists to keep the count intentional. Track monthly usage; a usage spike usually means someone looped snapshots over a data set.
Choose Percy when you want managed rendering, cross-browser visual diffs, and a mature review UI tightly integrated with PR checks. Consider Playwright's built-in toHaveScreenshot() when budget is zero and one rendering environment is acceptable (you own baseline storage and flake management); Chromatic when your whole visual surface lives in Storybook; Applitools when AI-based region matching is worth the price for highly dynamic UIs.
networkidle alone; wait for the specific element/fonts, spinners pass networkidle| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 21,875 | 19,938 | -9% | 1 | 1 | 0% | 2,713 | 4,191 | +54% | 0 | 0 | — |
case-02 | fail→fail | 26,438 | 22,784 | -14% | 1 | 1 | 0% | 3,685 | 5,781 | +57% | 0 | 0 | — |
case-03 | pass→pass | 16,234 | 15,311 | -6% | 1 | 1 | 0% | 1,817 | 3,296 | +81% | 0 | 0 | — |
case-04 | pass→pass | 19,802 | 16,136 | -19% | 1 | 1 | 0% | 1,967 | 3,071 | +56% | 0 | 0 | — |
case-05 | pass→pass | 22,126 | 17,236 | -22% | 1 | 1 | 0% | 2,724 | 3,742 | +37% | 0 | 0 | — |
case-06 | pass→pass | 20,334 | 20,834 | +2% | 1 | 1 | 0% | 2,283 | 3,749 | +64% | 0 | 0 | — |
case-07 | pass→pass | 17,973 | 18,813 | +5% | 1 | 1 | 0% | 1,996 | 3,511 | +76% | 0 | 0 | — |
case-08 | pass→pass | 16,379 | 18,548 | +13% | 1 | 1 | 0% | 1,663 | 3,244 | +95% | 0 | 0 | — |
case-09 | pass→pass | 24,819 | 15,745 | -37% | 1 | 1 | 0% | 2,502 | 3,571 | +43% | 0 | 0 | — |
case-10 | fail→pass | 20,112 | 17,590 | -13% | 1 | 1 | 0% | 2,119 | 3,150 | +49% | 0 | 0 | — |
case-11 | pass→pass | 23,241 | 22,608 | -3% | 1 | 1 | 0% | 2,675 | 4,052 | +51% | 0 | 0 | — |
case-12 | pass→pass | 19,017 | 14,698 | -23% | 1 | 1 | 0% | 1,879 | 2,895 | +54% | 0 | 0 | — |
case-13 | pass→pass | 20,477 | 19,009 | -7% | 1 | 1 | 0% | 2,278 | 3,377 | +48% | 0 | 0 | — |
case-14 | pass→pass | 8,513 | 6,135 | -28% | 1 | 1 | 0% | 1,091 | 2,405 | +120% | 0 | 0 | — |
case-15 | fail→pass | 21,282 | 21,206 | -0% | 1 | 1 | 0% | 2,533 | 3,881 | +53% | 0 | 0 | — |
case-16 | fail→pass | 22,866 | 21,617 | -5% | 1 | 1 | 0% | 2,595 | 3,959 | +53% | 0 | 0 | — |
case-17 | pass→pass | 15,050 | 8,067 | -46% | 1 | 1 | 0% | 1,374 | 2,657 | +93% | 0 | 0 | — |
case-18 | fail→pass | 17,978 | 16,190 | -10% | 1 | 1 | 0% | 1,615 | 2,881 | +78% | 0 | 0 | — |
case-19 | pass→pass | 20,532 | 19,971 | -3% | 1 | 1 | 0% | 2,642 | 4,025 | +52% | 0 | 0 | — |
case-20 | pass→fail | 14,538 | 12,725 | -12% | 1 | 1 | 0% | 1,553 | 2,660 | +71% | 0 | 0 | — |
case-21 | pass→pass | 14,004 | 14,859 | +6% | 1 | 1 | 0% | 1,489 | 3,132 | +110% | 0 | 0 | — |
case-22 | pass→pass | 19,355 | 17,513 | -10% | 1 | 1 | 0% | 2,400 | 3,497 | +46% | 0 | 0 | — |
case-23 | pass→pass | 18,633 | 15,326 | -18% | 1 | 1 | 0% | 2,299 | 3,132 | +36% | 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. 23 cases were attempted. The headline lift of +13 percentage points is the difference between those two pass rates over the 23 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
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.