@testivai/witness-playwright 2.0.10 → 2.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/README.md +4 -2
- package/dist/capture/element-map.d.ts +1 -1
- package/dist/capture/element-map.js +2 -1
- package/dist/capture/emulation.d.ts +41 -0
- package/dist/capture/emulation.js +79 -0
- package/dist/capture/health.d.ts +24 -0
- package/dist/capture/health.js +40 -0
- package/dist/reporter.js +4 -0
- package/dist/snapshot.js +63 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -100,8 +100,10 @@ jobs:
|
|
|
100
100
|
- run: npm ci
|
|
101
101
|
- run: npx playwright install chromium
|
|
102
102
|
- run: npx playwright test
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
# After a failing test too: the reporter writes the report whatever
|
|
104
|
+
# the tests did, and the action posts it.
|
|
105
|
+
- uses: testivai/testivai-oss@v1
|
|
106
|
+
if: ${{ !cancelled() }}
|
|
105
107
|
with:
|
|
106
108
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
107
109
|
report-dir: visual-report
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* injects the *same* page-side function. Keeping this shim means existing
|
|
5
5
|
* imports and unit tests continue to work unchanged.
|
|
6
6
|
*/
|
|
7
|
-
export { collectElementMap, buildElementMapExpression, DEFAULT_MAX_ELEMENTS, } from '@testivai/witness';
|
|
7
|
+
export { collectElementMap, buildElementMapExpression, DEFAULT_MAX_ELEMENTS, normalizeElementIdAttributes, } from '@testivai/witness';
|
|
8
8
|
export type { CollectedElement } from '@testivai/witness';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_MAX_ELEMENTS = exports.buildElementMapExpression = exports.collectElementMap = void 0;
|
|
3
|
+
exports.normalizeElementIdAttributes = exports.DEFAULT_MAX_ELEMENTS = exports.buildElementMapExpression = exports.collectElementMap = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Re-export of the canonical element-map collector, which now lives in
|
|
6
6
|
* `@testivai/witness` so every adapter (Playwright, Selenium, WebdriverIO)
|
|
@@ -11,3 +11,4 @@ var witness_1 = require("@testivai/witness");
|
|
|
11
11
|
Object.defineProperty(exports, "collectElementMap", { enumerable: true, get: function () { return witness_1.collectElementMap; } });
|
|
12
12
|
Object.defineProperty(exports, "buildElementMapExpression", { enumerable: true, get: function () { return witness_1.buildElementMapExpression; } });
|
|
13
13
|
Object.defineProperty(exports, "DEFAULT_MAX_ELEMENTS", { enumerable: true, get: function () { return witness_1.DEFAULT_MAX_ELEMENTS; } });
|
|
14
|
+
Object.defineProperty(exports, "normalizeElementIdAttributes", { enumerable: true, get: function () { return witness_1.normalizeElementIdAttributes; } });
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Page } from '@playwright/test';
|
|
2
|
+
/**
|
|
3
|
+
* Device emulation around the default full-page capture.
|
|
4
|
+
*
|
|
5
|
+
* The capture runs `Page.captureScreenshot` with `captureBeyondViewport` over
|
|
6
|
+
* a CDP session of our own. Chrome resizes the page for it and afterwards
|
|
7
|
+
* restores that session's emulation, which is none: the device emulation
|
|
8
|
+
* Playwright set on its own session is gone. Measured on the Pixel 7 profile:
|
|
9
|
+
* devicePixelRatio 2.625 → 1 and the touch pointer gone (`pointer: coarse`
|
|
10
|
+
* and `hover: none` false; maxTouchPoints 1 → 0 on Chrome 153, kept on
|
|
11
|
+
* Chrome 156) for the rest of the test. The image itself is
|
|
12
|
+
* rendered at a pixel ratio of 1 (412 px wide for a 412 px viewport).
|
|
13
|
+
*
|
|
14
|
+
* witness() reads the emulation before and right after the capture, records
|
|
15
|
+
* the pixel ratio the image was rendered at, and restores what the capture
|
|
16
|
+
* dropped once it has measured everything else in the page (the element map
|
|
17
|
+
* must stay aligned with the 1× image).
|
|
18
|
+
*/
|
|
19
|
+
export interface EmulationState {
|
|
20
|
+
dpr: number;
|
|
21
|
+
maxTouchPoints: number;
|
|
22
|
+
/** `(pointer: coarse)`: what touch emulation means to the page's CSS. */
|
|
23
|
+
coarsePointer: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** Evaluated in the page; a self-contained expression. */
|
|
26
|
+
export declare const EMULATION_PROBE = "({ dpr: window.devicePixelRatio, maxTouchPoints: navigator.maxTouchPoints, coarsePointer: window.matchMedia('(pointer: coarse)').matches })";
|
|
27
|
+
/** The page's pixel ratio and touch points, or null when the page cannot say. */
|
|
28
|
+
export declare function readEmulation(page: Pick<Page, 'evaluate'>): Promise<EmulationState | null>;
|
|
29
|
+
/**
|
|
30
|
+
* Put back what the capture dropped: the pixel ratio (Playwright re-applies
|
|
31
|
+
* its device metrics, mobile flag included, on setViewportSize) and touch
|
|
32
|
+
* emulation. The page sees two resize events, after a capture that resized
|
|
33
|
+
* it already. Nothing is done when nothing changed, so a profile the capture
|
|
34
|
+
* leaves alone is never touched. Best effort: never fails the capture.
|
|
35
|
+
*/
|
|
36
|
+
export declare function restoreEmulation(page: Page, before: EmulationState | null): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* The render probe with the pixel ratio the image was rendered at: the CDP
|
|
39
|
+
* capture renders at 1 whatever the profile says.
|
|
40
|
+
*/
|
|
41
|
+
export declare function withCapturedScale(probe: unknown, atCapture: EmulationState | null): unknown;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EMULATION_PROBE = void 0;
|
|
4
|
+
exports.readEmulation = readEmulation;
|
|
5
|
+
exports.restoreEmulation = restoreEmulation;
|
|
6
|
+
exports.withCapturedScale = withCapturedScale;
|
|
7
|
+
/** Evaluated in the page; a self-contained expression. */
|
|
8
|
+
exports.EMULATION_PROBE = "({ dpr: window.devicePixelRatio, maxTouchPoints: navigator.maxTouchPoints, coarsePointer: window.matchMedia('(pointer: coarse)').matches })";
|
|
9
|
+
/** The page's pixel ratio and touch points, or null when the page cannot say. */
|
|
10
|
+
async function readEmulation(page) {
|
|
11
|
+
try {
|
|
12
|
+
const raw = (await page.evaluate(exports.EMULATION_PROBE));
|
|
13
|
+
if (raw && typeof raw.dpr === 'number' && typeof raw.maxTouchPoints === 'number') {
|
|
14
|
+
return { dpr: raw.dpr, maxTouchPoints: raw.maxTouchPoints, coarsePointer: raw.coarsePointer === true };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// a page that cannot be evaluated has nothing to restore
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Touch emulation lives on the CDP session that set it, and goes when that
|
|
24
|
+
* session detaches, so the session that restores it stays attached for the
|
|
25
|
+
* page's lifetime, one per page.
|
|
26
|
+
*/
|
|
27
|
+
const touchSessions = new WeakMap();
|
|
28
|
+
/**
|
|
29
|
+
* Put back what the capture dropped: the pixel ratio (Playwright re-applies
|
|
30
|
+
* its device metrics, mobile flag included, on setViewportSize) and touch
|
|
31
|
+
* emulation. The page sees two resize events, after a capture that resized
|
|
32
|
+
* it already. Nothing is done when nothing changed, so a profile the capture
|
|
33
|
+
* leaves alone is never touched. Best effort: never fails the capture.
|
|
34
|
+
*/
|
|
35
|
+
async function restoreEmulation(page, before) {
|
|
36
|
+
if (!before)
|
|
37
|
+
return;
|
|
38
|
+
const now = await readEmulation(page);
|
|
39
|
+
if (!now)
|
|
40
|
+
return;
|
|
41
|
+
if (now.dpr === before.dpr && now.maxTouchPoints === before.maxTouchPoints && now.coarsePointer === before.coarsePointer)
|
|
42
|
+
return;
|
|
43
|
+
try {
|
|
44
|
+
// Touch emulation, whenever the page had touch and its touch points or
|
|
45
|
+
// pointer media changed. What the capture drops depends on Chrome:
|
|
46
|
+
// Chrome 153 drops the touch points and `pointer: coarse`; Chrome 156
|
|
47
|
+
// keeps the touch points and drops `pointer: coarse` and `hover: none`.
|
|
48
|
+
// Sending it again brings back all three; device metrics alone do not.
|
|
49
|
+
if (before.maxTouchPoints > 0 &&
|
|
50
|
+
(now.maxTouchPoints !== before.maxTouchPoints || now.coarsePointer !== before.coarsePointer)) {
|
|
51
|
+
let session = touchSessions.get(page);
|
|
52
|
+
if (!session) {
|
|
53
|
+
session = await page.context().newCDPSession(page);
|
|
54
|
+
touchSessions.set(page, session);
|
|
55
|
+
}
|
|
56
|
+
await session.send('Emulation.setTouchEmulationEnabled', { enabled: true, maxTouchPoints: before.maxTouchPoints });
|
|
57
|
+
}
|
|
58
|
+
// The device metrics (pixel ratio, mobile). Playwright does not send a viewport equal to
|
|
59
|
+
// the last one it set, so re-applying the same size does nothing: step
|
|
60
|
+
// 1 px away and back.
|
|
61
|
+
const viewport = page.viewportSize();
|
|
62
|
+
if (viewport) {
|
|
63
|
+
await page.setViewportSize({ width: viewport.width, height: viewport.height + 1 });
|
|
64
|
+
await page.setViewportSize(viewport);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// the capture is written; a page we cannot restore stays as Chrome left it
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The render probe with the pixel ratio the image was rendered at: the CDP
|
|
73
|
+
* capture renders at 1 whatever the profile says.
|
|
74
|
+
*/
|
|
75
|
+
function withCapturedScale(probe, atCapture) {
|
|
76
|
+
if (!atCapture || !probe || typeof probe !== 'object' || Array.isArray(probe))
|
|
77
|
+
return probe;
|
|
78
|
+
return { ...probe, deviceScaleFactor: atCapture.dpr };
|
|
79
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture health for the Playwright adapter: what failed to load (web fonts,
|
|
3
|
+
* stylesheets, images, requests that answered 400 or more), read by the
|
|
4
|
+
* shared probe in the page right AFTER the screenshot, so it sees what loaded,
|
|
5
|
+
* and BEFORE the ignoreSelectors and stabilize styles come off: removing them
|
|
6
|
+
* shows elements that were hidden in the capture, and a web font only such an
|
|
7
|
+
* element uses starts loading then, for glyphs that were never in the image.
|
|
8
|
+
* Merged into the capture's metadata.json as `health`; approving freezes it
|
|
9
|
+
* into the baseline's. See capture/health.ts in @testivai/witness for the
|
|
10
|
+
* shape and report/health.ts for when a capture is unhealthy.
|
|
11
|
+
*/
|
|
12
|
+
import { type BaselineStore, type CaptureHealth } from '@testivai/witness';
|
|
13
|
+
/** The part of a Playwright Page this needs (structural, so tests need no browser). */
|
|
14
|
+
export interface EvaluatesExpressions {
|
|
15
|
+
evaluate(expression: string): Promise<unknown>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Run the probe. Best-effort, like the rest of the metadata: a probe that
|
|
19
|
+
* throws or returns something malformed gives null, which reads as "not
|
|
20
|
+
* checked", never as healthy.
|
|
21
|
+
*/
|
|
22
|
+
export declare function probeCaptureHealth(page: EvaluatesExpressions): Promise<CaptureHealth | null>;
|
|
23
|
+
/** Record a probe's result in the capture's metadata.json; nothing for null (not checked). */
|
|
24
|
+
export declare function recordCaptureHealth(store: BaselineStore, name: string, health: CaptureHealth | null): void;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.probeCaptureHealth = probeCaptureHealth;
|
|
4
|
+
exports.recordCaptureHealth = recordCaptureHealth;
|
|
5
|
+
/**
|
|
6
|
+
* Capture health for the Playwright adapter: what failed to load (web fonts,
|
|
7
|
+
* stylesheets, images, requests that answered 400 or more), read by the
|
|
8
|
+
* shared probe in the page right AFTER the screenshot, so it sees what loaded,
|
|
9
|
+
* and BEFORE the ignoreSelectors and stabilize styles come off: removing them
|
|
10
|
+
* shows elements that were hidden in the capture, and a web font only such an
|
|
11
|
+
* element uses starts loading then, for glyphs that were never in the image.
|
|
12
|
+
* Merged into the capture's metadata.json as `health`; approving freezes it
|
|
13
|
+
* into the baseline's. See capture/health.ts in @testivai/witness for the
|
|
14
|
+
* shape and report/health.ts for when a capture is unhealthy.
|
|
15
|
+
*/
|
|
16
|
+
const witness_1 = require("@testivai/witness");
|
|
17
|
+
/**
|
|
18
|
+
* Run the probe. Best-effort, like the rest of the metadata: a probe that
|
|
19
|
+
* throws or returns something malformed gives null, which reads as "not
|
|
20
|
+
* checked", never as healthy.
|
|
21
|
+
*/
|
|
22
|
+
async function probeCaptureHealth(page) {
|
|
23
|
+
try {
|
|
24
|
+
return (0, witness_1.parseHealth)(await page.evaluate(witness_1.HEALTH_PROBE_EXPRESSION));
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Record a probe's result in the capture's metadata.json; nothing for null (not checked). */
|
|
31
|
+
function recordCaptureHealth(store, name, health) {
|
|
32
|
+
if (!health)
|
|
33
|
+
return;
|
|
34
|
+
try {
|
|
35
|
+
store.writeTempMetadata(name, { health });
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// not checked: the screenshot path never depends on it
|
|
39
|
+
}
|
|
40
|
+
}
|
package/dist/reporter.js
CHANGED
|
@@ -88,6 +88,10 @@ class TestivAIPlaywrightReporter {
|
|
|
88
88
|
console.log(`\n TestivAI: ${where} — captured to .testivai/temp/, comparison skipped.`);
|
|
89
89
|
console.log(' Collect every shard\'s .testivai/temp/, then compare once:');
|
|
90
90
|
console.log(' npx testivai merge-captures <dirs...> && npx testivai report --fail-on-diff');
|
|
91
|
+
// A CI step runs only on success by default, so one failing test would
|
|
92
|
+
// skip the comparison and leave the pull request without a report.
|
|
93
|
+
console.log(' Run that step even when a test fails: in GitHub Actions, `if: ${{ !cancelled() }}`');
|
|
94
|
+
console.log(' (and on a job that `needs` the shards, too).');
|
|
91
95
|
return;
|
|
92
96
|
}
|
|
93
97
|
// ── Generate the HTML report ──────────────────────────────────────────
|
package/dist/snapshot.js
CHANGED
|
@@ -48,7 +48,9 @@ const element_map_1 = require("./capture/element-map");
|
|
|
48
48
|
const stabilize_1 = require("./config/stabilize");
|
|
49
49
|
const witness_1 = require("@testivai/witness");
|
|
50
50
|
const environment_1 = require("./capture/environment");
|
|
51
|
+
const health_1 = require("./capture/health");
|
|
51
52
|
const full_page_1 = require("./capture/full-page");
|
|
53
|
+
const emulation_1 = require("./capture/emulation");
|
|
52
54
|
/**
|
|
53
55
|
* Generates a safe filename from a URL.
|
|
54
56
|
* @param pageUrl The URL of the page.
|
|
@@ -117,7 +119,11 @@ async function snapshot(page, testInfo, name, config) {
|
|
|
117
119
|
// recorded as capture provenance, and its user agent names the engine for
|
|
118
120
|
// environmentPolicy "separate" when the project does not.
|
|
119
121
|
const renderProbe = await page.evaluate(witness_1.RENDER_PROBE_EXPRESSION).catch(() => null);
|
|
120
|
-
|
|
122
|
+
// The device emulation before the capture, which the CDP capture drops
|
|
123
|
+
// (capture/emulation.ts); restored once everything is measured.
|
|
124
|
+
const emulationBefore = await (0, emulation_1.readEmulation)(page);
|
|
125
|
+
const localConfig = (0, witness_1.loadLocalConfig)(process.cwd());
|
|
126
|
+
const environmentPolicy = localConfig.environmentPolicy;
|
|
121
127
|
const snapshotName = (0, witness_1.applyEnvironmentPolicyToName)(effectiveSnapshotName(name || getSnapshotNameFromUrl(page.url()), testInfo), { browser: { name: (0, environment_1.playwrightBrowserName)(testInfo ?? {}, (0, witness_1.parseRenderProbe)(renderProbe)?.userAgent) }, os: { platform: process.platform } }, environmentPolicy);
|
|
122
128
|
// Which path took the screenshot (set below): part of the provenance, since
|
|
123
129
|
// another method changes the image and often its size.
|
|
@@ -307,6 +313,12 @@ async function snapshot(page, testInfo, name, config) {
|
|
|
307
313
|
}
|
|
308
314
|
}
|
|
309
315
|
}
|
|
316
|
+
else if (!hasCdp(page)) {
|
|
317
|
+
// WebKit and Firefox have no CDP: Playwright's own full-page screenshot
|
|
318
|
+
// is the capture there, not a fallback from a failure.
|
|
319
|
+
captureMethod = witness_1.CAPTURE_METHODS.PLAYWRIGHT_FULL_PAGE;
|
|
320
|
+
await page.screenshot({ path: screenshotPath, fullPage: true });
|
|
321
|
+
}
|
|
310
322
|
else {
|
|
311
323
|
// Use browser capture approach (default)
|
|
312
324
|
if (process.env.TESTIVAI_DEBUG === 'true') {
|
|
@@ -356,12 +368,28 @@ async function snapshot(page, testInfo, name, config) {
|
|
|
356
368
|
await client.detach();
|
|
357
369
|
}
|
|
358
370
|
catch (error) {
|
|
359
|
-
|
|
371
|
+
// An engine we could not name (a persistent context has no browser())
|
|
372
|
+
// answers that CDP is Chromium-only: the expected path, not a failure.
|
|
373
|
+
if (!/only available in Chromium/i.test(String(error?.message))) {
|
|
374
|
+
console.error('[TestivAI] Browser screenshot failed:', error.message);
|
|
375
|
+
}
|
|
360
376
|
// Fallback to regular screenshot
|
|
361
377
|
captureMethod = witness_1.CAPTURE_METHODS.PLAYWRIGHT_FULL_PAGE;
|
|
362
378
|
await page.screenshot({ path: screenshotPath, fullPage: true });
|
|
363
379
|
}
|
|
364
380
|
}
|
|
381
|
+
// Capture health: what failed to load (web fonts, stylesheets, images,
|
|
382
|
+
// requests that answered 400+), read right after the screenshot (every
|
|
383
|
+
// capture path above ends here) so it sees what loaded, and before the
|
|
384
|
+
// ignoreSelectors and stabilize styles come off: removing them shows
|
|
385
|
+
// elements that were hidden in the capture, and a web font only they use
|
|
386
|
+
// starts loading then, for glyphs that were never in the image. A font that
|
|
387
|
+
// fell back leaves the DOM and the declared styles the same, so only this
|
|
388
|
+
// tells an unhealthy capture from render noise. Recorded with the metadata.
|
|
389
|
+
const captureHealth = await (0, health_1.probeCaptureHealth)(page);
|
|
390
|
+
// The pixel ratio the image was rendered at (1 after a CDP capture,
|
|
391
|
+
// whatever the profile says): recorded with the capture's provenance.
|
|
392
|
+
const emulationAtCapture = await (0, emulation_1.readEmulation)(page);
|
|
365
393
|
// Restore any elements hidden for ignoreSelectors
|
|
366
394
|
if (ignoreStyleEl) {
|
|
367
395
|
await ignoreStyleEl.evaluate((el) => el.remove()).catch(() => { });
|
|
@@ -480,27 +508,58 @@ async function snapshot(page, testInfo, name, config) {
|
|
|
480
508
|
catch {
|
|
481
509
|
// a persistent context has no browser(): the probe's user agent is the fallback
|
|
482
510
|
}
|
|
483
|
-
const environment = (0, environment_1.playwrightEnvironment)({
|
|
511
|
+
const environment = (0, environment_1.playwrightEnvironment)({
|
|
512
|
+
testInfo: testInfo ?? {},
|
|
513
|
+
browserVersion,
|
|
514
|
+
probe: (0, emulation_1.withCapturedScale)(renderProbe, emulationAtCapture),
|
|
515
|
+
captureMethod,
|
|
516
|
+
});
|
|
484
517
|
new witness_1.BaselineStore(process.cwd()).writeTempMetadata(snapshotName, { name: snapshotName, environment });
|
|
485
518
|
}
|
|
486
519
|
catch {
|
|
487
520
|
// no environment only means the compare cannot tell drift from noise for this capture
|
|
488
521
|
}
|
|
522
|
+
// Capture health, probed right after the screenshot (above).
|
|
523
|
+
(0, health_1.recordCaptureHealth)(new witness_1.BaselineStore(process.cwd()), snapshotName, captureHealth);
|
|
489
524
|
// Element map: layout + computed-style digest for every visible
|
|
490
525
|
// element, powering attribution ("WHICH element changed"), exact
|
|
491
526
|
// shift classification, and the style fingerprint on the compare
|
|
492
527
|
// side. Best-effort — the screenshot path never depends on it.
|
|
493
528
|
try {
|
|
494
529
|
const mapIgnores = (0, ignore_selectors_1.collectIgnoreSelectors)(process.cwd(), projectConfig, effectiveConfig);
|
|
495
|
-
|
|
530
|
+
// Opt-in: paths anchored on attributes such as data-testid. Recorded
|
|
531
|
+
// with the capture only when set, so the compare can tell paths built
|
|
532
|
+
// another way; unset, nothing written changes.
|
|
533
|
+
const idAttributes = (0, witness_1.normalizeElementIdAttributes)(localConfig.elementIdAttributes);
|
|
534
|
+
const elementMap = await page.evaluate((0, element_map_1.buildElementMapExpression)(undefined, mapIgnores, idAttributes));
|
|
496
535
|
if (Array.isArray(elementMap) && elementMap.length > 0) {
|
|
497
536
|
await fs.writeJson(path.join(localSnapshotDir, 'elements.json'), elementMap);
|
|
537
|
+
if (idAttributes.length > 0) {
|
|
538
|
+
new witness_1.BaselineStore(process.cwd()).writeTempMetadata(snapshotName, { elementIdAttributes: idAttributes });
|
|
539
|
+
}
|
|
498
540
|
}
|
|
499
541
|
}
|
|
500
542
|
catch {
|
|
501
543
|
// missing elements.json only disables attribution for this capture
|
|
502
544
|
}
|
|
503
545
|
}
|
|
546
|
+
// Last, after everything measured in the page at the capture's scale:
|
|
547
|
+
// put back the device emulation the CDP capture dropped.
|
|
548
|
+
await (0, emulation_1.restoreEmulation)(page, emulationBefore);
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Whether the page's engine can have a CDP session. False only when the
|
|
552
|
+
* browser names an engine other than Chromium; unknown (no browser(), as in a
|
|
553
|
+
* persistent context) is tried, and its answer handled where it fails.
|
|
554
|
+
*/
|
|
555
|
+
function hasCdp(page) {
|
|
556
|
+
try {
|
|
557
|
+
const name = page.context().browser()?.browserType().name();
|
|
558
|
+
return name === undefined || name === 'chromium';
|
|
559
|
+
}
|
|
560
|
+
catch {
|
|
561
|
+
return true;
|
|
562
|
+
}
|
|
504
563
|
}
|
|
505
564
|
/**
|
|
506
565
|
* Read the global `mask` list from .testivai/config.json (selector strings
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testivai/witness-playwright",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Playwright 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",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"fs-extra": "^11.4.0",
|
|
48
48
|
"sharp": "^0.35.4",
|
|
49
|
-
"@testivai/witness": "2.
|
|
49
|
+
"@testivai/witness": "2.6.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@playwright/test": "^1.63.0",
|