@testivai/witness-selenium 0.2.0 → 0.3.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/dist/types.d.ts +7 -0
- package/dist/witness.js +54 -1
- package/package.json +2 -2
package/dist/types.d.ts
CHANGED
|
@@ -44,6 +44,13 @@ export interface WitnessOptions {
|
|
|
44
44
|
* leaves the pixel and DOM layers intact.
|
|
45
45
|
*/
|
|
46
46
|
skipElementMap?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Milliseconds the DOM must be free of mutations before capturing
|
|
49
|
+
* (default 150). Raise it for pages that stream content in.
|
|
50
|
+
*/
|
|
51
|
+
settleQuietMs?: number;
|
|
52
|
+
/** Ceiling on waiting for the page to settle (default 5000ms). */
|
|
53
|
+
settleTimeoutMs?: number;
|
|
47
54
|
/**
|
|
48
55
|
* Cap on elements walked for the map (default 3000). Lower it on very
|
|
49
56
|
* large pages where the single executeScript round trip is costly.
|
package/dist/witness.js
CHANGED
|
@@ -96,6 +96,47 @@ async function removeCaptureCss(driver) {
|
|
|
96
96
|
* Wait (bounded at 10s) for web fonts to finish loading so the capture
|
|
97
97
|
* never shows a fallback font — a fallback-font capture diffs 30%+.
|
|
98
98
|
*/
|
|
99
|
+
/**
|
|
100
|
+
* Wait until the page has stopped changing: document complete, images finished,
|
|
101
|
+
* fonts loaded, and no DOM mutations for `quietMs`.
|
|
102
|
+
*
|
|
103
|
+
* Deliberately NOT network idle — Playwright's own docs mark that DISCOURAGED
|
|
104
|
+
* for testing, and it is the wrong signal anyway: a page with analytics beacons
|
|
105
|
+
* never goes quiet, while a network-idle page can still be animating.
|
|
106
|
+
*
|
|
107
|
+
* Always bounded. A page that never settles yields a capture with a debug note
|
|
108
|
+
* rather than a hung suite.
|
|
109
|
+
*/
|
|
110
|
+
async function waitForSettled(driver, quietMs, timeoutMs) {
|
|
111
|
+
const expr = (0, witness_1.buildSettleProbeExpression)(quietMs);
|
|
112
|
+
const deadline = Date.now() + timeoutMs;
|
|
113
|
+
for (;;) {
|
|
114
|
+
try {
|
|
115
|
+
const state = await driver.executeScript(`return ${expr}`);
|
|
116
|
+
// A driver that cannot evaluate the probe returns nothing. Polling that
|
|
117
|
+
// to the timeout would add the full wait to EVERY capture, so treat an
|
|
118
|
+
// unusable answer as "cannot probe" and proceed immediately.
|
|
119
|
+
if (!state || typeof state.settled !== 'boolean')
|
|
120
|
+
return;
|
|
121
|
+
if (state.settled)
|
|
122
|
+
return;
|
|
123
|
+
if (Date.now() >= deadline)
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return; // probe unavailable — never block the capture
|
|
128
|
+
}
|
|
129
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async function stopSettleObserver(driver) {
|
|
133
|
+
try {
|
|
134
|
+
await driver.executeScript(witness_1.SETTLE_STOP_EXPRESSION);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// best-effort cleanup
|
|
138
|
+
}
|
|
139
|
+
}
|
|
99
140
|
async function waitForFonts(driver) {
|
|
100
141
|
const deadline = Date.now() + 10000;
|
|
101
142
|
for (;;) {
|
|
@@ -173,8 +214,10 @@ async function witness(driver, name, options = {}) {
|
|
|
173
214
|
let injected = false;
|
|
174
215
|
if (cssParts.length > 0 && typeof driver.executeScript === 'function') {
|
|
175
216
|
injected = await injectCaptureCss(driver, cssParts.join('\n'));
|
|
176
|
-
if (stabilize)
|
|
217
|
+
if (stabilize) {
|
|
177
218
|
await waitForFonts(driver);
|
|
219
|
+
await waitForSettled(driver, options.settleQuietMs ?? witness_1.DEFAULT_QUIET_MS, options.settleTimeoutMs ?? witness_1.DEFAULT_SETTLE_TIMEOUT_MS);
|
|
220
|
+
}
|
|
178
221
|
}
|
|
179
222
|
// 1. Capture screenshot (full-page on Chromium, viewport elsewhere)
|
|
180
223
|
let screenshot;
|
|
@@ -184,6 +227,8 @@ async function witness(driver, name, options = {}) {
|
|
|
184
227
|
finally {
|
|
185
228
|
if (injected)
|
|
186
229
|
await removeCaptureCss(driver);
|
|
230
|
+
if (stabilize)
|
|
231
|
+
await stopSettleObserver(driver);
|
|
187
232
|
}
|
|
188
233
|
// 2. Capture DOM (best-effort — never break the screenshot path)
|
|
189
234
|
let dom;
|
|
@@ -230,6 +275,14 @@ async function witness(driver, name, options = {}) {
|
|
|
230
275
|
// 4. Write to .testivai/temp/<name>/
|
|
231
276
|
const store = new witness_1.BaselineStore(process.cwd());
|
|
232
277
|
store.writeTemp(name, screenshot, dom);
|
|
278
|
+
// 5. Shard manifest. A Selenium script has no end-of-run hook to attach to,
|
|
279
|
+
// so it is refreshed on every capture — which is exactly what lets a
|
|
280
|
+
// Selenium suite join the same sharded CI flow as Playwright. `complete`
|
|
281
|
+
// stays false because this adapter cannot know the run finished.
|
|
282
|
+
const shard = (0, witness_1.parseShardEnv)(process.env.TESTIVAI_SHARD);
|
|
283
|
+
if (shard) {
|
|
284
|
+
(0, witness_1.writeShardManifest)(path.join(process.cwd(), '.testivai', 'temp'), shard);
|
|
285
|
+
}
|
|
233
286
|
if (elementMap !== undefined) {
|
|
234
287
|
const tempDir = path.join(process.cwd(), '.testivai', 'temp', name);
|
|
235
288
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testivai/witness-selenium",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Selenium WebDriver adapter for TestivAI Witness — local-first visual regression with pixel + DOM comparison",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@testivai/witness": "1.
|
|
52
|
+
"@testivai/witness": "1.13.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/jest": "^30.0.0",
|