@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,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser-facing half of the accessibility check: fetch the AX tree,
|
|
3
|
+
* sweep the DOM for tabindex (which the AX tree does not expose at all), and
|
|
4
|
+
* turn `ax-node:<id>` placeholders into real CSS selectors a human or an
|
|
5
|
+
* agent can act on. The rules themselves live in a11y.ts and stay pure.
|
|
6
|
+
*/
|
|
7
|
+
import type { CdpClient } from '../browser/cdp.js';
|
|
8
|
+
import type { A11yFinding, AxNode, StructureNode } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Rewrites `ax-node:<id>` placeholder locators into real CSS selectors.
|
|
11
|
+
* Bounded: a page with 400 unlabelled buttons must not cost 400 CDP
|
|
12
|
+
* round-trips, and nobody reads the 400th finding anyway.
|
|
13
|
+
*/
|
|
14
|
+
export declare function upgradeLocators(client: CdpClient, sessionId: string, findings: A11yFinding[], nodes: AxNode[]): Promise<A11yFinding[]>;
|
|
15
|
+
export interface A11yResult {
|
|
16
|
+
findings: A11yFinding[];
|
|
17
|
+
/**
|
|
18
|
+
* Flat signature of the same tree, for the run-to-run structural diff. It
|
|
19
|
+
* rides along here because the AX tree is already in hand — fetching it
|
|
20
|
+
* twice would double the cost of the most expensive collector.
|
|
21
|
+
*/
|
|
22
|
+
structure: StructureNode[];
|
|
23
|
+
}
|
|
24
|
+
export declare function collectA11y(client: CdpClient, sessionId: string, notes: string[]): Promise<A11yResult>;
|
|
25
|
+
//# sourceMappingURL=collect-a11y.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect-a11y.d.ts","sourceRoot":"","sources":["../../../src/report/collect-a11y.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAGnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAkB,aAAa,EAAE,MAAM,YAAY,CAAC;AAgGrF;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,SAAS,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,WAAW,EAAE,EACvB,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC,WAAW,EAAE,CAAC,CA2BxB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB;;;;OAIG;IACH,SAAS,EAAE,aAAa,EAAE,CAAC;CAC5B;AAED,wBAAsB,WAAW,CAC/B,MAAM,EAAE,SAAS,EACjB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC,UAAU,CAAC,CAuCrB"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser-facing half of the accessibility check: fetch the AX tree,
|
|
3
|
+
* sweep the DOM for tabindex (which the AX tree does not expose at all), and
|
|
4
|
+
* turn `ax-node:<id>` placeholders into real CSS selectors a human or an
|
|
5
|
+
* agent can act on. The rules themselves live in a11y.ts and stay pure.
|
|
6
|
+
*/
|
|
7
|
+
import { evaluateJs } from '../browser/actions.js';
|
|
8
|
+
import { runA11yRules } from './a11y.js';
|
|
9
|
+
import { buildStructure } from './structure.js';
|
|
10
|
+
import { message, STEP_TIMEOUT_MS, withTimeout } from './util.js';
|
|
11
|
+
/** Most AX findings we will spend a DOM round-trip on to get a real selector. */
|
|
12
|
+
const MAX_LOCATOR_LOOKUPS = 60;
|
|
13
|
+
/** Builds a short, stable CSS selector for an element, from inside the page. */
|
|
14
|
+
const LOCATOR_FN = `function(){
|
|
15
|
+
var el = this;
|
|
16
|
+
if (!el || el.nodeType !== 1) return '';
|
|
17
|
+
var esc = function(s){ return window.CSS && CSS.escape ? CSS.escape(s) : s; };
|
|
18
|
+
if (el.id) return '#' + esc(el.id);
|
|
19
|
+
var parts = [], node = el, depth = 0;
|
|
20
|
+
while (node && node.nodeType === 1 && depth < 5) {
|
|
21
|
+
var part = node.tagName.toLowerCase();
|
|
22
|
+
if (node.id) { parts.unshift('#' + esc(node.id)); break; }
|
|
23
|
+
var parent = node.parentElement;
|
|
24
|
+
if (parent) {
|
|
25
|
+
var sibs = Array.prototype.filter.call(parent.children, function(c){ return c.tagName === node.tagName; });
|
|
26
|
+
if (sibs.length > 1) part += ':nth-of-type(' + (sibs.indexOf(node) + 1) + ')';
|
|
27
|
+
}
|
|
28
|
+
parts.unshift(part);
|
|
29
|
+
node = parent;
|
|
30
|
+
depth++;
|
|
31
|
+
}
|
|
32
|
+
return parts.join(' > ');
|
|
33
|
+
}`;
|
|
34
|
+
/** DOM sweep for tabindex — the AX tree does not expose it at all. */
|
|
35
|
+
const FOCUS_CANDIDATES_JS = `(function(){
|
|
36
|
+
try {
|
|
37
|
+
var esc = function(s){ return window.CSS && CSS.escape ? CSS.escape(s) : s; };
|
|
38
|
+
var loc = function(el){
|
|
39
|
+
if (el.id) return '#' + esc(el.id);
|
|
40
|
+
var parts = [], node = el, depth = 0;
|
|
41
|
+
while (node && node.nodeType === 1 && depth < 5) {
|
|
42
|
+
var part = node.tagName.toLowerCase();
|
|
43
|
+
if (node.id) { parts.unshift('#' + esc(node.id)); break; }
|
|
44
|
+
var parent = node.parentElement;
|
|
45
|
+
if (parent) {
|
|
46
|
+
var sibs = Array.prototype.filter.call(parent.children, function(c){ return c.tagName === node.tagName; });
|
|
47
|
+
if (sibs.length > 1) part += ':nth-of-type(' + (sibs.indexOf(node) + 1) + ')';
|
|
48
|
+
}
|
|
49
|
+
parts.unshift(part);
|
|
50
|
+
node = parent;
|
|
51
|
+
depth++;
|
|
52
|
+
}
|
|
53
|
+
return parts.join(' > ');
|
|
54
|
+
};
|
|
55
|
+
var out = [];
|
|
56
|
+
var els = document.querySelectorAll('[tabindex]');
|
|
57
|
+
for (var i = 0; i < els.length && out.length < 100; i++) {
|
|
58
|
+
var el = els[i];
|
|
59
|
+
var ti = parseInt(el.getAttribute('tabindex'), 10);
|
|
60
|
+
if (!(ti < 0)) continue;
|
|
61
|
+
var name = (el.getAttribute('aria-label') || el.getAttribute('title') || (el.textContent || '')).trim();
|
|
62
|
+
out.push({
|
|
63
|
+
tag: el.tagName.toLowerCase(),
|
|
64
|
+
role: (el.getAttribute('role') || '').toLowerCase(),
|
|
65
|
+
name: name ? name.slice(0, 120) : null,
|
|
66
|
+
tabindex: ti,
|
|
67
|
+
locator: loc(el)
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
} catch (e) { return []; }
|
|
72
|
+
})()`;
|
|
73
|
+
async function resolveLocator(client, sessionId, backendNodeId) {
|
|
74
|
+
try {
|
|
75
|
+
const resolved = await client.send('DOM.resolveNode', { backendNodeId }, sessionId);
|
|
76
|
+
const objectId = resolved.object?.objectId;
|
|
77
|
+
if (!objectId)
|
|
78
|
+
return null;
|
|
79
|
+
try {
|
|
80
|
+
const call = await client.send('Runtime.callFunctionOn', { functionDeclaration: LOCATOR_FN, objectId, returnByValue: true }, sessionId);
|
|
81
|
+
return call.result?.value || null;
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
await client.send('Runtime.releaseObject', { objectId }, sessionId).catch(() => { });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Rewrites `ax-node:<id>` placeholder locators into real CSS selectors.
|
|
93
|
+
* Bounded: a page with 400 unlabelled buttons must not cost 400 CDP
|
|
94
|
+
* round-trips, and nobody reads the 400th finding anyway.
|
|
95
|
+
*/
|
|
96
|
+
export async function upgradeLocators(client, sessionId, findings, nodes) {
|
|
97
|
+
const backendByNodeId = new Map();
|
|
98
|
+
for (const n of nodes) {
|
|
99
|
+
if (n.backendDOMNodeId !== undefined)
|
|
100
|
+
backendByNodeId.set(String(n.nodeId), n.backendDOMNodeId);
|
|
101
|
+
}
|
|
102
|
+
let budget = MAX_LOCATOR_LOOKUPS;
|
|
103
|
+
const cache = new Map();
|
|
104
|
+
const out = [];
|
|
105
|
+
for (const finding of findings) {
|
|
106
|
+
if (!finding.locator.startsWith('ax-node:') || budget <= 0) {
|
|
107
|
+
out.push(finding);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const backendId = backendByNodeId.get(finding.locator.slice('ax-node:'.length));
|
|
111
|
+
if (backendId === undefined) {
|
|
112
|
+
out.push(finding);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (!cache.has(backendId)) {
|
|
116
|
+
budget--;
|
|
117
|
+
cache.set(backendId, await resolveLocator(client, sessionId, backendId));
|
|
118
|
+
}
|
|
119
|
+
const selector = cache.get(backendId);
|
|
120
|
+
out.push(selector ? { ...finding, locator: selector } : finding);
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
export async function collectA11y(client, sessionId, notes) {
|
|
125
|
+
let nodes = [];
|
|
126
|
+
try {
|
|
127
|
+
const tree = await withTimeout(client.send('Accessibility.getFullAXTree', {}, sessionId), STEP_TIMEOUT_MS, 'accessibility tree');
|
|
128
|
+
nodes = tree.nodes ?? [];
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
notes.push(`Accessibility tree unavailable: ${message(err)}`);
|
|
132
|
+
return { findings: [], structure: [] };
|
|
133
|
+
}
|
|
134
|
+
let focusCandidates = [];
|
|
135
|
+
try {
|
|
136
|
+
const raw = await withTimeout(evaluateJs(client, sessionId, FOCUS_CANDIDATES_JS), STEP_TIMEOUT_MS, 'tabindex sweep');
|
|
137
|
+
if (Array.isArray(raw))
|
|
138
|
+
focusCandidates = raw;
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
notes.push(`Tabindex sweep skipped: ${message(err)}`);
|
|
142
|
+
}
|
|
143
|
+
const structure = buildStructure(nodes);
|
|
144
|
+
const findings = runA11yRules({ nodes, focusCandidates });
|
|
145
|
+
try {
|
|
146
|
+
const upgraded = await withTimeout(upgradeLocators(client, sessionId, findings, nodes), STEP_TIMEOUT_MS, 'locator resolution');
|
|
147
|
+
return { findings: upgraded, structure };
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
notes.push('Some accessibility locators could not be resolved to CSS selectors.');
|
|
151
|
+
return { findings, structure };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=collect-a11y.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect-a11y.js","sourceRoot":"","sources":["../../../src/report/collect-a11y.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drives the browser and gathers the raw material for a report.
|
|
3
|
+
*
|
|
4
|
+
* Resilience is the design constraint: a page with no vitals, a site that
|
|
5
|
+
* never goes network-idle, a CSP that blocks our evaluate — each must
|
|
6
|
+
* degrade into a note in the report, never a hang and never a crash. Every
|
|
7
|
+
* collector is individually timed out and individually try/caught, and this
|
|
8
|
+
* function always returns a CaptureData.
|
|
9
|
+
*/
|
|
10
|
+
import type { CdpClient } from '../browser/cdp.js';
|
|
11
|
+
import type { EvidenceRecorderOptions } from './evidence.js';
|
|
12
|
+
import type { CaptureData, RequestEntry } from './types.js';
|
|
13
|
+
export interface CollectOptions {
|
|
14
|
+
url: string;
|
|
15
|
+
/** A CSS selector to wait for, or a number of milliseconds. */
|
|
16
|
+
wait?: string | number;
|
|
17
|
+
/** Device names from `listDevices()` to add to the screenshot matrix. */
|
|
18
|
+
devices?: string[];
|
|
19
|
+
vitalsWaitMs?: number;
|
|
20
|
+
loadTimeoutMs?: number;
|
|
21
|
+
screenshotDir?: string;
|
|
22
|
+
fullPage?: boolean;
|
|
23
|
+
/** Record screencast frames for the evidence timeline (RIG-11). */
|
|
24
|
+
record?: boolean;
|
|
25
|
+
recordOptions?: EvidenceRecorderOptions;
|
|
26
|
+
}
|
|
27
|
+
interface FailureRecord {
|
|
28
|
+
errorText?: string;
|
|
29
|
+
blockedReason?: string;
|
|
30
|
+
canceled?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface CapturedRequestLike {
|
|
33
|
+
id: string;
|
|
34
|
+
url: string;
|
|
35
|
+
method: string;
|
|
36
|
+
status?: number;
|
|
37
|
+
mimeType?: string;
|
|
38
|
+
startTime: number;
|
|
39
|
+
endTime?: number;
|
|
40
|
+
encodedSize?: number;
|
|
41
|
+
}
|
|
42
|
+
export declare function toRequestEntries(captured: CapturedRequestLike[], failures: Map<string, FailureRecord>, clockOffsetMs?: number): RequestEntry[];
|
|
43
|
+
export declare function collect(client: CdpClient, sessionId: string, options: CollectOptions): Promise<CaptureData>;
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=collect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect.d.ts","sourceRoot":"","sources":["../../../src/report/collect.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAmBnD,OAAO,KAAK,EAAoB,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAE/E,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAA0B,MAAM,YAAY,CAAC;AAGpF,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mEAAmE;IACnE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,uBAAuB,CAAC;CACzC;AAWD,UAAU,aAAa;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAkDD,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EACpC,aAAa,CAAC,EAAE,MAAM,GACrB,YAAY,EAAE,CAuBhB;AA2GD,wBAAsB,OAAO,CAC3B,MAAM,EAAE,SAAS,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,WAAW,CAAC,CAgJtB"}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drives the browser and gathers the raw material for a report.
|
|
3
|
+
*
|
|
4
|
+
* Resilience is the design constraint: a page with no vitals, a site that
|
|
5
|
+
* never goes network-idle, a CSP that blocks our evaluate — each must
|
|
6
|
+
* degrade into a note in the report, never a hang and never a crash. Every
|
|
7
|
+
* collector is individually timed out and individually try/caught, and this
|
|
8
|
+
* function always returns a CaptureData.
|
|
9
|
+
*/
|
|
10
|
+
import { evaluateJs } from '../browser/actions.js';
|
|
11
|
+
import { getCurrentTitle, getCurrentUrl, openUrl, waitForLoad } from '../browser/browser.js';
|
|
12
|
+
import { clearConsoleMessages, clearPageErrors, enableConsoleCapture, getConsoleMessages, getPageErrors, setupConsoleCapture, } from '../browser/console-log.js';
|
|
13
|
+
import { emulateDevice, listDevices } from '../browser/emulation.js';
|
|
14
|
+
import { clearCapturedRequests, getCapturedRequests, startRequestCapture, } from '../browser/network.js';
|
|
15
|
+
import { captureScreenshot } from '../browser/screenshot.js';
|
|
16
|
+
import { collectVitals } from '../browser/vitals.js';
|
|
17
|
+
import { waitFor } from '../browser/wait.js';
|
|
18
|
+
import { collectA11y } from './collect-a11y.js';
|
|
19
|
+
import { startEvidenceRecorder } from './evidence.js';
|
|
20
|
+
import { message, STEP_TIMEOUT_MS, sleep, withTimeout } from './util.js';
|
|
21
|
+
const DEFAULT_VITALS_WAIT_MS = 2500;
|
|
22
|
+
const DEFAULT_LOAD_TIMEOUT_MS = 20_000;
|
|
23
|
+
/** Cap on inlined screenshot bytes so the single HTML file stays openable. */
|
|
24
|
+
const MAX_INLINE_BYTES = 12 * 1024 * 1024;
|
|
25
|
+
/**
|
|
26
|
+
* CDP reports request timings on a monotonic clock with an arbitrary epoch,
|
|
27
|
+
* which cannot be compared with the `Date.now()` stamps on console entries.
|
|
28
|
+
* Sampling both clocks on the first request we see gives an offset that puts
|
|
29
|
+
* every request on the same timeline as everything else (RIG-11).
|
|
30
|
+
*/
|
|
31
|
+
function trackClockOffset(client, sessionId) {
|
|
32
|
+
let offset;
|
|
33
|
+
const off = client.on('Network.requestWillBeSent', (params, sid) => {
|
|
34
|
+
if (sid !== sessionId || offset !== undefined)
|
|
35
|
+
return;
|
|
36
|
+
const p = params;
|
|
37
|
+
if (typeof p.timestamp !== 'number')
|
|
38
|
+
return;
|
|
39
|
+
offset = Date.now() - p.timestamp * 1000;
|
|
40
|
+
});
|
|
41
|
+
return { offset: () => offset, stop: off };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* `startRequestCapture` records timings but drops `Network.loadingFailed`'s
|
|
45
|
+
* errorText, so a DNS failure and a still-in-flight request look identical in
|
|
46
|
+
* its output. Listen alongside it rather than editing it.
|
|
47
|
+
*/
|
|
48
|
+
function trackLoadingFailures(client, sessionId) {
|
|
49
|
+
const failures = new Map();
|
|
50
|
+
const off = client.on('Network.loadingFailed', (params, sid) => {
|
|
51
|
+
if (sid !== sessionId)
|
|
52
|
+
return;
|
|
53
|
+
const p = params;
|
|
54
|
+
if (!p.requestId)
|
|
55
|
+
return;
|
|
56
|
+
failures.set(p.requestId, {
|
|
57
|
+
errorText: p.errorText,
|
|
58
|
+
blockedReason: p.blockedReason,
|
|
59
|
+
canceled: p.canceled,
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
return { failures, stop: off };
|
|
63
|
+
}
|
|
64
|
+
export function toRequestEntries(captured, failures, clockOffsetMs) {
|
|
65
|
+
return captured.map((r) => {
|
|
66
|
+
const failure = failures.get(r.id);
|
|
67
|
+
const httpError = r.status !== undefined && r.status >= 400;
|
|
68
|
+
// A cancelled request is usually the page itself aborting a fetch on
|
|
69
|
+
// navigation — noise, not a defect. A blockedReason (CSP, mixed content,
|
|
70
|
+
// an ad blocker) is a real failure even though CDP also marks it cancelled.
|
|
71
|
+
const hardFailure = failure !== undefined && (!failure.canceled || !!failure.blockedReason);
|
|
72
|
+
const errorText = failure?.blockedReason
|
|
73
|
+
? `blocked: ${failure.blockedReason}`
|
|
74
|
+
: (failure?.errorText ?? undefined);
|
|
75
|
+
return {
|
|
76
|
+
url: r.url,
|
|
77
|
+
method: r.method,
|
|
78
|
+
startedAtMs: clockOffsetMs === undefined ? undefined : r.startTime + clockOffsetMs,
|
|
79
|
+
status: r.status,
|
|
80
|
+
mimeType: r.mimeType,
|
|
81
|
+
durationMs: r.endTime !== undefined ? Math.max(0, r.endTime - r.startTime) : undefined,
|
|
82
|
+
encodedSize: r.encodedSize,
|
|
83
|
+
failed: httpError || hardFailure,
|
|
84
|
+
errorText: hardFailure ? errorText : undefined,
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Screenshots
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
async function measure(client, sessionId, fullPage) {
|
|
92
|
+
try {
|
|
93
|
+
const raw = await evaluateJs(client, sessionId, fullPage
|
|
94
|
+
? '({width: document.documentElement.scrollWidth, height: document.documentElement.scrollHeight})'
|
|
95
|
+
: '({width: window.innerWidth, height: window.innerHeight})');
|
|
96
|
+
const size = raw;
|
|
97
|
+
return { width: Math.round(size?.width ?? 0), height: Math.round(size?.height ?? 0) };
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return { width: 0, height: 0 };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async function takeShot(client, sessionId, label, fullPage, dir, notes, inlineBudget) {
|
|
104
|
+
try {
|
|
105
|
+
const slug = label.replace(/[^a-z0-9]+/gi, '-').toLowerCase();
|
|
106
|
+
const path = dir ? `${dir}/${slug}.png` : undefined;
|
|
107
|
+
const shot = await withTimeout(captureScreenshot(client, sessionId, { fullPage, path }), STEP_TIMEOUT_MS, `screenshot (${label})`);
|
|
108
|
+
if (shot.dataUrl.length > inlineBudget.left) {
|
|
109
|
+
notes.push(`Screenshot "${label}" omitted from the HTML — inlining it would blow the size budget.`);
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
inlineBudget.left -= shot.dataUrl.length;
|
|
113
|
+
const size = await measure(client, sessionId, fullPage);
|
|
114
|
+
return {
|
|
115
|
+
label,
|
|
116
|
+
width: size.width,
|
|
117
|
+
height: size.height,
|
|
118
|
+
dataUrl: shot.dataUrl,
|
|
119
|
+
path: shot.path,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
notes.push(`Screenshot "${label}" failed: ${message(err)}`);
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function captureDeviceMatrix(client, sessionId, url, devices, dir, notes, inlineBudget) {
|
|
128
|
+
const known = new Set(listDevices());
|
|
129
|
+
const shots = [];
|
|
130
|
+
for (const device of devices) {
|
|
131
|
+
if (!known.has(device)) {
|
|
132
|
+
notes.push(`Unknown device "${device}" — known devices: ${[...known].join(', ')}`);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
await emulateDevice(client, sessionId, device);
|
|
137
|
+
// Re-navigate: responsive layout and media queries only settle on a
|
|
138
|
+
// fresh load, so resizing an already-rendered page can photograph a
|
|
139
|
+
// layout the device would never actually produce.
|
|
140
|
+
await openUrl(client, sessionId, url).catch(() => { });
|
|
141
|
+
await waitForLoad(client, sessionId, 'domcontentloaded', 5000).catch(() => { });
|
|
142
|
+
const shot = await takeShot(client, sessionId, device, false, dir, notes, inlineBudget);
|
|
143
|
+
if (shot)
|
|
144
|
+
shots.push(shot);
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
notes.push(`Device "${device}" skipped: ${message(err)}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Put the tab back the way we found it, so a later command in the same
|
|
151
|
+
// session is not silently stuck emulating an iPhone.
|
|
152
|
+
await client.send('Emulation.clearDeviceMetricsOverride', {}, sessionId).catch(() => { });
|
|
153
|
+
await client
|
|
154
|
+
.send('Emulation.setTouchEmulationEnabled', { enabled: false }, sessionId)
|
|
155
|
+
.catch(() => { });
|
|
156
|
+
await client.send('Emulation.setUserAgentOverride', { userAgent: '' }, sessionId).catch(() => { });
|
|
157
|
+
return shots;
|
|
158
|
+
}
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// Orchestration
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
export async function collect(client, sessionId, options) {
|
|
163
|
+
const notes = [];
|
|
164
|
+
const startedAt = Date.now();
|
|
165
|
+
const capturedAt = new Date().toISOString();
|
|
166
|
+
const loadTimeoutMs = options.loadTimeoutMs ?? DEFAULT_LOAD_TIMEOUT_MS;
|
|
167
|
+
const fullPage = options.fullPage ?? true;
|
|
168
|
+
// Capture must be armed BEFORE navigating, or the errors and requests that
|
|
169
|
+
// fire during load — the interesting ones — are already gone.
|
|
170
|
+
setupConsoleCapture(client, sessionId);
|
|
171
|
+
await enableConsoleCapture(client, sessionId).catch(() => { });
|
|
172
|
+
clearConsoleMessages(sessionId);
|
|
173
|
+
clearPageErrors(sessionId);
|
|
174
|
+
startRequestCapture(client, sessionId);
|
|
175
|
+
clearCapturedRequests(sessionId);
|
|
176
|
+
const tracker = trackLoadingFailures(client, sessionId);
|
|
177
|
+
const clock = trackClockOffset(client, sessionId);
|
|
178
|
+
// Armed before navigation for the same reason console capture is: the
|
|
179
|
+
// frames worth having are the ones showing the page fail to load.
|
|
180
|
+
let recorder = null;
|
|
181
|
+
if (options.record) {
|
|
182
|
+
try {
|
|
183
|
+
recorder = await startEvidenceRecorder(client, sessionId, startedAt, options.recordOptions);
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
notes.push(`Screen recording unavailable: ${message(err)}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
const navigation = openUrl(client, sessionId, options.url)
|
|
191
|
+
.then(() => 'ok')
|
|
192
|
+
.catch((err) => {
|
|
193
|
+
notes.push(`Navigation reported an error: ${message(err)}`);
|
|
194
|
+
return 'error';
|
|
195
|
+
});
|
|
196
|
+
const outcome = await Promise.race([
|
|
197
|
+
navigation,
|
|
198
|
+
sleep(loadTimeoutMs).then(() => 'timeout'),
|
|
199
|
+
]);
|
|
200
|
+
if (outcome === 'timeout') {
|
|
201
|
+
notes.push(`Page never went network-idle within ${loadTimeoutMs}ms — reporting on what had loaded by then.`);
|
|
202
|
+
await waitForLoad(client, sessionId, 'domcontentloaded', 5000).catch(() => { });
|
|
203
|
+
}
|
|
204
|
+
if (options.wait !== undefined) {
|
|
205
|
+
const waitMs = typeof options.wait === 'number' ? options.wait : Number(options.wait);
|
|
206
|
+
try {
|
|
207
|
+
if (Number.isFinite(waitMs)) {
|
|
208
|
+
await sleep(waitMs);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
await waitFor(client, sessionId, { selector: String(options.wait), timeout: 15_000 });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
notes.push(`--wait not satisfied: ${message(err)}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
let vitals = {};
|
|
219
|
+
const vitalsWaitMs = options.vitalsWaitMs ?? DEFAULT_VITALS_WAIT_MS;
|
|
220
|
+
try {
|
|
221
|
+
vitals = await withTimeout(collectVitals(client, sessionId, vitalsWaitMs), vitalsWaitMs + STEP_TIMEOUT_MS, 'web vitals');
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
notes.push(`Web vitals unavailable: ${message(err)}`);
|
|
225
|
+
}
|
|
226
|
+
const a11y = await collectA11y(client, sessionId, notes);
|
|
227
|
+
// Stop before the screenshots: a device matrix re-navigates the page
|
|
228
|
+
// several times, and those frames belong to no failure worth explaining.
|
|
229
|
+
if (recorder)
|
|
230
|
+
await recorder.stop().catch(() => { });
|
|
231
|
+
const finalUrl = await getCurrentUrl(client, sessionId).catch(() => options.url);
|
|
232
|
+
const title = await getCurrentTitle(client, sessionId).catch(() => '');
|
|
233
|
+
const consoleEntries = getConsoleMessages(sessionId);
|
|
234
|
+
const pageErrors = getPageErrors(sessionId);
|
|
235
|
+
const requests = toRequestEntries(getCapturedRequests(sessionId), tracker.failures, clock.offset());
|
|
236
|
+
const inlineBudget = { left: MAX_INLINE_BYTES };
|
|
237
|
+
const screenshots = [];
|
|
238
|
+
const main = await takeShot(client, sessionId, 'page', fullPage, options.screenshotDir, notes, inlineBudget);
|
|
239
|
+
if (main)
|
|
240
|
+
screenshots.push(main);
|
|
241
|
+
if (options.devices?.length) {
|
|
242
|
+
screenshots.push(...(await captureDeviceMatrix(client, sessionId, options.url, options.devices, options.screenshotDir, notes, inlineBudget)));
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
url: options.url,
|
|
246
|
+
finalUrl,
|
|
247
|
+
title,
|
|
248
|
+
capturedAt,
|
|
249
|
+
durationMs: Date.now() - startedAt,
|
|
250
|
+
console: consoleEntries,
|
|
251
|
+
pageErrors,
|
|
252
|
+
requests,
|
|
253
|
+
vitals,
|
|
254
|
+
a11y: a11y.findings,
|
|
255
|
+
structure: a11y.structure,
|
|
256
|
+
screenshots,
|
|
257
|
+
notes,
|
|
258
|
+
recording: recorder
|
|
259
|
+
? {
|
|
260
|
+
startedAtMs: startedAt,
|
|
261
|
+
frames: recorder.frames,
|
|
262
|
+
droppedFrames: recorder.droppedFrames,
|
|
263
|
+
requested: true,
|
|
264
|
+
}
|
|
265
|
+
: undefined,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
finally {
|
|
269
|
+
tracker.stop();
|
|
270
|
+
clock.stop();
|
|
271
|
+
if (recorder)
|
|
272
|
+
await recorder.stop().catch(() => { });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=collect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect.js","sourceRoot":"","sources":["../../../src/report/collect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recorded evidence for a bug report (RIG-11).
|
|
3
|
+
*
|
|
4
|
+
* When a run breaches its budget, "LCP was 4.1s" is a number; the frames
|
|
5
|
+
* either side of the moment it went wrong, lined up against the console and
|
|
6
|
+
* network entries from the same instant, is a bug report. This module records
|
|
7
|
+
* the frames and assembles that timeline.
|
|
8
|
+
*
|
|
9
|
+
* Size is the hard constraint. A screencast is tens of megabytes and a report
|
|
10
|
+
* is a single self-contained HTML file, so there are three separate bounds:
|
|
11
|
+
* the recorder's ring buffer (what we hold in memory), the frame selection
|
|
12
|
+
* (what we attach), and a byte budget applied over the selection. Nothing
|
|
13
|
+
* here can produce a 200MB report.
|
|
14
|
+
*
|
|
15
|
+
* `startEvidenceRecorder` talks to Chrome; everything below it is pure and
|
|
16
|
+
* unit-tested against fixture frames.
|
|
17
|
+
*/
|
|
18
|
+
import type { CdpClient } from '../browser/cdp.js';
|
|
19
|
+
import type { BudgetFailure, ConsoleEntry, Evidence, PageErrorEntry, RawFrame, RequestEntry } from './types.js';
|
|
20
|
+
export type { RawFrame };
|
|
21
|
+
export interface EvidenceRecorderOptions {
|
|
22
|
+
/** Frames held in memory before the buffer is decimated. */
|
|
23
|
+
maxFrames?: number;
|
|
24
|
+
/** Base64 bytes held in memory before the buffer is decimated. */
|
|
25
|
+
maxBytes?: number;
|
|
26
|
+
everyNthFrame?: number;
|
|
27
|
+
quality?: number;
|
|
28
|
+
maxWidth?: number;
|
|
29
|
+
maxHeight?: number;
|
|
30
|
+
}
|
|
31
|
+
export interface EvidenceRecorder {
|
|
32
|
+
frames: RawFrame[];
|
|
33
|
+
droppedFrames: number;
|
|
34
|
+
stop: () => Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
export declare function startEvidenceRecorder(client: CdpClient, sessionId: string, startedAtMs: number, options?: EvidenceRecorderOptions): Promise<EvidenceRecorder>;
|
|
37
|
+
export interface SelectFramesOptions {
|
|
38
|
+
/** Frames to attach to the report. */
|
|
39
|
+
maxFrames?: number;
|
|
40
|
+
/** Total base64 bytes to attach. */
|
|
41
|
+
maxBytes?: number;
|
|
42
|
+
/** Half-width of the window around the focus, in ms. */
|
|
43
|
+
windowMs?: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Pick the frames to attach.
|
|
47
|
+
*
|
|
48
|
+
* With a focus offset, frames nearest it win — that is the moment being
|
|
49
|
+
* explained. Without one (a vitals-only breach has no instant), the run is
|
|
50
|
+
* sampled evenly so the reader still sees it load. The byte budget is applied
|
|
51
|
+
* last, evicting the least relevant frame first, so the cap is never reached
|
|
52
|
+
* by truncating the interesting end.
|
|
53
|
+
*/
|
|
54
|
+
export declare function selectFrames(frames: RawFrame[], focusOffsetMs: number | null, options?: SelectFramesOptions): {
|
|
55
|
+
frames: RawFrame[];
|
|
56
|
+
dropped: number;
|
|
57
|
+
};
|
|
58
|
+
export interface BuildEvidenceInput {
|
|
59
|
+
reason: Evidence['reason'];
|
|
60
|
+
/** `Date.now()` at the start of the run — the zero of every offset. */
|
|
61
|
+
startedAtMs: number;
|
|
62
|
+
frames: RawFrame[];
|
|
63
|
+
/** Frames the recorder already dropped to stay inside its buffer. */
|
|
64
|
+
bufferDropped?: number;
|
|
65
|
+
console: ConsoleEntry[];
|
|
66
|
+
pageErrors: PageErrorEntry[];
|
|
67
|
+
requests: RequestEntry[];
|
|
68
|
+
failures: BudgetFailure[];
|
|
69
|
+
select?: SelectFramesOptions;
|
|
70
|
+
/** Cap on non-frame timeline rows. */
|
|
71
|
+
maxEvents?: number;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The instant the run went wrong: the earliest hard error we have a clock
|
|
75
|
+
* reading for. Returns null when the breach has no moment attached — a slow
|
|
76
|
+
* LCP is a property of the whole load, not an event.
|
|
77
|
+
*/
|
|
78
|
+
export declare function findFocusOffset(input: BuildEvidenceInput): number | null;
|
|
79
|
+
export declare function buildEvidence(input: BuildEvidenceInput): Evidence;
|
|
80
|
+
//# sourceMappingURL=evidence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evidence.d.ts","sourceRoot":"","sources":["../../../src/report/evidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,QAAQ,EAER,cAAc,EACd,QAAQ,EACR,YAAY,EAEb,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,QAAQ,EAAE,CAAC;AAMzB,MAAM,WAAW,uBAAuB;IACtC,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAkBD,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,SAAS,EACjB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,gBAAgB,CAAC,CAiE3B;AAMD,MAAM,WAAW,mBAAmB;IAClC,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAMD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,QAAQ,EAAE,EAClB,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,OAAO,GAAE,mBAAwB,GAChC;IAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CA+CzC;AAMD,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC3B,uEAAuE;IACvE,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAWD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,GAAG,IAAI,CAexE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,QAAQ,CA2GjE"}
|