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,65 @@
1
+ /**
2
+ * The retrying visual assertion: read the baseline once, then poll the browser
3
+ * until a screenshot matches or the monotonic deadline expires.
4
+ *
5
+ * The hot path is deliberately lazy. An exact byte match costs one screenshot
6
+ * plus a `Buffer.equals` and never decodes. A static mismatch decodes the
7
+ * expected and actual images once each, then subsequent polls only re-capture
8
+ * and compare compressed bytes. Only changing content pays a decode per retry.
9
+ * The diff image is allocated and encoded exactly once, after the final failure.
10
+ *
11
+ * Baselines are managed for the caller, WebdriverIO-style:
12
+ * - Missing baseline (ENOENT): capture until two consecutive frames settle,
13
+ * write it, pass. This is the default — no flag required.
14
+ * - Present but differs, with `update`: capture as usual and, only if the page
15
+ * never settles into a match, overwrite the baseline with the final actual
16
+ * and pass. A page that settles back into a match passes without rewriting.
17
+ * A present-but-corrupt baseline, and any non-ENOENT read error, stay hard
18
+ * configuration errors even under `update` — they are never overwritten.
19
+ *
20
+ * Every baseline write is atomic — the PNG is written to a temp file in the same
21
+ * directory and renamed into place — so an interrupted run cannot corrupt a
22
+ * committed baseline (this matters most under `update`, which replaces one).
23
+ *
24
+ * Timing (`now`) and delays (`sleep`) are injectable so retry behaviour can be
25
+ * driven deterministically in tests without wall-clock waits.
26
+ */
27
+ import type { PngCodec } from './codec.js';
28
+ import type { ExpectScreenshotOptions, ScreenshotMatchResult } from './types.js';
29
+ /** Capture one screenshot, bounding any internal visibility wait by `remainingMs`. */
30
+ export type CaptureFn = (remainingMs: number) => Promise<Buffer>;
31
+ export interface RunExpectScreenshotDeps {
32
+ expectedPath: string;
33
+ options: ExpectScreenshotOptions;
34
+ /** The browser's live default timeout, used when `options.timeout` is unset. */
35
+ defaultTimeout: number;
36
+ capture: CaptureFn;
37
+ /**
38
+ * When true, a baseline that is present but never matches is overwritten with
39
+ * the final actual instead of throwing. Missing-baseline creation is always on
40
+ * and does not depend on this flag. Driven by
41
+ * {@link shouldUpdateVisualBaselines} from the environment. Default false.
42
+ */
43
+ update?: boolean;
44
+ readFile?: (path: string) => Promise<Buffer>;
45
+ writeFile?: (path: string, data: Buffer) => Promise<void>;
46
+ rename?: (from: string, to: string) => Promise<void>;
47
+ unlink?: (path: string) => Promise<void>;
48
+ mkdir?: (dir: string, opts: {
49
+ recursive: true;
50
+ }) => Promise<unknown>;
51
+ /** Human-readable one-liner reported when a baseline is created or updated. */
52
+ report?: (message: string) => void;
53
+ now?: () => number;
54
+ sleep?: (ms: number) => Promise<void>;
55
+ codec?: PngCodec;
56
+ }
57
+ /**
58
+ * Strictly interpret the `CRAFTDRIVER_UPDATE_VISUAL_BASELINES` environment
59
+ * variable. Unset / empty / `false` → off; `true` → on (trimmed,
60
+ * case-insensitive). Anything else throws `INVALID_ARGUMENT` rather than being
61
+ * silently coerced — this flag mutates files on disk, so a typo must fail loud.
62
+ */
63
+ export declare function shouldUpdateVisualBaselines(raw: string | undefined): boolean;
64
+ export declare function runExpectScreenshot(deps: RunExpectScreenshotDeps): Promise<ScreenshotMatchResult>;
65
+ //# sourceMappingURL=expectScreenshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expectScreenshot.d.ts","sourceRoot":"","sources":["../../../src/lib/visual/expectScreenshot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAcH,OAAO,KAAK,EAAgB,QAAQ,EAAE,MAAM,YAAY,CAAC;AAMzD,OAAO,KAAK,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEjF,sFAAsF;AACtF,MAAM,MAAM,SAAS,GAAG,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAEjE,MAAM,WAAW,uBAAuB;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,uBAAuB,CAAC;IACjC,gFAAgF;IAChF,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,SAAS,CAAC;IACnB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,IAAI,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACrE,+EAA+E;IAC/E,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,KAAK,CAAC,EAAE,QAAQ,CAAC;CAClB;AAqBD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAS5E;AAsDD,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA8LvG"}
@@ -0,0 +1,287 @@
1
+ /**
2
+ * The retrying visual assertion: read the baseline once, then poll the browser
3
+ * until a screenshot matches or the monotonic deadline expires.
4
+ *
5
+ * The hot path is deliberately lazy. An exact byte match costs one screenshot
6
+ * plus a `Buffer.equals` and never decodes. A static mismatch decodes the
7
+ * expected and actual images once each, then subsequent polls only re-capture
8
+ * and compare compressed bytes. Only changing content pays a decode per retry.
9
+ * The diff image is allocated and encoded exactly once, after the final failure.
10
+ *
11
+ * Baselines are managed for the caller, WebdriverIO-style:
12
+ * - Missing baseline (ENOENT): capture until two consecutive frames settle,
13
+ * write it, pass. This is the default — no flag required.
14
+ * - Present but differs, with `update`: capture as usual and, only if the page
15
+ * never settles into a match, overwrite the baseline with the final actual
16
+ * and pass. A page that settles back into a match passes without rewriting.
17
+ * A present-but-corrupt baseline, and any non-ENOENT read error, stay hard
18
+ * configuration errors even under `update` — they are never overwritten.
19
+ *
20
+ * Every baseline write is atomic — the PNG is written to a temp file in the same
21
+ * directory and renamed into place — so an interrupted run cannot corrupt a
22
+ * committed baseline (this matters most under `update`, which replaces one).
23
+ *
24
+ * Timing (`now`) and delays (`sleep`) are injectable so retry behaviour can be
25
+ * driven deterministically in tests without wall-clock waits.
26
+ */
27
+ import { readFile as fsReadFile, writeFile as fsWriteFile, mkdir as fsMkdir, rename as fsRename, unlink as fsUnlink, } from 'node:fs/promises';
28
+ import { randomBytes } from 'node:crypto';
29
+ import { dirname } from 'node:path';
30
+ import { By } from '../by.js';
31
+ import { CraftdriverError, ErrorCode } from '../errors.js';
32
+ import { VISUAL_RETRY_INTERVAL_MS } from '../timing.js';
33
+ import { pngjsCodec } from './pngjsCodec.js';
34
+ import { validatePngHeaderAndSize } from './pngHeader.js';
35
+ import { compareDecoded, resolveCompareOptions } from './compare.js';
36
+ import { createDiff } from './diff.js';
37
+ import { VisualMismatchError } from './visualError.js';
38
+ const realSleep = (ms) => new Promise((r) => setTimeout(r, ms));
39
+ const defaultReport = (message) => {
40
+ process.stderr.write(`[craftdriver] ${message}\n`);
41
+ };
42
+ function invalid(message, detail) {
43
+ throw new CraftdriverError(ErrorCode.INVALID_ARGUMENT, message, detail ? { detail } : undefined);
44
+ }
45
+ function isFileNotFound(err) {
46
+ return (typeof err === 'object' &&
47
+ err !== null &&
48
+ 'code' in err &&
49
+ err.code === 'ENOENT');
50
+ }
51
+ /**
52
+ * Strictly interpret the `CRAFTDRIVER_UPDATE_VISUAL_BASELINES` environment
53
+ * variable. Unset / empty / `false` → off; `true` → on (trimmed,
54
+ * case-insensitive). Anything else throws `INVALID_ARGUMENT` rather than being
55
+ * silently coerced — this flag mutates files on disk, so a typo must fail loud.
56
+ */
57
+ export function shouldUpdateVisualBaselines(raw) {
58
+ if (raw === undefined)
59
+ return false;
60
+ const value = raw.trim().toLowerCase();
61
+ if (value === '' || value === 'false')
62
+ return false;
63
+ if (value === 'true')
64
+ return true;
65
+ invalid(`CRAFTDRIVER_UPDATE_VISUAL_BASELINES must be "true" or "false" (or unset), got ${JSON.stringify(raw)}.`, { value: raw });
66
+ }
67
+ /** Validate the retry-specific options (compare options are validated separately). */
68
+ function resolveRetryOptions(options, defaultTimeout) {
69
+ const timeout = options.timeout ?? defaultTimeout;
70
+ if (!Number.isFinite(timeout) || timeout < 0) {
71
+ invalid(`timeout must be a finite number >= 0, got ${timeout}.`, { timeout });
72
+ }
73
+ const interval = options.interval ?? VISUAL_RETRY_INTERVAL_MS;
74
+ if (!Number.isInteger(interval) || interval < 10) {
75
+ invalid(`interval must be an integer >= 10 ms, got ${interval}.`, { interval });
76
+ }
77
+ // Validate the capture scope's shape for JavaScript callers (TS already
78
+ // constrains it): correct field types, then the fullPage/selector exclusion.
79
+ const scope = options.screenshot;
80
+ if (scope !== undefined) {
81
+ if (typeof scope !== 'object' || scope === null) {
82
+ invalid('screenshot must be an object with { fullPage } or { selector }.');
83
+ }
84
+ const hasFullPage = 'fullPage' in scope && scope.fullPage !== undefined;
85
+ const hasSelector = 'selector' in scope && scope.selector !== undefined;
86
+ if (hasFullPage && typeof scope.fullPage !== 'boolean') {
87
+ invalid('screenshot.fullPage must be a boolean.');
88
+ }
89
+ if (hasSelector) {
90
+ const selector = scope.selector;
91
+ if (typeof selector !== 'string' && !(selector instanceof By)) {
92
+ invalid('screenshot.selector must be a string or a By locator.');
93
+ }
94
+ }
95
+ if (hasFullPage && scope.fullPage === true && hasSelector) {
96
+ invalid('screenshot.fullPage and screenshot.selector are mutually exclusive.');
97
+ }
98
+ }
99
+ return { timeout, interval };
100
+ }
101
+ /** A monotonic deadline built once, queried throughout a run. */
102
+ function makeClock(now, timeout) {
103
+ const start = now();
104
+ const deadline = start + timeout;
105
+ return {
106
+ elapsed: () => now() - start,
107
+ remaining: () => Math.max(0, deadline - now()),
108
+ expired: () => now() >= deadline,
109
+ };
110
+ }
111
+ export async function runExpectScreenshot(deps) {
112
+ const { expectedPath, options, defaultTimeout, capture } = deps;
113
+ const update = deps.update ?? false;
114
+ const readFile = deps.readFile ?? fsReadFile;
115
+ const writeFile = deps.writeFile ?? fsWriteFile;
116
+ const rename = deps.rename ?? fsRename;
117
+ const unlink = deps.unlink ?? fsUnlink;
118
+ const mkdir = deps.mkdir ?? fsMkdir;
119
+ const report = deps.report ?? defaultReport;
120
+ const now = deps.now ?? (() => performance.now());
121
+ const sleep = deps.sleep ?? realSleep;
122
+ const codec = deps.codec ?? pngjsCodec;
123
+ const compareOpts = resolveCompareOptions(options);
124
+ const { timeout, interval } = resolveRetryOptions(options, defaultTimeout);
125
+ const limits = { maxImagePixels: compareOpts.maxImagePixels, maxInputBytes: compareOpts.maxInputBytes };
126
+ /**
127
+ * Write a baseline atomically: the PNG is written in full to a uniquely-named
128
+ * temporary file in the destination's own directory, then renamed over the
129
+ * destination. A crash, disk error, or partial write therefore never leaves a
130
+ * truncated or half-updated committed baseline — the old file survives intact.
131
+ * On any failure the temp file is best-effort removed and the error rethrown.
132
+ */
133
+ const writeBaselineAtomically = async (destination, data) => {
134
+ const tmp = `${destination}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`;
135
+ try {
136
+ await writeFile(tmp, data);
137
+ await rename(tmp, destination);
138
+ }
139
+ catch (err) {
140
+ await unlink(tmp).catch(() => { });
141
+ throw err;
142
+ }
143
+ };
144
+ /**
145
+ * No baseline on disk yet: capture until two consecutive frames are
146
+ * byte-identical (or the timeout elapses) so we don't enshrine a mid-animation
147
+ * frame, then write that settled capture as the new baseline and pass.
148
+ */
149
+ const createMissingBaseline = async () => {
150
+ const clock = makeClock(now, timeout);
151
+ let settled;
152
+ let settledDims;
153
+ let attempts = 0;
154
+ let didSettle = false;
155
+ for (;;) {
156
+ const buffer = await capture(clock.remaining());
157
+ attempts++;
158
+ const dims = validatePngHeaderAndSize(buffer, limits, 'screenshot');
159
+ const isSettled = settled !== undefined && settled.equals(buffer);
160
+ settled = buffer;
161
+ settledDims = dims;
162
+ if (isSettled) {
163
+ didSettle = true;
164
+ break;
165
+ }
166
+ if (clock.expired())
167
+ break;
168
+ await sleep(Math.min(interval, clock.remaining()));
169
+ }
170
+ await mkdir(dirname(expectedPath), { recursive: true });
171
+ await writeBaselineAtomically(expectedPath, settled);
172
+ // Warn on every unsettled write — including `timeout: 0`, which deliberately
173
+ // takes a single capture — so an arbitrary frame is never written silently.
174
+ // We still create the baseline (a page with a live region would otherwise be
175
+ // impossible to baseline); the message just flags the frame for review.
176
+ report(didSettle
177
+ ? `Created visual baseline: ${expectedPath}`
178
+ : `Created visual baseline (page did not settle within ${timeout}ms — review it before committing): ${expectedPath}`);
179
+ return {
180
+ matches: true,
181
+ diffPixels: 0,
182
+ diffPercentage: 0,
183
+ ignoredAntialiasPixels: 0,
184
+ expectedWidth: settledDims.width,
185
+ expectedHeight: settledDims.height,
186
+ actualWidth: settledDims.width,
187
+ actualHeight: settledDims.height,
188
+ dimensionMismatch: false,
189
+ attempts,
190
+ elapsedMs: clock.elapsed(),
191
+ baseline: 'created',
192
+ };
193
+ };
194
+ // Read + validate the baseline once, before the deadline starts, so `timeout`
195
+ // measures browser stabilization time rather than disk latency. A missing file
196
+ // is the create signal; any other read error stays a caller-facing failure.
197
+ let expectedBuffer;
198
+ try {
199
+ expectedBuffer = await readFile(expectedPath);
200
+ }
201
+ catch (err) {
202
+ if (!isFileNotFound(err))
203
+ throw err;
204
+ return createMissingBaseline();
205
+ }
206
+ const expectedDims = validatePngHeaderAndSize(expectedBuffer, limits, 'baseline');
207
+ const clock = makeClock(now, timeout);
208
+ let expectedImage;
209
+ let previousActualBuffer;
210
+ let previousActualImage;
211
+ let previousComparison;
212
+ let attempts = 0;
213
+ for (;;) {
214
+ const actualBuffer = await capture(clock.remaining());
215
+ attempts++;
216
+ // Fast path 1: identical PNG bytes necessarily means identical pixels.
217
+ if (expectedBuffer.length === actualBuffer.length && expectedBuffer.equals(actualBuffer)) {
218
+ return {
219
+ matches: true,
220
+ diffPixels: 0,
221
+ diffPercentage: 0,
222
+ ignoredAntialiasPixels: 0,
223
+ expectedWidth: expectedDims.width,
224
+ expectedHeight: expectedDims.height,
225
+ actualWidth: expectedDims.width,
226
+ actualHeight: expectedDims.height,
227
+ dimensionMismatch: false,
228
+ attempts,
229
+ elapsedMs: clock.elapsed(),
230
+ baseline: 'matched',
231
+ };
232
+ }
233
+ // Fast path 2: an unchanged failed screenshot has the same comparison result.
234
+ if (previousActualBuffer?.equals(actualBuffer)) {
235
+ if (clock.expired())
236
+ break;
237
+ await sleep(Math.min(interval, clock.remaining()));
238
+ continue;
239
+ }
240
+ validatePngHeaderAndSize(actualBuffer, limits, 'screenshot');
241
+ expectedImage ??= await codec.decode(expectedBuffer);
242
+ const actualImage = await codec.decode(actualBuffer);
243
+ const comparison = compareDecoded(actualImage, expectedImage, compareOpts);
244
+ if (comparison.matches) {
245
+ return { ...comparison, attempts, elapsedMs: clock.elapsed(), baseline: 'matched' };
246
+ }
247
+ // Retain only the most recent failed attempt — never every retry image.
248
+ previousActualBuffer = actualBuffer;
249
+ previousActualImage = actualImage;
250
+ previousComparison = { ...comparison, attempts, elapsedMs: clock.elapsed(), baseline: 'matched' };
251
+ if (clock.expired())
252
+ break;
253
+ await sleep(Math.min(interval, clock.remaining()));
254
+ }
255
+ // The page never settled into a match. Either accept the new rendering by
256
+ // overwriting the baseline (update mode), or fail with the diff.
257
+ if (update) {
258
+ await writeBaselineAtomically(expectedPath, previousActualBuffer);
259
+ report(`Updated visual baseline: ${expectedPath} ` +
260
+ `(${previousComparison.diffPixels} px, ${previousComparison.diffPercentage.toFixed(2)}% changed)`);
261
+ return {
262
+ matches: true,
263
+ diffPixels: 0,
264
+ diffPercentage: 0,
265
+ ignoredAntialiasPixels: 0,
266
+ expectedWidth: previousComparison.actualWidth,
267
+ expectedHeight: previousComparison.actualHeight,
268
+ actualWidth: previousComparison.actualWidth,
269
+ actualHeight: previousComparison.actualHeight,
270
+ dimensionMismatch: false,
271
+ attempts,
272
+ elapsedMs: clock.elapsed(),
273
+ baseline: 'updated',
274
+ };
275
+ }
276
+ // Final failure: reuse the retained decoded images, paint the diff once, encode once.
277
+ const rawDiff = createDiff(previousActualImage, expectedImage, compareOpts);
278
+ const diff = await codec.encode(rawDiff);
279
+ throw new VisualMismatchError(previousActualBuffer, diff, {
280
+ expectedPath,
281
+ timeout,
282
+ attempts,
283
+ elapsedMs: clock.elapsed(),
284
+ comparison: previousComparison,
285
+ });
286
+ }
287
+ //# sourceMappingURL=expectScreenshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expectScreenshot.js","sourceRoot":"","sources":["../../../src/lib/visual/expectScreenshot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Controlled exports for the visual-testing feature. The public entrypoint
3
+ * (`src/index.ts`) re-exports from here; pngjs and other codec internals are
4
+ * never surfaced.
5
+ */
6
+ export { compareScreenshots } from './compare.js';
7
+ export { VisualMismatchError } from './visualError.js';
8
+ export { runExpectScreenshot, shouldUpdateVisualBaselines, type CaptureFn, } from './expectScreenshot.js';
9
+ export type { ScreenshotCompareOptions, ExpectScreenshotOptions, VisualScreenshotOptions, VisualComparisonResult, ScreenshotMatchResult, } from './types.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/visual/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EACL,mBAAmB,EACnB,2BAA2B,EAC3B,KAAK,SAAS,GACf,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,wBAAwB,EACxB,uBAAuB,EACvB,uBAAuB,EACvB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Controlled exports for the visual-testing feature. The public entrypoint
3
+ * (`src/index.ts`) re-exports from here; pngjs and other codec internals are
4
+ * never surfaced.
5
+ */
6
+ export { compareScreenshots } from './compare.js';
7
+ export { VisualMismatchError } from './visualError.js';
8
+ export { runExpectScreenshot, shouldUpdateVisualBaselines, } from './expectScreenshot.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/visual/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EACL,mBAAmB,EACnB,2BAA2B,GAE5B,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Low-level per-pixel math shared by the comparison loop and the anti-alias
3
+ * detector. Kept in its own module so `compare.ts` and `antialias.ts` can both
4
+ * use it without an import cycle.
5
+ *
6
+ * Every visible-colour computation composites over a **white** background. Two
7
+ * fully transparent pixels therefore composite to white and compare equal, and
8
+ * hidden RGB bytes under alpha zero never influence the result. This is the
9
+ * single documented background used everywhere: ordinary comparison, sibling
10
+ * equality, and the anti-alias brightness heuristic. If any of them used a
11
+ * different background they would disagree about transparent pixels.
12
+ */
13
+ /** Composite one 0..255 channel with 0..255 alpha over a white background. */
14
+ export declare function blendOverWhite(channel: number, alpha: number): number;
15
+ /** ITU-R BT.601 luma of an already-composited RGB triple. */
16
+ export declare function rgb2y(r: number, g: number, b: number): number;
17
+ /** Read a pixel's white-composited visible RGB into `out` (length ≥ 3). */
18
+ export declare function visibleRgb(data: Uint8Array, pos: number, out: number[]): void;
19
+ /**
20
+ * Brightness (luma) delta between two pixels of the same image, both composited
21
+ * over white. This is Pixelmatch's `colorDelta(..., yOnly=true)` with
22
+ * `checkerboard: false`, i.e. white compositing to match craftdriver's contract.
23
+ */
24
+ export declare function brightnessDelta(data: Uint8Array, pos1: number, pos2: number): number;
25
+ /**
26
+ * Whether two pixels have the same white-composited visible colour. Used for
27
+ * sibling counting in the AA heuristic — deliberately compares visible colour,
28
+ * not the raw RGBA bytes, so transparent pixels with differing hidden RGB still
29
+ * count as identical siblings.
30
+ */
31
+ export declare function visibleEqual(data: Uint8Array, pos1: number, pos2: number): boolean;
32
+ //# sourceMappingURL=pixelMath.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pixelMath.d.ts","sourceRoot":"","sources":["../../../src/lib/visual/pixelMath.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGrE;AAED,6DAA6D;AAC7D,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,CAW7E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAUpF;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAelF"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Low-level per-pixel math shared by the comparison loop and the anti-alias
3
+ * detector. Kept in its own module so `compare.ts` and `antialias.ts` can both
4
+ * use it without an import cycle.
5
+ *
6
+ * Every visible-colour computation composites over a **white** background. Two
7
+ * fully transparent pixels therefore composite to white and compare equal, and
8
+ * hidden RGB bytes under alpha zero never influence the result. This is the
9
+ * single documented background used everywhere: ordinary comparison, sibling
10
+ * equality, and the anti-alias brightness heuristic. If any of them used a
11
+ * different background they would disagree about transparent pixels.
12
+ */
13
+ /** Composite one 0..255 channel with 0..255 alpha over a white background. */
14
+ export function blendOverWhite(channel, alpha) {
15
+ // 255 + (channel - 255) * (alpha / 255): alpha 255 → channel, alpha 0 → white.
16
+ return 255 + ((channel - 255) * alpha) / 255;
17
+ }
18
+ /** ITU-R BT.601 luma of an already-composited RGB triple. */
19
+ export function rgb2y(r, g, b) {
20
+ return r * 0.29889531 + g * 0.58662247 + b * 0.11448223;
21
+ }
22
+ /** Read a pixel's white-composited visible RGB into `out` (length ≥ 3). */
23
+ export function visibleRgb(data, pos, out) {
24
+ const a = data[pos + 3];
25
+ if (a === 255) {
26
+ out[0] = data[pos];
27
+ out[1] = data[pos + 1];
28
+ out[2] = data[pos + 2];
29
+ }
30
+ else {
31
+ out[0] = blendOverWhite(data[pos], a);
32
+ out[1] = blendOverWhite(data[pos + 1], a);
33
+ out[2] = blendOverWhite(data[pos + 2], a);
34
+ }
35
+ }
36
+ /**
37
+ * Brightness (luma) delta between two pixels of the same image, both composited
38
+ * over white. This is Pixelmatch's `colorDelta(..., yOnly=true)` with
39
+ * `checkerboard: false`, i.e. white compositing to match craftdriver's contract.
40
+ */
41
+ export function brightnessDelta(data, pos1, pos2) {
42
+ const a1 = data[pos1 + 3];
43
+ const a2 = data[pos2 + 3];
44
+ const r1 = a1 === 255 ? data[pos1] : blendOverWhite(data[pos1], a1);
45
+ const g1 = a1 === 255 ? data[pos1 + 1] : blendOverWhite(data[pos1 + 1], a1);
46
+ const b1 = a1 === 255 ? data[pos1 + 2] : blendOverWhite(data[pos1 + 2], a1);
47
+ const r2 = a2 === 255 ? data[pos2] : blendOverWhite(data[pos2], a2);
48
+ const g2 = a2 === 255 ? data[pos2 + 1] : blendOverWhite(data[pos2 + 1], a2);
49
+ const b2 = a2 === 255 ? data[pos2 + 2] : blendOverWhite(data[pos2 + 2], a2);
50
+ return rgb2y(r1, g1, b1) - rgb2y(r2, g2, b2);
51
+ }
52
+ /**
53
+ * Whether two pixels have the same white-composited visible colour. Used for
54
+ * sibling counting in the AA heuristic — deliberately compares visible colour,
55
+ * not the raw RGBA bytes, so transparent pixels with differing hidden RGB still
56
+ * count as identical siblings.
57
+ */
58
+ export function visibleEqual(data, pos1, pos2) {
59
+ const a1 = data[pos1 + 3];
60
+ const a2 = data[pos2 + 3];
61
+ if (a1 === 255 && a2 === 255) {
62
+ return (data[pos1] === data[pos2] &&
63
+ data[pos1 + 1] === data[pos2 + 1] &&
64
+ data[pos1 + 2] === data[pos2 + 2]);
65
+ }
66
+ return (blendOverWhite(data[pos1], a1) === blendOverWhite(data[pos2], a2) &&
67
+ blendOverWhite(data[pos1 + 1], a1) === blendOverWhite(data[pos2 + 1], a2) &&
68
+ blendOverWhite(data[pos1 + 2], a1) === blendOverWhite(data[pos2 + 2], a2));
69
+ }
70
+ //# sourceMappingURL=pixelMath.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pixelMath.js","sourceRoot":"","sources":["../../../src/lib/visual/pixelMath.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Cheap, allocation-bounding PNG validation performed *before* a buffer is
3
+ * handed to pngjs.
4
+ *
5
+ * This is deliberately not a decoder. It reads only the 8-byte signature and
6
+ * the fixed-layout IHDR chunk to learn the declared dimensions, so a
7
+ * hostile/corrupt header cannot make the decoder allocate a huge RGBA array
8
+ * before the real (CRC-checked) decode runs. Both limits are enforced with
9
+ * overflow-safe arithmetic. The same checks apply to the baseline and to every
10
+ * unique actual screenshot.
11
+ */
12
+ export interface PngSizeLimits {
13
+ maxImagePixels: number;
14
+ maxInputBytes: number;
15
+ }
16
+ /** Where this buffer came from, for a clearer error message. */
17
+ export type PngSource = 'baseline' | 'screenshot';
18
+ /**
19
+ * Validate a compressed PNG's signature, IHDR, declared dimensions, and both
20
+ * size limits. Returns the parsed dimensions so callers can reuse them. Throws
21
+ * {@link CraftdriverError} with `INVALID_ARGUMENT` on any violation.
22
+ */
23
+ export declare function validatePngHeaderAndSize(buffer: Uint8Array, limits: PngSizeLimits, source: PngSource): {
24
+ width: number;
25
+ height: number;
26
+ };
27
+ //# sourceMappingURL=pngHeader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pngHeader.d.ts","sourceRoot":"","sources":["../../../src/lib/visual/pngHeader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAcH,MAAM,WAAW,aAAa;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,gEAAgE;AAChE,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,YAAY,CAAC;AAQlD;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,SAAS,GAChB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CA2CnC"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Cheap, allocation-bounding PNG validation performed *before* a buffer is
3
+ * handed to pngjs.
4
+ *
5
+ * This is deliberately not a decoder. It reads only the 8-byte signature and
6
+ * the fixed-layout IHDR chunk to learn the declared dimensions, so a
7
+ * hostile/corrupt header cannot make the decoder allocate a huge RGBA array
8
+ * before the real (CRC-checked) decode runs. Both limits are enforced with
9
+ * overflow-safe arithmetic. The same checks apply to the baseline and to every
10
+ * unique actual screenshot.
11
+ */
12
+ import { CraftdriverError, ErrorCode } from '../errors.js';
13
+ /** 8-byte PNG magic signature. */
14
+ const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
15
+ /** Byte offset of the IHDR chunk's length field (immediately after the signature). */
16
+ const IHDR_LENGTH_OFFSET = 8;
17
+ /** IHDR chunk data is a fixed 13 bytes. */
18
+ const IHDR_DATA_LENGTH = 13;
19
+ /** Minimum bytes we must see to read the full IHDR: sig + len(4) + type(4) + data(13) + crc(4). */
20
+ const MIN_HEADER_BYTES = IHDR_LENGTH_OFFSET + 4 + 4 + IHDR_DATA_LENGTH + 4;
21
+ function fail(source, message, detail) {
22
+ throw new CraftdriverError(ErrorCode.INVALID_ARGUMENT, `${source} ${message}`, {
23
+ detail: { source, ...detail },
24
+ });
25
+ }
26
+ /**
27
+ * Validate a compressed PNG's signature, IHDR, declared dimensions, and both
28
+ * size limits. Returns the parsed dimensions so callers can reuse them. Throws
29
+ * {@link CraftdriverError} with `INVALID_ARGUMENT` on any violation.
30
+ */
31
+ export function validatePngHeaderAndSize(buffer, limits, source) {
32
+ if (buffer.length > limits.maxInputBytes) {
33
+ fail(source, `PNG is ${buffer.length} bytes, exceeding the ${limits.maxInputBytes}-byte limit.`, {
34
+ inputBytes: buffer.length,
35
+ maxInputBytes: limits.maxInputBytes,
36
+ });
37
+ }
38
+ if (buffer.length < MIN_HEADER_BYTES) {
39
+ fail(source, 'PNG is too small to contain a valid header.', { inputBytes: buffer.length });
40
+ }
41
+ const buf = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength);
42
+ if (!buf.subarray(0, 8).equals(PNG_SIGNATURE)) {
43
+ fail(source, 'buffer is not a PNG (bad signature).');
44
+ }
45
+ const ihdrLength = buf.readUInt32BE(IHDR_LENGTH_OFFSET);
46
+ const ihdrType = buf.toString('ascii', IHDR_LENGTH_OFFSET + 4, IHDR_LENGTH_OFFSET + 8);
47
+ if (ihdrType !== 'IHDR' || ihdrLength !== IHDR_DATA_LENGTH) {
48
+ fail(source, 'PNG has an invalid IHDR chunk.', { ihdrType, ihdrLength });
49
+ }
50
+ const dataOffset = IHDR_LENGTH_OFFSET + 8;
51
+ const width = buf.readUInt32BE(dataOffset);
52
+ const height = buf.readUInt32BE(dataOffset + 4);
53
+ if (width === 0 || height === 0) {
54
+ fail(source, `PNG has zero dimension (${width}x${height}).`, { width, height });
55
+ }
56
+ // Overflow-safe: compare against the limit using division so width*height
57
+ // never overflows a JS safe integer for adversarial 2^32-scale dimensions.
58
+ if (width > limits.maxImagePixels || height > limits.maxImagePixels / width) {
59
+ fail(source, `PNG is ${width}x${height} pixels, exceeding the ${limits.maxImagePixels}-pixel limit.`, { width, height, pixels: width * height, maxImagePixels: limits.maxImagePixels });
60
+ }
61
+ return { width, height };
62
+ }
63
+ //# sourceMappingURL=pngHeader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pngHeader.js","sourceRoot":"","sources":["../../../src/lib/visual/pngHeader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
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 type { PngCodec } from './codec.js';
21
+ export declare const pngjsCodec: PngCodec;
22
+ //# sourceMappingURL=pngjsCodec.d.ts.map