WebdriverIO
The @testivai/witness-webdriverio adapter is a WDIO service plus an explicit testivai.witness(browser, name) capture call. Same .testivai/baselines/ layout as the Playwright adapter, same HTML report, same approval workflow.
Everything runs on your machine — no account, no API key, fully standalone.
Prerequisites
- Node.js 18 or higher
- WebdriverIO 8 or 9
- A working WDIO config (
wdio.conf.tsorwdio.conf.js)
1. Install
npm install -D @testivai/witness @testivai/witness-webdriverio
webdriverio is a peer dependency — you should already have it from your existing setup.
2. Configure local mode
Create .testivai/config.json at your project root:
{
"mode": "local",
"threshold": 0.1,
"reportDir": "visual-report",
"autoOpen": false,
"maxDiffPercent": 0,
"noiseAutoPass": false,
"stabilize": true,
"ignoreSelectors": []
}
This file is the local-mode marker. Without it, the adapter logs a warning and skips report generation — pixels are still captured, but no report is built.
3. Register the WDIO service
In your wdio.conf.ts:
import { TestivaiService } from '@testivai/witness-webdriverio/service';
export const config = {
// ... your existing config
services: [
[TestivaiService, { quiet: false }],
],
};
The service runs onComplete after the test suite finishes. It compares everything in .testivai/temp/ against .testivai/baselines/ and writes the report to visual-report/.
4. Capture inside your tests
import { testivai } from '@testivai/witness-webdriverio';
describe('Homepage', () => {
it('renders correctly', async () => {
await browser.url('http://localhost:3000');
await testivai.witness(browser, 'homepage');
});
it('product detail page', async () => {
await browser.url('http://localhost:3000/products/widget');
await testivai.witness(browser, 'product-widget');
});
});
The capture function calls browser.takeScreenshot() (full-page screenshot via the WebDriver protocol) and browser.execute(() => document.documentElement.outerHTML) (page DOM for the noise-hint signal).
5. Run
npx wdio run wdio.conf.ts
- First run: baselines are written to
.testivai/baselines/<name>/. Commit them:git add .testivai/baselines. - Later runs: screenshots are diffed and a self-contained HTML report is written to
./visual-report/.
Approving changes
# Open the report
open visual-report/index.html
# Approve a single snapshot
npx testivai approve "homepage"
# Approve everything that changed
npx testivai approve --all
# Undo the last approval
npx testivai approve --undo "homepage"
Approved snapshots overwrite the baseline; the previous baseline is backed up to .testivai/baselines/<name>/.previous/.
Pixel + DOM comparison
Each snapshot stores both the screenshot and the page DOM. When the report compares them:
- Pixels match →
passed - Pixels differ, DOM matches →
changedwith a "DOM unchanged — likely render noise" hint - Pixels differ, DOM differs →
changedwith a count of added / removed / attribute changes
The same hint appears in PR comments via the TestivAI GitHub Action.
To skip DOM capture for a single snapshot (rare — useful only when DOM serialization is slow on a particular page):
await testivai.witness(browser, 'heavy-page', { skipDom: true });
// Hide dynamic elements for this snapshot only (merged with the global
// ignoreSelectors list from .testivai/config.json)
await testivai.witness(browser, 'dashboard', { ignoreSelectors: ['.live-feed'] });
// Opt out of capture stabilization (animations frozen, fonts awaited) per call
await testivai.witness(browser, 'animation-demo', { stabilize: false });
// Multi-capability runs: key baselines per capability so they don't collide
await testivai.witness(browser, 'homepage', { variant: 'firefox-mobile' });
Service options
[TestivaiService, {
projectRoot: process.cwd(), // project root for .testivai/
reportDir: 'visual-report', // override report output dir
threshold: 0.1, // pixel diff threshold (0–1)
autoOpen: false, // open report after generation
quiet: false, // suppress logging
}]
All options are optional; defaults come from .testivai/config.json.
Local-only by design
The WDIO adapter is local-only: captures, diffs, and the report are all produced on disk. A legacy config carrying the retired mode: "cloud" value logs a warning and skips report generation — set "mode": "local" (or delete the field) to fix it.
See also
@testivai/witness-webdriveriopackage README- Quickstart for the Playwright adapter (similar shape, different framework)