Agent skill

Vitest Visual Testing

by FranciscoMoretti in FranciscoMoretti/chat-js

Make @uiverify/vitest (Vitest browser-mode) captures deterministic so component visual tests stop coming back "changed" without a real change (flaky diffs).

Apache-2.0Auto-check passedTesting & QA

Install Vitest Visual Testing

skills CLI
$ npx skills add FranciscoMoretti/chat-js --skill vitest-visual-testing -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install FranciscoMoretti/chat-js vitest-visual-testing --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/FranciscoMoretti/chat-js.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.agents/skills/vitest-visual-testing .claude/skills/vitest-visual-testing && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
vitest-visual-testing
GitHub stars
1.2k
Token cost
~2.5k tokens
SKILL.md length
1,145 words
Files
1
Skills in repo
9
Repo updated
First seen
Licence
Apache-2.0

At a glance

Make @uiverify/vitest (Vitest browser-mode) captures deterministic so component visual tests stop coming back "changed" without a real change (flaky diffs).

  • Works in 5 steps: Freeze the data — the one that actually… → Freeze the clock → Infinite JS animations → …
  • Debugging visual tests over Vitest browser-mode component tests
  • SKILL.md covers Mental model — component…, The one Vitest-specific trap:…, The checklist (only what the… and Anti-patterns
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md

What it does

Vitest Visual Testing is an agent skill from FranciscoMoretti/chat-js. Make @uiverify/vitest (Vitest browser-mode) captures deterministic so component visual tests stop coming back "changed" without a real change (flaky diffs). Use when setting up or debugging visual tests over Vitest browser-mode component tests. Focuses only on the run-to-run variation the capturer can't neutralize from outside your app — above all live/dynamic data, the highest-value step (freeze it with static fixtures and the whole content-noise class disappears), plus the clock, infinite JS animations, and…

Its SKILL.md is about 2.5k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Testing & QA, covering Unit testing and Visual regression testing. It works with Vitest, OpenAI and Google Gemini. The repository describes itself as: Production-ready AI chat. Start here and make it your own. Formerly Sparka AI. The licence is Apache-2.0.

When your agent uses it

  • Debugging visual tests over Vitest browser-mode component tests
  • Tasks that involve Unit testing
  • Tasks that involve Visual regression testing

Example prompts

  • “changed”
  • “/vitest-visual-testing”

Workflow steps

5 steps, taken from the step headings in SKILL.md.

  1. Freeze the data — the one that actually matters
  2. Freeze the clock
  3. Infinite JS animations
  4. Non-Math.random randomness
  5. Dynamic layout

What it can do on your machine

Read from SKILL.md and the folder at commit 7da0efb. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are typescript).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Vitest Visual Testing loads about 2.5k tokens when it runs. Until then it costs about 161 tokens; SKILL.md has 1,145 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~161
When it runs · the whole SKILL.md, loaded when a task matches
~2.5k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from FranciscoMoretti/chat-js at commit 7da0efb, republished under its Apache-2.0 licence (© FranciscoMoretti). 1,145 words, ~2,526 tokens.

Download SKILL.mdSave it as .claude/skills/vitest-visual-testing/SKILL.md (or your agent's skills folder).
name
vitest-visual-testing
description
Make @uiverify/vitest (Vitest browser-mode) captures deterministic so component visual tests stop coming back "changed" without a real change (flaky diffs). Use when setting up or debugging visual tests over Vitest browser-mode component tests. Focuses only on the run-to-run variation the capturer can't neutralize from outside your app — above all live/dynamic data, the highest-value step (freeze it with static fixtures and the whole content-noise class disappears), plus the clock, infinite JS animations, and non-Math.random randomness, and the one Vitest-specific trap - capturing before the component has settled.

Deterministic Vitest captures (browser mode)

Mental model — component isolation already removed most of the flake

@uiverify/vitest archives each browser-mode test's final DOM + every resource the page loaded; UI Verify re-renders and pixel-diffs that archive server-side. Because a browser-mode component test renders an isolated component (no page scroll, no A/B / analytics / chat / consent scripts, no lazy-load-on-scroll races), you get the same head start Storybook gives you: the determinism work here is narrow. If you reach for scroll-settling or third-party stubbing, you're fighting a problem component isolation already removed (that's a real-page concern — see playwright-visual-testing).

Whatever the component is at the end of the test (or at your takeSnapshot() call) is baked into the archive forever. Your job is to drive it to one canonical state before capture.

Integration is one plugin (no per-test code):

ts
// vitest.config.ts
import { playwright } from "@vitest/browser-playwright";
import { uiverifyPlugin } from "@uiverify/vitest/plugin";
export default defineConfig({
  plugins: [uiverifyPlugin()],
  test: {
    browser: {
      enabled: true,
      provider: playwright(),
      instances: [{ browser: "chromium" }],
    },
  },
});

Every browser-mode test then archives its final DOM automatically; takeSnapshot('name') adds an intermediate checkpoint.

UI Verify's capturer neutralizes these automatically — do NOT hand-fix them: CSS animations & transitions (killed at render) and the Web Animations API (disabled); prefers-reduced-motion: reduce (emulated); Math.random (seeded before your app code runs); web fonts and <img> loading (waited for); finite JS animations (captured at their settled final frame). And unlike a real page (playwright-visual-testing), a browser-mode test has no SSR — no server-rendered random pick to reconcile. So the checklist below is only the remainder — what lives inside your app.

The headline: freeze the data. Once the list above is off the table, the one thing left that floods a component suite with false "changes" is live/dynamic data — star counts, follower counts, contributor lists, tiles, timestamps. Feed every component static fixtures and that entire class disappears at the source: static data can't churn run-to-run, so there is nothing to diff. This is the single highest-value determinism step here — do it first (step 1), and most components need nothing else.

The one Vitest-specific trap: capture before the component settled

The auto-snapshot fires at the end of a passing test, and takeSnapshot() fires the moment you call it. If the component is still resolving a promise, running a transition, or hasn't rendered its data yet, you archive a half-rendered frame. Drive it to its final state first — await your render helper, wait for the content to appear, then let the test end (or call takeSnapshot()):

ts
import { expect } from 'vitest';
import { render } from 'vitest-browser-react'; // or your framework's browser render helper
import { takeSnapshot } from '@uiverify/vitest';

test('user card', async () => {
  const screen = await render(<UserCard id="u_1" />); // render() is async - await it, or `screen` is a Promise
  await expect.element(screen.getByText('Ada Lovelace')).toBeVisible(); // wait for settled content
  await takeSnapshot();
});

This is the analog of a Playwright test's navigation + assertions: your render + waits are the determinism surface.

The checklist (only what the tool can't do for you)

1. Freeze the data — the one that actually matters

A component fed live/dynamic data is flaky by construction: the stars, followers, contributor list, tile order, and timestamps move between runs, so the diff lights up with no code change. Give every component static fixtures and the whole class is gone. Never let a test hit a real backend.

Concrete — feed fixed, ordered, complete fixtures:

  • fixed counts (stars, followers, downloads) — literal numbers, not a live fetch;
  • a fixed contributor/author list: fixed names and avatar URLs (or inlined avatars), in a fixed order;
  • a fixed set of tiles/rows in a fixed order (a live "trending" sort reorders every run);
  • fixed timestamps (pair with the clock, step 2).

Two ways to inject them, both fine:

ts
// (a) pass fixtures as props — the simplest, when the component takes its data as props
await render(<LibraryTile name="ktor" stars={12873} platforms={['jvm', 'js', 'native']} />);

// (b) mock the data module the component imports — when it fetches internally
vi.mock('../api/library', () => ({ getLibrary: () => fixtures.ktor }));

If a page is a server component that fetches, render its client presentational subtree with fixture props instead of the fetching wrapper — a browser-mode test has no server to run the fetch anyway. MSW in a setup file also works for fetch-based components; the rule is only no real request.

Copy the dogfood — it's the reference implementation. apps/docs/e2e/docs-visual.browser.test.ts is the in-repo example: call takeSnapshot('name') after the page settles, with static content.

One canvas per component, not N stories. Render every variant × state of a component (a Button's sizes/states, every tile kind) in a single grid and take one snapshot — cheaper (one screenshot), and you eyeball the whole component's surface at once. Keep each page/component in its own test file so --only-changed carries the untouched ones forward, and add a path filter so the visual job only runs on UI PRs — both keep the suite cheap at scale.

Show full SKILL.md (487 more words)Show less
2. Freeze the clock

The one thing the capturer deliberately does not do. Any component that reads the clock — a relative timestamp, a date defaulting to "today", a chart's day axis — drifts every run. Pin it with Vitest's fake timers before you render:

ts
import { beforeEach, afterEach, vi } from "vitest";

beforeEach(() => {
  vi.useFakeTimers();
  vi.setSystemTime(new Date("2020-01-01T00:00:00Z"));
});
afterEach(() => vi.useRealTimers());

If a component animates on mount and fake timers freeze it half-way, advance to the end (vi.runAllTimers()) or set the time after the render settles.

3. Infinite JS animations

A CSS/WAAPI/finite animation is handled for you (above). What's left is an infinite JS loop with no final frame — framer-motion pulsing dots, a Lottie loop, an autoplay spinner, or a <canvas> / requestAnimationFrame loop (which no media query can reach). Two fixes:

  • Preferred — honor reduced motion. The capturer emulates prefers-reduced-motion: reduce, so make the component respect it (framer-motion: <MotionConfig reducedMotion="user">, or gate the loop with useReducedMotion()). One line, and it's good app behavior anyway.
  • Escape hatch — detect the capture and render the end state (for a <canvas> rAF loop: if (isUIVerify()) drawOneStaticFrame(); else startRaf(); in the effect). The canonical helper reads two signals — a UIVerify navigator.userAgent marker and a window.__UI_VERIFY__ global:
    ts
    export const isUIVerify = () =>
      (typeof navigator !== "undefined" &&
        navigator.userAgent.includes("UIVerify")) ||
      (typeof window !== "undefined" && "__UI_VERIFY__" in window);
    tsx
    <RadarChart isAnimationActive={!isUIVerify()} />
    In Vitest browser mode the load-bearing signal is the window.__UI_VERIFY__ global (the browser provider owns the context, so the SDK sets the global, not the UA marker) — use the helper as-is; the global is what fires here.
4. Non-Math.random randomness

Math.random is seeded for you, but crypto.randomUUID(), a uuid library, or faker are not. Use fixed fixtures for anything that reaches the DOM (an id, a faker name), or set a fixed faker seed.

5. Dynamic layout

A JS-measured layout that reflows or reorders on its own (packing driven by measured size, a shuffled list) can vary run-to-run even with identical content. Force a deterministic variant (a fixed order/size), or mask the region.

One measured-layout case the SDK already handles: a component that measures text width on mount (a sliding tab or switch highlight) can bake a 1px-shifted position if the font wasn't ready at that first layout. The SDK preloads fonts before each test so the first measurement uses real metrics. If your CSS or a font registers late (an unusual setup) and you still see a sub-pixel shift, call preloadFonts() from @uiverify/vitest before render() to force it - settling after render can't undo a measurement already taken.

Anti-patterns

  • A test that fetches live data → mock it (vi.mock / MSW).
  • takeSnapshot() (or letting the test end) before the component committed or rendered its data → archives a half-rendered or blank frame; await render(...) (it is async), then await the settled state.
  • Disabling CSS animations / seeding Math.random / waiting on fonts by hand → wasted effort; the capturer already does all three. Spend the effort on the clock, settling, and infinite loops.
  • Unseeded crypto/uuid/faker or a bare Date.now() → fixtures + freeze the clock.
  • Reaching for scroll-settle / A-B stubbing → wrong path; that's a real-page concern.

© FranciscoMoretti, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in .agents/skills/vitest-visual-testing of FranciscoMoretti/chat-js.

Open the folder on GitHubat commit 7da0efb

Compare with similar skills

Vitest Visual Testing next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Vitest Visual Testing compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Vitest Visual Testing this skillFranciscoMoretti/chat-js1.2k—~2.5kAutomated safety check: PassApache-2.0
Playwright Testingchongdashu/vibejam-starter-pack149—~2.2kAutomated safety check: PassNone
Testingradix-ng/primitives274—~3.3kAutomated safety check: PassMIT
Web Testing with Playwright and Vitestwithkynam/vibecode-pro-max-kit1.1k—~892Automated safety check: PassApache-2.0
Sanity Visual Regressionsanity-io/sanity6.4k—~3.4kAutomated safety check: PassMIT
Test CommanderEliasOulkadi/shokunin114—~3kAutomated safety check: NotesMIT

Similar skills

  • Playwright Testing

    chongdashu/vibejam-starter-pack

    Plan, implement, and debug frontend tests: unit/integration/E2E/visual/a11y.

    149 GitHub stars~2.2k tokensUpdated 5 mo ago
    Testing & QAAuto-check passed
  • Testing

    radix-ng/primitives

    Test Radix NG primitives across every layer and pick the RIGHT one for a change: Vitest unit (zoneless), jest-axe a11y, Playwright browser regression (apps/visual-regression), SSR…

    274 GitHub stars~3.3k tokensUpdated 9 days ago
    Testing & QAAuto-check passed
  • Web Testing with Playwright and Vitest

    withkynam/vibecode-pro-max-kit

    Covers web testing from unit to E2E, load, visual, accessibility and security checks, with Playwright, Vitest and k6 guides plus a Playwright setup script.

    1.1k GitHub stars~892 tokensUpdated 3 mo ago
    Testing & QAAuto-check passed
  • Sanity Visual Regression

    sanity-io/sanity

    Official

    Add, review, and maintain Chromatic visual regression coverage in the Sanity monorepo via dev/storybook stories, the vitest browser-mode suite, and Playwright e2e snapshots.

    6.4k GitHub stars~3.4k tokensUpdated today
    Testing & QAAuto-check passed
  • Test Commander

    EliasOulkadi/shokunin

    Generate unit, integration, E2E, and visual regression tests following the Testing Trophy methodology (80% integration).

    114 GitHub stars~3k tokensUpdated 3 days ago
    Testing & QAAuto-check: notes
  • Storybook Testing

    yonatangross/orchestkit

    Storybook 10 testing patterns with Vitest integration, ESM-only distribution, CSF3 typesafe factories, play() interaction tests, Chromatic TurboSnap visual regression, module automocking…

    289 GitHub stars~2.3k tokensUpdated today
    Testing & QAAuto-check passed

More from FranciscoMoretti/chat-js

All 9 skills in this repo
  • Trpc Patterns

    FranciscoMoretti/chat-js

    Implement ChatJS tRPC routers, TanStack Query hooks, mutations, and cache invalidation.

    1.2k GitHub stars~288 tokensUpdated today
    Auto-check passed
  • Worktree Ports

    FranciscoMoretti/chat-js

    Discover and use stable per-app ports assigned to a local monorepo worktree.

    1.2k GitHub stars~231 tokensUpdated today
    Auto-check: notes
  • Generate Code Snippet

    FranciscoMoretti/chat-js

    Generate a concise, shareable code snippet URL for code.franciscomoretti.com.

    1.2k GitHub stars~178 tokensUpdated today
    Auto-check passed
  • Lazy Prefetch Pattern

    FranciscoMoretti/chat-js

    Use the ChatJS React Query v5 lazy-prefetch pattern without blocking server rendering.

    1.2k GitHub stars~217 tokensUpdated today
    Auto-check passed
  • Create Vercel AI Issue

    FranciscoMoretti/chat-js

    Create a well-formed GitHub issue in vercel/ai using the gh CLI.

    1.2k GitHub stars~203 tokensUpdated today
    Auto-check passed
  • Dev Auth Bypass

    FranciscoMoretti/chat-js

    Authenticate a browser against a local ChatJS app without OAuth.

    1.2k GitHub stars~141 tokensUpdated today
    Auto-check passed

Categories

Questions about Vitest Visual Testing

What does Vitest Visual Testing do?

Make @uiverify/vitest (Vitest browser-mode) captures deterministic so component visual tests stop coming back "changed" without a real change (flaky diffs). Vitest Visual Testing is an agent skill from FranciscoMoretti/chat-js. Make @uiverify/vitest (Vitest browser-mode) captures deterministic so component visual tests stop coming back "changed" without a real change (flaky diffs).

When should I use Vitest Visual Testing?

Vitest Visual Testing fits situations like: debugging visual tests over Vitest browser-mode component tests; tasks that involve Unit testing; tasks that involve Visual regression testing.

How do I install Vitest Visual Testing in Claude Code?

Run `npx skills add FranciscoMoretti/chat-js --skill vitest-visual-testing -a claude-code`. Or copy the skill folder (.agents/skills/vitest-visual-testing in FranciscoMoretti/chat-js) into .claude/skills/vitest-visual-testing in your project. Claude Code loads it when a task matches its description.

How do I install Vitest Visual Testing in Codex?

Run `npx skills add FranciscoMoretti/chat-js --skill vitest-visual-testing -a codex`. Or copy the skill folder (.agents/skills/vitest-visual-testing in FranciscoMoretti/chat-js) into .agents/skills/vitest-visual-testing in your project. Codex loads it when a task matches its description.

Can I use Vitest Visual Testing in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add FranciscoMoretti/chat-js --skill vitest-visual-testing -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/vitest-visual-testing, .gemini/skills/vitest-visual-testing, .github/skills/vitest-visual-testing and .opencode/skills/vitest-visual-testing in your project.

What does Vitest Visual Testing need to run?

SKILL.md names no scripts, command-line tools or credentials: Vitest Visual Testing is instructions for the agent only.

Does Vitest Visual Testing access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Vitest Visual Testing safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Vitest Visual Testing use?

Vitest Visual Testing is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Vitest Visual Testing use?

About 2.5k tokens (SKILL.md is roughly 10k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Vitest Visual Testing?

Skills that share tags, products or a category with Vitest Visual Testing: Playwright Testing (chongdashu/vibejam-starter-pack, 149 stars), Testing (radix-ng/primitives, 274 stars), Web Testing with Playwright and Vitest (withkynam/vibecode-pro-max-kit, 1.1k stars) and Sanity Visual Regression (sanity-io/sanity, 6.4k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Vitest Visual Testing?

FranciscoMoretti (a GitHub user) maintains it in FranciscoMoretti/chat-js, which has 1,201 GitHub stars. The repository holds 9 skills in this directory. The repository was last updated on October 7, 2026.

Source: FranciscoMoretti/chat-js on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.