craftdriver 1.6.0 → 1.7.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/CHANGELOG.md +17 -0
- package/README.md +4 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/browser.d.ts +32 -0
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/browser.js +56 -0
- package/dist/lib/browser.js.map +1 -1
- package/dist/lib/errors.d.ts +7 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +7 -0
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/timing.d.ts +8 -0
- package/dist/lib/timing.d.ts.map +1 -1
- package/dist/lib/timing.js +8 -0
- package/dist/lib/timing.js.map +1 -1
- package/dist/lib/visual/antialias.d.ts +41 -0
- package/dist/lib/visual/antialias.d.ts.map +1 -0
- package/dist/lib/visual/antialias.js +126 -0
- package/dist/lib/visual/antialias.js.map +1 -0
- package/dist/lib/visual/codec.d.ts +24 -0
- package/dist/lib/visual/codec.d.ts.map +1 -0
- package/dist/lib/visual/codec.js +11 -0
- package/dist/lib/visual/codec.js.map +1 -0
- package/dist/lib/visual/compare.d.ts +38 -0
- package/dist/lib/visual/compare.d.ts.map +1 -0
- package/dist/lib/visual/compare.js +209 -0
- package/dist/lib/visual/compare.js.map +1 -0
- package/dist/lib/visual/diff.d.ts +18 -0
- package/dist/lib/visual/diff.d.ts.map +1 -0
- package/dist/lib/visual/diff.js +23 -0
- package/dist/lib/visual/diff.js.map +1 -0
- package/dist/lib/visual/expectScreenshot.d.ts +65 -0
- package/dist/lib/visual/expectScreenshot.d.ts.map +1 -0
- package/dist/lib/visual/expectScreenshot.js +287 -0
- package/dist/lib/visual/expectScreenshot.js.map +1 -0
- package/dist/lib/visual/index.d.ts +10 -0
- package/dist/lib/visual/index.d.ts.map +1 -0
- package/dist/lib/visual/index.js +9 -0
- package/dist/lib/visual/index.js.map +1 -0
- package/dist/lib/visual/pixelMath.d.ts +32 -0
- package/dist/lib/visual/pixelMath.d.ts.map +1 -0
- package/dist/lib/visual/pixelMath.js +70 -0
- package/dist/lib/visual/pixelMath.js.map +1 -0
- package/dist/lib/visual/pngHeader.d.ts +27 -0
- package/dist/lib/visual/pngHeader.d.ts.map +1 -0
- package/dist/lib/visual/pngHeader.js +63 -0
- package/dist/lib/visual/pngHeader.js.map +1 -0
- package/dist/lib/visual/pngjsCodec.d.ts +22 -0
- package/dist/lib/visual/pngjsCodec.d.ts.map +1 -0
- package/dist/lib/visual/pngjsCodec.js +70 -0
- package/dist/lib/visual/pngjsCodec.js.map +1 -0
- package/dist/lib/visual/types.d.ts +89 -0
- package/dist/lib/visual/types.d.ts.map +1 -0
- package/dist/lib/visual/types.js +20 -0
- package/dist/lib/visual/types.js.map +1 -0
- package/dist/lib/visual/visualError.d.ts +32 -0
- package/dist/lib/visual/visualError.d.ts.map +1 -0
- package/dist/lib/visual/visualError.js +59 -0
- package/dist/lib/visual/visualError.js.map +1 -0
- package/dist/lib/wait.d.ts.map +1 -1
- package/dist/lib/wait.js +7 -1
- package/dist/lib/wait.js.map +1 -1
- package/docs/api-reference.md +15 -1
- package/docs/error-codes.md +1 -0
- package/docs/screenshots.md +15 -6
- package/docs/visual-testing.md +254 -0
- package/docs/why-craftdriver.md +3 -2
- package/package.json +3 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pngjsCodec.d.ts","sourceRoot":"","sources":["../../../src/lib/visual/pngjsCodec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,KAAK,EAAgB,QAAQ,EAAE,MAAM,YAAY,CAAC;AAwDzD,eAAO,MAAM,UAAU,EAAE,QAA6B,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Isolated pngjs adapter — the single place that touches the CommonJS-shaped
|
|
3
|
+
* `pngjs` API. The rest of craftdriver is ESM and works only with the
|
|
4
|
+
* promise-based {@link PngCodec} and normalized {@link DecodedImage} defined in
|
|
5
|
+
* `./codec`; pngjs types never leak past this file.
|
|
6
|
+
*
|
|
7
|
+
* Decode and encode both use pngjs's asynchronous stream APIs (`parse` /
|
|
8
|
+
* `pack`) rather than the synchronous variants, so a large image cannot block
|
|
9
|
+
* the event loop inside a single call. CRC checking is left at pngjs's default
|
|
10
|
+
* (enabled) so corrupt chunks are rejected rather than silently decoded.
|
|
11
|
+
*
|
|
12
|
+
* Maintenance risk (recorded, not hidden): `pngjs@7.0.0` is pinned exactly. It
|
|
13
|
+
* had no reported npm/OSV advisory as of 2026-07-17, but that is a timestamped
|
|
14
|
+
* check, not proof of safety — its last release was 2023 and it publishes no
|
|
15
|
+
* security policy. It relies on Node's built-in `zlib`, so keeping Node patched
|
|
16
|
+
* is part of the posture. CI runs `npm audit --omit=dev`; this seam is kept
|
|
17
|
+
* narrow (see `./codec`) so the codec can be replaced without touching callers
|
|
18
|
+
* if the project goes unmaintained or an advisory lands.
|
|
19
|
+
*/
|
|
20
|
+
import { PNG } from 'pngjs';
|
|
21
|
+
import { CraftdriverError, ErrorCode } from '../errors.js';
|
|
22
|
+
function wrapDecodeError(cause) {
|
|
23
|
+
return new CraftdriverError(ErrorCode.INVALID_ARGUMENT, 'Failed to decode PNG image.', {
|
|
24
|
+
cause,
|
|
25
|
+
hint: 'The buffer is not a valid PNG, or its chunks failed CRC validation.',
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Decode a compressed PNG buffer to a fresh, tightly-packed 8-bit RGBA view.
|
|
30
|
+
*
|
|
31
|
+
* pngjs always normalizes palette/grayscale/16-bit/interlaced inputs to 8-bit
|
|
32
|
+
* RGBA in `png.data`, but that buffer may be a view into a larger pool. We copy
|
|
33
|
+
* into a right-sized `Uint8Array` so downstream code can rely on
|
|
34
|
+
* `data.length === width * height * 4` and on a predictable backing buffer.
|
|
35
|
+
*/
|
|
36
|
+
function decode(buffer) {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
const png = new PNG();
|
|
39
|
+
const input = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
|
|
40
|
+
png.parse(input, (err, decoded) => {
|
|
41
|
+
if (err || !decoded) {
|
|
42
|
+
reject(wrapDecodeError(err ?? new Error('pngjs produced no image')));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const { width, height } = decoded;
|
|
46
|
+
const expected = width * height * 4;
|
|
47
|
+
const data = new Uint8Array(expected);
|
|
48
|
+
data.set(decoded.data.subarray(0, expected));
|
|
49
|
+
resolve({ width, height, data });
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/** Encode normalized 8-bit RGBA back to a compressed PNG buffer. */
|
|
54
|
+
function encode(image) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
const png = new PNG({ width: image.width, height: image.height });
|
|
57
|
+
// pngjs owns a Buffer at png.data of the right length; fill it in place.
|
|
58
|
+
png.data = Buffer.from(image.data.buffer, image.data.byteOffset, image.data.byteLength);
|
|
59
|
+
const chunks = [];
|
|
60
|
+
png
|
|
61
|
+
.pack()
|
|
62
|
+
.on('data', (chunk) => chunks.push(chunk))
|
|
63
|
+
.on('end', () => resolve(Buffer.concat(chunks)))
|
|
64
|
+
.on('error', (err) => reject(new CraftdriverError(ErrorCode.DRIVER_ERROR, 'Failed to encode diff PNG image.', {
|
|
65
|
+
cause: err,
|
|
66
|
+
})));
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
export const pngjsCodec = { decode, encode };
|
|
70
|
+
//# sourceMappingURL=pngjsCodec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pngjsCodec.js","sourceRoot":"","sources":["../../../src/lib/visual/pngjsCodec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG3D,SAAS,eAAe,CAAC,KAAc;IACrC,OAAO,IAAI,gBAAgB,CAAC,SAAS,CAAC,gBAAgB,EAAE,6BAA6B,EAAE;QACrF,KAAK;QACL,IAAI,EAAE,qEAAqE;KAC5E,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,MAAM,CAAC,MAAkB;IAChC,OAAO,IAAI,OAAO,CAAe,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrE,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE;YAChC,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;gBACpB,MAAM,CAAC,eAAe,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC,CAAC,CAAC;gBACrE,OAAO;YACT,CAAC;YACD,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;YAClC,MAAM,QAAQ,GAAG,KAAK,GAAG,MAAM,GAAG,CAAC,CAAC;YACpC,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAC;YACtC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;YAC7C,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,oEAAoE;AACpE,SAAS,MAAM,CAAC,KAAmB;IACjC,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC7C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QAClE,yEAAyE;QACzE,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACxF,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,GAAG;aACA,IAAI,EAAE;aACN,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;aACjD,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;aAC/C,EAAE,CAAC,OAAO,EAAE,CAAC,GAAY,EAAE,EAAE,CAC5B,MAAM,CACJ,IAAI,gBAAgB,CAAC,SAAS,CAAC,YAAY,EAAE,kCAAkC,EAAE;YAC/E,KAAK,EAAE,GAAG;SACX,CAAC,CACH,CACF,CAAC;IACN,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAa,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public and internal types for visual screenshot assertions.
|
|
3
|
+
*
|
|
4
|
+
* The `(actual, expected)` argument order is used consistently across every
|
|
5
|
+
* public and internal helper (`compareScreenshots`, `compareDecoded`,
|
|
6
|
+
* `createDiff`). Both buffers share a type, so reversing the order would be a
|
|
7
|
+
* silent bug — never deviate from it.
|
|
8
|
+
*/
|
|
9
|
+
import type { By } from '../by.js';
|
|
10
|
+
/**
|
|
11
|
+
* Default upper bound on decoded image size, in pixels, enforced before RGBA
|
|
12
|
+
* allocation. RGBA is ≥4 bytes/pixel, so expected + actual + final raw diff is
|
|
13
|
+
* ~240 MB of raw arrays at this limit before compressed buffers and pngjs
|
|
14
|
+
* working memory — and parallel assertions multiply that. Raise it explicitly
|
|
15
|
+
* for unusually long full-page screenshots; lower it when many workers run
|
|
16
|
+
* concurrently. A 100M default would permit ≥1.2 GB and is not a real guard.
|
|
17
|
+
*/
|
|
18
|
+
export declare const DEFAULT_MAX_IMAGE_PIXELS = 20000000;
|
|
19
|
+
/** Default upper bound on a compressed PNG input buffer, in bytes (50 MiB). */
|
|
20
|
+
export declare const DEFAULT_MAX_INPUT_BYTES: number;
|
|
21
|
+
/**
|
|
22
|
+
* Mutually exclusive capture scope forwarded to `browser.screenshot()`.
|
|
23
|
+
* `fullPage` and `selector` cannot be combined (enforced by the union here and
|
|
24
|
+
* re-checked at runtime for JavaScript callers).
|
|
25
|
+
*/
|
|
26
|
+
export type VisualScreenshotOptions = {
|
|
27
|
+
fullPage: true;
|
|
28
|
+
selector?: never;
|
|
29
|
+
} | {
|
|
30
|
+
selector: string | By;
|
|
31
|
+
fullPage?: false;
|
|
32
|
+
} | {
|
|
33
|
+
fullPage?: false;
|
|
34
|
+
selector?: never;
|
|
35
|
+
};
|
|
36
|
+
/** Policies applied to a single buffer-to-buffer comparison. */
|
|
37
|
+
export interface ScreenshotCompareOptions {
|
|
38
|
+
/** Inclusive maximum difference for each visible RGB channel. Integer 0..255. Default: 0. */
|
|
39
|
+
pixelTolerance?: number;
|
|
40
|
+
/** Maximum counted different pixels. Non-negative integer. */
|
|
41
|
+
maxDiffPixels?: number;
|
|
42
|
+
/** Maximum counted different pixels as a percentage of all pixels. 0..100. */
|
|
43
|
+
maxDiffPercentage?: number;
|
|
44
|
+
/** Exclude pixels classified as anti-aliasing from diff totals. Default: false. */
|
|
45
|
+
ignoreAntialiasing?: boolean;
|
|
46
|
+
/** Reject images above this decoded size before allocation. Default: 20 million pixels. */
|
|
47
|
+
maxImagePixels?: number;
|
|
48
|
+
/** Reject compressed PNG input above this size. Default: 50 MiB. */
|
|
49
|
+
maxInputBytes?: number;
|
|
50
|
+
}
|
|
51
|
+
/** Options for the retrying `browser.expectScreenshot()` assertion. */
|
|
52
|
+
export interface ExpectScreenshotOptions extends ScreenshotCompareOptions {
|
|
53
|
+
/** Overall retry time in ms. Defaults to the browser's default timeout (5000 ms). */
|
|
54
|
+
timeout?: number;
|
|
55
|
+
/** Delay between failed attempts in ms. Integer >= 10. Default: 50. */
|
|
56
|
+
interval?: number;
|
|
57
|
+
/** Mutually exclusive capture scope passed to `browser.screenshot()`. */
|
|
58
|
+
screenshot?: VisualScreenshotOptions;
|
|
59
|
+
}
|
|
60
|
+
/** Result of one buffer-to-buffer comparison. */
|
|
61
|
+
export interface VisualComparisonResult {
|
|
62
|
+
matches: boolean;
|
|
63
|
+
diffPixels: number;
|
|
64
|
+
/** Raw, unrounded value in 0..100. */
|
|
65
|
+
diffPercentage: number;
|
|
66
|
+
ignoredAntialiasPixels: number;
|
|
67
|
+
expectedWidth: number;
|
|
68
|
+
expectedHeight: number;
|
|
69
|
+
actualWidth: number;
|
|
70
|
+
actualHeight: number;
|
|
71
|
+
dimensionMismatch: boolean;
|
|
72
|
+
}
|
|
73
|
+
/** Result of a successful `browser.expectScreenshot()` assertion. */
|
|
74
|
+
export interface ScreenshotMatchResult extends VisualComparisonResult {
|
|
75
|
+
attempts: number;
|
|
76
|
+
elapsedMs: number;
|
|
77
|
+
/**
|
|
78
|
+
* How the on-disk baseline was resolved:
|
|
79
|
+
* - `'matched'` — an existing baseline was compared and passed.
|
|
80
|
+
* - `'created'` — no baseline existed; the captured screenshot was written as
|
|
81
|
+
* the new baseline (always on; the diff fields are zero).
|
|
82
|
+
* - `'updated'` — an existing baseline differed and was overwritten with the
|
|
83
|
+
* captured screenshot (only under `CRAFTDRIVER_UPDATE_VISUAL_BASELINES=true`;
|
|
84
|
+
* the diff fields describe state relative to the new baseline, i.e. zero —
|
|
85
|
+
* the magnitude of the accepted change is reported to stderr).
|
|
86
|
+
*/
|
|
87
|
+
baseline: 'matched' | 'created' | 'updated';
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/visual/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AAEnC;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,WAAa,CAAC;AAEnD,+EAA+E;AAC/E,eAAO,MAAM,uBAAuB,QAAmB,CAAC;AAExD;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAC/B;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,EAAE,KAAK,CAAA;CAAE,GACpC;IAAE,QAAQ,EAAE,MAAM,GAAG,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,KAAK,CAAA;CAAE,GAC3C;IAAE,QAAQ,CAAC,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC;AAE3C,gEAAgE;AAChE,MAAM,WAAW,wBAAwB;IACvC,6FAA6F;IAC7F,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B,2FAA2F;IAC3F,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,uEAAuE;AACvE,MAAM,WAAW,uBAAwB,SAAQ,wBAAwB;IACvE,qFAAqF;IACrF,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,yEAAyE;IACzE,UAAU,CAAC,EAAE,uBAAuB,CAAC;CACtC;AAED,iDAAiD;AACjD,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,cAAc,EAAE,MAAM,CAAC;IACvB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED,qEAAqE;AACrE,MAAM,WAAW,qBAAsB,SAAQ,sBAAsB;IACnE,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAElB;;;;;;;;;OASG;IACH,QAAQ,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;CAC7C"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public and internal types for visual screenshot assertions.
|
|
3
|
+
*
|
|
4
|
+
* The `(actual, expected)` argument order is used consistently across every
|
|
5
|
+
* public and internal helper (`compareScreenshots`, `compareDecoded`,
|
|
6
|
+
* `createDiff`). Both buffers share a type, so reversing the order would be a
|
|
7
|
+
* silent bug — never deviate from it.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Default upper bound on decoded image size, in pixels, enforced before RGBA
|
|
11
|
+
* allocation. RGBA is ≥4 bytes/pixel, so expected + actual + final raw diff is
|
|
12
|
+
* ~240 MB of raw arrays at this limit before compressed buffers and pngjs
|
|
13
|
+
* working memory — and parallel assertions multiply that. Raise it explicitly
|
|
14
|
+
* for unusually long full-page screenshots; lower it when many workers run
|
|
15
|
+
* concurrently. A 100M default would permit ≥1.2 GB and is not a real guard.
|
|
16
|
+
*/
|
|
17
|
+
export const DEFAULT_MAX_IMAGE_PIXELS = 20_000_000;
|
|
18
|
+
/** Default upper bound on a compressed PNG input buffer, in bytes (50 MiB). */
|
|
19
|
+
export const DEFAULT_MAX_INPUT_BYTES = 50 * 1024 * 1024;
|
|
20
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/lib/visual/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,UAAU,CAAC;AAEnD,+EAA+E;AAC/E,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed failure for a visual assertion.
|
|
3
|
+
*
|
|
4
|
+
* JavaScript cannot both throw and return a normal value, so the final actual
|
|
5
|
+
* screenshot and generated diff PNG travel on the error as `Buffer`
|
|
6
|
+
* properties, while a JSON-serializable summary lives in `detail` for agents.
|
|
7
|
+
*/
|
|
8
|
+
import { CraftdriverError } from '../errors.js';
|
|
9
|
+
import type { VisualComparisonResult } from './types.js';
|
|
10
|
+
export interface VisualMismatchInfo {
|
|
11
|
+
expectedPath: string;
|
|
12
|
+
timeout: number;
|
|
13
|
+
attempts: number;
|
|
14
|
+
elapsedMs: number;
|
|
15
|
+
comparison: VisualComparisonResult;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Thrown by `browser.expectScreenshot()` when no screenshot matched the
|
|
19
|
+
* baseline before the timeout. Carries the final actual and diff PNG buffers
|
|
20
|
+
* plus the comparison summary; `code` is `VISUAL_MISMATCH`.
|
|
21
|
+
*/
|
|
22
|
+
export declare class VisualMismatchError extends CraftdriverError {
|
|
23
|
+
/** Final browser screenshot (compressed PNG). */
|
|
24
|
+
readonly actual: Buffer;
|
|
25
|
+
/** Final generated diff (compressed PNG). */
|
|
26
|
+
readonly diff: Buffer;
|
|
27
|
+
readonly comparison: VisualComparisonResult;
|
|
28
|
+
readonly attempts: number;
|
|
29
|
+
readonly elapsedMs: number;
|
|
30
|
+
constructor(actual: Buffer, diff: Buffer, info: VisualMismatchInfo);
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=visualError.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visualError.d.ts","sourceRoot":"","sources":["../../../src/lib/visual/visualError.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,gBAAgB,EAAa,MAAM,cAAc,CAAC;AAC3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,sBAAsB,CAAC;CACpC;AAgBD;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,gBAAgB;IACvD,iDAAiD;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,sBAAsB,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB;CAyBnE"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed failure for a visual assertion.
|
|
3
|
+
*
|
|
4
|
+
* JavaScript cannot both throw and return a normal value, so the final actual
|
|
5
|
+
* screenshot and generated diff PNG travel on the error as `Buffer`
|
|
6
|
+
* properties, while a JSON-serializable summary lives in `detail` for agents.
|
|
7
|
+
*/
|
|
8
|
+
import { CraftdriverError, ErrorCode } from '../errors.js';
|
|
9
|
+
function buildMessage(info) {
|
|
10
|
+
const { comparison, timeout, attempts } = info;
|
|
11
|
+
if (comparison.dimensionMismatch) {
|
|
12
|
+
return (`Screenshot did not match after ${timeout} ms: dimensions differ ` +
|
|
13
|
+
`(expected ${comparison.expectedWidth}x${comparison.expectedHeight}, ` +
|
|
14
|
+
`actual ${comparison.actualWidth}x${comparison.actualHeight}) across ${attempts} attempts`);
|
|
15
|
+
}
|
|
16
|
+
const pixels = comparison.diffPixels.toLocaleString('en-US');
|
|
17
|
+
const pct = comparison.diffPercentage.toFixed(4);
|
|
18
|
+
return `Screenshot did not match after ${timeout} ms: ${pixels} pixels differed (${pct}%) across ${attempts} attempts`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Thrown by `browser.expectScreenshot()` when no screenshot matched the
|
|
22
|
+
* baseline before the timeout. Carries the final actual and diff PNG buffers
|
|
23
|
+
* plus the comparison summary; `code` is `VISUAL_MISMATCH`.
|
|
24
|
+
*/
|
|
25
|
+
export class VisualMismatchError extends CraftdriverError {
|
|
26
|
+
/** Final browser screenshot (compressed PNG). */
|
|
27
|
+
actual;
|
|
28
|
+
/** Final generated diff (compressed PNG). */
|
|
29
|
+
diff;
|
|
30
|
+
comparison;
|
|
31
|
+
attempts;
|
|
32
|
+
elapsedMs;
|
|
33
|
+
constructor(actual, diff, info) {
|
|
34
|
+
super(ErrorCode.VISUAL_MISMATCH, buildMessage(info), {
|
|
35
|
+
detail: {
|
|
36
|
+
expectedPath: info.expectedPath,
|
|
37
|
+
timeout: info.timeout,
|
|
38
|
+
attempts: info.attempts,
|
|
39
|
+
elapsedMs: info.elapsedMs,
|
|
40
|
+
diffPixels: info.comparison.diffPixels,
|
|
41
|
+
diffPercentage: info.comparison.diffPercentage,
|
|
42
|
+
ignoredAntialiasPixels: info.comparison.ignoredAntialiasPixels,
|
|
43
|
+
expectedWidth: info.comparison.expectedWidth,
|
|
44
|
+
expectedHeight: info.comparison.expectedHeight,
|
|
45
|
+
actualWidth: info.comparison.actualWidth,
|
|
46
|
+
actualHeight: info.comparison.actualHeight,
|
|
47
|
+
dimensionMismatch: info.comparison.dimensionMismatch,
|
|
48
|
+
},
|
|
49
|
+
hint: 'Inspect error.actual and error.diff (PNG buffers) to see the regression. If the change is intentional, re-run with CRAFTDRIVER_UPDATE_VISUAL_BASELINES=true to overwrite the baseline.',
|
|
50
|
+
});
|
|
51
|
+
this.name = 'VisualMismatchError';
|
|
52
|
+
this.actual = actual;
|
|
53
|
+
this.diff = diff;
|
|
54
|
+
this.comparison = info.comparison;
|
|
55
|
+
this.attempts = info.attempts;
|
|
56
|
+
this.elapsedMs = info.elapsedMs;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=visualError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visualError.js","sourceRoot":"","sources":["../../../src/lib/visual/visualError.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAW3D,SAAS,YAAY,CAAC,IAAwB;IAC5C,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IAC/C,IAAI,UAAU,CAAC,iBAAiB,EAAE,CAAC;QACjC,OAAO,CACL,kCAAkC,OAAO,yBAAyB;YAClE,aAAa,UAAU,CAAC,aAAa,IAAI,UAAU,CAAC,cAAc,IAAI;YACtE,UAAU,UAAU,CAAC,WAAW,IAAI,UAAU,CAAC,YAAY,YAAY,QAAQ,WAAW,CAC3F,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;IAC7D,MAAM,GAAG,GAAG,UAAU,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACjD,OAAO,kCAAkC,OAAO,QAAQ,MAAM,qBAAqB,GAAG,aAAa,QAAQ,WAAW,CAAC;AACzH,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,mBAAoB,SAAQ,gBAAgB;IACvD,iDAAiD;IACxC,MAAM,CAAS;IACxB,6CAA6C;IACpC,IAAI,CAAS;IACb,UAAU,CAAyB;IACnC,QAAQ,CAAS;IACjB,SAAS,CAAS;IAE3B,YAAY,MAAc,EAAE,IAAY,EAAE,IAAwB;QAChE,KAAK,CAAC,SAAS,CAAC,eAAe,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE;YACnD,MAAM,EAAE;gBACN,YAAY,EAAE,IAAI,CAAC,YAAY;gBAC/B,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,UAAU;gBACtC,cAAc,EAAE,IAAI,CAAC,UAAU,CAAC,cAAc;gBAC9C,sBAAsB,EAAE,IAAI,CAAC,UAAU,CAAC,sBAAsB;gBAC9D,aAAa,EAAE,IAAI,CAAC,UAAU,CAAC,aAAa;gBAC5C,cAAc,EAAE,IAAI,CAAC,UAAU,CAAC,cAAc;gBAC9C,WAAW,EAAE,IAAI,CAAC,UAAU,CAAC,WAAW;gBACxC,YAAY,EAAE,IAAI,CAAC,UAAU,CAAC,YAAY;gBAC1C,iBAAiB,EAAE,IAAI,CAAC,UAAU,CAAC,iBAAiB;aACrD;YACD,IAAI,EAAE,wLAAwL;SAC/L,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QAClC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IAClC,CAAC;CACF"}
|
package/dist/lib/wait.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wait.d.ts","sourceRoot":"","sources":["../../src/lib/wait.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI7C,MAAM,MAAM,SAAS,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;AAEhE,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAQD,qBAAa,aAAa;IAMtB,OAAO,CAAC,MAAM;IALhB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAC,CAAS;gBAGlB,MAAM,EAAE,MAAM,EACtB,gBAAgB,CAAC,EAAE,MAAM,GAAG,WAAW,EACvC,UAAU,CAAC,EAAE,MAAM;IAYf,KAAK,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"wait.d.ts","sourceRoot":"","sources":["../../src/lib/wait.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI7C,MAAM,MAAM,SAAS,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;AAEhE,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAQD,qBAAa,aAAa;IAMtB,OAAO,CAAC,MAAM;IALhB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAC,CAAS;gBAGlB,MAAM,EAAE,MAAM,EACtB,gBAAgB,CAAC,EAAE,MAAM,GAAG,WAAW,EACvC,UAAU,CAAC,EAAE,MAAM;IAYf,KAAK,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;CAmCtE;AAOD,eAAO,MAAM,KAAK;8BACU,EAAE,MAAY,QAAQ,MAAM;6BAI7B,EAAE,MAAY,QAAQ,MAAM;gCASzB,EAAE,MAAY,QAAQ,MAAM;+BAS7B,EAAE,GAAG,UAAU,MAAY,QAAQ,MAAM;kCAUtC,EAAE,GAAG,UAAU,MAAY,QAAQ,MAAM;CAaxE,CAAC"}
|
package/dist/lib/wait.js
CHANGED
|
@@ -27,7 +27,11 @@ export class WebDriverWait {
|
|
|
27
27
|
async until(condition, message) {
|
|
28
28
|
const deadline = Date.now() + this.timeoutMs;
|
|
29
29
|
let lastErr;
|
|
30
|
-
|
|
30
|
+
// Check-first: the condition is always evaluated at least once, even with a
|
|
31
|
+
// zero (or already-elapsed) timeout. A `while (Date.now() < deadline)` loop
|
|
32
|
+
// would run zero iterations at timeout 0 and report an already-satisfiable
|
|
33
|
+
// condition as a spurious timeout.
|
|
34
|
+
for (;;) {
|
|
31
35
|
try {
|
|
32
36
|
const result = await condition(this.driver);
|
|
33
37
|
// Resolve on truthy or non-null/undefined result
|
|
@@ -38,6 +42,8 @@ export class WebDriverWait {
|
|
|
38
42
|
catch (e) {
|
|
39
43
|
lastErr = e;
|
|
40
44
|
}
|
|
45
|
+
if (Date.now() >= deadline)
|
|
46
|
+
break;
|
|
41
47
|
await new Promise((r) => setTimeout(r, this.intervalMs));
|
|
42
48
|
}
|
|
43
49
|
const errMsg = message ?? this.timeoutMsg ?? `Wait timed out after ${this.timeoutMs}ms`;
|
package/dist/lib/wait.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wait.js","sourceRoot":"","sources":["../../src/lib/wait.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAUnF,SAAS,oBAAoB,CAAC,GAAY;IACxC,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,SAAS,CAAC,YAAY,CAAC;QAAE,OAAO,KAAK,CAAC;IACpE,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC;IAClD,OAAO,cAAc,KAAK,iBAAiB,IAAI,cAAc,KAAK,yBAAyB,CAAC;AAC9F,CAAC;AAED,MAAM,OAAO,aAAa;IAMd;IALF,SAAS,CAAS;IAClB,UAAU,CAAS;IACnB,UAAU,CAAU;IAE5B,YACU,MAAc,EACtB,gBAAuC,EACvC,UAAmB;QAFX,WAAM,GAAN,MAAM,CAAQ;QAItB,IAAI,OAAO,gBAAgB,KAAK,QAAQ,EAAE,CAAC;YACzC,IAAI,CAAC,SAAS,GAAG,gBAAgB,EAAE,OAAO,IAAI,0BAA0B,CAAC;YACzE,IAAI,CAAC,UAAU,GAAG,gBAAgB,EAAE,QAAQ,IAAI,wBAAwB,CAAC;YACzE,IAAI,CAAC,UAAU,GAAG,gBAAgB,EAAE,UAAU,CAAC;QACjD,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,SAAS,GAAG,gBAAgB,IAAI,0BAA0B,CAAC;YAChE,IAAI,CAAC,UAAU,GAAG,UAAU,IAAI,wBAAwB,CAAC;QAC3D,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK,CAAI,SAAuB,EAAE,OAAgB;QACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC;QAC7C,IAAI,OAAgB,CAAC;QACrB,
|
|
1
|
+
{"version":3,"file":"wait.js","sourceRoot":"","sources":["../../src/lib/wait.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAUnF,SAAS,oBAAoB,CAAC,GAAY;IACxC,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,SAAS,CAAC,YAAY,CAAC;QAAE,OAAO,KAAK,CAAC;IACpE,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC;IAClD,OAAO,cAAc,KAAK,iBAAiB,IAAI,cAAc,KAAK,yBAAyB,CAAC;AAC9F,CAAC;AAED,MAAM,OAAO,aAAa;IAMd;IALF,SAAS,CAAS;IAClB,UAAU,CAAS;IACnB,UAAU,CAAU;IAE5B,YACU,MAAc,EACtB,gBAAuC,EACvC,UAAmB;QAFX,WAAM,GAAN,MAAM,CAAQ;QAItB,IAAI,OAAO,gBAAgB,KAAK,QAAQ,EAAE,CAAC;YACzC,IAAI,CAAC,SAAS,GAAG,gBAAgB,EAAE,OAAO,IAAI,0BAA0B,CAAC;YACzE,IAAI,CAAC,UAAU,GAAG,gBAAgB,EAAE,QAAQ,IAAI,wBAAwB,CAAC;YACzE,IAAI,CAAC,UAAU,GAAG,gBAAgB,EAAE,UAAU,CAAC;QACjD,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,SAAS,GAAG,gBAAgB,IAAI,0BAA0B,CAAC;YAChE,IAAI,CAAC,UAAU,GAAG,UAAU,IAAI,wBAAwB,CAAC;QAC3D,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK,CAAI,SAAuB,EAAE,OAAgB;QACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC;QAC7C,IAAI,OAAgB,CAAC;QACrB,4EAA4E;QAC5E,4EAA4E;QAC5E,2EAA2E;QAC3E,mCAAmC;QACnC,SAAS,CAAC;YACR,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBAC5C,iDAAiD;gBACjD,IAAI,MAAM,IAAK,MAAc,KAAK,CAAC,IAAK,MAAc,KAAK,KAAK,EAAE,CAAC;oBACjE,OAAO,MAAM,CAAC;gBAChB,CAAC;YACH,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,OAAO,GAAG,CAAC,CAAC;YACd,CAAC;YACD,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ;gBAAE,MAAM;YAClC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAC3D,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,IAAI,IAAI,CAAC,UAAU,IAAI,wBAAwB,IAAI,CAAC,SAAS,IAAI,CAAC;QACxF,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,MAAM,KAAK,MAAM,CAAE,OAAe,EAAE,OAAO,IAAI,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;QAC7F,iFAAiF;QACjF,IAAI,gBAAgB,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,EAAE,CAAC;YACnE,MAAM,IAAI,gBAAgB,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE;gBAC7C,MAAM,EAAE,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE;gBAC9D,KAAK,EAAE,OAAO;gBACd,IAAI,EAAE,OAAO,CAAC,IAAI;aACnB,CAAC,CAAC;QACL,CAAC;QACD,MAAM,IAAI,gBAAgB,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE;YAClD,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE;YACnC,KAAK,EAAE,OAAO;SACf,CAAC,CAAC;IACL,CAAC;CACF;AAED,KAAK,UAAU,cAAc,CAAC,MAAc,EAAE,MAAuB;IACnE,IAAI,MAAM,YAAY,UAAU;QAAE,OAAO,MAAM,CAAC;IAChD,OAAO,MAAM,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,cAAc,EAAE,CAAC,OAAW,EAAE,EAAE,CAAC,KAAK,EAAE,MAAc,EAAE,EAAE;QACxD,OAAO,MAAM,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;IAED,aAAa,EAAE,CAAC,OAAW,EAAE,EAAE,CAAC,KAAK,EAAE,MAAc,EAAE,EAAE;QACvD,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;YAClC,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAY,CAAC,CAAC,eAAe;QACtC,CAAC;IACH,CAAC;IAED,gBAAgB,EAAE,CAAC,OAAW,EAAE,EAAE,CAAC,KAAK,EAAE,MAAc,EAAE,EAAE;QAC1D,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;YAClC,OAAO,KAAY,CAAC,CAAC,6BAA6B;QACpD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,gBAAgB,EAAE,CAAC,MAAuB,EAAE,EAAE,CAAC,KAAK,EAAE,MAAc,EAAE,EAAE;QACtE,MAAM,EAAE,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAChD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,WAAW,EAAE,CAAC;YACvC,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAE,SAAiB,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAgB,CAAC,CAAC,qCAAqC;QAChE,CAAC;IACH,CAAC;IAED,mBAAmB,EAAE,CAAC,MAAuB,EAAE,EAAE,CAAC,KAAK,EAAE,MAAc,EAAE,EAAE;QACzE,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAChD,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,WAAW,EAAE,CAAC;gBACvC,OAAO,OAAO,CAAC,CAAC,CAAE,KAAa,CAAC,CAAC,CAAC,IAAI,CAAC;YACzC,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAC,CAAC,wCAAwC;YACvD,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC,CAAC,YAAY;QAC3B,CAAC;IACH,CAAC;CACF,CAAC"}
|
package/docs/api-reference.md
CHANGED
|
@@ -124,6 +124,20 @@ Launch packaged Electron applications, drive their renderer, and opt in to main-
|
|
|
124
124
|
| `ElectronMainLogHandler` | type | — | [electron](electron.md) |
|
|
125
125
|
| `ElectronMainLogLevel` | type | Normalized level for a main-process log entry. | [electron](electron.md) |
|
|
126
126
|
|
|
127
|
+
## Visual Testing
|
|
128
|
+
|
|
129
|
+
Assert screenshots against baselines with pixel/percentage tolerances and optional anti-alias handling.
|
|
130
|
+
|
|
131
|
+
| Symbol | Kind | Summary | Docs |
|
|
132
|
+
|---|---|---|---|
|
|
133
|
+
| `compareScreenshots` | function | Compare two compressed PNG buffers without disk access, browser capture, retries, or throwing on a normal mismatch. Applies the same byte/pixel input limits as the retrying assertion. | [visual-testing](visual-testing.md) |
|
|
134
|
+
| `VisualMismatchError` | class | Thrown by `browser.expectScreenshot()` when no screenshot matched the baseline before the timeout. Carries the final actual and diff PNG buffers plus the comparison summary; `code` is `VISUAL_MISMATCH`. | [visual-testing](visual-testing.md) |
|
|
135
|
+
| `ScreenshotCompareOptions` | type | Policies applied to a single buffer-to-buffer comparison. | [visual-testing](visual-testing.md) |
|
|
136
|
+
| `ExpectScreenshotOptions` | type | Options for the retrying `browser.expectScreenshot()` assertion. | [visual-testing](visual-testing.md) |
|
|
137
|
+
| `VisualScreenshotOptions` | type | Mutually exclusive capture scope forwarded to `browser.screenshot()`. `fullPage` and `selector` cannot be combined (enforced by the union here and re-checked at runtime for JavaScript callers). | [visual-testing](visual-testing.md) |
|
|
138
|
+
| `VisualComparisonResult` | type | Result of one buffer-to-buffer comparison. | [visual-testing](visual-testing.md) |
|
|
139
|
+
| `ScreenshotMatchResult` | type | Result of a successful `browser.expectScreenshot()` assertion. | [visual-testing](visual-testing.md) |
|
|
140
|
+
|
|
127
141
|
## Accessibility
|
|
128
142
|
|
|
129
143
|
Run axe-core accessibility checks and inspect violation details.
|
|
@@ -170,4 +184,4 @@ Exports that are public but not yet assigned to a feature group.
|
|
|
170
184
|
| `RemoteAuth` | type | — | — |
|
|
171
185
|
| `RemoteWebDriverOptions` | type | Options for connecting to any W3C-compatible remote WebDriver endpoint — a self-hosted Selenium Grid, BrowserStack, or another cloud provider. Provider-specific detail (BrowserStack's `bstack:options`, etc.) is forwarded through `capabilities` without schema conversion. | — |
|
|
172
186
|
|
|
173
|
-
Total exports: **
|
|
187
|
+
Total exports: **93**.
|
package/docs/error-codes.md
CHANGED
|
@@ -54,6 +54,7 @@ try {
|
|
|
54
54
|
| `ELECTRON_MAIN_UNAVAILABLE` | `browser.electron.executeMain(...)` couldn't reach the app's main process. | Launch with `electron: { mainProcess: true }`; ensure the app build keeps the `EnableNodeCliInspectArguments` fuse enabled (default). `hint` names the fix. |
|
|
55
55
|
| `ELECTRON_MAIN_THREW` | The `executeMain(...)` callback threw inside the Electron main process. | The main-process exception text is in the message; treat like a failed assertion about main-process state. |
|
|
56
56
|
| `ELECTRON_DEEPLINK_FAILED` | `browser.electron.triggerDeeplink(url)` could not open the custom-protocol URL. | An unsupported platform, a missing `appBinaryPath` on Windows, or the OS `open`/`gio`/`rundll32` launcher failed (`error.detail.command`, `error.cause`). An invalid or `http(s)`/`file` URL throws `INVALID_ARGUMENT` instead. |
|
|
57
|
+
| `VISUAL_MISMATCH` | `browser.expectScreenshot()` never matched the baseline within its timeout. | Thrown as `VisualMismatchError` (a `CraftdriverError` subclass). Read `error.actual` and `error.diff` (PNG `Buffer`s) to see the regression; `error.detail` carries JSON-safe path/dimensions/`diffPixels`/`diffPercentage`/`attempts`. If the change is intentional, re-run with `CRAFTDRIVER_UPDATE_VISUAL_BASELINES=true` to overwrite the baseline. See [visual-testing.md](./visual-testing.md). |
|
|
57
58
|
|
|
58
59
|
## Stability
|
|
59
60
|
|
package/docs/screenshots.md
CHANGED
|
@@ -17,6 +17,10 @@ craftdriver exposes a single options-bag `screenshot()` method on both
|
|
|
17
17
|
|
|
18
18
|
`fullPage` and `selector` are mutually exclusive.
|
|
19
19
|
|
|
20
|
+
> To **compare** a screenshot against a committed baseline (with retries and
|
|
21
|
+
> pixel tolerances) rather than just capture one, see
|
|
22
|
+
> [Visual testing](./visual-testing.md) and `browser.expectScreenshot()`.
|
|
23
|
+
|
|
20
24
|
```typescript
|
|
21
25
|
// Viewport only (default), buffer
|
|
22
26
|
const buffer = await browser.screenshot();
|
|
@@ -78,15 +82,20 @@ afterEach(async (context) => {
|
|
|
78
82
|
|
|
79
83
|
### Visual comparison baseline
|
|
80
84
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
For visual regression testing you don't manage baseline and candidate PNGs by
|
|
86
|
+
hand or reach for a separate image-diff library. `browser.expectScreenshot()`
|
|
87
|
+
captures, compares against a committed baseline, retries until it settles, and
|
|
88
|
+
throws a typed diff on a real change — creating the baseline on the first run:
|
|
84
89
|
|
|
85
|
-
|
|
86
|
-
await browser.
|
|
87
|
-
|
|
90
|
+
```typescript
|
|
91
|
+
await browser.expectScreenshot('baselines/homepage.png', {
|
|
92
|
+
screenshot: { fullPage: true },
|
|
93
|
+
});
|
|
88
94
|
```
|
|
89
95
|
|
|
96
|
+
See [Visual testing](./visual-testing.md) for baselines, thresholds, ignoring
|
|
97
|
+
anti-aliasing, updating baselines, and making CI deterministic.
|
|
98
|
+
|
|
90
99
|
### Component screenshots
|
|
91
100
|
|
|
92
101
|
```typescript
|