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.
Files changed (71) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +4 -2
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +2 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/lib/browser.d.ts +32 -0
  8. package/dist/lib/browser.d.ts.map +1 -1
  9. package/dist/lib/browser.js +56 -0
  10. package/dist/lib/browser.js.map +1 -1
  11. package/dist/lib/errors.d.ts +7 -0
  12. package/dist/lib/errors.d.ts.map +1 -1
  13. package/dist/lib/errors.js +7 -0
  14. package/dist/lib/errors.js.map +1 -1
  15. package/dist/lib/timing.d.ts +8 -0
  16. package/dist/lib/timing.d.ts.map +1 -1
  17. package/dist/lib/timing.js +8 -0
  18. package/dist/lib/timing.js.map +1 -1
  19. package/dist/lib/visual/antialias.d.ts +41 -0
  20. package/dist/lib/visual/antialias.d.ts.map +1 -0
  21. package/dist/lib/visual/antialias.js +126 -0
  22. package/dist/lib/visual/antialias.js.map +1 -0
  23. package/dist/lib/visual/codec.d.ts +24 -0
  24. package/dist/lib/visual/codec.d.ts.map +1 -0
  25. package/dist/lib/visual/codec.js +11 -0
  26. package/dist/lib/visual/codec.js.map +1 -0
  27. package/dist/lib/visual/compare.d.ts +38 -0
  28. package/dist/lib/visual/compare.d.ts.map +1 -0
  29. package/dist/lib/visual/compare.js +209 -0
  30. package/dist/lib/visual/compare.js.map +1 -0
  31. package/dist/lib/visual/diff.d.ts +18 -0
  32. package/dist/lib/visual/diff.d.ts.map +1 -0
  33. package/dist/lib/visual/diff.js +23 -0
  34. package/dist/lib/visual/diff.js.map +1 -0
  35. package/dist/lib/visual/expectScreenshot.d.ts +65 -0
  36. package/dist/lib/visual/expectScreenshot.d.ts.map +1 -0
  37. package/dist/lib/visual/expectScreenshot.js +287 -0
  38. package/dist/lib/visual/expectScreenshot.js.map +1 -0
  39. package/dist/lib/visual/index.d.ts +10 -0
  40. package/dist/lib/visual/index.d.ts.map +1 -0
  41. package/dist/lib/visual/index.js +9 -0
  42. package/dist/lib/visual/index.js.map +1 -0
  43. package/dist/lib/visual/pixelMath.d.ts +32 -0
  44. package/dist/lib/visual/pixelMath.d.ts.map +1 -0
  45. package/dist/lib/visual/pixelMath.js +70 -0
  46. package/dist/lib/visual/pixelMath.js.map +1 -0
  47. package/dist/lib/visual/pngHeader.d.ts +27 -0
  48. package/dist/lib/visual/pngHeader.d.ts.map +1 -0
  49. package/dist/lib/visual/pngHeader.js +63 -0
  50. package/dist/lib/visual/pngHeader.js.map +1 -0
  51. package/dist/lib/visual/pngjsCodec.d.ts +22 -0
  52. package/dist/lib/visual/pngjsCodec.d.ts.map +1 -0
  53. package/dist/lib/visual/pngjsCodec.js +70 -0
  54. package/dist/lib/visual/pngjsCodec.js.map +1 -0
  55. package/dist/lib/visual/types.d.ts +89 -0
  56. package/dist/lib/visual/types.d.ts.map +1 -0
  57. package/dist/lib/visual/types.js +20 -0
  58. package/dist/lib/visual/types.js.map +1 -0
  59. package/dist/lib/visual/visualError.d.ts +32 -0
  60. package/dist/lib/visual/visualError.d.ts.map +1 -0
  61. package/dist/lib/visual/visualError.js +59 -0
  62. package/dist/lib/visual/visualError.js.map +1 -0
  63. package/dist/lib/wait.d.ts.map +1 -1
  64. package/dist/lib/wait.js +7 -1
  65. package/dist/lib/wait.js.map +1 -1
  66. package/docs/api-reference.md +15 -1
  67. package/docs/error-codes.md +1 -0
  68. package/docs/screenshots.md +15 -6
  69. package/docs/visual-testing.md +254 -0
  70. package/docs/why-craftdriver.md +3 -2
  71. 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"}
@@ -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;CA8BtE;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"}
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
- while (Date.now() < deadline) {
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`;
@@ -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,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;YAC7B,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,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"}
1
+ {"version":3,"file":"wait.js","sourceRoot":"","sources":["../../src/lib/wait.ts"],"names":[],"mappings":"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"}
@@ -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: **86**.
187
+ Total exports: **93**.
@@ -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
 
@@ -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
- ```typescript
82
- await browser.navigateTo('https://example.com');
83
- await browser.screenshot({ path: 'baseline/homepage.png' });
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
- // Later, capture the candidate and diff with an image library:
86
- await browser.navigateTo('https://example.com');
87
- await browser.screenshot({ path: 'current/homepage.png' });
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