@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 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 a PNG Buffer.
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
- return await (0, full_page_1.captureFullPageScreenshot)(driver, captureLog);
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.11",
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.4.0"
52
+ "@testivai/witness": "2.6.0"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/jest": "^30.0.0",