@michaelmagan/dnumb 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +180 -0
- package/LICENSE +21 -0
- package/README.md +297 -0
- package/dist/analysis/cluster.cjs +114 -0
- package/dist/analysis/cluster.d.cts +68 -0
- package/dist/analysis/cluster.d.ts +68 -0
- package/dist/analysis/cluster.js +109 -0
- package/dist/analysis/distances.cjs +124 -0
- package/dist/analysis/distances.d.cts +58 -0
- package/dist/analysis/distances.d.ts +58 -0
- package/dist/analysis/distances.js +119 -0
- package/dist/analysis/geometry.cjs +60 -0
- package/dist/analysis/geometry.d.cts +34 -0
- package/dist/analysis/geometry.d.ts +34 -0
- package/dist/analysis/geometry.js +53 -0
- package/dist/analysis/layout-tree.cjs +245 -0
- package/dist/analysis/layout-tree.d.cts +45 -0
- package/dist/analysis/layout-tree.d.ts +45 -0
- package/dist/analysis/layout-tree.js +242 -0
- package/dist/analysis/region-query.cjs +71 -0
- package/dist/analysis/region-query.d.cts +25 -0
- package/dist/analysis/region-query.d.ts +25 -0
- package/dist/analysis/region-query.js +67 -0
- package/dist/analysis/types.cjs +7 -0
- package/dist/analysis/types.d.cts +143 -0
- package/dist/analysis/types.d.ts +143 -0
- package/dist/analysis/types.js +6 -0
- package/dist/bundle.cjs +182 -0
- package/dist/bundle.d.cts +91 -0
- package/dist/bundle.d.ts +91 -0
- package/dist/bundle.js +175 -0
- package/dist/cli.cjs +755 -0
- package/dist/cli.d.cts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +719 -0
- package/dist/diff/changes.cjs +656 -0
- package/dist/diff/changes.d.cts +2 -0
- package/dist/diff/changes.d.ts +2 -0
- package/dist/diff/changes.js +653 -0
- package/dist/diff/identity.cjs +167 -0
- package/dist/diff/identity.d.cts +62 -0
- package/dist/diff/identity.d.ts +62 -0
- package/dist/diff/identity.js +159 -0
- package/dist/diff/index.cjs +84 -0
- package/dist/diff/index.d.cts +76 -0
- package/dist/diff/index.d.ts +76 -0
- package/dist/diff/index.js +68 -0
- package/dist/diff/match.cjs +156 -0
- package/dist/diff/match.d.cts +17 -0
- package/dist/diff/match.d.ts +17 -0
- package/dist/diff/match.js +152 -0
- package/dist/diff/render.cjs +540 -0
- package/dist/diff/render.d.cts +2 -0
- package/dist/diff/render.d.ts +2 -0
- package/dist/diff/render.js +537 -0
- package/dist/diff/types.cjs +2 -0
- package/dist/diff/types.d.cts +211 -0
- package/dist/diff/types.d.ts +211 -0
- package/dist/diff/types.js +1 -0
- package/dist/digest/index.cjs +8 -0
- package/dist/digest/index.d.cts +4 -0
- package/dist/digest/index.d.ts +4 -0
- package/dist/digest/index.js +2 -0
- package/dist/digest/parse.cjs +163 -0
- package/dist/digest/parse.d.cts +80 -0
- package/dist/digest/parse.d.ts +80 -0
- package/dist/digest/parse.js +160 -0
- package/dist/digest/render.cjs +1575 -0
- package/dist/digest/render.d.cts +41 -0
- package/dist/digest/render.d.ts +41 -0
- package/dist/digest/render.js +1571 -0
- package/dist/digest/types.cjs +10 -0
- package/dist/digest/types.d.cts +86 -0
- package/dist/digest/types.d.ts +86 -0
- package/dist/digest/types.js +9 -0
- package/dist/index.cjs +61 -0
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +19 -0
- package/dist/page.cjs +35 -0
- package/dist/page.d.cts +40 -0
- package/dist/page.d.ts +40 -0
- package/dist/page.js +31 -0
- package/dist/playwright.cjs +138 -0
- package/dist/playwright.d.cts +69 -0
- package/dist/playwright.d.ts +69 -0
- package/dist/playwright.js +131 -0
- package/dist/query.cjs +1152 -0
- package/dist/query.d.cts +47 -0
- package/dist/query.d.ts +47 -0
- package/dist/query.js +1146 -0
- package/dist/snapshot/capture.cjs +80 -0
- package/dist/snapshot/capture.d.cts +34 -0
- package/dist/snapshot/capture.d.ts +34 -0
- package/dist/snapshot/capture.js +77 -0
- package/dist/snapshot/cdp.cjs +450 -0
- package/dist/snapshot/cdp.d.cts +36 -0
- package/dist/snapshot/cdp.d.ts +36 -0
- package/dist/snapshot/cdp.js +447 -0
- package/dist/snapshot/clip.cjs +100 -0
- package/dist/snapshot/clip.d.cts +26 -0
- package/dist/snapshot/clip.d.ts +26 -0
- package/dist/snapshot/clip.js +96 -0
- package/dist/snapshot/collect.cjs +358 -0
- package/dist/snapshot/collect.d.cts +18 -0
- package/dist/snapshot/collect.d.ts +18 -0
- package/dist/snapshot/collect.js +355 -0
- package/dist/snapshot/color.cjs +1039 -0
- package/dist/snapshot/color.d.cts +316 -0
- package/dist/snapshot/color.d.ts +316 -0
- package/dist/snapshot/color.js +1026 -0
- package/dist/snapshot/facts.cjs +167 -0
- package/dist/snapshot/facts.d.cts +48 -0
- package/dist/snapshot/facts.d.ts +48 -0
- package/dist/snapshot/facts.js +161 -0
- package/dist/snapshot/limits.cjs +69 -0
- package/dist/snapshot/limits.d.cts +59 -0
- package/dist/snapshot/limits.d.ts +59 -0
- package/dist/snapshot/limits.js +65 -0
- package/dist/snapshot/not-measured.cjs +44 -0
- package/dist/snapshot/not-measured.d.cts +14 -0
- package/dist/snapshot/not-measured.d.ts +14 -0
- package/dist/snapshot/not-measured.js +41 -0
- package/dist/snapshot/types.cjs +2 -0
- package/dist/snapshot/types.d.cts +238 -0
- package/dist/snapshot/types.d.ts +238 -0
- package/dist/snapshot/types.js +1 -0
- package/dist/spec/index.cjs +9 -0
- package/dist/spec/index.d.cts +15 -0
- package/dist/spec/index.d.ts +15 -0
- package/dist/spec/index.js +6 -0
- package/package.json +141 -0
- package/skills/dnumb/SKILL.md +136 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared types for `src/digest/*`.
|
|
4
|
+
*
|
|
5
|
+
* The digest reads `ElementRecord[]` and the facts derived from them, and
|
|
6
|
+
* nothing else — no CLI types, no Playwright, no check engine. The prototype
|
|
7
|
+
* enforced the same boundary and it is the reason the digest and the diff read
|
|
8
|
+
* one geometry implementation rather than two that quietly diverge.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for `src/digest/*`.
|
|
3
|
+
*
|
|
4
|
+
* The digest reads `ElementRecord[]` and the facts derived from them, and
|
|
5
|
+
* nothing else — no CLI types, no Playwright, no check engine. The prototype
|
|
6
|
+
* enforced the same boundary and it is the reason the digest and the diff read
|
|
7
|
+
* one geometry implementation rather than two that quietly diverge.
|
|
8
|
+
*/
|
|
9
|
+
export interface DigestMeta {
|
|
10
|
+
url: string;
|
|
11
|
+
viewport: {
|
|
12
|
+
name?: string;
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* The state this capture was taken in — `menu-open`, `dark`, `after-submit`.
|
|
18
|
+
* New in v2, and the reason the format can describe more than one moment of a
|
|
19
|
+
* page: without it, two captures of the same URL are indistinguishable in
|
|
20
|
+
* their own headers.
|
|
21
|
+
*/
|
|
22
|
+
state?: string;
|
|
23
|
+
capturedAt?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Which capture path produced this snapshot. Surfaced in the header because
|
|
26
|
+
* `cdp` and `walk` do not see the same page on any page using shadow DOM —
|
|
27
|
+
* `walk` cannot enter a shadow root at all, `cdp` flattens one in — so a
|
|
28
|
+
* reader comparing two digests needs to know which capability tier each one
|
|
29
|
+
* came from, not just what it found.
|
|
30
|
+
*/
|
|
31
|
+
strategy?: 'cdp' | 'walk';
|
|
32
|
+
/**
|
|
33
|
+
* True when `strategy` is `'walk'` only because the CDP fast path was tried
|
|
34
|
+
* on a Chromium page and its own protocol call failed — an unplanned
|
|
35
|
+
* downgrade, not a deliberate portable capture. See
|
|
36
|
+
* `CaptureResult.cdpFellBack`.
|
|
37
|
+
*/
|
|
38
|
+
cdpFellBack?: boolean;
|
|
39
|
+
}
|
|
40
|
+
export interface RenderDigestOptions {
|
|
41
|
+
/**
|
|
42
|
+
* Soft target for total body characters. Default 32768 (~11.5K tokens at
|
|
43
|
+
* the corpus's measured 2.84 chars/token) -- see `DEFAULT_BUDGET_CHARS`'s
|
|
44
|
+
* own doc comment in `digest/render.ts` and `docs/notes/06-budget.md` §2
|
|
45
|
+
* and §5 for why characters replaced lines as the meter, and why that
|
|
46
|
+
* number. Exceeding it makes folding progressively more aggressive, then
|
|
47
|
+
* collapses subtrees leaf-up (`collapseCandidates`, shallowest subtree
|
|
48
|
+
* first), rather than truncating -- a digest that stops mid-tree is worse
|
|
49
|
+
* than one that summarises the repetitive parts. Not applied under
|
|
50
|
+
* `--focus`/`options.focus` -- see that option's doc comment.
|
|
51
|
+
*/
|
|
52
|
+
budgetChars?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Soft target for total body *lines* -- the budget's original unit, before
|
|
55
|
+
* `docs/notes/06-budget.md` replaced it with `budgetChars` (lines predict
|
|
56
|
+
* a reader's real token cost within a factor of ~15 on ordinary bodies,
|
|
57
|
+
* characters within ±20%). Still honored when a caller passes it
|
|
58
|
+
* explicitly: `src/cli.ts`'s `--budget` flag and `src/query.ts`'s
|
|
59
|
+
* `QueryOptions` both still type and forward it, and this package is not
|
|
60
|
+
* the sole owner of either tonight, so this stays a real, working option
|
|
61
|
+
* rather than a silently-dropped one. It carries no default of its own,
|
|
62
|
+
* though -- a bare `renderDigest(snapshot, meta)` call is bounded by
|
|
63
|
+
* `budgetChars` alone -- and when both are set, the render stays under
|
|
64
|
+
* whichever limit is stricter.
|
|
65
|
+
*/
|
|
66
|
+
budgetLines?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Scope to one element's subtree, by element id or tag. Neither budget
|
|
69
|
+
* option applies once focused: stage 2 (fold) and stage 3 (collapse) are
|
|
70
|
+
* both disabled entirely under `--focus`, so a focused render can exceed
|
|
71
|
+
* `budgetChars`/`budgetLines` when the focused subtree itself is large.
|
|
72
|
+
* That is deliberate, not an oversight -- see `renderDigest`'s own doc
|
|
73
|
+
* comment on the stage-3 loop for why re-applying a budget-driven collapse
|
|
74
|
+
* inside an already-focused subtree would reopen the exact non-convergence
|
|
75
|
+
* bug disabling it there was written to fix (chasing a `(--focus <id>)`
|
|
76
|
+
* hint that lands on another partial, re-collapsed view of the same
|
|
77
|
+
* subtree instead of the full content the caller asked for).
|
|
78
|
+
*/
|
|
79
|
+
focus?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Emit the edge-cluster histogram header. Off by default: it is a page-wide
|
|
82
|
+
* summary that costs several dense lines and answers a question most captures
|
|
83
|
+
* are not asking.
|
|
84
|
+
*/
|
|
85
|
+
edges?: boolean;
|
|
86
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for `src/digest/*`.
|
|
3
|
+
*
|
|
4
|
+
* The digest reads `ElementRecord[]` and the facts derived from them, and
|
|
5
|
+
* nothing else — no CLI types, no Playwright, no check engine. The prototype
|
|
6
|
+
* enforced the same boundary and it is the reason the digest and the diff read
|
|
7
|
+
* one geometry implementation rather than two that quietly diverge.
|
|
8
|
+
*/
|
|
9
|
+
export interface DigestMeta {
|
|
10
|
+
url: string;
|
|
11
|
+
viewport: {
|
|
12
|
+
name?: string;
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* The state this capture was taken in — `menu-open`, `dark`, `after-submit`.
|
|
18
|
+
* New in v2, and the reason the format can describe more than one moment of a
|
|
19
|
+
* page: without it, two captures of the same URL are indistinguishable in
|
|
20
|
+
* their own headers.
|
|
21
|
+
*/
|
|
22
|
+
state?: string;
|
|
23
|
+
capturedAt?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Which capture path produced this snapshot. Surfaced in the header because
|
|
26
|
+
* `cdp` and `walk` do not see the same page on any page using shadow DOM —
|
|
27
|
+
* `walk` cannot enter a shadow root at all, `cdp` flattens one in — so a
|
|
28
|
+
* reader comparing two digests needs to know which capability tier each one
|
|
29
|
+
* came from, not just what it found.
|
|
30
|
+
*/
|
|
31
|
+
strategy?: 'cdp' | 'walk';
|
|
32
|
+
/**
|
|
33
|
+
* True when `strategy` is `'walk'` only because the CDP fast path was tried
|
|
34
|
+
* on a Chromium page and its own protocol call failed — an unplanned
|
|
35
|
+
* downgrade, not a deliberate portable capture. See
|
|
36
|
+
* `CaptureResult.cdpFellBack`.
|
|
37
|
+
*/
|
|
38
|
+
cdpFellBack?: boolean;
|
|
39
|
+
}
|
|
40
|
+
export interface RenderDigestOptions {
|
|
41
|
+
/**
|
|
42
|
+
* Soft target for total body characters. Default 32768 (~11.5K tokens at
|
|
43
|
+
* the corpus's measured 2.84 chars/token) -- see `DEFAULT_BUDGET_CHARS`'s
|
|
44
|
+
* own doc comment in `digest/render.ts` and `docs/notes/06-budget.md` §2
|
|
45
|
+
* and §5 for why characters replaced lines as the meter, and why that
|
|
46
|
+
* number. Exceeding it makes folding progressively more aggressive, then
|
|
47
|
+
* collapses subtrees leaf-up (`collapseCandidates`, shallowest subtree
|
|
48
|
+
* first), rather than truncating -- a digest that stops mid-tree is worse
|
|
49
|
+
* than one that summarises the repetitive parts. Not applied under
|
|
50
|
+
* `--focus`/`options.focus` -- see that option's doc comment.
|
|
51
|
+
*/
|
|
52
|
+
budgetChars?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Soft target for total body *lines* -- the budget's original unit, before
|
|
55
|
+
* `docs/notes/06-budget.md` replaced it with `budgetChars` (lines predict
|
|
56
|
+
* a reader's real token cost within a factor of ~15 on ordinary bodies,
|
|
57
|
+
* characters within ±20%). Still honored when a caller passes it
|
|
58
|
+
* explicitly: `src/cli.ts`'s `--budget` flag and `src/query.ts`'s
|
|
59
|
+
* `QueryOptions` both still type and forward it, and this package is not
|
|
60
|
+
* the sole owner of either tonight, so this stays a real, working option
|
|
61
|
+
* rather than a silently-dropped one. It carries no default of its own,
|
|
62
|
+
* though -- a bare `renderDigest(snapshot, meta)` call is bounded by
|
|
63
|
+
* `budgetChars` alone -- and when both are set, the render stays under
|
|
64
|
+
* whichever limit is stricter.
|
|
65
|
+
*/
|
|
66
|
+
budgetLines?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Scope to one element's subtree, by element id or tag. Neither budget
|
|
69
|
+
* option applies once focused: stage 2 (fold) and stage 3 (collapse) are
|
|
70
|
+
* both disabled entirely under `--focus`, so a focused render can exceed
|
|
71
|
+
* `budgetChars`/`budgetLines` when the focused subtree itself is large.
|
|
72
|
+
* That is deliberate, not an oversight -- see `renderDigest`'s own doc
|
|
73
|
+
* comment on the stage-3 loop for why re-applying a budget-driven collapse
|
|
74
|
+
* inside an already-focused subtree would reopen the exact non-convergence
|
|
75
|
+
* bug disabling it there was written to fix (chasing a `(--focus <id>)`
|
|
76
|
+
* hint that lands on another partial, re-collapsed view of the same
|
|
77
|
+
* subtree instead of the full content the caller asked for).
|
|
78
|
+
*/
|
|
79
|
+
focus?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Emit the edge-cluster histogram header. Off by default: it is a page-wide
|
|
82
|
+
* summary that costs several dense lines and answers a question most captures
|
|
83
|
+
* are not asking.
|
|
84
|
+
*/
|
|
85
|
+
edges?: boolean;
|
|
86
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for `src/digest/*`.
|
|
3
|
+
*
|
|
4
|
+
* The digest reads `ElementRecord[]` and the facts derived from them, and
|
|
5
|
+
* nothing else — no CLI types, no Playwright, no check engine. The prototype
|
|
6
|
+
* enforced the same boundary and it is the reason the digest and the diff read
|
|
7
|
+
* one geometry implementation rather than two that quietly diverge.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Public library entrypoint.
|
|
4
|
+
*
|
|
5
|
+
* The capture bundle, not the browser session, is the load-bearing object here.
|
|
6
|
+
* Everything in this package is a pure function over `ElementRecord[]` plus a
|
|
7
|
+
* thin host that produces them.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.SPEC_VERSION = exports.formatNotMeasured = exports.renderFacts = exports.factsForAll = exports.factsFor = exports.toHex = exports.resolveBackdrop = exports.renderColorFact = exports.relativeLuminance = exports.parseCssColor = exports.groupColorFacts = exports.detectOverlay = exports.contrastRatio = exports.colorFactsForAll = exports.colorFactFor = exports.backdropLabel = exports.collectSnapshot = exports.clipFactFor = exports.attachClipFacts = exports.capture = exports.viewportOf = exports.isCDPCapable = exports.SPARSE_CAPTURE_MARKER = exports.renderDigest = exports.parseDigestLine = exports.renderDiff = exports.prepare = exports.match = exports.diffCaptures = exports.computeChanges = exports.serializeBundle = exports.parseBundle = exports.buildBundle = exports.BundleFormatError = exports.intersect = exports.gapAlong = exports.contains = exports.center = exports.area = void 0;
|
|
11
|
+
var geometry_js_1 = require("./analysis/geometry.cjs");
|
|
12
|
+
Object.defineProperty(exports, "area", { enumerable: true, get: function () { return geometry_js_1.area; } });
|
|
13
|
+
Object.defineProperty(exports, "center", { enumerable: true, get: function () { return geometry_js_1.center; } });
|
|
14
|
+
Object.defineProperty(exports, "contains", { enumerable: true, get: function () { return geometry_js_1.contains; } });
|
|
15
|
+
Object.defineProperty(exports, "gapAlong", { enumerable: true, get: function () { return geometry_js_1.gapAlong; } });
|
|
16
|
+
Object.defineProperty(exports, "intersect", { enumerable: true, get: function () { return geometry_js_1.intersect; } });
|
|
17
|
+
var bundle_js_1 = require("./bundle.cjs");
|
|
18
|
+
Object.defineProperty(exports, "BundleFormatError", { enumerable: true, get: function () { return bundle_js_1.BundleFormatError; } });
|
|
19
|
+
Object.defineProperty(exports, "buildBundle", { enumerable: true, get: function () { return bundle_js_1.buildBundle; } });
|
|
20
|
+
Object.defineProperty(exports, "parseBundle", { enumerable: true, get: function () { return bundle_js_1.parseBundle; } });
|
|
21
|
+
Object.defineProperty(exports, "serializeBundle", { enumerable: true, get: function () { return bundle_js_1.serializeBundle; } });
|
|
22
|
+
var index_js_1 = require("./diff/index.cjs");
|
|
23
|
+
Object.defineProperty(exports, "computeChanges", { enumerable: true, get: function () { return index_js_1.computeChanges; } });
|
|
24
|
+
Object.defineProperty(exports, "diffCaptures", { enumerable: true, get: function () { return index_js_1.diffCaptures; } });
|
|
25
|
+
Object.defineProperty(exports, "match", { enumerable: true, get: function () { return index_js_1.match; } });
|
|
26
|
+
Object.defineProperty(exports, "prepare", { enumerable: true, get: function () { return index_js_1.prepare; } });
|
|
27
|
+
Object.defineProperty(exports, "renderDiff", { enumerable: true, get: function () { return index_js_1.renderDiff; } });
|
|
28
|
+
var index_js_2 = require("./digest/index.cjs");
|
|
29
|
+
Object.defineProperty(exports, "parseDigestLine", { enumerable: true, get: function () { return index_js_2.parseDigestLine; } });
|
|
30
|
+
Object.defineProperty(exports, "renderDigest", { enumerable: true, get: function () { return index_js_2.renderDigest; } });
|
|
31
|
+
Object.defineProperty(exports, "SPARSE_CAPTURE_MARKER", { enumerable: true, get: function () { return index_js_2.SPARSE_CAPTURE_MARKER; } });
|
|
32
|
+
var page_js_1 = require("./page.cjs");
|
|
33
|
+
Object.defineProperty(exports, "isCDPCapable", { enumerable: true, get: function () { return page_js_1.isCDPCapable; } });
|
|
34
|
+
Object.defineProperty(exports, "viewportOf", { enumerable: true, get: function () { return page_js_1.viewportOf; } });
|
|
35
|
+
var capture_js_1 = require("./snapshot/capture.cjs");
|
|
36
|
+
Object.defineProperty(exports, "capture", { enumerable: true, get: function () { return capture_js_1.capture; } });
|
|
37
|
+
var clip_js_1 = require("./snapshot/clip.cjs");
|
|
38
|
+
Object.defineProperty(exports, "attachClipFacts", { enumerable: true, get: function () { return clip_js_1.attachClipFacts; } });
|
|
39
|
+
Object.defineProperty(exports, "clipFactFor", { enumerable: true, get: function () { return clip_js_1.clipFactFor; } });
|
|
40
|
+
var collect_js_1 = require("./snapshot/collect.cjs");
|
|
41
|
+
Object.defineProperty(exports, "collectSnapshot", { enumerable: true, get: function () { return collect_js_1.collectSnapshot; } });
|
|
42
|
+
var color_js_1 = require("./snapshot/color.cjs");
|
|
43
|
+
Object.defineProperty(exports, "backdropLabel", { enumerable: true, get: function () { return color_js_1.backdropLabel; } });
|
|
44
|
+
Object.defineProperty(exports, "colorFactFor", { enumerable: true, get: function () { return color_js_1.colorFactFor; } });
|
|
45
|
+
Object.defineProperty(exports, "colorFactsForAll", { enumerable: true, get: function () { return color_js_1.colorFactsForAll; } });
|
|
46
|
+
Object.defineProperty(exports, "contrastRatio", { enumerable: true, get: function () { return color_js_1.contrastRatio; } });
|
|
47
|
+
Object.defineProperty(exports, "detectOverlay", { enumerable: true, get: function () { return color_js_1.detectOverlay; } });
|
|
48
|
+
Object.defineProperty(exports, "groupColorFacts", { enumerable: true, get: function () { return color_js_1.groupColorFacts; } });
|
|
49
|
+
Object.defineProperty(exports, "parseCssColor", { enumerable: true, get: function () { return color_js_1.parseCssColor; } });
|
|
50
|
+
Object.defineProperty(exports, "relativeLuminance", { enumerable: true, get: function () { return color_js_1.relativeLuminance; } });
|
|
51
|
+
Object.defineProperty(exports, "renderColorFact", { enumerable: true, get: function () { return color_js_1.renderColorFact; } });
|
|
52
|
+
Object.defineProperty(exports, "resolveBackdrop", { enumerable: true, get: function () { return color_js_1.resolveBackdrop; } });
|
|
53
|
+
Object.defineProperty(exports, "toHex", { enumerable: true, get: function () { return color_js_1.toHex; } });
|
|
54
|
+
var facts_js_1 = require("./snapshot/facts.cjs");
|
|
55
|
+
Object.defineProperty(exports, "factsFor", { enumerable: true, get: function () { return facts_js_1.factsFor; } });
|
|
56
|
+
Object.defineProperty(exports, "factsForAll", { enumerable: true, get: function () { return facts_js_1.factsForAll; } });
|
|
57
|
+
Object.defineProperty(exports, "renderFacts", { enumerable: true, get: function () { return facts_js_1.renderFacts; } });
|
|
58
|
+
var not_measured_js_1 = require("./snapshot/not-measured.cjs");
|
|
59
|
+
Object.defineProperty(exports, "formatNotMeasured", { enumerable: true, get: function () { return not_measured_js_1.formatNotMeasured; } });
|
|
60
|
+
var index_js_3 = require("./spec/index.cjs");
|
|
61
|
+
Object.defineProperty(exports, "SPEC_VERSION", { enumerable: true, get: function () { return index_js_3.SPEC_VERSION; } });
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public library entrypoint.
|
|
3
|
+
*
|
|
4
|
+
* The capture bundle, not the browser session, is the load-bearing object here.
|
|
5
|
+
* Everything in this package is a pure function over `ElementRecord[]` plus a
|
|
6
|
+
* thin host that produces them.
|
|
7
|
+
*/
|
|
8
|
+
export { area, center, contains, gapAlong, intersect } from "./analysis/geometry.cjs";
|
|
9
|
+
export type { BuildBundleOptions, Bundle } from "./bundle.cjs";
|
|
10
|
+
export { BundleFormatError, buildBundle, parseBundle, serializeBundle } from "./bundle.cjs";
|
|
11
|
+
export type { BoxDelta, Change, ChangeKind, DiffCapturesOptions, DiffMeta, DiffOptions, DiffResult, Match, Prepared, } from "./diff/index.cjs";
|
|
12
|
+
export { computeChanges, diffCaptures, match, prepare, renderDiff } from "./diff/index.cjs";
|
|
13
|
+
export type { DigestMeta, ParsedElementLine, ParsedLine, RenderDigestOptions, } from "./digest/index.cjs";
|
|
14
|
+
export { parseDigestLine, renderDigest, SPARSE_CAPTURE_MARKER } from "./digest/index.cjs";
|
|
15
|
+
export type { CDPCapablePage, CDPSessionLike, PageLike } from "./page.cjs";
|
|
16
|
+
export { isCDPCapable, viewportOf } from "./page.cjs";
|
|
17
|
+
export type { CaptureOptions, CaptureResult } from "./snapshot/capture.cjs";
|
|
18
|
+
export { capture } from "./snapshot/capture.cjs";
|
|
19
|
+
export { attachClipFacts, clipFactFor } from "./snapshot/clip.cjs";
|
|
20
|
+
export { collectSnapshot } from "./snapshot/collect.cjs";
|
|
21
|
+
export type { Backdrop, BackdropLayer, ColorFact, ColorPair, ParsedColor, RatioReason, ResolvedBackdrop, RGBA, } from "./snapshot/color.cjs";
|
|
22
|
+
export { backdropLabel, colorFactFor, colorFactsForAll, contrastRatio, detectOverlay, groupColorFacts, parseCssColor, relativeLuminance, renderColorFact, resolveBackdrop, toHex, } from "./snapshot/color.cjs";
|
|
23
|
+
export type { Fact, FactKind } from "./snapshot/facts.cjs";
|
|
24
|
+
export { factsFor, factsForAll, renderFacts } from "./snapshot/facts.cjs";
|
|
25
|
+
export { formatNotMeasured } from "./snapshot/not-measured.cjs";
|
|
26
|
+
export type { BoxSides, ClipFact, ElementRecord, NotMeasured, Rect, Snapshot, } from "./snapshot/types.cjs";
|
|
27
|
+
export { SPEC_VERSION } from "./spec/index.cjs";
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public library entrypoint.
|
|
3
|
+
*
|
|
4
|
+
* The capture bundle, not the browser session, is the load-bearing object here.
|
|
5
|
+
* Everything in this package is a pure function over `ElementRecord[]` plus a
|
|
6
|
+
* thin host that produces them.
|
|
7
|
+
*/
|
|
8
|
+
export { area, center, contains, gapAlong, intersect } from "./analysis/geometry.js";
|
|
9
|
+
export type { BuildBundleOptions, Bundle } from "./bundle.js";
|
|
10
|
+
export { BundleFormatError, buildBundle, parseBundle, serializeBundle } from "./bundle.js";
|
|
11
|
+
export type { BoxDelta, Change, ChangeKind, DiffCapturesOptions, DiffMeta, DiffOptions, DiffResult, Match, Prepared, } from "./diff/index.js";
|
|
12
|
+
export { computeChanges, diffCaptures, match, prepare, renderDiff } from "./diff/index.js";
|
|
13
|
+
export type { DigestMeta, ParsedElementLine, ParsedLine, RenderDigestOptions, } from "./digest/index.js";
|
|
14
|
+
export { parseDigestLine, renderDigest, SPARSE_CAPTURE_MARKER } from "./digest/index.js";
|
|
15
|
+
export type { CDPCapablePage, CDPSessionLike, PageLike } from "./page.js";
|
|
16
|
+
export { isCDPCapable, viewportOf } from "./page.js";
|
|
17
|
+
export type { CaptureOptions, CaptureResult } from "./snapshot/capture.js";
|
|
18
|
+
export { capture } from "./snapshot/capture.js";
|
|
19
|
+
export { attachClipFacts, clipFactFor } from "./snapshot/clip.js";
|
|
20
|
+
export { collectSnapshot } from "./snapshot/collect.js";
|
|
21
|
+
export type { Backdrop, BackdropLayer, ColorFact, ColorPair, ParsedColor, RatioReason, ResolvedBackdrop, RGBA, } from "./snapshot/color.js";
|
|
22
|
+
export { backdropLabel, colorFactFor, colorFactsForAll, contrastRatio, detectOverlay, groupColorFacts, parseCssColor, relativeLuminance, renderColorFact, resolveBackdrop, toHex, } from "./snapshot/color.js";
|
|
23
|
+
export type { Fact, FactKind } from "./snapshot/facts.js";
|
|
24
|
+
export { factsFor, factsForAll, renderFacts } from "./snapshot/facts.js";
|
|
25
|
+
export { formatNotMeasured } from "./snapshot/not-measured.js";
|
|
26
|
+
export type { BoxSides, ClipFact, ElementRecord, NotMeasured, Rect, Snapshot, } from "./snapshot/types.js";
|
|
27
|
+
export { SPEC_VERSION } from "./spec/index.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public library entrypoint.
|
|
3
|
+
*
|
|
4
|
+
* The capture bundle, not the browser session, is the load-bearing object here.
|
|
5
|
+
* Everything in this package is a pure function over `ElementRecord[]` plus a
|
|
6
|
+
* thin host that produces them.
|
|
7
|
+
*/
|
|
8
|
+
export { area, center, contains, gapAlong, intersect } from "./analysis/geometry.js";
|
|
9
|
+
export { BundleFormatError, buildBundle, parseBundle, serializeBundle } from "./bundle.js";
|
|
10
|
+
export { computeChanges, diffCaptures, match, prepare, renderDiff } from "./diff/index.js";
|
|
11
|
+
export { parseDigestLine, renderDigest, SPARSE_CAPTURE_MARKER } from "./digest/index.js";
|
|
12
|
+
export { isCDPCapable, viewportOf } from "./page.js";
|
|
13
|
+
export { capture } from "./snapshot/capture.js";
|
|
14
|
+
export { attachClipFacts, clipFactFor } from "./snapshot/clip.js";
|
|
15
|
+
export { collectSnapshot } from "./snapshot/collect.js";
|
|
16
|
+
export { backdropLabel, colorFactFor, colorFactsForAll, contrastRatio, detectOverlay, groupColorFacts, parseCssColor, relativeLuminance, renderColorFact, resolveBackdrop, toHex, } from "./snapshot/color.js";
|
|
17
|
+
export { factsFor, factsForAll, renderFacts } from "./snapshot/facts.js";
|
|
18
|
+
export { formatNotMeasured } from "./snapshot/not-measured.js";
|
|
19
|
+
export { SPEC_VERSION } from "./spec/index.js";
|
package/dist/page.cjs
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The driver interface, defined structurally so the core of this package needs
|
|
4
|
+
* no Playwright at all.
|
|
5
|
+
*
|
|
6
|
+
* A caller passes whatever `Page` they already have — Playwright's, or anything
|
|
7
|
+
* that answers `evaluate`. `playwright-core` is an *optional* peer precisely so
|
|
8
|
+
* that a consumer who never captures live (offline `dnumb --from <bundle>`)
|
|
9
|
+
* pays nothing for it.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.isCDPCapable = isCDPCapable;
|
|
13
|
+
exports.viewportOf = viewportOf;
|
|
14
|
+
function isCDPCapable(page) {
|
|
15
|
+
const context = page.context;
|
|
16
|
+
if (typeof context !== 'function')
|
|
17
|
+
return false;
|
|
18
|
+
try {
|
|
19
|
+
return typeof context.call(page)?.newCDPSession === 'function';
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Some drivers throw rather than returning a context. Not CDP-capable.
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The viewport, read from inside the page.
|
|
28
|
+
*
|
|
29
|
+
* Playwright's `page.viewportSize()` returns **null** over `connectOverCDP`,
|
|
30
|
+
* which is exactly the mode the interactive path uses — so it cannot be the
|
|
31
|
+
* source of truth. `innerWidth`/`innerHeight` are always right.
|
|
32
|
+
*/
|
|
33
|
+
async function viewportOf(page) {
|
|
34
|
+
return page.evaluate(() => ({ width: window.innerWidth, height: window.innerHeight }));
|
|
35
|
+
}
|
package/dist/page.d.cts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The driver interface, defined structurally so the core of this package needs
|
|
3
|
+
* no Playwright at all.
|
|
4
|
+
*
|
|
5
|
+
* A caller passes whatever `Page` they already have — Playwright's, or anything
|
|
6
|
+
* that answers `evaluate`. `playwright-core` is an *optional* peer precisely so
|
|
7
|
+
* that a consumer who never captures live (offline `dnumb --from <bundle>`)
|
|
8
|
+
* pays nothing for it.
|
|
9
|
+
*/
|
|
10
|
+
/** The minimum a page must answer for the portable capture path. */
|
|
11
|
+
export interface PageLike {
|
|
12
|
+
evaluate<R>(pageFunction: () => R): Promise<R>;
|
|
13
|
+
}
|
|
14
|
+
/** A Chrome DevTools Protocol session. Chromium only. */
|
|
15
|
+
export interface CDPSessionLike {
|
|
16
|
+
send(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
17
|
+
detach(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A page that can open a CDP session. Chromium only, and the shape is loose on
|
|
21
|
+
* purpose: `newCDPSession` takes the page itself, and we do not want this
|
|
22
|
+
* package's types to depend on Playwright's.
|
|
23
|
+
*/
|
|
24
|
+
export interface CDPCapablePage extends PageLike {
|
|
25
|
+
context(): {
|
|
26
|
+
newCDPSession(page: unknown): Promise<CDPSessionLike>;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export declare function isCDPCapable(page: PageLike): page is CDPCapablePage;
|
|
30
|
+
/**
|
|
31
|
+
* The viewport, read from inside the page.
|
|
32
|
+
*
|
|
33
|
+
* Playwright's `page.viewportSize()` returns **null** over `connectOverCDP`,
|
|
34
|
+
* which is exactly the mode the interactive path uses — so it cannot be the
|
|
35
|
+
* source of truth. `innerWidth`/`innerHeight` are always right.
|
|
36
|
+
*/
|
|
37
|
+
export declare function viewportOf(page: PageLike): Promise<{
|
|
38
|
+
width: number;
|
|
39
|
+
height: number;
|
|
40
|
+
}>;
|
package/dist/page.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The driver interface, defined structurally so the core of this package needs
|
|
3
|
+
* no Playwright at all.
|
|
4
|
+
*
|
|
5
|
+
* A caller passes whatever `Page` they already have — Playwright's, or anything
|
|
6
|
+
* that answers `evaluate`. `playwright-core` is an *optional* peer precisely so
|
|
7
|
+
* that a consumer who never captures live (offline `dnumb --from <bundle>`)
|
|
8
|
+
* pays nothing for it.
|
|
9
|
+
*/
|
|
10
|
+
/** The minimum a page must answer for the portable capture path. */
|
|
11
|
+
export interface PageLike {
|
|
12
|
+
evaluate<R>(pageFunction: () => R): Promise<R>;
|
|
13
|
+
}
|
|
14
|
+
/** A Chrome DevTools Protocol session. Chromium only. */
|
|
15
|
+
export interface CDPSessionLike {
|
|
16
|
+
send(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
17
|
+
detach(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A page that can open a CDP session. Chromium only, and the shape is loose on
|
|
21
|
+
* purpose: `newCDPSession` takes the page itself, and we do not want this
|
|
22
|
+
* package's types to depend on Playwright's.
|
|
23
|
+
*/
|
|
24
|
+
export interface CDPCapablePage extends PageLike {
|
|
25
|
+
context(): {
|
|
26
|
+
newCDPSession(page: unknown): Promise<CDPSessionLike>;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export declare function isCDPCapable(page: PageLike): page is CDPCapablePage;
|
|
30
|
+
/**
|
|
31
|
+
* The viewport, read from inside the page.
|
|
32
|
+
*
|
|
33
|
+
* Playwright's `page.viewportSize()` returns **null** over `connectOverCDP`,
|
|
34
|
+
* which is exactly the mode the interactive path uses — so it cannot be the
|
|
35
|
+
* source of truth. `innerWidth`/`innerHeight` are always right.
|
|
36
|
+
*/
|
|
37
|
+
export declare function viewportOf(page: PageLike): Promise<{
|
|
38
|
+
width: number;
|
|
39
|
+
height: number;
|
|
40
|
+
}>;
|
package/dist/page.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The driver interface, defined structurally so the core of this package needs
|
|
3
|
+
* no Playwright at all.
|
|
4
|
+
*
|
|
5
|
+
* A caller passes whatever `Page` they already have — Playwright's, or anything
|
|
6
|
+
* that answers `evaluate`. `playwright-core` is an *optional* peer precisely so
|
|
7
|
+
* that a consumer who never captures live (offline `dnumb --from <bundle>`)
|
|
8
|
+
* pays nothing for it.
|
|
9
|
+
*/
|
|
10
|
+
export function isCDPCapable(page) {
|
|
11
|
+
const context = page.context;
|
|
12
|
+
if (typeof context !== 'function')
|
|
13
|
+
return false;
|
|
14
|
+
try {
|
|
15
|
+
return typeof context.call(page)?.newCDPSession === 'function';
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Some drivers throw rather than returning a context. Not CDP-capable.
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The viewport, read from inside the page.
|
|
24
|
+
*
|
|
25
|
+
* Playwright's `page.viewportSize()` returns **null** over `connectOverCDP`,
|
|
26
|
+
* which is exactly the mode the interactive path uses — so it cannot be the
|
|
27
|
+
* source of truth. `innerWidth`/`innerHeight` are always right.
|
|
28
|
+
*/
|
|
29
|
+
export async function viewportOf(page) {
|
|
30
|
+
return page.evaluate(() => ({ width: window.innerWidth, height: window.innerHeight }));
|
|
31
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_OUTPUT_DIR = void 0;
|
|
4
|
+
exports.safeName = safeName;
|
|
5
|
+
exports.bundlePath = bundlePath;
|
|
6
|
+
exports.writeBundle = writeBundle;
|
|
7
|
+
exports.capture = capture;
|
|
8
|
+
/**
|
|
9
|
+
* `dnumb/playwright` — one import and one call inside a suite that already has
|
|
10
|
+
* a running app, an authenticated session, and a `page` in scope.
|
|
11
|
+
*
|
|
12
|
+
* This is the wedge: the cost of a capture has to be a single line in a test
|
|
13
|
+
* someone already wrote, or the bundle never gets produced and nothing
|
|
14
|
+
* downstream of it matters.
|
|
15
|
+
*
|
|
16
|
+
* Nothing here imports Playwright, not even as a type. `playwright-core` is an
|
|
17
|
+
* *optional* peer, so a `import type { Page } from 'playwright-core'` in this
|
|
18
|
+
* file would break typechecking for a consumer who installed dnumb for the
|
|
19
|
+
* offline `--from <bundle>` path alone. The interfaces are structural instead,
|
|
20
|
+
* and a real `Page` / `TestInfo` satisfies them by shape.
|
|
21
|
+
*/
|
|
22
|
+
const node_fs_1 = require("node:fs");
|
|
23
|
+
const node_path_1 = require("node:path");
|
|
24
|
+
const bundle_js_1 = require("./bundle.cjs");
|
|
25
|
+
const capture_js_1 = require("./snapshot/capture.cjs");
|
|
26
|
+
/** Mirrors Playwright's own `.playwright-artifacts` habit: hidden, gitignorable, per-repo. */
|
|
27
|
+
exports.DEFAULT_OUTPUT_DIR = '.dnumb';
|
|
28
|
+
/** Everything a filename can carry without quoting on any of the three platforms. */
|
|
29
|
+
const UNSAFE = /[^a-zA-Z0-9._-]+/g;
|
|
30
|
+
/** ext4 and APFS both cap a name at 255 bytes; leave room for the extension. */
|
|
31
|
+
const MAX_NAME = 120;
|
|
32
|
+
/**
|
|
33
|
+
* A capture name is caller-chosen text that becomes a path segment, so it is
|
|
34
|
+
* the one untrusted input here. Separators collapse into `-` rather than being
|
|
35
|
+
* rejected, because `settings/desktop` is a reasonable thing to type and
|
|
36
|
+
* failing a test over it would be hostile — but a leading run of dots or dashes
|
|
37
|
+
* is stripped outright, which is what turns `../../etc/passwd` into
|
|
38
|
+
* `etc-passwd` instead of a traversal.
|
|
39
|
+
*/
|
|
40
|
+
function safeName(name) {
|
|
41
|
+
const flat = name
|
|
42
|
+
.trim()
|
|
43
|
+
.replace(UNSAFE, '-')
|
|
44
|
+
.replace(/^[-.]+/, '')
|
|
45
|
+
.replace(/[-.]+$/, '');
|
|
46
|
+
if (!flat) {
|
|
47
|
+
throw new Error(`dnumb: capture name ${JSON.stringify(name)} has no filesystem-safe characters in it. ` +
|
|
48
|
+
'Names become filenames; use letters, digits, "." "_" or "-".');
|
|
49
|
+
}
|
|
50
|
+
return flat.slice(0, MAX_NAME);
|
|
51
|
+
}
|
|
52
|
+
/** Where `writeBundle` puts a bundle of this name. Exported so a caller can predict it. */
|
|
53
|
+
function bundlePath(dir, name) {
|
|
54
|
+
return (0, node_path_1.join)(dir, `${safeName(name)}.json`);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The bundle keeps the caller's name verbatim — it is the state label the
|
|
58
|
+
* digest header prints — and only the *path* is sanitized. Two names that
|
|
59
|
+
* differ solely in punctuation therefore land on one file: `settings/desktop`
|
|
60
|
+
* and `settings-desktop` both write `settings-desktop.json`.
|
|
61
|
+
*
|
|
62
|
+
* Re-capturing the same name overwrites, which is what a test re-run should do.
|
|
63
|
+
* A *different* name landing on an existing file is a collision, and silently
|
|
64
|
+
* discarding one of two captures a suite meant to keep is the kind of data loss
|
|
65
|
+
* that gets noticed weeks later, on the run that mattered. So that case throws,
|
|
66
|
+
* and the message names both captures and the fix.
|
|
67
|
+
*/
|
|
68
|
+
function writeBundle(bundle, dir = exports.DEFAULT_OUTPUT_DIR) {
|
|
69
|
+
const path = bundlePath(dir, bundle.name);
|
|
70
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true });
|
|
71
|
+
if ((0, node_fs_1.existsSync)(path)) {
|
|
72
|
+
const existing = readExistingName(path);
|
|
73
|
+
if (existing !== null && existing !== bundle.name) {
|
|
74
|
+
throw new Error(`dnumb: capture ${JSON.stringify(bundle.name)} and capture ${JSON.stringify(existing)} ` +
|
|
75
|
+
`both write to ${path}, because names become filenames. Rename one of them.`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
(0, node_fs_1.writeFileSync)(path, (0, bundle_js_1.serializeBundle)(bundle), 'utf8');
|
|
79
|
+
return path;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The `name` of the bundle already at `path`, or `null` if it cannot be read as
|
|
83
|
+
* one. Unreadable is not a collision — an unrelated file in the output
|
|
84
|
+
* directory should not stop a capture — so it falls through to the overwrite.
|
|
85
|
+
*/
|
|
86
|
+
function readExistingName(path) {
|
|
87
|
+
try {
|
|
88
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(path, 'utf8'));
|
|
89
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
90
|
+
return null;
|
|
91
|
+
const name = parsed.name;
|
|
92
|
+
return typeof name === 'string' ? name : null;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Capture `page` as a bundle on disk.
|
|
100
|
+
*
|
|
101
|
+
* ```ts
|
|
102
|
+
* import { capture } from '@michaelmagan/dnumb/playwright';
|
|
103
|
+
*
|
|
104
|
+
* test('settings page', async ({ page }, testInfo) => {
|
|
105
|
+
* await page.goto('/settings');
|
|
106
|
+
* await capture(page, 'settings-desktop', { testInfo });
|
|
107
|
+
* });
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
async function capture(page, name, options = {}) {
|
|
111
|
+
const dir = options.outputDir ?? exports.DEFAULT_OUTPUT_DIR;
|
|
112
|
+
// Fail on an unusable name before spending a capture on it.
|
|
113
|
+
safeName(name);
|
|
114
|
+
const result = await (0, capture_js_1.capture)(page, options.strategy ? { strategy: options.strategy } : {});
|
|
115
|
+
// The URL is read from inside the page rather than taken from the caller for
|
|
116
|
+
// the same reason the viewport is (`page.viewportSize()` is null over
|
|
117
|
+
// `connectOverCDP`): the page is the only thing that knows where it ended up
|
|
118
|
+
// after redirects, and a bundle that names the URL the test *asked* for is a
|
|
119
|
+
// bundle that lies six months later.
|
|
120
|
+
const url = await page.evaluate(() => location.href);
|
|
121
|
+
const bundle = (0, bundle_js_1.buildBundle)(result, {
|
|
122
|
+
name,
|
|
123
|
+
url,
|
|
124
|
+
capturedAt: new Date().toISOString(),
|
|
125
|
+
...(options.viewportName ? { viewportName: options.viewportName } : {}),
|
|
126
|
+
...(options.labels ? { labels: options.labels } : {}),
|
|
127
|
+
});
|
|
128
|
+
const path = writeBundle(bundle, dir);
|
|
129
|
+
if (options.testInfo) {
|
|
130
|
+
// `body`, not `path`: the attachment has to survive the output directory
|
|
131
|
+
// being cleaned between the run and the report being read in CI.
|
|
132
|
+
await options.testInfo.attach(`${safeName(name)}.dnumb.json`, {
|
|
133
|
+
body: (0, bundle_js_1.serializeBundle)(bundle),
|
|
134
|
+
contentType: 'application/json',
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return { bundle, path };
|
|
138
|
+
}
|