@testivai/witness-selenium 0.3.11 → 0.4.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 +6 -0
- package/dist/witness.js +54 -6
- package/package.json +2 -2
package/dist/types.d.ts
CHANGED
|
@@ -27,6 +27,12 @@ export interface WitnessDriver {
|
|
|
27
27
|
* Safari drivers, which fall back to the viewport screenshot.
|
|
28
28
|
*/
|
|
29
29
|
sendAndGetDevToolsCommand?<T = unknown>(cmd: string, params?: object): Promise<T>;
|
|
30
|
+
/**
|
|
31
|
+
* selenium-webdriver's `driver.getCapabilities()`. Optional: when present,
|
|
32
|
+
* the browser, its version and the driver are recorded as capture
|
|
33
|
+
* provenance in the capture's metadata.json.
|
|
34
|
+
*/
|
|
35
|
+
getCapabilities?(): Promise<unknown>;
|
|
30
36
|
}
|
|
31
37
|
/**
|
|
32
38
|
* Options for a single witness() call. All optional.
|
package/dist/witness.js
CHANGED
|
@@ -59,6 +59,8 @@ const witness_1 = require("@testivai/witness");
|
|
|
59
59
|
const fs = __importStar(require("fs"));
|
|
60
60
|
const path = __importStar(require("path"));
|
|
61
61
|
const full_page_1 = require("./capture/full-page");
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
63
|
+
const ADAPTER_VERSION = require('../package.json').version;
|
|
62
64
|
/** id of the style element injected for the duration of a capture */
|
|
63
65
|
const STYLE_ID = '__testivai_capture_css__';
|
|
64
66
|
/**
|
|
@@ -154,7 +156,7 @@ async function waitForFonts(driver) {
|
|
|
154
156
|
}
|
|
155
157
|
/**
|
|
156
158
|
* Full-page screenshot where the driver supports it, viewport otherwise.
|
|
157
|
-
* Returns
|
|
159
|
+
* Returns the PNG and the capture method it came from.
|
|
158
160
|
*/
|
|
159
161
|
async function captureScreenshot(driver) {
|
|
160
162
|
// Chromium drivers expose raw CDP; captureBeyondViewport gives a true
|
|
@@ -168,7 +170,8 @@ async function captureScreenshot(driver) {
|
|
|
168
170
|
? (message) => console.log(`[TestivAI] Full-page capture: ${message}`)
|
|
169
171
|
: undefined;
|
|
170
172
|
try {
|
|
171
|
-
|
|
173
|
+
const png = await (0, full_page_1.captureFullPageScreenshot)(driver, captureLog);
|
|
174
|
+
return { png, method: witness_1.CAPTURE_METHODS.CDP_FULL_PAGE };
|
|
172
175
|
}
|
|
173
176
|
catch {
|
|
174
177
|
// fall through to the viewport screenshot
|
|
@@ -178,7 +181,7 @@ async function captureScreenshot(driver) {
|
|
|
178
181
|
if (typeof base64 !== 'string' || base64.length === 0) {
|
|
179
182
|
throw new Error('testivai.witness: driver.takeScreenshot() returned an empty value');
|
|
180
183
|
}
|
|
181
|
-
return Buffer.from(base64, 'base64');
|
|
184
|
+
return { png: Buffer.from(base64, 'base64'), method: witness_1.CAPTURE_METHODS.WEBDRIVER_VIEWPORT };
|
|
182
185
|
}
|
|
183
186
|
/**
|
|
184
187
|
* Capture a screenshot + DOM and write them as a temp snapshot.
|
|
@@ -202,10 +205,35 @@ async function witness(driver, name, options = {}) {
|
|
|
202
205
|
if (options.variant) {
|
|
203
206
|
name = `${name}__${options.variant.replace(/[^a-z0-9_-]+/gi, '_').toLowerCase()}`;
|
|
204
207
|
}
|
|
208
|
+
const localConfig = (0, witness_1.loadLocalConfig)(process.cwd());
|
|
209
|
+
// Capture provenance: what the page is rendered with, measured before
|
|
210
|
+
// anything is injected, plus the session's capabilities. Best-effort.
|
|
211
|
+
let renderProbe = null;
|
|
212
|
+
let capabilities = null;
|
|
213
|
+
if (typeof driver.executeScript === 'function') {
|
|
214
|
+
try {
|
|
215
|
+
renderProbe = await driver.executeScript(`return ${witness_1.RENDER_PROBE_EXPRESSION}`);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// no probe: no render settings recorded
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (typeof driver.getCapabilities === 'function') {
|
|
222
|
+
try {
|
|
223
|
+
capabilities = await driver.getCapabilities();
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
// no capabilities: the probe's user agent is the fallback
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const browserName = (0, witness_1.browserFromCapabilities)(capabilities)?.browser.name ?? (() => {
|
|
230
|
+
const ua = (0, witness_1.parseRenderProbe)(renderProbe)?.userAgent;
|
|
231
|
+
return (ua ? (0, witness_1.browserFromUserAgent)(ua)?.name : undefined) ?? 'unknown';
|
|
232
|
+
})();
|
|
233
|
+
name = (0, witness_1.applyEnvironmentPolicyToName)(name, { browser: { name: browserName }, os: { platform: process.platform } }, localConfig.environmentPolicy);
|
|
205
234
|
// 0. Prepare the page: stabilization CSS (animations/caret/fonts — the
|
|
206
235
|
// flake killers) + ignoreSelectors from config.json and this call,
|
|
207
236
|
// injected as one style tag and removed after the capture.
|
|
208
|
-
const localConfig = (0, witness_1.loadLocalConfig)(process.cwd());
|
|
209
237
|
const stabilize = options.stabilize ?? localConfig.stabilize;
|
|
210
238
|
const ignoreSelectors = [
|
|
211
239
|
...new Set([...(localConfig.ignoreSelectors ?? []), ...(options.ignoreSelectors ?? [])]),
|
|
@@ -225,8 +253,9 @@ async function witness(driver, name, options = {}) {
|
|
|
225
253
|
}
|
|
226
254
|
// 1. Capture screenshot (full-page on Chromium, viewport elsewhere)
|
|
227
255
|
let screenshot;
|
|
256
|
+
let captureMethod;
|
|
228
257
|
try {
|
|
229
|
-
screenshot = await captureScreenshot(driver);
|
|
258
|
+
({ png: screenshot, method: captureMethod } = await captureScreenshot(driver));
|
|
230
259
|
}
|
|
231
260
|
finally {
|
|
232
261
|
if (injected)
|
|
@@ -262,10 +291,13 @@ async function witness(driver, name, options = {}) {
|
|
|
262
291
|
// side. The injected function is the SAME one the Playwright adapter
|
|
263
292
|
// uses (`@testivai/witness`), so both lanes produce identical maps.
|
|
264
293
|
// Selenium's executeScript needs an explicit `return`.
|
|
294
|
+
// Opt-in: paths anchored on attributes such as data-testid (config
|
|
295
|
+
// `elementIdAttributes`), recorded with the capture only when set.
|
|
296
|
+
const idAttributes = (0, witness_1.normalizeElementIdAttributes)(localConfig.elementIdAttributes);
|
|
265
297
|
let elementMap;
|
|
266
298
|
if (!options.skipElementMap && typeof driver.executeScript === 'function') {
|
|
267
299
|
try {
|
|
268
|
-
const expr = (0, witness_1.buildElementMapExpression)(options.maxElements ?? witness_1.DEFAULT_MAX_ELEMENTS, ignoreSelectors);
|
|
300
|
+
const expr = (0, witness_1.buildElementMapExpression)(options.maxElements ?? witness_1.DEFAULT_MAX_ELEMENTS, ignoreSelectors, idAttributes);
|
|
269
301
|
const result = await driver.executeScript(`return ${expr}`);
|
|
270
302
|
if (Array.isArray(result) && result.length > 0) {
|
|
271
303
|
elementMap = result;
|
|
@@ -279,6 +311,20 @@ async function witness(driver, name, options = {}) {
|
|
|
279
311
|
// 4. Write to .testivai/temp/<name>/
|
|
280
312
|
const store = new witness_1.BaselineStore(process.cwd());
|
|
281
313
|
store.writeTemp(name, screenshot, dom);
|
|
314
|
+
const environment = (0, witness_1.webDriverEnvironment)({
|
|
315
|
+
capabilities,
|
|
316
|
+
probe: renderProbe,
|
|
317
|
+
adapter: { name: '@testivai/witness-selenium', version: ADAPTER_VERSION, captureMethod },
|
|
318
|
+
framework: { name: 'selenium-webdriver', version: (0, witness_1.installedPackageVersion)('selenium-webdriver') },
|
|
319
|
+
});
|
|
320
|
+
if (environment) {
|
|
321
|
+
try {
|
|
322
|
+
store.writeTempMetadata(name, { name, environment });
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
// without it the compare cannot tell rendering drift from noise for this capture
|
|
326
|
+
}
|
|
327
|
+
}
|
|
282
328
|
// 5. Shard manifest. A Selenium script has no end-of-run hook to attach to,
|
|
283
329
|
// so it is refreshed on every capture — which is exactly what lets a
|
|
284
330
|
// Selenium suite join the same sharded CI flow as Playwright. `complete`
|
|
@@ -292,6 +338,8 @@ async function witness(driver, name, options = {}) {
|
|
|
292
338
|
try {
|
|
293
339
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
294
340
|
fs.writeFileSync(path.join(tempDir, 'elements.json'), JSON.stringify(elementMap));
|
|
341
|
+
if (idAttributes.length > 0)
|
|
342
|
+
store.writeTempMetadata(name, { elementIdAttributes: idAttributes });
|
|
295
343
|
}
|
|
296
344
|
catch {
|
|
297
345
|
// A map we cannot persist is not worth failing a capture over.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testivai/witness-selenium",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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": "2.
|
|
52
|
+
"@testivai/witness": "2.6.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/jest": "^30.0.0",
|