Testivai oss agent

Local-first visual regression testing for Playwright, Selenium, WebdriverIO, Python, Java and Ruby.

by testivai·MIT license·★ 5 Stars on the repo·GitHub ↗

Inside this agent

README.md · the author's own words
# TestivAI Open Source

[![@testivai/witness](https://img.shields.io/npm/v/@testivai/witness.svg?label=%40testivai%2Fwitness)](https://www.npmjs.com/package/@testi…
[![@testivai/witness-playwright](https://img.shields.io/npm/v/@testivai/witness-playwright.svg?label=%40testivai%2Fwitness-playwright)](htt…
[![@testivai/witness-webdriverio](https://img.shields.io/npm/v/@testivai/witness-webdriverio.svg?label=%40testivai%2Fwitness-webdriverio)](…
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)

**Local-first visual regression testing SDKs for modern web applications.**

This is the home of TestivAI. It contains everything you need to capture, diff, and report visual regressions **fully locally** — MIT-licen…

> **[See a live report →](https://testivai.github.io/testivai-example/)** — a real TestivAI OSS report rendered in your browser, straight f…

[![Real TestivAI report — style-only-change verdict, selector-attributed regions, heatmap diff](./docs/assets/oss-report.png)](https://test…

## Why TestivAI?

Word-for-word excerpt, not a result. Read the full README.md ↓

Files of Testivai oss

testivai/main1 file
README.md
Show the full text368 lines

TestivAI Open Source

@testivai/witness @testivai/witness-playwright @testivai/witness-webdriverio License: MIT

Local-first visual regression testing SDKs for modern web applications.

This is the home of TestivAI. It contains everything you need to capture, diff, and report visual regressions fully locally — MIT-licensed, no account, no server.

See a live report → — a real TestivAI OSS report rendered in your browser, straight from CI. No install, no signup.

Real TestivAI report — style-only-change verdict, selector-attributed regions, heatmap diff

Why TestivAI?

Pixel-only visual testing drowns you in false positives — a font re-hint or an anti-aliasing shift across machines lights up as a "change," and you spend your time re-approving noise.

TestivAI pairs the screenshot with a snapshot of the page DOM (every adapter and the crawler capture one by default; the experimental testivai run sidecar does not) and, from most test-framework adapters, a digest of every element's computed styles. When pixels differ but the DOM and the computed styles are identical and the diff is small and scattered, the report labels the diff likely render noise instead of crying wolf. When the DOM and styles match but the diff sits in one area, it says that too: the style check covers a fixed list of properties, not size, overflow, transform or images, so that is a bad capture or a real change, not noise. It still shows the diff; it tells you what it most likely is. When only the styles changed, it says so: a style-only change is real. When the styles could not be compared, it says styles not compared and does not call the diff noise, because a stylesheet-only change is not ruled out. When the DOM actually changed, you see exactly what (2 added, 1 removed). That layered signal is the difference between a flaky test wall and a report you trust.

  • Fully local, no account — captures, diffs, and a self-contained HTML report all stay on your machine.
  • DOM + style-aware noise hint — tells likely render jitter from real changes, and catches the stylesheet-only case. When both captures carry an element map (the Playwright JS/TS, WebdriverIO, Selenium and Ruby adapters record one), an identical DOM with changed computed styles reads as "Styles changed on button.cta", never as noise. Without an element map on both sides (the testivai witness <url> crawler, playwright-python, playwright-java), computed styles are not compared, and an identical-DOM diff reads "styles not compared", not "likely render noise".
  • Auditable masks & region-level diffs — exclude dynamic areas (selectors or coordinates) with the masked region hatched in the diff, and get "3 changed regions" with bounding boxes instead of a raw pixel percentage.
  • Element attribution & exact shift detection — the report names which element changed ("div.card:nth-of-type(2) shifted +8px vertically — content unchanged") and spots the injected-banner case ("everything below y=80 moved +24px"), derived from layout, not pixel guesswork. No local-first tool does this.
  • First-class adapters — Playwright (TS/JS and Python, Java experimental) and WebdriverIO, using each framework's native APIs; every language shares one set of baselines and one report.
  • Mobile web, no extra package — Playwright device emulation works through the same witness() call: one project per device profile, baselines keyed per device automatically, and the DOM and computed-style attribution survives emulation, so a change behind a @media query is caught on mobile and correctly passes on desktop. See the mobile web guide.
  • PR-native workflow — a GitHub Action posts the diff and approves baselines from a /testivai approve comment.
  • No lock-in — MIT license, baselines live in your git, and results.json is a semver-governed public contract.

Evaluating this for a team? Maintenance & roadmap covers who builds it, the release cadence, and what happens to your setup if maintenance ever stops.

Already using Playwright's toHaveScreenshot()? It's good, and if a pixel diff answers your question you should keep using it. Here's an honest look at what TestivAI adds and when it isn't worth the dependency.

Eyes for your coding agent

If an AI agent (Claude Code, Cursor, Copilot, …) writes your UI code, someone still has to check what the UI looks like — and it shouldn't be you, one screenshot at a time. TestivAI is built to be that check:

  • No account, no API key, no network — an agent can run it inside any sandbox without you provisioning secrets.
  • Machine-readable output — every run writes visual-report/results.json (a semver-governed schema) with per-snapshot diff percentages and DOM change summaries, so an agent can read the result and self-correct.
  • Noise-aware verdicts — the DOM and computed-style checks tell the agent whether a pixel diff is likely render noise (identical DOM, styles compared and identical, a small and scattered diff), a concentrated or large diff (identical DOM and styles, but the diff sits in one area or covers over 1% of pixels: a bad capture or a change the style check does not cover), a style-only change (identical DOM, restyled elements), a real structural change (2 added, 1 removed), or styles not compared (identical DOM, but no style data to compare, so a stylesheet-only change is not ruled out). It doesn't chase anti-aliasing ghosts, and it doesn't label a diff it couldn't check as noise.
  • Explanations, bring-your-own-model — the MCP explain_snapshot tool hands your agent layered evidence (which selectors shifted vs changed, whole-page shift detection, style-only changes) and your model writes the narrative: "card #2 shifted +24px — likely the banner injected above it." No hosted AI service in the loop.
  • Human approval stays in the PR — the agent iterates locally; you approve baselines with one /testivai approve comment.

Paste this into your project's AGENTS.md / CLAUDE.md to wire it up (full guide with MCP setup, a real agent transcript, and the approval rule: docs/guides/ai-agents.md):

## Visual verification
After changing any UI code, run `npx playwright test` (TestivAI captures
screenshots automatically), then read `visual-report/results.json`.
- `status: "changed"` with `dom.changed: true` → describe the DOM summary and
  ask whether the change is intended before approving.
- `status: "changed"` with `dom.changed: false` and `dom.styleCheck: "mismatch"`
  → style-only change; real, not noise. Compare `dom.styleChanges` with what
  you intended.
- `status: "changed"` with `dom.noiseHint: true`, `dom.styleCheck: "match"`,
  `diffPercent` at most 1, and no `regions` or regions spread out (their
  combined box covers more than a third of the page) → likely render noise;
  mention it but don't block.
- `status: "changed"` with `dom.noiseHint: true` and `dom.styleCheck: "match"`
  but every region within a third of the page, or `diffPercent` over 1 → not
  noise: a bad capture or a change the style check does not cover (size,
  overflow, transform, images). Look at the diff image before approving.
- `status: "changed"` with `dom.noiseHint: true` and `dom.styleCheck`
  `"unavailable"` or missing → styles not compared: could be render noise or a
  stylesheet-only change. Don't treat it as noise: look at the diff image before approving.
- Never run `testivai approve` yourself — baseline approval is a human decision.

Packages

Live versions are shown by the badges at the top of this README.

Package Description
@testivai/witness Core SDK: CLI, local diffing, baselines, HTML report generator
@testivai/witness-playwright Playwright reporter/adapter built on top of @testivai/witness
@testivai/witness-webdriverio WebdriverIO service + capture function (local mode)
@testivai/witness-selenium Selenium WebDriver capture adapter (Python/Java Selenium live in python/ and java/)
@testivai/mcp MCP server — visual results + diff images for AI coding agents
testivai (PyPI) Python adapter for playwright-python + pytest plugin — same baselines & report
ai.testiv:testivai Java adapter for playwright-java + JUnit 5 extension (experimental)
testivai (RubyGems) Ruby adapter for Capybara / RSpec / Cucumber — same baselines & report

Plus:

  • action/ — GitHub Action for PR-based visual approvals
  • examples/ — minimal real-world example projects
    • examples/site-check/ — an hourly GitHub Actions check that fails when a page is up but broken (stylesheet, script or font missing, JavaScript error, blank page), with one issue per outage. testiv.ai runs it on itself.
  • docs/ — public documentation
  • e2e/ — OSS smoke E2E test suite

No test suite? One command.

AI-built and vibe-coded apps (Lovable, Bolt, v0, ...) usually ship with zero tests. You still get the full safety net:

npx testivai witness http://localhost:3000

TestivAI launches a headless Chrome, discovers your pages (or takes --pages "/,/pricing"), and captures a screenshot and the DOM of each one. Baselines, pixel and DOM diffs, the HTML report and PR approvals work as below, no test framework required. The crawler records no element map, so computed styles are not compared: a pixel diff with an identical DOM reads styles not compared (could be render noise or a stylesheet-only change), never likely noise, and there is no selector attribution or page-shift detection. See the vibe-coded apps guide.

Quick Start (Playwright, Local Mode)

# 1. Install
npm install -D @testivai/witness-playwright @playwright/test
npx playwright install chromium
// 2. (OPTIONAL) Customize tolerances and report settings.
// Everything runs locally — no config needed.
// Only create this file if you want to tune threshold, reportDir, etc.
// File: .testivai/config.json
{
  "threshold": 0.1,            // per-pixel color sensitivity (0-1)
  "maxDiffPercent": 0,         // pass diffs at or below this % (your tolerance dial)
  "noiseAutoPass": false,      // true: diffs with identical DOM AND matching computed styles, not concentrated in one area, within noiseMaxDiffPercent, pass
  "stabilize": true,           // freeze animations, hide caret, wait for fonts
  "ignoreSelectors": [],       // e.g. [".live-chat", "[data-testid=clock]"]
  "reportDir": "visual-report",
  "autoOpen": false
}
// 3. Wire the reporter — playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  reporter: [
    ['list'],
    ['@testivai/witness-playwright/reporter'],
  ],
});
// 4. Add a capture call — tests/example.spec.ts
import { test } from '@playwright/test';
import { witness } from '@testivai/witness-playwright';

test('homepage looks correct', async ({ page }, testInfo) => {
  await page.goto('http://localhost:3000');
  await witness(page, testInfo, 'homepage');
});
# 5. Run
npx playwright test

First run: baselines are written to .testivai/baselines/. Later runs: screenshots are diffed and a self-contained HTML report is written to ./visual-report/.

What you get out of the box (free, no account)

  • Full-page screenshot capture via Playwright
  • Stabilized captures by default — animations/transitions frozen, caret hidden, web fonts awaited (the top causes of flaky visual tests, neutralized before every screenshot)
  • Local pixel diff with configurable threshold
  • Tunable pass criteria, all opt-in — maxDiffPercent / maxDiffPixels tolerances, shiftTolerance for pure element shifts, and noiseAutoPass, which passes a diff only when the DOM is identical, the computed styles were compared and match, and the diff is not concentrated in one area (never one whose styles were not compared). Nothing auto-passes until you configure it, and auto-passed snapshots stay labeled in the report
  • ignoreSelectors and mask for dynamic content such as timestamps (global or per-snapshot): stabilization does not hide changing content, so you list it
  • Self-contained HTML report (visual-report/index.html)
  • Machine-readable results (visual-report/results.json)
  • Committed baselines under .testivai/baselines/ (just git add them)

CI Integration (GitHub Actions)

Copy this single workflow file into your repository. It handles both running the visual regression tests and processing /testivai approve commands from PR comments — no extra secrets, no external services required.

# .github/workflows/testivai-oss.yml
name: TestivAI OSS

on:
  pull_request:
    branches: [main]
  issue_comment:
    types: [created]        # listens for /testivai approve commands

permissions:
  contents: write           # approve action commits updated baselines to the branch
  pull-requests: write      # post PR diff comment
  statuses: write           # set pass/fail indicator on the PR
  actions: read             # approve action finds the CI run and downloads its report artifact

jobs:

  # Runs on every PR — captures screenshots, diffs against baselines, posts report
  visual-regression:
    name: Visual Regression (OSS)
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci
      - run: npx playwright install chromium --with-deps
      - run: npm run build
      - run: npm run test:oss          # runs playwright.oss.config.ts

      - name: Post results + upload report
        uses: testivai/testivai-oss@v1
        if: always()
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          report-dir: visual-report   # where @testivai/witness writes results.json

  # Runs when a collaborator comments /testivai approve on the PR
  approve-baselines:
    name: Approve Baselines
    if: |
      github.event_name == 'issue_comment' &&
      github.event.issue.pull_request != null &&
      startsWith(github.event.comment.body, '/testivai')
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: testivai/testivai-oss/approve@v1
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          workflow: testivai-oss.yml   # this file's name — used to find the report artifact

  # Optional: Claude triages every diff as glitch / intended change / bug /
  # possible bug. Skips (no tokens) without the secret, or with enabled: false.
  ai-triage:
    name: AI triage
    needs: visual-regression
    if: ${{ !cancelled() && github.event_name == 'pull_request' }}
    concurrency: { group: 'ai-triage-${{ github.event.pull_request.number }}', cancel-in-progress: true }
    runs-on: ubuntu-latest
    timeout-minutes: 15
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        continue-on-error: true   # no report when the visual job failed early: the triage skips
        with: { name: testivai-visual-report, path: visual-report }
      - uses: testivai/testivai-oss/review@v1
        with:
          anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
          # claude-code-oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}   # instead, for a Claude subscription
          # enabled: false   # turn it off

The ai-triage job runs on every pull request with a changed or new snapshot or a missing baseline (unless .testivai/config.json sets failOnMissing: false) once an ANTHROPIC_API_KEY secret is set (or a CLAUDE_CODE_OAUTH_TOKEN secret, passed as claude-code-oauth-token), and posts one advisory comment: which diffs look like glitches, which match the PR, and which look like bugs. It never changes the check or a baseline. See AI triage of every diff.

Approve changed baselines

After CI posts the diff report on your PR, review the testivai-visual-report artifact, then comment:

Comment Effect
/testivai approve homepage Approves one named snapshot
/testivai approve --all Approves every changed snapshot at once

What happens:

  1. Action verifies you have write access to the repository (others get a polite rejection)
  2. Downloads the testivai-visual-report artifact from the latest CI run on your branch
  3. Copies approved screenshots into .testivai/baselines/ and commits them to your PR branch
  4. Posts a confirmation comment listing what was approved
  5. CI runs again on the new commit — approved snapshots now pass

Step 5 needs a token other than GITHUB_TOKEN: GitHub starts no workflow run for a push made with GITHUB_TOKEN. Give the approve step a fine-grained personal access token (github-token: ${{ secrets.TESTIVAI_APPROVE_TOKEN || secrets.GITHUB_TOKEN }}), or push another commit after approving. See Approve with a token that re-runs CI.

What the PR comment looks like
TestivAI Visual Report

4 passed | 4 changed | 1 new — 9 total

Changed Snapshots

▼ homepage — 0.31% different
  DOM unchanged — pixel diff is likely render noise (anti-aliasing, font hinting). Styles verified unchanged; the diff is small and scattered.

▼ products — 0.03% different
  DOM and styles unchanged, but the diff is concentrated in one area — 3 regions within 155×52 px, 0.8% of the page: a bad capture, or a change the style check does not cover (size, overflow, position, transform, text decoration, filters, pseudo-elements, or images and other assets). Compare the images before approving.

▼ pricing — 0.42% different
  DOM unchanged, styles not compared — could be render noise or a stylesheet-only change (no comparable style digests on both sides). Compare the images before approving.

▼ dashboard — 8.91% different
  DOM changed — 2 added, 1 removed.

Real-World Example

A complete, minimal consumer project lives at testivai-example: a static page, three witness() calls, the PR /testivai approve flow, and a live report on Pages — all against the published packages.

Repository Layout

packages/
  witness/     @testivai/witness              — CLI, diff engine, baselines, report
  playwright/  @testivai/witness-playwright   — Playwright reporter + capture
  webdriverio/ @testivai/witness-webdriverio  — WebdriverIO service + capture
  selenium/    @testivai/witness-selenium     — Selenium adapter
  mcp/         @testivai/mcp                  — MCP server for AI agents
action/        GitHub Action for PR comments
approve/       GitHub Action for /testivai approve
examples/      framework-specific minimal examples
docs/          public documentation (Markdown)
e2e/           OSS smoke E2E

Development

# Prereqs: Node 20+, pnpm 10+
pnpm install
pnpm build       # tsc all packages
pnpm test        # unit tests across all packages
pnpm e2e         # smoke E2E
pnpm pack:dry    # validate publish artifacts

Contributing

Bug reports, feature requests, and PRs welcome. Please see:

Releases

Releases are published to npm under the latest dist-tag, with provenance attestations. The flow is Changesets-driven: a PR that changes a published package adds a changeset, merging it opens a "version packages" PR collecting the pending bumps, and merging that publishes. See .changeset/README.md for the contributor side and .github/workflows/release.yml for the workflow itself.

Attribution

This repository was extracted from the private TestivAI monorepo with a clean initial git history. Original development history is preserved internally; this public repository is the new source of truth for the SDKs going forward.

License

MIT — see LICENSE.

1# TestivAI Open Source
2 
3[![@testivai/witness](https://img.shields.io/npm/v/@testivai/witness.svg?label=%40testivai%2Fwitness)](https://www.npmjs.com/package/@testivai/witness)
4[![@testivai/witness-playwright](https://img.shields.io/npm/v/@testivai/witness-playwright.svg?label=%40testivai%2Fwitness-playwright)](https://www.npmjs.com/package/@testivai/witness-playwright)
5[![@testivai/witness-webdriverio](https://img.shields.io/npm/v/@testivai/witness-webdriverio.svg?label=%40testivai%2Fwitness-webdriverio)](https://www.npmjs.com/package/@testivai/witness-webdriverio)
6[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
7 
8**Local-first visual regression testing SDKs for modern web applications.**
9 
10This is the home of TestivAI. It contains everything you need to capture, diff, and report visual regressions **fully locally** — MIT-licensed, no account, no server.
11 
12> **[See a live report →](https://testivai.github.io/testivai-example/)** — a real TestivAI OSS report rendered in your browser, straight from CI. No install, no signup.
13 
14[![Real TestivAI report — style-only-change verdict, selector-attributed regions, heatmap diff](./docs/assets/oss-report.png)](https://testivai.github.io/testivai-example/)
15 
16## Why TestivAI?
17 
18Pixel-only visual testing drowns you in false positives — a font re-hint or an anti-aliasing shift across machines lights up as a "change," and you spend your time re-approving noise.
19 
20**TestivAI pairs the screenshot with a snapshot of the page DOM** (every adapter and the crawler capture one by default; the experimental `testivai run` sidecar does not) and, from most test-framework adapters, a digest of every element's computed styles. When pixels differ but the DOM and the computed styles are identical and the diff is small and scattered, the report labels the diff **likely render noise** instead of crying wolf. When the DOM and styles match but the diff sits in one area, it says that too: the style check covers a fixed list of properties, not size, overflow, transform or images, so that is a bad capture or a real change, not noise. It still shows the diff; it tells you what it most likely is. When only the styles changed, it says so: a style-only change is real. When the styles could not be compared, it says *styles not compared* and does not call the diff noise, because a stylesheet-only change is not ruled out. When the DOM actually changed, you see exactly what (`2 added, 1 removed`). That layered signal is the difference between a flaky test wall and a report you trust.
21 
22- **Fully local, no account** — captures, diffs, and a self-contained HTML report all stay on your machine.
23- **DOM + style-aware noise hint** — tells likely render jitter from real changes, and catches the stylesheet-only case. When both captures carry an element map (the Playwright JS/TS, WebdriverIO, Selenium and Ruby adapters record one), an identical DOM with changed computed styles reads as "Styles changed on `button.cta`", never as noise. Without an element map on both sides (the `testivai witness <url>` crawler, playwright-python, playwright-java), computed styles are not compared, and an identical-DOM diff reads "styles not compared", not "likely render noise".
24- **Auditable masks & region-level diffs** — exclude dynamic areas (selectors or coordinates) with the masked region hatched in the diff, and get "3 changed regions" with bounding boxes instead of a raw pixel percentage.
25- **Element attribution & exact shift detection** — the report names *which element* changed ("`div.card:nth-of-type(2)` shifted +8px vertically — content unchanged") and spots the injected-banner case ("everything below y=80 moved +24px"), derived from layout, not pixel guesswork. No local-first tool does this.
26- **First-class adapters** — Playwright (TS/JS **and Python**, Java experimental) and WebdriverIO, using each framework's native APIs; every language shares one set of baselines and one report.
27- **Mobile web, no extra package** — Playwright device emulation works through the same `witness()` call: one project per device profile, baselines keyed per device automatically, and the DOM and computed-style attribution survives emulation, so a change behind a `@media` query is caught on mobile and correctly passes on desktop. See the [mobile web guide](docs/guides/mobile-web.md).
28- **PR-native workflow** — a GitHub Action posts the diff and approves baselines from a `/testivai approve` comment.
29- **No lock-in** — MIT license, baselines live in your git, and `results.json` is a semver-governed public contract.
30 
31> **Evaluating this for a team?** [Maintenance & roadmap](docs/maintenance.md)
32> covers who builds it, the release cadence, and what happens to your setup if
33> maintenance ever stops.
34 
35> **Already using Playwright's `toHaveScreenshot()`?** It's good, and if a pixel
36> diff answers your question you should keep using it. Here's an honest look at
37> [what TestivAI adds and when it isn't worth the dependency](docs/vs-playwright-builtin.md).
38 
39## Eyes for your coding agent
40 
41If an AI agent (Claude Code, Cursor, Copilot, …) writes your UI code, someone still has to check what the UI *looks like* — and it shouldn't be you, one screenshot at a time. TestivAI is built to be that check:
42 
43- **No account, no API key, no network** — an agent can run it inside any sandbox without you provisioning secrets.
44- **Machine-readable output** — every run writes `visual-report/results.json` (a [semver-governed schema](./docs/how-it-works.md)) with per-snapshot diff percentages and DOM change summaries, so an agent can read the result and self-correct.
45- **Noise-aware verdicts** — the DOM and computed-style checks tell the agent whether a pixel diff is *likely render noise* (identical DOM, styles compared and identical, a small and scattered diff), a *concentrated or large diff* (identical DOM and styles, but the diff sits in one area or covers over 1% of pixels: a bad capture or a change the style check does not cover), a *style-only change* (identical DOM, restyled elements), a *real structural change* (`2 added, 1 removed`), or *styles not compared* (identical DOM, but no style data to compare, so a stylesheet-only change is not ruled out). It doesn't chase anti-aliasing ghosts, and it doesn't label a diff it couldn't check as noise.
46- **Explanations, bring-your-own-model** — the MCP `explain_snapshot` tool hands your agent layered evidence (which selectors shifted vs changed, whole-page shift detection, style-only changes) and your model writes the narrative: *"card #2 shifted +24px — likely the banner injected above it."* No hosted AI service in the loop.
47- **Human approval stays in the PR** — the agent iterates locally; you approve baselines with one `/testivai approve` comment.
48 
49Paste this into your project's `AGENTS.md` / `CLAUDE.md` to wire it up (full guide with MCP setup, a real agent transcript, and the approval rule: [docs/guides/ai-agents.md](./docs/guides/ai-agents.md)):
50 
51```markdown
52## Visual verification
53After changing any UI code, run `npx playwright test` (TestivAI captures
54screenshots automatically), then read `visual-report/results.json`.
55- `status: "changed"` with `dom.changed: true` → describe the DOM summary and
56 ask whether the change is intended before approving.
57- `status: "changed"` with `dom.changed: false` and `dom.styleCheck: "mismatch"`
58 → style-only change; real, not noise. Compare `dom.styleChanges` with what
59 you intended.
60- `status: "changed"` with `dom.noiseHint: true`, `dom.styleCheck: "match"`,
61 `diffPercent` at most 1, and no `regions` or regions spread out (their
62 combined box covers more than a third of the page) → likely render noise;
63 mention it but don't block.
64- `status: "changed"` with `dom.noiseHint: true` and `dom.styleCheck: "match"`
65 but every region within a third of the page, or `diffPercent` over 1 → not
66 noise: a bad capture or a change the style check does not cover (size,
67 overflow, transform, images). Look at the diff image before approving.
68- `status: "changed"` with `dom.noiseHint: true` and `dom.styleCheck`
69 `"unavailable"` or missing → styles not compared: could be render noise or a
70 stylesheet-only change. Don't treat it as noise: look at the diff image before approving.
71- Never run `testivai approve` yourself — baseline approval is a human decision.
72```
73 
74## Packages
75 
76Live versions are shown by the badges at the top of this README.
77 
78| Package | Description |
79|---|---|
80| [`@testivai/witness`](./packages/witness) | Core SDK: CLI, local diffing, baselines, HTML report generator |
81| [`@testivai/witness-playwright`](./packages/playwright) | Playwright reporter/adapter built on top of `@testivai/witness` |
82| [`@testivai/witness-webdriverio`](./packages/webdriverio) | WebdriverIO service + capture function (local mode) |
83| [`@testivai/witness-selenium`](./packages/selenium) | Selenium WebDriver capture adapter (Python/Java Selenium live in `python/` and `java/`) |
84| [`@testivai/mcp`](./packages/mcp) | MCP server — visual results + diff images for AI coding agents |
85| [`testivai` (PyPI)](./python) | Python adapter for playwright-python + pytest plugin — same baselines & report |
86| [`ai.testiv:testivai`](./java) | Java adapter for playwright-java + JUnit 5 extension (experimental) |
87| [`testivai` (RubyGems)](./ruby) | Ruby adapter for Capybara / RSpec / Cucumber — same baselines & report |
88 
89Plus:
90- [`action/`](./action) — GitHub Action for PR-based visual approvals
91- [`examples/`](./examples) — minimal real-world example projects
92 - [`examples/site-check/`](./examples/site-check) — an hourly GitHub Actions check that fails when a page is up but broken (stylesheet, script or font missing, JavaScript error, blank page), with one issue per outage. testiv.ai runs it on itself.
93- [`docs/`](./docs) — public documentation
94- [`e2e/`](./e2e) — OSS smoke E2E test suite
95 
96## No test suite? One command.
97 
98AI-built and vibe-coded apps (Lovable, Bolt, v0, ...) usually ship with zero
99tests. You still get the full safety net:
100 
101```bash
102npx testivai witness http://localhost:3000
103```
104 
105TestivAI launches a headless Chrome, discovers your pages (or takes
106`--pages "/,/pricing"`), and captures a screenshot and the DOM of each one.
107Baselines, pixel and DOM diffs, the HTML report and PR approvals work as
108below, no test framework required. The crawler records no element map, so
109computed styles are not compared: a pixel diff with an identical DOM reads
110*styles not compared* (could be render noise or a stylesheet-only change),
111never likely noise, and there is no selector attribution or page-shift
112detection. See the [vibe-coded apps guide](./docs/guides/vibe-coded-apps.md).
113 
114## Quick Start (Playwright, Local Mode)
115 
116```bash
117# 1. Install
118npm install -D @testivai/witness-playwright @playwright/test
119npx playwright install chromium
120```
121 
122```jsonc
123// 2. (OPTIONAL) Customize tolerances and report settings.
124// Everything runs locally — no config needed.
125// Only create this file if you want to tune threshold, reportDir, etc.
126// File: .testivai/config.json
127{
128 "threshold": 0.1, // per-pixel color sensitivity (0-1)
129 "maxDiffPercent": 0, // pass diffs at or below this % (your tolerance dial)
130 "noiseAutoPass": false, // true: diffs with identical DOM AND matching computed styles, not concentrated in one area, within noiseMaxDiffPercent, pass
131 "stabilize": true, // freeze animations, hide caret, wait for fonts
132 "ignoreSelectors": [], // e.g. [".live-chat", "[data-testid=clock]"]
133 "reportDir": "visual-report",
134 "autoOpen": false
135}
136```
137 
138```ts
139// 3. Wire the reporter — playwright.config.ts
140import { defineConfig } from '@playwright/test';
141 
142export default defineConfig({
143 reporter: [
144 ['list'],
145 ['@testivai/witness-playwright/reporter'],
146 ],
147});
148```
149 
150```ts
151// 4. Add a capture call — tests/example.spec.ts
152import { test } from '@playwright/test';
153import { witness } from '@testivai/witness-playwright';
154 
155test('homepage looks correct', async ({ page }, testInfo) => {
156 await page.goto('http://localhost:3000');
157 await witness(page, testInfo, 'homepage');
158});
159```
160 
161```bash
162# 5. Run
163npx playwright test
164```
165 
166**First run:** baselines are written to `.testivai/baselines/`.
167**Later runs:** screenshots are diffed and a self-contained HTML report is written to `./visual-report/`.
168 
169## What you get out of the box (free, no account)
170 
171- Full-page screenshot capture via Playwright
172- **Stabilized captures by default** — animations/transitions frozen, caret hidden, web fonts awaited (the top causes of flaky visual tests, neutralized before every screenshot)
173- Local pixel diff with configurable threshold
174- **Tunable pass criteria, all opt-in** — `maxDiffPercent` / `maxDiffPixels` tolerances, `shiftTolerance` for pure element shifts, and `noiseAutoPass`, which passes a diff only when the DOM is identical, the computed styles were compared and match, and the diff is not concentrated in one area (never one whose styles were not compared). Nothing auto-passes until you configure it, and auto-passed snapshots stay labeled in the report
175- `ignoreSelectors` and `mask` for dynamic content such as timestamps (global or per-snapshot): stabilization does not hide changing content, so you list it
176- Self-contained HTML report (`visual-report/index.html`)
177- Machine-readable results (`visual-report/results.json`)
178- Committed baselines under `.testivai/baselines/` (just `git add` them)
179 
180## CI Integration (GitHub Actions)
181 
182Copy this single workflow file into your repository. It handles both running the visual regression tests **and** processing `/testivai approve` commands from PR comments — no extra secrets, no external services required.
183 
184```yaml
185# .github/workflows/testivai-oss.yml
186name: TestivAI OSS
187 
188on:
189 pull_request:
190 branches: [main]
191 issue_comment:
192 types: [created] # listens for /testivai approve commands
193 
194permissions:
195 contents: write # approve action commits updated baselines to the branch
196 pull-requests: write # post PR diff comment
197 statuses: write # set pass/fail indicator on the PR
198 actions: read # approve action finds the CI run and downloads its report artifact
199 
200jobs:
201 
202 # Runs on every PR — captures screenshots, diffs against baselines, posts report
203 visual-regression:
204 name: Visual Regression (OSS)
205 if: github.event_name == 'pull_request'
206 runs-on: ubuntu-latest
207 timeout-minutes: 15
208 steps:
209 - uses: actions/checkout@v4
210 - uses: actions/setup-node@v4
211 with: { node-version: '20', cache: 'npm' }
212 - run: npm ci
213 - run: npx playwright install chromium --with-deps
214 - run: npm run build
215 - run: npm run test:oss # runs playwright.oss.config.ts
216 
217 - name: Post results + upload report
218 uses: testivai/testivai-oss@v1
219 if: always()
220 with:
221 github-token: ${{ secrets.GITHUB_TOKEN }}
222 report-dir: visual-report # where @testivai/witness writes results.json
223 
224 # Runs when a collaborator comments /testivai approve on the PR
225 approve-baselines:
226 name: Approve Baselines
227 if: |
228 github.event_name == 'issue_comment' &&
229 github.event.issue.pull_request != null &&
230 startsWith(github.event.comment.body, '/testivai')
231 runs-on: ubuntu-latest
232 timeout-minutes: 10
233 steps:
234 - uses: testivai/testivai-oss/approve@v1
235 with:
236 github-token: ${{ secrets.GITHUB_TOKEN }}
237 workflow: testivai-oss.yml # this file's name — used to find the report artifact
238 
239 # Optional: Claude triages every diff as glitch / intended change / bug /
240 # possible bug. Skips (no tokens) without the secret, or with enabled: false.
241 ai-triage:
242 name: AI triage
243 needs: visual-regression
244 if: ${{ !cancelled() && github.event_name == 'pull_request' }}
245 concurrency: { group: 'ai-triage-${{ github.event.pull_request.number }}', cancel-in-progress: true }
246 runs-on: ubuntu-latest
247 timeout-minutes: 15
248 permissions:
249 contents: read
250 pull-requests: write
251 steps:
252 - uses: actions/checkout@v4
253 - uses: actions/download-artifact@v4
254 continue-on-error: true # no report when the visual job failed early: the triage skips
255 with: { name: testivai-visual-report, path: visual-report }
256 - uses: testivai/testivai-oss/review@v1
257 with:
258 anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
259 # claude-code-oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # instead, for a Claude subscription
260 # enabled: false # turn it off
261```
262 
263The `ai-triage` job runs on every pull request with a changed or new
264snapshot or a missing baseline (unless .testivai/config.json sets
265failOnMissing: false) once an `ANTHROPIC_API_KEY` secret is set (or a
266`CLAUDE_CODE_OAUTH_TOKEN` secret, passed as `claude-code-oauth-token`), and
267posts one advisory comment: which diffs look like glitches, which
268match the PR, and which look like bugs. It never changes the check or a
269baseline. See [AI triage of every diff](docs/github-action.md#ai-triage-of-every-diff).
270 
271### Approve changed baselines
272 
273After CI posts the diff report on your PR, review the `testivai-visual-report` artifact, then comment:
274 
275| Comment | Effect |
276|---|---|
277| `/testivai approve homepage` | Approves one named snapshot |
278| `/testivai approve --all` | Approves every changed snapshot at once |
279 
280**What happens:**
2811. Action verifies you have **write** access to the repository (others get a polite rejection)
2822. Downloads the `testivai-visual-report` artifact from the latest CI run on your branch
2833. Copies approved screenshots into `.testivai/baselines/` and commits them to your PR branch
2844. Posts a confirmation comment listing what was approved
2855. CI runs again on the new commit — approved snapshots now pass
286 
287Step 5 needs a token other than `GITHUB_TOKEN`: GitHub starts no workflow run for a push made with `GITHUB_TOKEN`. Give the approve step a fine-grained personal access token (`github-token: ${{ secrets.TESTIVAI_APPROVE_TOKEN || secrets.GITHUB_TOKEN }}`), or push another commit after approving. See [Approve with a token that re-runs CI](docs/github-action.md#approve-with-a-token-that-re-runs-ci).
288 
289### What the PR comment looks like
290 
291```
292TestivAI Visual Report
293 
2944 passed | 4 changed | 1 new — 9 total
295 
296Changed Snapshots
297 
298▼ homepage — 0.31% different
299 DOM unchanged — pixel diff is likely render noise (anti-aliasing, font hinting). Styles verified unchanged; the diff is small and scattered.
300 
301▼ products — 0.03% different
302 DOM and styles unchanged, but the diff is concentrated in one area — 3 regions within 155×52 px, 0.8% of the page: a bad capture, or a change the style check does not cover (size, overflow, position, transform, text decoration, filters, pseudo-elements, or images and other assets). Compare the images before approving.
303 
304▼ pricing — 0.42% different
305 DOM unchanged, styles not compared — could be render noise or a stylesheet-only change (no comparable style digests on both sides). Compare the images before approving.
306 
307▼ dashboard — 8.91% different
308 DOM changed — 2 added, 1 removed.
309```
310 
311---
312 
313## Real-World Example
314 
315A complete, minimal consumer project lives at [`testivai-example`](https://github.com/testivai/testivai-example): a static page, three `witness()` calls, the PR `/testivai approve` flow, and a [live report on Pages](https://testivai.github.io/testivai-example/) — all against the published packages.
316 
317## Repository Layout
318 
319```
320packages/
321 witness/ @testivai/witness — CLI, diff engine, baselines, report
322 playwright/ @testivai/witness-playwright — Playwright reporter + capture
323 webdriverio/ @testivai/witness-webdriverio — WebdriverIO service + capture
324 selenium/ @testivai/witness-selenium — Selenium adapter
325 mcp/ @testivai/mcp — MCP server for AI agents
326action/ GitHub Action for PR comments
327approve/ GitHub Action for /testivai approve
328examples/ framework-specific minimal examples
329docs/ public documentation (Markdown)
330e2e/ OSS smoke E2E
331```
332 
333## Development
334 
335```bash
336# Prereqs: Node 20+, pnpm 10+
337pnpm install
338pnpm build # tsc all packages
339pnpm test # unit tests across all packages
340pnpm e2e # smoke E2E
341pnpm pack:dry # validate publish artifacts
342```
343 
344## Contributing
345 
346Bug reports, feature requests, and PRs welcome. Please see:
347- [`CONTRIBUTING.md`](./CONTRIBUTING.md)
348- [`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md)
349- [`.github/ISSUE_TEMPLATE/`](./.github/ISSUE_TEMPLATE/)
350 
351## Releases
352 
353Releases are published to npm under the `latest` dist-tag, with provenance
354attestations. The flow is Changesets-driven: a PR that changes a published
355package adds a changeset, merging it opens a "version packages" PR collecting
356the pending bumps, and merging *that* publishes. See
357[`.changeset/README.md`](./.changeset/README.md) for the contributor side and
358[`.github/workflows/release.yml`](./.github/workflows/release.yml) for the
359workflow itself.
360 
361## Attribution
362 
363This repository was extracted from the private TestivAI monorepo with a clean initial git history. Original development history is preserved internally; this public repository is the new source of truth for the SDKs going forward.
364 
365## License
366 
367MIT — see [LICENSE](./LICENSE).
368 

Discussion

Alternatives

Xberg Document ExtractionExtract text, tables, metadata, and images from 107 document formats (PDF, Office, images, HTML, email, archives, academic) using Xberg. Use when writing code that calls Xberg APIs in Python, Node.js/TypeScript, Rust, or CLI. Covers installation, extraction (sync/async), configuration (OCR, chunking, output format), batch processing, error handling, and plugins.Coding · MITCode migrationMove code from one implementation to another function by function, tests first and code second, and check two implementations of one app for parity, with jscpd --compare as the progress measure and a coverage map binding each function to its tests. Use when porting a library or app to another language or framework (Java to Kotlin, JavaScript to Rust, a Python library to TypeScript, an iOS app to Android), when asked what is left to port, or when comparing the Android and iOS versions of an app.Coding · MITAhrefs pythonManages Ahrefs API usage in Python using `ahrefs-python` library. Use when working with SEO / marketing related tasks or with data including backlinks, keywords, domain ratings, organic traffic, site audits, rank tracking, and brand monitoring. Covers `ahrefs-python` usage including AhrefsClient / AsyncAhrefsClient, typed request/response models, error handling, and all API sections.Coding · MITadeuDocx ↔ LLM translator. Projects .docx office files to Markdown for editing. Projects edits back to OOXML as tracked changes (redlines). Python and Node.js implementations.Content & docs · MIT