@monoes/monobrowse 1.0.12 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/__tests__/profiler-heap.test.d.ts +2 -0
- package/dist/src/__tests__/profiler-heap.test.d.ts.map +1 -0
- package/dist/src/__tests__/profiler-heap.test.js +154 -0
- package/dist/src/__tests__/profiler-heap.test.js.map +1 -0
- package/dist/src/__tests__/report-a11y.test.d.ts +2 -0
- package/dist/src/__tests__/report-a11y.test.d.ts.map +1 -0
- package/dist/src/__tests__/report-a11y.test.js +147 -0
- package/dist/src/__tests__/report-a11y.test.js.map +1 -0
- package/dist/src/__tests__/report-analyze.test.d.ts +2 -0
- package/dist/src/__tests__/report-analyze.test.d.ts.map +1 -0
- package/dist/src/__tests__/report-analyze.test.js +157 -0
- package/dist/src/__tests__/report-analyze.test.js.map +1 -0
- package/dist/src/__tests__/report-budget.test.d.ts +2 -0
- package/dist/src/__tests__/report-budget.test.d.ts.map +1 -0
- package/dist/src/__tests__/report-budget.test.js +80 -0
- package/dist/src/__tests__/report-budget.test.js.map +1 -0
- package/dist/src/__tests__/report-diff.test.d.ts +2 -0
- package/dist/src/__tests__/report-diff.test.d.ts.map +1 -0
- package/dist/src/__tests__/report-diff.test.js +231 -0
- package/dist/src/__tests__/report-diff.test.js.map +1 -0
- package/dist/src/__tests__/report-evidence.test.d.ts +2 -0
- package/dist/src/__tests__/report-evidence.test.d.ts.map +1 -0
- package/dist/src/__tests__/report-evidence.test.js +214 -0
- package/dist/src/__tests__/report-evidence.test.js.map +1 -0
- package/dist/src/__tests__/report-flake.test.d.ts +2 -0
- package/dist/src/__tests__/report-flake.test.d.ts.map +1 -0
- package/dist/src/__tests__/report-flake.test.js +182 -0
- package/dist/src/__tests__/report-flake.test.js.map +1 -0
- package/dist/src/__tests__/report-history.test.d.ts +2 -0
- package/dist/src/__tests__/report-history.test.d.ts.map +1 -0
- package/dist/src/__tests__/report-history.test.js +229 -0
- package/dist/src/__tests__/report-history.test.js.map +1 -0
- package/dist/src/__tests__/report-output.test.d.ts +2 -0
- package/dist/src/__tests__/report-output.test.d.ts.map +1 -0
- package/dist/src/__tests__/report-output.test.js +169 -0
- package/dist/src/__tests__/report-output.test.js.map +1 -0
- package/dist/src/__tests__/report-png.test.d.ts +2 -0
- package/dist/src/__tests__/report-png.test.d.ts.map +1 -0
- package/dist/src/__tests__/report-png.test.js +84 -0
- package/dist/src/__tests__/report-png.test.js.map +1 -0
- package/dist/src/__tests__/report-render-extras.test.d.ts +2 -0
- package/dist/src/__tests__/report-render-extras.test.d.ts.map +1 -0
- package/dist/src/__tests__/report-render-extras.test.js +285 -0
- package/dist/src/__tests__/report-render-extras.test.js.map +1 -0
- package/dist/src/__tests__/report-render.test.d.ts +2 -0
- package/dist/src/__tests__/report-render.test.d.ts.map +1 -0
- package/dist/src/__tests__/report-render.test.js +143 -0
- package/dist/src/__tests__/report-render.test.js.map +1 -0
- package/dist/src/__tests__/report-run.test.d.ts +2 -0
- package/dist/src/__tests__/report-run.test.d.ts.map +1 -0
- package/dist/src/__tests__/report-run.test.js +234 -0
- package/dist/src/__tests__/report-run.test.js.map +1 -0
- package/dist/src/browser/index.d.ts +1 -0
- package/dist/src/browser/index.d.ts.map +1 -1
- package/dist/src/browser/index.js +1 -0
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/browser/profiler.d.ts.map +1 -1
- package/dist/src/browser/profiler.js +19 -22
- package/dist/src/browser/profiler.js.map +1 -1
- package/dist/src/cli/commands.d.ts.map +1 -1
- package/dist/src/cli/commands.js +254 -0
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/report/a11y.d.ts +52 -0
- package/dist/src/report/a11y.d.ts.map +1 -0
- package/dist/src/report/a11y.js +231 -0
- package/dist/src/report/a11y.js.map +1 -0
- package/dist/src/report/analyze.d.ts +20 -0
- package/dist/src/report/analyze.d.ts.map +1 -0
- package/dist/src/report/analyze.js +136 -0
- package/dist/src/report/analyze.js.map +1 -0
- package/dist/src/report/budget.d.ts +27 -0
- package/dist/src/report/budget.d.ts.map +1 -0
- package/dist/src/report/budget.js +143 -0
- package/dist/src/report/budget.js.map +1 -0
- package/dist/src/report/collect-a11y.d.ts +25 -0
- package/dist/src/report/collect-a11y.d.ts.map +1 -0
- package/dist/src/report/collect-a11y.js +154 -0
- package/dist/src/report/collect-a11y.js.map +1 -0
- package/dist/src/report/collect.d.ts +45 -0
- package/dist/src/report/collect.d.ts.map +1 -0
- package/dist/src/report/collect.js +275 -0
- package/dist/src/report/collect.js.map +1 -0
- package/dist/src/report/evidence.d.ts +80 -0
- package/dist/src/report/evidence.d.ts.map +1 -0
- package/dist/src/report/evidence.js +279 -0
- package/dist/src/report/evidence.js.map +1 -0
- package/dist/src/report/flake.d.ts +88 -0
- package/dist/src/report/flake.d.ts.map +1 -0
- package/dist/src/report/flake.js +232 -0
- package/dist/src/report/flake.js.map +1 -0
- package/dist/src/report/history.d.ts +85 -0
- package/dist/src/report/history.d.ts.map +1 -0
- package/dist/src/report/history.js +176 -0
- package/dist/src/report/history.js.map +1 -0
- package/dist/src/report/index.d.ts +102 -0
- package/dist/src/report/index.d.ts.map +1 -0
- package/dist/src/report/index.js +216 -0
- package/dist/src/report/index.js.map +1 -0
- package/dist/src/report/pixel-diff.d.ts +30 -0
- package/dist/src/report/pixel-diff.d.ts.map +1 -0
- package/dist/src/report/pixel-diff.js +175 -0
- package/dist/src/report/pixel-diff.js.map +1 -0
- package/dist/src/report/png.d.ts +28 -0
- package/dist/src/report/png.d.ts.map +1 -0
- package/dist/src/report/png.js +259 -0
- package/dist/src/report/png.js.map +1 -0
- package/dist/src/report/render-evidence.d.ts +16 -0
- package/dist/src/report/render-evidence.d.ts.map +1 -0
- package/dist/src/report/render-evidence.js +174 -0
- package/dist/src/report/render-evidence.js.map +1 -0
- package/dist/src/report/render-trend.d.ts +16 -0
- package/dist/src/report/render-trend.d.ts.map +1 -0
- package/dist/src/report/render-trend.js +194 -0
- package/dist/src/report/render-trend.js.map +1 -0
- package/dist/src/report/render.d.ts +21 -0
- package/dist/src/report/render.d.ts.map +1 -0
- package/dist/src/report/render.js +286 -0
- package/dist/src/report/render.js.map +1 -0
- package/dist/src/report/run-diff.d.ts +18 -0
- package/dist/src/report/run-diff.d.ts.map +1 -0
- package/dist/src/report/run-diff.js +51 -0
- package/dist/src/report/run-diff.js.map +1 -0
- package/dist/src/report/structure.d.ts +41 -0
- package/dist/src/report/structure.d.ts.map +1 -0
- package/dist/src/report/structure.js +210 -0
- package/dist/src/report/structure.js.map +1 -0
- package/dist/src/report/trend.d.ts +26 -0
- package/dist/src/report/trend.d.ts.map +1 -0
- package/dist/src/report/trend.js +164 -0
- package/dist/src/report/trend.js.map +1 -0
- package/dist/src/report/types.d.ts +320 -0
- package/dist/src/report/types.d.ts.map +1 -0
- package/dist/src/report/types.js +11 -0
- package/dist/src/report/types.js.map +1 -0
- package/dist/src/report/util.d.ts +17 -0
- package/dist/src/report/util.d.ts.map +1 -0
- package/dist/src/report/util.js +45 -0
- package/dist/src/report/util.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/__tests__/profiler-heap.test.ts +191 -0
- package/src/__tests__/report-a11y.test.ts +180 -0
- package/src/__tests__/report-analyze.test.ts +202 -0
- package/src/__tests__/report-budget.test.ts +92 -0
- package/src/__tests__/report-diff.test.ts +272 -0
- package/src/__tests__/report-evidence.test.ts +276 -0
- package/src/__tests__/report-flake.test.ts +214 -0
- package/src/__tests__/report-history.test.ts +293 -0
- package/src/__tests__/report-output.test.ts +210 -0
- package/src/__tests__/report-png.test.ts +95 -0
- package/src/__tests__/report-render-extras.test.ts +315 -0
- package/src/__tests__/report-render.test.ts +175 -0
- package/src/__tests__/report-run.test.ts +264 -0
- package/src/browser/index.ts +1 -0
- package/src/browser/profiler.ts +28 -21
- package/src/cli/commands.ts +272 -0
- package/src/report/a11y.ts +242 -0
- package/src/report/analyze.ts +181 -0
- package/src/report/budget.ts +155 -0
- package/src/report/collect-a11y.ts +200 -0
- package/src/report/collect.ts +408 -0
- package/src/report/evidence.ts +379 -0
- package/src/report/flake.ts +303 -0
- package/src/report/history.ts +219 -0
- package/src/report/index.ts +307 -0
- package/src/report/pixel-diff.ts +197 -0
- package/src/report/png.ts +290 -0
- package/src/report/render-evidence.ts +194 -0
- package/src/report/render-trend.ts +225 -0
- package/src/report/render.ts +314 -0
- package/src/report/run-diff.ts +63 -0
- package/src/report/structure.ts +212 -0
- package/src/report/trend.ts +189 -0
- package/src/report/types.ts +356 -0
- package/src/report/util.ts +48 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent run history for `monobrowse report` (RIG-13).
|
|
3
|
+
*
|
|
4
|
+
* Every run is appended to a per-URL directory under the monomind data dir,
|
|
5
|
+
* so the next run can say "LCP has crept up 400ms over nine runs" instead of
|
|
6
|
+
* reporting a number with no context. Deliberately not a database: one JSON
|
|
7
|
+
* file per run plus its screenshot is greppable, diffable, rsync-able, and
|
|
8
|
+
* survives this tool being uninstalled.
|
|
9
|
+
*
|
|
10
|
+
* ~/.monomind/browser-reports/
|
|
11
|
+
* shop.test-checkout-1f4c9a2b/
|
|
12
|
+
* url.txt <- the full URL, for humans
|
|
13
|
+
* 20260921T100000Z-8f2a1c4d.json <- HistoryRun
|
|
14
|
+
* 20260921T100000Z-8f2a1c4d.png <- main screenshot, for the diff
|
|
15
|
+
*
|
|
16
|
+
* Set `MONOBROWSE_HISTORY_DIR` to relocate the root (the tests do).
|
|
17
|
+
*/
|
|
18
|
+
import type { ImageSize, Report, StructureNode, VerdictValue, VitalsData } from './types.js';
|
|
19
|
+
/** Runs kept per URL before the oldest are pruned. Override with --history-max. */
|
|
20
|
+
export declare const DEFAULT_HISTORY_MAX = 20;
|
|
21
|
+
export interface HistoryRun {
|
|
22
|
+
/** `<compact ISO timestamp>-<8 hex>`, also the basename of its files. */
|
|
23
|
+
id: string;
|
|
24
|
+
url: string;
|
|
25
|
+
finalUrl: string;
|
|
26
|
+
title: string;
|
|
27
|
+
capturedAt: string;
|
|
28
|
+
durationMs: number;
|
|
29
|
+
verdict: VerdictValue;
|
|
30
|
+
counts: Report['counts'];
|
|
31
|
+
vitals: VitalsData;
|
|
32
|
+
failures: Array<{
|
|
33
|
+
budget: string;
|
|
34
|
+
expected: string;
|
|
35
|
+
actual: string;
|
|
36
|
+
}>;
|
|
37
|
+
/** `<rule>@<locator>` per finding, so a11y regressions are attributable. */
|
|
38
|
+
a11ySignatures: string[];
|
|
39
|
+
structure: StructureNode[];
|
|
40
|
+
/** Basename of the sibling PNG, when one was archived. */
|
|
41
|
+
screenshotFile?: string;
|
|
42
|
+
screenshotSize?: ImageSize;
|
|
43
|
+
}
|
|
44
|
+
export declare function historyRoot(): string;
|
|
45
|
+
/**
|
|
46
|
+
* Directory name for a URL: readable prefix plus a hash of the full URL.
|
|
47
|
+
*
|
|
48
|
+
* The hash is what makes it correct — `?page=2` and `?page=3` must not share
|
|
49
|
+
* a history — and the prefix is what makes `ls` useful.
|
|
50
|
+
*/
|
|
51
|
+
export declare function historyKey(url: string): string;
|
|
52
|
+
export declare function historyDirFor(url: string, root?: string): string;
|
|
53
|
+
export declare function compactTimestamp(iso: string): string;
|
|
54
|
+
export declare function newRunId(capturedAt: string): string;
|
|
55
|
+
/** The record we persist — a Report minus the megabytes of inline base64. */
|
|
56
|
+
export declare function toHistoryRun(report: Report, id: string): HistoryRun;
|
|
57
|
+
/**
|
|
58
|
+
* Every run for a URL, oldest first.
|
|
59
|
+
*
|
|
60
|
+
* A file that will not parse is skipped rather than thrown: a half-written
|
|
61
|
+
* record from a killed process must not permanently break the next report.
|
|
62
|
+
*/
|
|
63
|
+
export declare function listRuns(dir: string): Promise<HistoryRun[]>;
|
|
64
|
+
/** Delete all but the newest `max` runs, taking each one's PNG with it. */
|
|
65
|
+
export declare function pruneHistory(dir: string, max: number): Promise<string[]>;
|
|
66
|
+
export interface SaveRunOptions {
|
|
67
|
+
root?: string;
|
|
68
|
+
max?: number;
|
|
69
|
+
/** Fixed id, for deterministic tests. */
|
|
70
|
+
id?: string;
|
|
71
|
+
}
|
|
72
|
+
export interface SaveRunResult {
|
|
73
|
+
dir: string;
|
|
74
|
+
file: string;
|
|
75
|
+
run: HistoryRun;
|
|
76
|
+
pruned: string[];
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Append this run to its URL's history and prune the tail. Callers treat a
|
|
80
|
+
* rejection as non-fatal — a read-only home directory should cost you the
|
|
81
|
+
* trend section, not the report.
|
|
82
|
+
*/
|
|
83
|
+
export declare function saveRun(report: Report, options?: SaveRunOptions): Promise<SaveRunResult>;
|
|
84
|
+
export declare function loadRunScreenshot(dir: string, run: HistoryRun): Promise<Uint8Array | null>;
|
|
85
|
+
//# sourceMappingURL=history.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../../src/report/history.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAOH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7F,mFAAmF;AACnF,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAStC,MAAM,WAAW,UAAU;IACzB,yEAAyE;IACzE,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtE,4EAA4E;IAC5E,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,0DAA0D;IAC1D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,SAAS,CAAC;CAC5B;AAED,wBAAgB,WAAW,IAAI,MAAM,CAIpC;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAY9C;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,SAAgB,GAAG,MAAM,CAEvE;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,UAAU,CAmBnE;AAID;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAiBjE;AAED,2EAA2E;AAC3E,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAc9E;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,UAAU,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAC3B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,aAAa,CAAC,CA4BxB;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAOhG"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent run history for `monobrowse report` (RIG-13).
|
|
3
|
+
*
|
|
4
|
+
* Every run is appended to a per-URL directory under the monomind data dir,
|
|
5
|
+
* so the next run can say "LCP has crept up 400ms over nine runs" instead of
|
|
6
|
+
* reporting a number with no context. Deliberately not a database: one JSON
|
|
7
|
+
* file per run plus its screenshot is greppable, diffable, rsync-able, and
|
|
8
|
+
* survives this tool being uninstalled.
|
|
9
|
+
*
|
|
10
|
+
* ~/.monomind/browser-reports/
|
|
11
|
+
* shop.test-checkout-1f4c9a2b/
|
|
12
|
+
* url.txt <- the full URL, for humans
|
|
13
|
+
* 20260921T100000Z-8f2a1c4d.json <- HistoryRun
|
|
14
|
+
* 20260921T100000Z-8f2a1c4d.png <- main screenshot, for the diff
|
|
15
|
+
*
|
|
16
|
+
* Set `MONOBROWSE_HISTORY_DIR` to relocate the root (the tests do).
|
|
17
|
+
*/
|
|
18
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
19
|
+
import { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
20
|
+
import { homedir } from 'node:os';
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
import { pngFromDataUrl } from './png.js';
|
|
23
|
+
/** Runs kept per URL before the oldest are pruned. Override with --history-max. */
|
|
24
|
+
export const DEFAULT_HISTORY_MAX = 20;
|
|
25
|
+
/**
|
|
26
|
+
* Screenshots above this are not archived for the diff. A 30MB full-page PNG
|
|
27
|
+
* per run would turn a 20-run history into 600MB on someone's laptop, and the
|
|
28
|
+
* pixel diff is the only consumer.
|
|
29
|
+
*/
|
|
30
|
+
const MAX_ARCHIVED_SCREENSHOT_BYTES = 8 * 1024 * 1024;
|
|
31
|
+
export function historyRoot() {
|
|
32
|
+
const override = process.env.MONOBROWSE_HISTORY_DIR;
|
|
33
|
+
if (override?.trim())
|
|
34
|
+
return override.trim();
|
|
35
|
+
return join(homedir(), '.monomind', 'browser-reports');
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Directory name for a URL: readable prefix plus a hash of the full URL.
|
|
39
|
+
*
|
|
40
|
+
* The hash is what makes it correct — `?page=2` and `?page=3` must not share
|
|
41
|
+
* a history — and the prefix is what makes `ls` useful.
|
|
42
|
+
*/
|
|
43
|
+
export function historyKey(url) {
|
|
44
|
+
const hash = createHash('sha256').update(url).digest('hex').slice(0, 8);
|
|
45
|
+
let readable;
|
|
46
|
+
try {
|
|
47
|
+
const parsed = new URL(url);
|
|
48
|
+
const path = parsed.pathname.replace(/\/+$/, '').replace(/[^a-z0-9]+/gi, '-');
|
|
49
|
+
readable = `${parsed.hostname}${path}`;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
readable = url.replace(/[^a-z0-9]+/gi, '-');
|
|
53
|
+
}
|
|
54
|
+
readable = readable.replace(/-+/g, '-').replace(/^-|-$/g, '').toLowerCase().slice(0, 60);
|
|
55
|
+
return `${readable || 'page'}-${hash}`;
|
|
56
|
+
}
|
|
57
|
+
export function historyDirFor(url, root = historyRoot()) {
|
|
58
|
+
return join(root, historyKey(url));
|
|
59
|
+
}
|
|
60
|
+
export function compactTimestamp(iso) {
|
|
61
|
+
return iso.replace(/[-:]/g, '').replace(/\.\d+Z$/, 'Z');
|
|
62
|
+
}
|
|
63
|
+
export function newRunId(capturedAt) {
|
|
64
|
+
return `${compactTimestamp(capturedAt)}-${randomBytes(4).toString('hex')}`;
|
|
65
|
+
}
|
|
66
|
+
/** The record we persist — a Report minus the megabytes of inline base64. */
|
|
67
|
+
export function toHistoryRun(report, id) {
|
|
68
|
+
return {
|
|
69
|
+
id,
|
|
70
|
+
url: report.url,
|
|
71
|
+
finalUrl: report.finalUrl,
|
|
72
|
+
title: report.title,
|
|
73
|
+
capturedAt: report.capturedAt,
|
|
74
|
+
durationMs: report.durationMs,
|
|
75
|
+
verdict: report.verdict,
|
|
76
|
+
counts: report.counts,
|
|
77
|
+
vitals: report.vitals,
|
|
78
|
+
failures: report.failures.map((f) => ({
|
|
79
|
+
budget: String(f.budget),
|
|
80
|
+
expected: f.expected,
|
|
81
|
+
actual: f.actual,
|
|
82
|
+
})),
|
|
83
|
+
a11ySignatures: report.a11y.map((f) => `${f.rule}@${f.locator}`),
|
|
84
|
+
structure: report.structure ?? [],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const RUN_FILE = /^(\d{8}T\d{6}Z)-([0-9a-f]{8})\.json$/;
|
|
88
|
+
/**
|
|
89
|
+
* Every run for a URL, oldest first.
|
|
90
|
+
*
|
|
91
|
+
* A file that will not parse is skipped rather than thrown: a half-written
|
|
92
|
+
* record from a killed process must not permanently break the next report.
|
|
93
|
+
*/
|
|
94
|
+
export async function listRuns(dir) {
|
|
95
|
+
let names;
|
|
96
|
+
try {
|
|
97
|
+
names = await readdir(dir);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
const runs = [];
|
|
103
|
+
for (const name of names.filter((n) => RUN_FILE.test(n)).sort()) {
|
|
104
|
+
try {
|
|
105
|
+
const parsed = JSON.parse(await readFile(join(dir, name), 'utf8'));
|
|
106
|
+
if (parsed && typeof parsed.capturedAt === 'string')
|
|
107
|
+
runs.push(parsed);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Unreadable record — ignore it and carry on.
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return runs.sort((a, b) => a.capturedAt.localeCompare(b.capturedAt));
|
|
114
|
+
}
|
|
115
|
+
/** Delete all but the newest `max` runs, taking each one's PNG with it. */
|
|
116
|
+
export async function pruneHistory(dir, max) {
|
|
117
|
+
if (!Number.isFinite(max) || max <= 0)
|
|
118
|
+
return [];
|
|
119
|
+
const runs = await listRuns(dir);
|
|
120
|
+
if (runs.length <= max)
|
|
121
|
+
return [];
|
|
122
|
+
const doomed = runs.slice(0, runs.length - max);
|
|
123
|
+
const removed = [];
|
|
124
|
+
for (const run of doomed) {
|
|
125
|
+
for (const file of [`${run.id}.json`, run.screenshotFile]) {
|
|
126
|
+
if (!file)
|
|
127
|
+
continue;
|
|
128
|
+
await rm(join(dir, file), { force: true }).catch(() => { });
|
|
129
|
+
removed.push(file);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return removed;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Append this run to its URL's history and prune the tail. Callers treat a
|
|
136
|
+
* rejection as non-fatal — a read-only home directory should cost you the
|
|
137
|
+
* trend section, not the report.
|
|
138
|
+
*/
|
|
139
|
+
export async function saveRun(report, options = {}) {
|
|
140
|
+
const root = options.root ?? historyRoot();
|
|
141
|
+
const dir = join(root, historyKey(report.url));
|
|
142
|
+
await mkdir(dir, { recursive: true });
|
|
143
|
+
await writeFile(join(dir, 'url.txt'), `${report.url}\n`, 'utf8').catch(() => { });
|
|
144
|
+
const id = options.id ?? newRunId(report.capturedAt);
|
|
145
|
+
const run = toHistoryRun(report, id);
|
|
146
|
+
const main = report.screenshots.find((s) => s.label === 'page') ?? report.screenshots[0];
|
|
147
|
+
if (main?.dataUrl) {
|
|
148
|
+
try {
|
|
149
|
+
const bytes = pngFromDataUrl(main.dataUrl);
|
|
150
|
+
if (bytes.length <= MAX_ARCHIVED_SCREENSHOT_BYTES) {
|
|
151
|
+
const file = `${id}.png`;
|
|
152
|
+
await writeFile(join(dir, file), bytes);
|
|
153
|
+
run.screenshotFile = file;
|
|
154
|
+
run.screenshotSize = { width: main.width, height: main.height };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// No archived screenshot means no pixel diff next run. Not fatal.
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const file = join(dir, `${id}.json`);
|
|
162
|
+
await writeFile(file, `${JSON.stringify(run, null, 2)}\n`, 'utf8');
|
|
163
|
+
const pruned = await pruneHistory(dir, options.max ?? DEFAULT_HISTORY_MAX);
|
|
164
|
+
return { dir, file, run, pruned };
|
|
165
|
+
}
|
|
166
|
+
export async function loadRunScreenshot(dir, run) {
|
|
167
|
+
if (!run.screenshotFile)
|
|
168
|
+
return null;
|
|
169
|
+
try {
|
|
170
|
+
return new Uint8Array(await readFile(join(dir, run.screenshotFile)));
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.js","sourceRoot":"","sources":["../../../src/report/history.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `monobrowse report <url>` — open a page, measure it, judge it against a
|
|
3
|
+
* budget, and leave behind one self-contained HTML file plus a sibling JSON.
|
|
4
|
+
*
|
|
5
|
+
* collect.ts (drive the browser) -> analyze.ts (a11y + budgets -> verdict)
|
|
6
|
+
* -> history/trend/diff (what changed since last time) -> render.ts (HTML).
|
|
7
|
+
* This module is the seam that wires them together and writes the two files.
|
|
8
|
+
*/
|
|
9
|
+
import type { CdpClient } from '../browser/cdp.js';
|
|
10
|
+
import type { CollectOptions } from './collect.js';
|
|
11
|
+
import { type FlakeReport } from './flake.js';
|
|
12
|
+
import { type HistoryRun } from './history.js';
|
|
13
|
+
import type { Budget, Report } from './types.js';
|
|
14
|
+
export * from './a11y.js';
|
|
15
|
+
export * from './analyze.js';
|
|
16
|
+
export * from './budget.js';
|
|
17
|
+
export * from './collect.js';
|
|
18
|
+
export * from './collect-a11y.js';
|
|
19
|
+
export * from './evidence.js';
|
|
20
|
+
export * from './flake.js';
|
|
21
|
+
export * from './history.js';
|
|
22
|
+
export * from './pixel-diff.js';
|
|
23
|
+
export * from './png.js';
|
|
24
|
+
export * from './render.js';
|
|
25
|
+
export * from './run-diff.js';
|
|
26
|
+
export * from './structure.js';
|
|
27
|
+
export * from './trend.js';
|
|
28
|
+
export * from './types.js';
|
|
29
|
+
/** Prior runs a trend is charted over. Older runs stay on disk, off the chart. */
|
|
30
|
+
export declare const DEFAULT_TREND_WINDOW = 10;
|
|
31
|
+
export interface RunReportOptions extends CollectOptions {
|
|
32
|
+
/** Output path: a `.html` file, or a directory to drop the default name in. */
|
|
33
|
+
out?: string;
|
|
34
|
+
/** Budget file path or inline JSON. Omitted means DEFAULT_BUDGET. */
|
|
35
|
+
budget?: string | Budget;
|
|
36
|
+
cwd?: string;
|
|
37
|
+
/** Persist this run and compare against previous ones. Default true. */
|
|
38
|
+
history?: boolean;
|
|
39
|
+
/** Runs kept per URL. Default DEFAULT_HISTORY_MAX. */
|
|
40
|
+
historyMax?: number;
|
|
41
|
+
/** Root of the history store. Default `~/.monomind/browser-reports`. */
|
|
42
|
+
historyDir?: string;
|
|
43
|
+
/** Prior runs charted. Default DEFAULT_TREND_WINDOW. */
|
|
44
|
+
trendWindow?: number;
|
|
45
|
+
}
|
|
46
|
+
export interface ReportResult {
|
|
47
|
+
report: Report;
|
|
48
|
+
htmlPath: string;
|
|
49
|
+
jsonPath: string;
|
|
50
|
+
summary: string;
|
|
51
|
+
/** Present when the run was saved to the history store. */
|
|
52
|
+
historyDir?: string;
|
|
53
|
+
/** Present only for a `--repeat` run (RIG-14); see RepeatResult. */
|
|
54
|
+
flake?: FlakeReport;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve `--out` into an absolute .html path. A path that is an existing
|
|
58
|
+
* directory, or that ends in a separator, gets the generated filename
|
|
59
|
+
* appended — `--out ./reports` should not silently produce a file called
|
|
60
|
+
* `reports`.
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveOutPath(out: string | undefined, url: string, capturedAt: string, cwd: string): Promise<string>;
|
|
63
|
+
/**
|
|
64
|
+
* The JSON sibling drops the base64 image payloads — they are megabytes of
|
|
65
|
+
* noise for anything reading this programmatically, and the HTML already
|
|
66
|
+
* carries them inline. `path` still points at the PNG on disk. The pixel
|
|
67
|
+
* diff's highlight image and the evidence frames go the same way: every
|
|
68
|
+
* measurement survives, only the pixels are dropped.
|
|
69
|
+
*/
|
|
70
|
+
export declare function toJsonReport(report: Report): Record<string, unknown>;
|
|
71
|
+
export declare function writeReportFiles(report: Report, htmlPath: string, extras?: {
|
|
72
|
+
flake?: FlakeReport;
|
|
73
|
+
}): Promise<{
|
|
74
|
+
htmlPath: string;
|
|
75
|
+
jsonPath: string;
|
|
76
|
+
}>;
|
|
77
|
+
/** One run: collect, judge, compare against history, write the two files. */
|
|
78
|
+
export declare function runReport(client: CdpClient, sessionId: string, options: RunReportOptions): Promise<ReportResult>;
|
|
79
|
+
export interface RepeatResult extends ReportResult {
|
|
80
|
+
flake: FlakeReport;
|
|
81
|
+
/** Every run, in order. `report` is the last of them. */
|
|
82
|
+
reports: Report[];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* `--repeat N`: run the same URL N times and judge the set (RIG-14).
|
|
86
|
+
*
|
|
87
|
+
* Only the last run is written out in full — N runs' worth of inlined
|
|
88
|
+
* screenshots is not something anyone wants on disk — but the flake analysis
|
|
89
|
+
* covers all of them, and it is the flake verdict that decides the exit code.
|
|
90
|
+
* A check that passed three times and failed twice must not exit 0.
|
|
91
|
+
*/
|
|
92
|
+
export declare function runReportRepeated(client: CdpClient, sessionId: string, options: RunReportOptions & {
|
|
93
|
+
repeat: number;
|
|
94
|
+
}): Promise<RepeatResult>;
|
|
95
|
+
/** `--history <url>`: the stored runs for a URL, oldest first. */
|
|
96
|
+
export declare function readHistory(url: string, options?: {
|
|
97
|
+
historyDir?: string;
|
|
98
|
+
}): Promise<{
|
|
99
|
+
dir: string;
|
|
100
|
+
runs: HistoryRun[];
|
|
101
|
+
}>;
|
|
102
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/report/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `monobrowse report <url>` — open a page, measure it, judge it against a
|
|
3
|
+
* budget, and leave behind one self-contained HTML file plus a sibling JSON.
|
|
4
|
+
*
|
|
5
|
+
* collect.ts (drive the browser) -> analyze.ts (a11y + budgets -> verdict)
|
|
6
|
+
* -> history/trend/diff (what changed since last time) -> render.ts (HTML).
|
|
7
|
+
* This module is the seam that wires them together and writes the two files.
|
|
8
|
+
*/
|
|
9
|
+
import { mkdir, stat, writeFile } from 'node:fs/promises';
|
|
10
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
11
|
+
import { buildReport, summarizeVerdict } from './analyze.js';
|
|
12
|
+
import { loadBudget } from './budget.js';
|
|
13
|
+
import { collect } from './collect.js';
|
|
14
|
+
import { buildEvidence } from './evidence.js';
|
|
15
|
+
import { analyzeFlake, summarizeFlake } from './flake.js';
|
|
16
|
+
import { DEFAULT_HISTORY_MAX, historyDirFor, historyRoot, listRuns, saveRun, } from './history.js';
|
|
17
|
+
import { renderHtml } from './render.js';
|
|
18
|
+
import { buildRunDiff } from './run-diff.js';
|
|
19
|
+
import { buildTrend } from './trend.js';
|
|
20
|
+
export * from './a11y.js';
|
|
21
|
+
export * from './analyze.js';
|
|
22
|
+
export * from './budget.js';
|
|
23
|
+
export * from './collect.js';
|
|
24
|
+
export * from './collect-a11y.js';
|
|
25
|
+
export * from './evidence.js';
|
|
26
|
+
export * from './flake.js';
|
|
27
|
+
export * from './history.js';
|
|
28
|
+
export * from './pixel-diff.js';
|
|
29
|
+
export * from './png.js';
|
|
30
|
+
export * from './render.js';
|
|
31
|
+
export * from './run-diff.js';
|
|
32
|
+
export * from './structure.js';
|
|
33
|
+
export * from './trend.js';
|
|
34
|
+
export * from './types.js';
|
|
35
|
+
/** Prior runs a trend is charted over. Older runs stay on disk, off the chart. */
|
|
36
|
+
export const DEFAULT_TREND_WINDOW = 10;
|
|
37
|
+
function slugFor(url) {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = new URL(url);
|
|
40
|
+
const path = parsed.pathname.replace(/\/+$/, '').replace(/[^a-z0-9]+/gi, '-');
|
|
41
|
+
return `${parsed.hostname}${path}`.replace(/-+/g, '-').replace(/^-|-$/g, '').toLowerCase();
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return (url
|
|
45
|
+
.replace(/[^a-z0-9]+/gi, '-')
|
|
46
|
+
.replace(/^-|-$/g, '')
|
|
47
|
+
.toLowerCase() || 'page');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function timestamp(iso) {
|
|
51
|
+
return iso.replace(/[-:]/g, '').replace(/\.\d+Z$/, 'Z');
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolve `--out` into an absolute .html path. A path that is an existing
|
|
55
|
+
* directory, or that ends in a separator, gets the generated filename
|
|
56
|
+
* appended — `--out ./reports` should not silently produce a file called
|
|
57
|
+
* `reports`.
|
|
58
|
+
*/
|
|
59
|
+
export async function resolveOutPath(out, url, capturedAt, cwd) {
|
|
60
|
+
const defaultName = `monobrowse-report-${slugFor(url)}-${timestamp(capturedAt)}.html`;
|
|
61
|
+
if (!out)
|
|
62
|
+
return join(cwd, defaultName);
|
|
63
|
+
const abs = isAbsolute(out) ? out : resolve(cwd, out);
|
|
64
|
+
if (/[\\/]$/.test(out))
|
|
65
|
+
return join(abs, defaultName);
|
|
66
|
+
const isDir = await stat(abs)
|
|
67
|
+
.then((s) => s.isDirectory())
|
|
68
|
+
.catch(() => false);
|
|
69
|
+
if (isDir)
|
|
70
|
+
return join(abs, defaultName);
|
|
71
|
+
return abs.toLowerCase().endsWith('.html') || abs.toLowerCase().endsWith('.htm')
|
|
72
|
+
? abs
|
|
73
|
+
: `${abs}.html`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The JSON sibling drops the base64 image payloads — they are megabytes of
|
|
77
|
+
* noise for anything reading this programmatically, and the HTML already
|
|
78
|
+
* carries them inline. `path` still points at the PNG on disk. The pixel
|
|
79
|
+
* diff's highlight image and the evidence frames go the same way: every
|
|
80
|
+
* measurement survives, only the pixels are dropped.
|
|
81
|
+
*/
|
|
82
|
+
export function toJsonReport(report) {
|
|
83
|
+
const { screenshots, recording, diff, evidence, ...rest } = report;
|
|
84
|
+
void recording;
|
|
85
|
+
return {
|
|
86
|
+
...rest,
|
|
87
|
+
screenshots: screenshots.map(({ label, width, height, path }) => ({
|
|
88
|
+
label,
|
|
89
|
+
width,
|
|
90
|
+
height,
|
|
91
|
+
path,
|
|
92
|
+
})),
|
|
93
|
+
...(diff
|
|
94
|
+
? {
|
|
95
|
+
diff: {
|
|
96
|
+
...diff,
|
|
97
|
+
pixels: diff.pixels ? { ...diff.pixels, diffDataUrl: undefined } : undefined,
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
: {}),
|
|
101
|
+
...(evidence
|
|
102
|
+
? {
|
|
103
|
+
evidence: {
|
|
104
|
+
...evidence,
|
|
105
|
+
frames: evidence.frames.map(({ offsetMs, bytes }) => ({ offsetMs, bytes })),
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
: {}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
export async function writeReportFiles(report, htmlPath, extras = {}) {
|
|
112
|
+
const jsonPath = `${htmlPath.replace(/\.html?$/i, '')}.json`;
|
|
113
|
+
await mkdir(dirname(htmlPath), { recursive: true });
|
|
114
|
+
await writeFile(htmlPath, renderHtml(report, extras), 'utf8');
|
|
115
|
+
const json = extras.flake
|
|
116
|
+
? { ...toJsonReport(report), flake: extras.flake }
|
|
117
|
+
: toJsonReport(report);
|
|
118
|
+
await writeFile(jsonPath, `${JSON.stringify(json, null, 2)}\n`, 'utf8');
|
|
119
|
+
return { htmlPath, jsonPath };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Attach the trend and the diff, then persist the run.
|
|
123
|
+
*
|
|
124
|
+
* Order matters: history is read BEFORE the run is saved, so a run never
|
|
125
|
+
* trends or diffs against itself. A failure anywhere in here becomes a note
|
|
126
|
+
* on the report rather than an error — an unwritable home directory should
|
|
127
|
+
* cost you the trend section, not the report.
|
|
128
|
+
*/
|
|
129
|
+
async function applyHistory(report, options) {
|
|
130
|
+
if (options.history === false)
|
|
131
|
+
return undefined;
|
|
132
|
+
const root = options.historyDir ?? historyRoot();
|
|
133
|
+
const dir = historyDirFor(report.url, root);
|
|
134
|
+
try {
|
|
135
|
+
const previous = await listRuns(dir);
|
|
136
|
+
const window = options.trendWindow ?? DEFAULT_TREND_WINDOW;
|
|
137
|
+
report.trend = buildTrend(report, previous.slice(-window));
|
|
138
|
+
report.diff = await buildRunDiff(dir, previous.at(-1), report);
|
|
139
|
+
await saveRun(report, { root, max: options.historyMax ?? DEFAULT_HISTORY_MAX });
|
|
140
|
+
return dir;
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
report.notes.push(`Run history unavailable (${err.message}) — no trend or diff for this run.`);
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/** Turn the recorder's raw frames into the evidence block, when warranted. */
|
|
148
|
+
function applyEvidence(report, data) {
|
|
149
|
+
const recording = data.recording;
|
|
150
|
+
if (!recording)
|
|
151
|
+
return;
|
|
152
|
+
if (report.verdict !== 'fail' && !recording.requested)
|
|
153
|
+
return;
|
|
154
|
+
report.evidence = buildEvidence({
|
|
155
|
+
reason: report.verdict === 'fail' ? 'budget-failure' : 'requested',
|
|
156
|
+
startedAtMs: recording.startedAtMs,
|
|
157
|
+
frames: recording.frames,
|
|
158
|
+
bufferDropped: recording.droppedFrames,
|
|
159
|
+
console: report.console,
|
|
160
|
+
pageErrors: report.pageErrors,
|
|
161
|
+
requests: report.requests,
|
|
162
|
+
failures: report.failures,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
async function resolveBudget(options) {
|
|
166
|
+
return options.budget && typeof options.budget === 'object'
|
|
167
|
+
? options.budget
|
|
168
|
+
: await loadBudget(options.budget);
|
|
169
|
+
}
|
|
170
|
+
/** One run: collect, judge, compare against history, write the two files. */
|
|
171
|
+
export async function runReport(client, sessionId, options) {
|
|
172
|
+
const budget = await resolveBudget(options);
|
|
173
|
+
const data = await collect(client, sessionId, options);
|
|
174
|
+
const report = buildReport(data, budget);
|
|
175
|
+
applyEvidence(report, data);
|
|
176
|
+
const historyDir = await applyHistory(report, options);
|
|
177
|
+
// Working material, not report content — and megabytes of it.
|
|
178
|
+
report.recording = undefined;
|
|
179
|
+
const htmlPath = await resolveOutPath(options.out, options.url, data.capturedAt, options.cwd ?? process.cwd());
|
|
180
|
+
const paths = await writeReportFiles(report, htmlPath);
|
|
181
|
+
return { report, ...paths, summary: summarizeVerdict(report), historyDir };
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* `--repeat N`: run the same URL N times and judge the set (RIG-14).
|
|
185
|
+
*
|
|
186
|
+
* Only the last run is written out in full — N runs' worth of inlined
|
|
187
|
+
* screenshots is not something anyone wants on disk — but the flake analysis
|
|
188
|
+
* covers all of them, and it is the flake verdict that decides the exit code.
|
|
189
|
+
* A check that passed three times and failed twice must not exit 0.
|
|
190
|
+
*/
|
|
191
|
+
export async function runReportRepeated(client, sessionId, options) {
|
|
192
|
+
const times = Math.max(1, Math.floor(options.repeat));
|
|
193
|
+
const budget = await resolveBudget(options);
|
|
194
|
+
const reports = [];
|
|
195
|
+
for (let i = 0; i < times; i++) {
|
|
196
|
+
const data = await collect(client, sessionId, options);
|
|
197
|
+
const report = buildReport(data, budget);
|
|
198
|
+
applyEvidence(report, data);
|
|
199
|
+
report.recording = undefined;
|
|
200
|
+
reports.push(report);
|
|
201
|
+
}
|
|
202
|
+
const flake = analyzeFlake(reports);
|
|
203
|
+
const report = reports[reports.length - 1];
|
|
204
|
+
// Only the run we keep is persisted: writing N history entries for one
|
|
205
|
+
// command would let a single `--repeat 20` evict the whole trend window.
|
|
206
|
+
const historyDir = await applyHistory(report, options);
|
|
207
|
+
const htmlPath = await resolveOutPath(options.out, options.url, report.capturedAt, options.cwd ?? process.cwd());
|
|
208
|
+
const paths = await writeReportFiles(report, htmlPath, { flake });
|
|
209
|
+
return { report, reports, flake, ...paths, summary: summarizeFlake(flake), historyDir };
|
|
210
|
+
}
|
|
211
|
+
/** `--history <url>`: the stored runs for a URL, oldest first. */
|
|
212
|
+
export async function readHistory(url, options = {}) {
|
|
213
|
+
const dir = historyDirFor(url, options.historyDir ?? historyRoot());
|
|
214
|
+
return { dir, runs: await listRuns(dir) };
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/report/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pixel diff between two screenshots (RIG-10), with no image dependency —
|
|
3
|
+
* png.ts decodes and re-encodes using node's own zlib.
|
|
4
|
+
*
|
|
5
|
+
* Two design choices worth stating:
|
|
6
|
+
*
|
|
7
|
+
* - A per-channel tolerance, not exact equality. Chrome's rasteriser is not
|
|
8
|
+
* bit-deterministic across runs (font hinting, GPU vs. software paths), so
|
|
9
|
+
* an exact compare reports 4% of every screenshot as "changed" and the
|
|
10
|
+
* number becomes noise nobody reads.
|
|
11
|
+
* - Differing dimensions are a result, not an error. A page that grew 200px
|
|
12
|
+
* taller is exactly the regression this is for, so we compare the
|
|
13
|
+
* overlapping region, count the rest as changed, and say so.
|
|
14
|
+
*/
|
|
15
|
+
import type { PixelDiff } from './types.js';
|
|
16
|
+
export interface PixelDiffOptions {
|
|
17
|
+
threshold?: number;
|
|
18
|
+
maxPixels?: number;
|
|
19
|
+
/** Set false to skip building the highlight image (counts only). */
|
|
20
|
+
renderImage?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Compare two PNGs. Never throws for image-shaped reasons — an undecodable
|
|
24
|
+
* screenshot produces a result carrying `note` and no counts, because a
|
|
25
|
+
* report that renders minus one section beats a report that fails to build.
|
|
26
|
+
*/
|
|
27
|
+
export declare function pixelDiff(previousPng: Uint8Array, currentPng: Uint8Array, options?: PixelDiffOptions): PixelDiff;
|
|
28
|
+
/** One line for a CI log. */
|
|
29
|
+
export declare function summarizePixelDiff(diff: PixelDiff): string;
|
|
30
|
+
//# sourceMappingURL=pixel-diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pixel-diff.d.ts","sourceRoot":"","sources":["../../../src/report/pixel-diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAW5C,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAwED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,WAAW,EAAE,UAAU,EACvB,UAAU,EAAE,UAAU,EACtB,OAAO,GAAE,gBAAqB,GAC7B,SAAS,CAwEX;AAED,6BAA6B;AAC7B,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAQ1D"}
|