@testivai/witness-selenium 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +58 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +28 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.js +5 -0
- package/dist/witness.d.ts +32 -0
- package/dist/witness.js +178 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 TestivAI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# @testivai/witness-selenium
|
|
2
|
+
|
|
3
|
+
Selenium WebDriver adapter for [TestivAI Witness](https://github.com/mcbuddy/testivai-oss) — local-first visual regression testing with pixel + DOM comparison. No cloud account, nothing uploaded.
|
|
4
|
+
|
|
5
|
+
Shares baselines, tolerances, the HTML report, and the PR approval flow with every other TestivAI adapter (Playwright, WebdriverIO, Python, Java) through one on-disk contract.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i -D @testivai/witness-selenium @testivai/witness selenium-webdriver
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Use
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
const { Builder } = require('selenium-webdriver');
|
|
17
|
+
const { testivai } = require('@testivai/witness-selenium');
|
|
18
|
+
|
|
19
|
+
const driver = await new Builder().forBrowser('chrome').build();
|
|
20
|
+
await driver.get('http://localhost:3000');
|
|
21
|
+
await testivai.witness(driver, 'homepage');
|
|
22
|
+
await driver.quit();
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then compare against baselines and render the report:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx testivai report # add --fail-on-diff in CI
|
|
29
|
+
npx testivai approve --all # promote current captures to baselines
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Works inside any runner (Jest, Mocha, plain Node scripts) — the adapter only needs the `driver` object.
|
|
33
|
+
|
|
34
|
+
## Options
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
await testivai.witness(driver, 'pricing', {
|
|
38
|
+
ignoreSelectors: ['.live-chat', '[data-testid="ad"]'], // hidden from pixels AND the DOM snapshot
|
|
39
|
+
variant: 'firefox', // baselines keyed as pricing__firefox
|
|
40
|
+
stabilize: false, // skip animation-freeze + font wait for this capture
|
|
41
|
+
skipDom: true, // pixel diff only, no noise hint
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Global settings (thresholds, tolerances, `ignoreSelectors`) live in `.testivai/config.json` — see the [main docs](https://github.com/mcbuddy/testivai-oss/tree/main/docs).
|
|
46
|
+
|
|
47
|
+
## Full-page screenshots
|
|
48
|
+
|
|
49
|
+
- **Chrome / Edge** — true full-page capture via CDP (`captureBeyondViewport`), the same mechanism Playwright uses. Automatic; nothing to configure.
|
|
50
|
+
- **Firefox / Safari** — the Selenium JS bindings expose no full-page API, so captures are viewport-sized. Set the window to the size you want compared; runs are consistent either way. (The Python and Java TestivAI adapters do full-page on Firefox via its native API.)
|
|
51
|
+
|
|
52
|
+
## Stabilized captures
|
|
53
|
+
|
|
54
|
+
Before every capture (default on): CSS animations/transitions complete instantly at their final state, the caret is hidden, smooth scrolling is disabled, and web fonts are awaited (bounded 10s). These are the top causes of flaky visual diffs.
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @testivai/witness-selenium
|
|
3
|
+
*
|
|
4
|
+
* Selenium WebDriver adapter for the TestivAI Witness OSS toolkit. Pairs
|
|
5
|
+
* with `@testivai/witness` (the local CLI + diff engine) and shares the
|
|
6
|
+
* same `.testivai/baselines/` layout as every other TestivAI adapter.
|
|
7
|
+
*
|
|
8
|
+
* import { testivai } from '@testivai/witness-selenium';
|
|
9
|
+
* await testivai.witness(driver, 'homepage');
|
|
10
|
+
*
|
|
11
|
+
* Then run `npx testivai report` (add `--fail-on-diff` in CI) to compare
|
|
12
|
+
* against committed baselines and render visual-report/index.html.
|
|
13
|
+
*/
|
|
14
|
+
import { witness } from './witness';
|
|
15
|
+
export { witness } from './witness';
|
|
16
|
+
export type { WitnessDriver, WitnessOptions } from './types';
|
|
17
|
+
/**
|
|
18
|
+
* Convenience namespace mirroring @testivai/witness-playwright's API:
|
|
19
|
+
*
|
|
20
|
+
* await testivai.witness(driver, 'homepage');
|
|
21
|
+
*/
|
|
22
|
+
export declare const testivai: {
|
|
23
|
+
witness: typeof witness;
|
|
24
|
+
};
|
|
25
|
+
export declare const VERSION: string;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @testivai/witness-selenium
|
|
4
|
+
*
|
|
5
|
+
* Selenium WebDriver adapter for the TestivAI Witness OSS toolkit. Pairs
|
|
6
|
+
* with `@testivai/witness` (the local CLI + diff engine) and shares the
|
|
7
|
+
* same `.testivai/baselines/` layout as every other TestivAI adapter.
|
|
8
|
+
*
|
|
9
|
+
* import { testivai } from '@testivai/witness-selenium';
|
|
10
|
+
* await testivai.witness(driver, 'homepage');
|
|
11
|
+
*
|
|
12
|
+
* Then run `npx testivai report` (add `--fail-on-diff` in CI) to compare
|
|
13
|
+
* against committed baselines and render visual-report/index.html.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.VERSION = exports.testivai = exports.witness = void 0;
|
|
17
|
+
const witness_1 = require("./witness");
|
|
18
|
+
var witness_2 = require("./witness");
|
|
19
|
+
Object.defineProperty(exports, "witness", { enumerable: true, get: function () { return witness_2.witness; } });
|
|
20
|
+
/**
|
|
21
|
+
* Convenience namespace mirroring @testivai/witness-playwright's API:
|
|
22
|
+
*
|
|
23
|
+
* await testivai.witness(driver, 'homepage');
|
|
24
|
+
*/
|
|
25
|
+
exports.testivai = {
|
|
26
|
+
witness: witness_1.witness,
|
|
27
|
+
};
|
|
28
|
+
exports.VERSION = require('../package.json').version;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for @testivai/witness-selenium.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Minimal subset of the selenium-webdriver `WebDriver` the witness function
|
|
6
|
+
* relies on. Structural typing means consumers can pass any Selenium-shaped
|
|
7
|
+
* object (real driver or mock) without us pulling `selenium-webdriver`'s
|
|
8
|
+
* types into our public surface.
|
|
9
|
+
*/
|
|
10
|
+
export interface WitnessDriver {
|
|
11
|
+
/**
|
|
12
|
+
* Capture a screenshot of the current viewport. Returns a base64 PNG,
|
|
13
|
+
* matching selenium-webdriver's `driver.takeScreenshot()` return shape.
|
|
14
|
+
* Used as the fallback when no full-page mechanism is available.
|
|
15
|
+
*/
|
|
16
|
+
takeScreenshot(): Promise<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Execute a script in the page context. We only use the string form with
|
|
19
|
+
* trailing args (`arguments[0]`… inside the script).
|
|
20
|
+
*/
|
|
21
|
+
executeScript<T = unknown>(script: string, ...args: unknown[]): Promise<T>;
|
|
22
|
+
/**
|
|
23
|
+
* Chromium-only (chrome.Driver / edge.Driver): raw CDP command. Presence
|
|
24
|
+
* of this method is how the adapter detects that full-page capture via
|
|
25
|
+
* `Page.captureScreenshot` (captureBeyondViewport) is available — the
|
|
26
|
+
* same mechanism Playwright uses under the hood. Absent on Firefox and
|
|
27
|
+
* Safari drivers, which fall back to the viewport screenshot.
|
|
28
|
+
*/
|
|
29
|
+
sendAndGetDevToolsCommand?<T = unknown>(cmd: string, params?: object): Promise<T>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Options for a single witness() call. All optional.
|
|
33
|
+
*/
|
|
34
|
+
export interface WitnessOptions {
|
|
35
|
+
/**
|
|
36
|
+
* Skip DOM capture even if the adapter would normally write dom.html
|
|
37
|
+
* alongside the screenshot. Use for pages where DOM serialization is
|
|
38
|
+
* known to be expensive or unstable.
|
|
39
|
+
*/
|
|
40
|
+
skipDom?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* CSS selectors for elements to hide (`visibility: hidden`) for the
|
|
43
|
+
* duration of the capture, so dynamic content (timestamps, ads, live
|
|
44
|
+
* widgets) never contributes to the pixel diff. Merged with the global
|
|
45
|
+
* `ignoreSelectors` list from `.testivai/config.json`. Matched elements
|
|
46
|
+
* are also excluded from the DOM snapshot (one consistent semantic).
|
|
47
|
+
*/
|
|
48
|
+
ignoreSelectors?: string[];
|
|
49
|
+
/**
|
|
50
|
+
* Variant key for multi-browser / multi-viewport runs. Folded into the
|
|
51
|
+
* snapshot name (`<name>__<variant>`) so parallel sessions don't
|
|
52
|
+
* overwrite each other's baselines. Example: `'firefox'`, `'chrome-1280'`.
|
|
53
|
+
*/
|
|
54
|
+
variant?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Stabilize the page before capture: disable CSS animations/transitions,
|
|
57
|
+
* hide the caret, and wait for web fonts. Overrides the `stabilize`
|
|
58
|
+
* setting from `.testivai/config.json` (default true).
|
|
59
|
+
*/
|
|
60
|
+
stabilize?: boolean;
|
|
61
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @testivai/witness-selenium — capture function
|
|
3
|
+
*
|
|
4
|
+
* The user-facing call inside a Selenium (JavaScript) test:
|
|
5
|
+
*
|
|
6
|
+
* import { testivai } from '@testivai/witness-selenium';
|
|
7
|
+
* await testivai.witness(driver, 'homepage');
|
|
8
|
+
*
|
|
9
|
+
* Captures a screenshot and the page DOM through Selenium's public APIs,
|
|
10
|
+
* then writes both into `.testivai/temp/<name>/` using the same
|
|
11
|
+
* BaselineStore layout as every other TestivAI adapter. Run
|
|
12
|
+
* `npx testivai report` after the tests to compare against baselines.
|
|
13
|
+
*
|
|
14
|
+
* Full-page screenshots per browser:
|
|
15
|
+
* - Chromium (Chrome/Edge): CDP `Page.captureScreenshot` with
|
|
16
|
+
* captureBeyondViewport via `sendAndGetDevToolsCommand` — the same
|
|
17
|
+
* mechanism Playwright uses under the hood
|
|
18
|
+
* - Firefox/Safari: the JS bindings expose no full-page API, so the
|
|
19
|
+
* capture is viewport-only (keep the window sized to what you want
|
|
20
|
+
* compared; baselines are consistent run-to-run either way)
|
|
21
|
+
*/
|
|
22
|
+
import type { WitnessDriver, WitnessOptions } from './types';
|
|
23
|
+
/**
|
|
24
|
+
* Capture a screenshot + DOM and write them as a temp snapshot.
|
|
25
|
+
*
|
|
26
|
+
* @param driver selenium-webdriver WebDriver (must expose
|
|
27
|
+
* `takeScreenshot()` and `executeScript()`).
|
|
28
|
+
* @param name Snapshot name. Becomes the directory under
|
|
29
|
+
* `.testivai/temp/<name>/` and the key in the rendered report.
|
|
30
|
+
* @param options Per-call overrides.
|
|
31
|
+
*/
|
|
32
|
+
export declare function witness(driver: WitnessDriver, name: string, options?: WitnessOptions): Promise<void>;
|
package/dist/witness.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @testivai/witness-selenium — capture function
|
|
4
|
+
*
|
|
5
|
+
* The user-facing call inside a Selenium (JavaScript) test:
|
|
6
|
+
*
|
|
7
|
+
* import { testivai } from '@testivai/witness-selenium';
|
|
8
|
+
* await testivai.witness(driver, 'homepage');
|
|
9
|
+
*
|
|
10
|
+
* Captures a screenshot and the page DOM through Selenium's public APIs,
|
|
11
|
+
* then writes both into `.testivai/temp/<name>/` using the same
|
|
12
|
+
* BaselineStore layout as every other TestivAI adapter. Run
|
|
13
|
+
* `npx testivai report` after the tests to compare against baselines.
|
|
14
|
+
*
|
|
15
|
+
* Full-page screenshots per browser:
|
|
16
|
+
* - Chromium (Chrome/Edge): CDP `Page.captureScreenshot` with
|
|
17
|
+
* captureBeyondViewport via `sendAndGetDevToolsCommand` — the same
|
|
18
|
+
* mechanism Playwright uses under the hood
|
|
19
|
+
* - Firefox/Safari: the JS bindings expose no full-page API, so the
|
|
20
|
+
* capture is viewport-only (keep the window sized to what you want
|
|
21
|
+
* compared; baselines are consistent run-to-run either way)
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.witness = witness;
|
|
25
|
+
const witness_1 = require("@testivai/witness");
|
|
26
|
+
/** id of the style element injected for the duration of a capture */
|
|
27
|
+
const STYLE_ID = '__testivai_capture_css__';
|
|
28
|
+
/**
|
|
29
|
+
* Freezes CSS animations/transitions, hides the caret, and forces instant
|
|
30
|
+
* scrolling — the top causes of flaky visual diffs. Near-zero durations
|
|
31
|
+
* (not `none`) let entry animations COMPLETE at their final state.
|
|
32
|
+
* Mirrors the Playwright adapter's STABILIZE_CSS.
|
|
33
|
+
*/
|
|
34
|
+
const STABILIZE_CSS = '*, *::before, *::after { animation-duration: 0.001s !important; animation-delay: 0s !important; ' +
|
|
35
|
+
'animation-iteration-count: 1 !important; transition-duration: 0.001s !important; ' +
|
|
36
|
+
'transition-delay: 0s !important; caret-color: transparent !important; scroll-behavior: auto !important; }';
|
|
37
|
+
/** Build the ignoreSelectors CSS block (visibility preserves layout). */
|
|
38
|
+
function buildIgnoreCss(selectors) {
|
|
39
|
+
return selectors.map((s) => `${s} { visibility: hidden !important; }`).join('\n');
|
|
40
|
+
}
|
|
41
|
+
async function injectCaptureCss(driver, css) {
|
|
42
|
+
try {
|
|
43
|
+
await driver.executeScript(`var el = document.createElement('style');` +
|
|
44
|
+
`el.id = arguments[0]; el.textContent = arguments[1];` +
|
|
45
|
+
`document.head.appendChild(el);`, STYLE_ID, css);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false; // locked-down page; capture proceeds without stabilization
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function removeCaptureCss(driver) {
|
|
53
|
+
try {
|
|
54
|
+
await driver.executeScript(`var el = document.getElementById(arguments[0]); if (el) el.remove();`, STYLE_ID);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// best-effort cleanup
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Wait (bounded at 10s) for web fonts to finish loading so the capture
|
|
62
|
+
* never shows a fallback font — a fallback-font capture diffs 30%+.
|
|
63
|
+
*/
|
|
64
|
+
async function waitForFonts(driver) {
|
|
65
|
+
const deadline = Date.now() + 10000;
|
|
66
|
+
for (;;) {
|
|
67
|
+
try {
|
|
68
|
+
const ready = await driver.executeScript(`return document.fonts ? document.fonts.status !== 'loading' : true;`);
|
|
69
|
+
if (ready || Date.now() >= deadline)
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return; // no FontFaceSet or executeScript failed — proceed
|
|
74
|
+
}
|
|
75
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Full-page screenshot where the driver supports it, viewport otherwise.
|
|
80
|
+
* Returns a PNG Buffer.
|
|
81
|
+
*/
|
|
82
|
+
async function captureScreenshot(driver) {
|
|
83
|
+
// Chromium drivers expose raw CDP; captureBeyondViewport gives a true
|
|
84
|
+
// full-page capture without resizing the window (which breaks 100vh
|
|
85
|
+
// layouts — the reason we never use the resize trick).
|
|
86
|
+
if (typeof driver.sendAndGetDevToolsCommand === 'function') {
|
|
87
|
+
try {
|
|
88
|
+
const result = await driver.sendAndGetDevToolsCommand('Page.captureScreenshot', { captureBeyondViewport: true, format: 'png' });
|
|
89
|
+
if (result && typeof result.data === 'string' && result.data.length > 0) {
|
|
90
|
+
return Buffer.from(result.data, 'base64');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// fall through to the viewport screenshot
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const base64 = await driver.takeScreenshot();
|
|
98
|
+
if (typeof base64 !== 'string' || base64.length === 0) {
|
|
99
|
+
throw new Error('testivai.witness: driver.takeScreenshot() returned an empty value');
|
|
100
|
+
}
|
|
101
|
+
return Buffer.from(base64, 'base64');
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Capture a screenshot + DOM and write them as a temp snapshot.
|
|
105
|
+
*
|
|
106
|
+
* @param driver selenium-webdriver WebDriver (must expose
|
|
107
|
+
* `takeScreenshot()` and `executeScript()`).
|
|
108
|
+
* @param name Snapshot name. Becomes the directory under
|
|
109
|
+
* `.testivai/temp/<name>/` and the key in the rendered report.
|
|
110
|
+
* @param options Per-call overrides.
|
|
111
|
+
*/
|
|
112
|
+
async function witness(driver, name, options = {}) {
|
|
113
|
+
if (!name || typeof name !== 'string') {
|
|
114
|
+
throw new Error('testivai.witness: snapshot name is required and must be a non-empty string');
|
|
115
|
+
}
|
|
116
|
+
if (!driver || typeof driver.takeScreenshot !== 'function') {
|
|
117
|
+
throw new Error('testivai.witness: driver argument must expose takeScreenshot()');
|
|
118
|
+
}
|
|
119
|
+
// Multi-browser runs: fold the variant into the name so sessions don't
|
|
120
|
+
// overwrite each other's baselines (same mechanism as the Playwright
|
|
121
|
+
// adapter's multi-project handling).
|
|
122
|
+
if (options.variant) {
|
|
123
|
+
name = `${name}__${options.variant.replace(/[^a-z0-9_-]+/gi, '_').toLowerCase()}`;
|
|
124
|
+
}
|
|
125
|
+
// 0. Prepare the page: stabilization CSS (animations/caret/fonts — the
|
|
126
|
+
// flake killers) + ignoreSelectors from config.json and this call,
|
|
127
|
+
// injected as one style tag and removed after the capture.
|
|
128
|
+
const localConfig = (0, witness_1.loadLocalConfig)(process.cwd());
|
|
129
|
+
const stabilize = options.stabilize ?? localConfig.stabilize;
|
|
130
|
+
const ignoreSelectors = [
|
|
131
|
+
...new Set([...(localConfig.ignoreSelectors ?? []), ...(options.ignoreSelectors ?? [])]),
|
|
132
|
+
];
|
|
133
|
+
const cssParts = [];
|
|
134
|
+
if (stabilize)
|
|
135
|
+
cssParts.push(STABILIZE_CSS);
|
|
136
|
+
if (ignoreSelectors.length > 0)
|
|
137
|
+
cssParts.push(buildIgnoreCss(ignoreSelectors));
|
|
138
|
+
let injected = false;
|
|
139
|
+
if (cssParts.length > 0 && typeof driver.executeScript === 'function') {
|
|
140
|
+
injected = await injectCaptureCss(driver, cssParts.join('\n'));
|
|
141
|
+
if (stabilize)
|
|
142
|
+
await waitForFonts(driver);
|
|
143
|
+
}
|
|
144
|
+
// 1. Capture screenshot (full-page on Chromium, viewport elsewhere)
|
|
145
|
+
let screenshot;
|
|
146
|
+
try {
|
|
147
|
+
screenshot = await captureScreenshot(driver);
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
if (injected)
|
|
151
|
+
await removeCaptureCss(driver);
|
|
152
|
+
}
|
|
153
|
+
// 2. Capture DOM (best-effort — never break the screenshot path)
|
|
154
|
+
let dom;
|
|
155
|
+
if (!options.skipDom && typeof driver.executeScript === 'function') {
|
|
156
|
+
try {
|
|
157
|
+
const result = await driver.executeScript(
|
|
158
|
+
// ignoreSelectors excludes elements from the pixel diff, so they
|
|
159
|
+
// are excluded from the DOM/text signal too (consistent semantic).
|
|
160
|
+
`var clone = document.documentElement.cloneNode(true);` +
|
|
161
|
+
`var sels = arguments[0] || [];` +
|
|
162
|
+
`for (var i = 0; i < sels.length; i++) {` +
|
|
163
|
+
` try { clone.querySelectorAll(sels[i]).forEach(function(el){ el.remove(); }); } catch (e) {}` +
|
|
164
|
+
`}` +
|
|
165
|
+
`return clone.outerHTML;`, ignoreSelectors);
|
|
166
|
+
if (typeof result === 'string' && result.length > 0) {
|
|
167
|
+
dom = result;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// Suppressed by design. DOM capture failure means the noise hint
|
|
172
|
+
// is unavailable for this snapshot; pixel diff still works.
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// 3. Write to .testivai/temp/<name>/
|
|
176
|
+
const store = new witness_1.BaselineStore(process.cwd());
|
|
177
|
+
store.writeTemp(name, screenshot, dom);
|
|
178
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@testivai/witness-selenium",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Selenium WebDriver adapter for TestivAI Witness — local-first visual regression with pixel + DOM comparison",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"selenium",
|
|
15
|
+
"selenium-webdriver",
|
|
16
|
+
"visual-regression",
|
|
17
|
+
"testing",
|
|
18
|
+
"screenshot-testing",
|
|
19
|
+
"testivai"
|
|
20
|
+
],
|
|
21
|
+
"author": "TestivAI",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/mcbuddy/testivai-oss.git",
|
|
26
|
+
"directory": "packages/selenium"
|
|
27
|
+
},
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/mcbuddy/testivai-oss/issues"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/mcbuddy/testivai-oss/tree/main/packages/selenium#readme",
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"README.md",
|
|
38
|
+
"LICENSE"
|
|
39
|
+
],
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18.0.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"selenium-webdriver": ">=4.6.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependenciesMeta": {
|
|
47
|
+
"selenium-webdriver": {
|
|
48
|
+
"optional": false
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@testivai/witness": "1.3.1"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/jest": "^30.0.0",
|
|
56
|
+
"@types/node": "^20.0.0",
|
|
57
|
+
"jest": "^30.4.2",
|
|
58
|
+
"ts-jest": "^29.4.11",
|
|
59
|
+
"typescript": "^6.0.3"
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"build": "tsc",
|
|
63
|
+
"clean": "rm -rf dist",
|
|
64
|
+
"dev": "tsc --watch",
|
|
65
|
+
"test": "jest",
|
|
66
|
+
"test:unit": "jest",
|
|
67
|
+
"test:watch": "jest --watch",
|
|
68
|
+
"test:coverage": "jest --coverage"
|
|
69
|
+
}
|
|
70
|
+
}
|