AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Playwright Visual Testing

skill-uiverify-uiverify-playwright-visual-testing · by uiverify

Make @uiverify/playwright (archive-replay) captures deterministic so real-page visual tests stop coming back "changed" without a real change (flaky diffs). Use when driving a real page — your app under test, a staging build, or live production — and the diff flakes. A symptom-to-lever checklist for the run-to-run variation you probably have: feature flags, live data, the clock, third-party widget…

No reviews yet
0 installs
10 views
0.0% view→install

Install

$ agentstack add skill-uiverify-uiverify-playwright-visual-testing

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-uiverify-uiverify-playwright-visual-testing)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
17d ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Playwright Visual Testing? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Deterministic Playwright captures (archive-replay)

Mental model

@uiverify/playwright captures each test's final DOM + every resource the page loaded into an archive; UI Verify re-renders and pixel-diffs that archive server-side. So:

> Whatever the page was at the instant of capture is baked into the archive forever. Your job is to > drive the page to one canonical state before the snapshot. UI Verify can't un-see a random A/B variant, > a live feed, or a piece of lazy content that never loaded.

The snapshot fires automatically when the test passes, so your beforeEach + navigation is the determinism surface. Integration is one import:

- import { test, expect } from '@playwright/test';
+ import { test, expect } from '@uiverify/playwright';

What UI Verify handles server-side (don't hand-fix these): it re-renders the archived DOM with animations disabled, prefers-reduced-motion emulated, and fonts and images settled. So pixel-level settling — waiting on document.fonts.ready, sticky-header animation, image decode — is not your job. Your job is narrower: make sure the content and state the archive records is the same every run.

The core skill: recognize the class, then pull the lever at record time

Everything below is the same move — something rendered differently run-to-run and it wasn't a real change; find what injected the variation and pin it before the snapshot.

| Symptom in the diff | Cause class | Lever | |---|---|---| | Whole page reflows; height differs between runs | Feature flag / A-B experiment rendering a different variant per load (Optimizely, LaunchDarkly, VWO, your own) | Stub the flag calls so one fixed variant renders → recipe 1 | | A list / feed / counts differ | Live data from an API | Mock the endpoint with a fixed response → recipe 2 | | Timestamps, "today", a year, a chart's day axis drift | The clock (Date.now) at render time | Freeze Date before navigation → recipe 3 | | A banner / chat / consent overlay appears or shifts | Third-party widget injecting DOM | Stub or dismiss it → recipe 1 + 4 | | Blank/placeholder where an image should be | Lazy content that never loaded, so its bytes aren't in the archive | Trigger it at record time → recipe 5 | | Same content, packed or ordered differently each run | Dynamic layout that reflows or reorders on its own | Mask it (or pin a fixed order/size) → recipe 6 |

Prefer removing a whole class at the source: test a build/staging with flags and third-party scripts off and rows 1 and 4 disappear before you write any code.

Recipes

1. Stub feature-flag / third-party scripts (register in beforeEach, before goto):

await page.route(/optimizely|optly|launchdarkly|segment|intercom/i, (route) =>
  route.fulfill({ status: 204, body: '' }).catch(() => undefined),
);

Use fulfill({ status: 204 }), not route.abort() — an aborted request reads as pending network activity and can hang the page's readiness wait. A 204 completes it empty: the script "loads," resolves no variant, and the page renders its control DOM. Identify the culprit by loading the page a few times and watching document.body.scrollHeight flip between values.

2. Mock live data: intercept the API route and fulfill a fixed JSON fixture — same shape every run.

await page.route('**/api/items', (route) =>
  route.fulfill({ json: fixtures.items }),
);

3. Freeze the clock (before navigation — it changes what the app renders into the DOM). Use Playwright's built-in clock so Date, Date.now(), and Date()-without-new all stay correct:

await page.clock.setFixedTime(new Date('2020-01-01T00:00:00Z'));

4. Dismiss overlays (best-effort; absent = fine):

await page.getByRole('button', { name: 'Accept All' }).click({ timeout: 4000 }).catch(() => {});

5. Trigger lazy content so its bytes land in the archive. This is the one "settle" that is yours — not for pixel-stability (UI Verify handles that) but because content that never loaded was never recorded. Scroll it into view before the snapshot:

await page.evaluate(async () => {
  for (let y = 0; y  setTimeout(r, 60));
  }
  scrollTo(0, 0);
});

6. Neutralize what can't be pinned — there's no separate mask step; UI Verify diffs the archived DOM, so hide the unstable element in the DOM before the snapshot and its varying content never lands in the archive:

// target the container that reflows or reorders between runs
await page
  .locator('[data-testid="dynamic-region"]')
  .evaluateAll((els) => els.forEach((el) => (el.style.visibility = "hidden")));

Diagnosing a flaky diff

  1. Localized band vs whole-page shift? A whole-page shift = something changed the page height (a

variant, missing lazy content, or JS layout), reflowing everything — not a style change.

  1. Whole-page → compare document.body.scrollHeight across runs. Two discrete values = a flag / third

party (recipe 1). Continuous variation = JS layout (recipe 6, neutralize it).

  1. Content differs = live data (recipe 2) or the clock (recipe 3).

Then upload the archive:

npx playwright test                       # archives land in uiverify-archive/
uiverify upload --static-dir uiverify-archive

Anti-patterns

  • route.abort() to block a script → can hang page readiness; use fulfill(204).
  • Hand-settling fonts / sticky animation / image decode → UI Verify re-renders and settles those; only

content loading (recipe 5) is your concern.

  • Testing live prod with flags on → you're archiving a random variant.
  • Unseeded crypto/uuid/faker or live data → the archive is only as stable as what rendered into it.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.