@monoes/monobrowse 1.0.11 → 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__/adapters.test.js +5 -2
- package/dist/src/__tests__/adapters.test.js.map +1 -1
- package/dist/src/__tests__/batch-eval-parsing.test.js +4 -2
- package/dist/src/__tests__/batch-eval-parsing.test.js.map +1 -1
- package/dist/src/__tests__/box-model.test.js +9 -5
- package/dist/src/__tests__/box-model.test.js.map +1 -1
- package/dist/src/__tests__/browser-launch.test.d.ts +10 -0
- package/dist/src/__tests__/browser-launch.test.d.ts.map +1 -1
- package/dist/src/__tests__/browser-launch.test.js +17 -10
- package/dist/src/__tests__/browser-launch.test.js.map +1 -1
- package/dist/src/__tests__/cdp-client.test.js +9 -3
- package/dist/src/__tests__/cdp-client.test.js.map +1 -1
- package/dist/src/__tests__/close-browser.test.d.ts +13 -0
- package/dist/src/__tests__/close-browser.test.d.ts.map +1 -1
- package/dist/src/__tests__/close-browser.test.js +5 -5
- package/dist/src/__tests__/close-browser.test.js.map +1 -1
- package/dist/src/__tests__/console-log.test.js +17 -6
- package/dist/src/__tests__/console-log.test.js.map +1 -1
- package/dist/src/__tests__/find.test.js +26 -6
- package/dist/src/__tests__/find.test.js.map +1 -1
- package/dist/src/__tests__/frame-command.test.js +9 -3
- package/dist/src/__tests__/frame-command.test.js.map +1 -1
- package/dist/src/__tests__/open-url-navigate-error.test.js +1 -3
- package/dist/src/__tests__/open-url-navigate-error.test.js.map +1 -1
- package/dist/src/__tests__/output.test.js +1 -1
- package/dist/src/__tests__/output.test.js.map +1 -1
- 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__/reap-idle-browser.test.d.ts +15 -0
- package/dist/src/__tests__/reap-idle-browser.test.d.ts.map +1 -0
- package/dist/src/__tests__/reap-idle-browser.test.js +238 -0
- package/dist/src/__tests__/reap-idle-browser.test.js.map +1 -0
- package/dist/src/__tests__/ref-cache.test.js +18 -10
- package/dist/src/__tests__/ref-cache.test.js.map +1 -1
- 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/__tests__/session-state.test.d.ts +8 -0
- package/dist/src/__tests__/session-state.test.d.ts.map +1 -1
- package/dist/src/__tests__/session-state.test.js +10 -10
- package/dist/src/__tests__/session-state.test.js.map +1 -1
- package/dist/src/__tests__/snapshot.test.js +44 -14
- package/dist/src/__tests__/snapshot.test.js.map +1 -1
- package/dist/src/__tests__/storage-eval.test.js +14 -6
- package/dist/src/__tests__/storage-eval.test.js.map +1 -1
- package/dist/src/__tests__/tabs.test.js +25 -9
- package/dist/src/__tests__/tabs.test.js.map +1 -1
- package/dist/src/browser/action-builder/analyzer.d.ts +1 -1
- package/dist/src/browser/action-builder/analyzer.d.ts.map +1 -1
- package/dist/src/browser/action-builder/analyzer.js +25 -9
- package/dist/src/browser/action-builder/analyzer.js.map +1 -1
- package/dist/src/browser/action-builder/types.d.ts.map +1 -1
- package/dist/src/browser/actions.d.ts +1 -1
- package/dist/src/browser/actions.d.ts.map +1 -1
- package/dist/src/browser/actions.js +16 -6
- package/dist/src/browser/actions.js.map +1 -1
- package/dist/src/browser/adapters/gemini.d.ts.map +1 -1
- package/dist/src/browser/adapters/gemini.js.map +1 -1
- package/dist/src/browser/adapters/google.d.ts.map +1 -1
- package/dist/src/browser/adapters/google.js.map +1 -1
- package/dist/src/browser/adapters/index.d.ts.map +1 -1
- package/dist/src/browser/adapters/index.js +3 -3
- package/dist/src/browser/adapters/index.js.map +1 -1
- package/dist/src/browser/adapters/instagram.d.ts.map +1 -1
- package/dist/src/browser/adapters/instagram.js.map +1 -1
- package/dist/src/browser/adapters/linkedin.d.ts.map +1 -1
- package/dist/src/browser/adapters/linkedin.js.map +1 -1
- package/dist/src/browser/adapters/microsoft.d.ts.map +1 -1
- package/dist/src/browser/adapters/microsoft.js.map +1 -1
- package/dist/src/browser/adapters/x.d.ts.map +1 -1
- package/dist/src/browser/adapters/x.js.map +1 -1
- package/dist/src/browser/browser.d.ts +30 -0
- package/dist/src/browser/browser.d.ts.map +1 -1
- package/dist/src/browser/browser.js +122 -15
- package/dist/src/browser/browser.js.map +1 -1
- package/dist/src/browser/cdp.d.ts.map +1 -1
- package/dist/src/browser/cdp.js +21 -8
- package/dist/src/browser/cdp.js.map +1 -1
- package/dist/src/browser/console-log.d.ts.map +1 -1
- package/dist/src/browser/console-log.js +2 -2
- package/dist/src/browser/console-log.js.map +1 -1
- package/dist/src/browser/dashboard/server.d.ts.map +1 -1
- package/dist/src/browser/dashboard/server.js +29 -19
- package/dist/src/browser/dashboard/server.js.map +1 -1
- package/dist/src/browser/dashboard/ui.html +153 -165
- package/dist/src/browser/dialog.d.ts.map +1 -1
- package/dist/src/browser/dialog.js +3 -1
- package/dist/src/browser/dialog.js.map +1 -1
- package/dist/src/browser/emulation.d.ts.map +1 -1
- package/dist/src/browser/emulation.js +21 -6
- package/dist/src/browser/emulation.js.map +1 -1
- package/dist/src/browser/find.d.ts +4 -4
- package/dist/src/browser/find.d.ts.map +1 -1
- package/dist/src/browser/find.js +14 -6
- package/dist/src/browser/find.js.map +1 -1
- package/dist/src/browser/har.d.ts.map +1 -1
- package/dist/src/browser/har.js +14 -7
- package/dist/src/browser/har.js.map +1 -1
- package/dist/src/browser/index.d.ts +17 -16
- package/dist/src/browser/index.d.ts.map +1 -1
- package/dist/src/browser/index.js +17 -16
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/browser/network.d.ts.map +1 -1
- package/dist/src/browser/network.js +14 -4
- package/dist/src/browser/network.js.map +1 -1
- package/dist/src/browser/pdf.d.ts.map +1 -1
- package/dist/src/browser/pdf.js +3 -3
- package/dist/src/browser/pdf.js.map +1 -1
- package/dist/src/browser/profiler.d.ts.map +1 -1
- package/dist/src/browser/profiler.js +22 -20
- package/dist/src/browser/profiler.js.map +1 -1
- package/dist/src/browser/record.d.ts +1 -1
- package/dist/src/browser/record.d.ts.map +1 -1
- package/dist/src/browser/record.js +18 -7
- package/dist/src/browser/record.js.map +1 -1
- package/dist/src/browser/ref-cache.d.ts.map +1 -1
- package/dist/src/browser/ref-cache.js +9 -4
- package/dist/src/browser/ref-cache.js.map +1 -1
- package/dist/src/browser/screenshot.d.ts.map +1 -1
- package/dist/src/browser/screenshot.js +11 -7
- package/dist/src/browser/screenshot.js.map +1 -1
- package/dist/src/browser/session.d.ts +1 -1
- package/dist/src/browser/session.d.ts.map +1 -1
- package/dist/src/browser/session.js +27 -8
- package/dist/src/browser/session.js.map +1 -1
- package/dist/src/browser/snapshot.d.ts +1 -1
- package/dist/src/browser/snapshot.d.ts.map +1 -1
- package/dist/src/browser/snapshot.js +8 -4
- package/dist/src/browser/snapshot.js.map +1 -1
- package/dist/src/browser/storage.d.ts.map +1 -1
- package/dist/src/browser/storage.js.map +1 -1
- package/dist/src/browser/tabs.d.ts.map +1 -1
- package/dist/src/browser/tabs.js +9 -3
- package/dist/src/browser/tabs.js.map +1 -1
- package/dist/src/browser/trace.d.ts.map +1 -1
- package/dist/src/browser/trace.js +3 -3
- package/dist/src/browser/trace.js.map +1 -1
- package/dist/src/browser/types.d.ts.map +1 -1
- package/dist/src/browser/types.js +23 -4
- package/dist/src/browser/types.js.map +1 -1
- package/dist/src/browser/vitals.d.ts.map +1 -1
- package/dist/src/browser/vitals.js +1 -1
- package/dist/src/browser/vitals.js.map +1 -1
- package/dist/src/browser/wait.d.ts.map +1 -1
- package/dist/src/browser/wait.js +11 -5
- package/dist/src/browser/wait.js.map +1 -1
- package/dist/src/cli/action.d.ts.map +1 -1
- package/dist/src/cli/action.js +14 -11
- package/dist/src/cli/action.js.map +1 -1
- package/dist/src/cli/commands.d.ts.map +1 -1
- package/dist/src/cli/commands.js +707 -159
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/output.js +5 -5
- package/dist/src/cli/output.js.map +1 -1
- package/dist/src/cli/platform.d.ts.map +1 -1
- package/dist/src/cli/platform.js +15 -11
- package/dist/src/cli/platform.js.map +1 -1
- package/dist/src/cli/types.d.ts +1 -1
- package/dist/src/cli/types.d.ts.map +1 -1
- package/dist/src/cli.js +21 -9
- package/dist/src/cli.js.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.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__/adapters.test.ts +15 -8
- package/src/__tests__/batch-eval-parsing.test.ts +4 -2
- package/src/__tests__/box-model.test.ts +9 -5
- package/src/__tests__/browser-launch.test.ts +26 -15
- package/src/__tests__/cdp-client.test.ts +25 -9
- package/src/__tests__/close-browser.test.ts +11 -6
- package/src/__tests__/console-log.test.ts +36 -13
- package/src/__tests__/find.test.ts +58 -20
- package/src/__tests__/frame-command.test.ts +12 -6
- package/src/__tests__/open-url-navigate-error.test.ts +2 -4
- package/src/__tests__/output.test.ts +1 -1
- package/src/__tests__/profiler-heap.test.ts +191 -0
- package/src/__tests__/reap-idle-browser.test.ts +276 -0
- package/src/__tests__/ref-cache.test.ts +25 -12
- 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/__tests__/session-state.test.ts +40 -28
- package/src/__tests__/snapshot.test.ts +60 -21
- package/src/__tests__/storage-eval.test.ts +30 -16
- package/src/__tests__/tabs.test.ts +26 -10
- package/src/browser/action-builder/analyzer.ts +39 -15
- package/src/browser/action-builder/types.ts +45 -8
- package/src/browser/actions.ts +288 -123
- package/src/browser/adapters/gemini.ts +7 -3
- package/src/browser/adapters/google.ts +5 -3
- package/src/browser/adapters/index.ts +5 -4
- package/src/browser/adapters/instagram.ts +4 -2
- package/src/browser/adapters/linkedin.ts +4 -2
- package/src/browser/adapters/microsoft.ts +9 -5
- package/src/browser/adapters/x.ts +7 -3
- package/src/browser/browser.ts +188 -42
- package/src/browser/cdp.ts +43 -11
- package/src/browser/console-log.ts +24 -7
- package/src/browser/dashboard/server.ts +44 -25
- package/src/browser/dashboard/ui.html +153 -165
- package/src/browser/dialog.ts +25 -8
- package/src/browser/emulation.ts +86 -36
- package/src/browser/find.ts +110 -47
- package/src/browser/har.ts +58 -26
- package/src/browser/index.ts +17 -16
- package/src/browser/network.ts +147 -46
- package/src/browser/pdf.ts +4 -4
- package/src/browser/profiler.ts +39 -19
- package/src/browser/record.ts +38 -19
- package/src/browser/ref-cache.ts +38 -17
- package/src/browser/screenshot.ts +58 -32
- package/src/browser/session.ts +51 -18
- package/src/browser/snapshot.ts +34 -20
- package/src/browser/storage.ts +73 -17
- package/src/browser/tabs.ts +29 -17
- package/src/browser/trace.ts +14 -10
- package/src/browser/types.ts +23 -4
- package/src/browser/vitals.ts +33 -16
- package/src/browser/wait.ts +47 -21
- package/src/cli/action.ts +52 -21
- package/src/cli/commands.ts +1365 -385
- package/src/cli/output.ts +5 -5
- package/src/cli/platform.ts +44 -23
- package/src/cli/types.ts +1 -1
- package/src/cli.ts +34 -18
- package/src/index.ts +2 -2
- 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,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"}
|
|
@@ -0,0 +1,279 @@
|
|
|
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
|
+
const DEFAULT_MAX_BUFFER_FRAMES = 160;
|
|
19
|
+
const DEFAULT_MAX_BUFFER_BYTES = 32 * 1024 * 1024;
|
|
20
|
+
/**
|
|
21
|
+
* Halve the temporal resolution of the buffer instead of dropping its head.
|
|
22
|
+
*
|
|
23
|
+
* A plain ring buffer keeps the end of the run, which is the wrong half: a
|
|
24
|
+
* page that throws during load has its evidence at the start. Decimating
|
|
25
|
+
* keeps coverage of the whole run and merely coarsens it, which degrades the
|
|
26
|
+
* timeline gracefully however long the run turns out to be.
|
|
27
|
+
*/
|
|
28
|
+
function decimate(frames) {
|
|
29
|
+
const kept = frames.filter((_, i) => i % 2 === 0);
|
|
30
|
+
return { kept, dropped: frames.length - kept.length };
|
|
31
|
+
}
|
|
32
|
+
export async function startEvidenceRecorder(client, sessionId, startedAtMs, options = {}) {
|
|
33
|
+
const maxFrames = options.maxFrames ?? DEFAULT_MAX_BUFFER_FRAMES;
|
|
34
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BUFFER_BYTES;
|
|
35
|
+
const recorder = {
|
|
36
|
+
frames: [],
|
|
37
|
+
droppedFrames: 0,
|
|
38
|
+
stop: async () => { },
|
|
39
|
+
};
|
|
40
|
+
let bytes = 0;
|
|
41
|
+
const off = client.on('Page.screencastFrame', (params, sid) => {
|
|
42
|
+
if (sid !== sessionId)
|
|
43
|
+
return;
|
|
44
|
+
const p = params;
|
|
45
|
+
// Ack first and unconditionally: Chrome stops sending frames until the
|
|
46
|
+
// previous one is acknowledged, so a failure to ack silently ends the
|
|
47
|
+
// recording.
|
|
48
|
+
if (p.sessionId !== undefined) {
|
|
49
|
+
void client
|
|
50
|
+
.send('Page.screencastFrameAck', { sessionId: p.sessionId }, sessionId)
|
|
51
|
+
.catch(() => { });
|
|
52
|
+
}
|
|
53
|
+
if (!p.data)
|
|
54
|
+
return;
|
|
55
|
+
recorder.frames.push({
|
|
56
|
+
offsetMs: Math.max(0, Date.now() - startedAtMs),
|
|
57
|
+
data: p.data,
|
|
58
|
+
bytes: p.data.length,
|
|
59
|
+
});
|
|
60
|
+
bytes += p.data.length;
|
|
61
|
+
if (recorder.frames.length > maxFrames || bytes > maxBytes) {
|
|
62
|
+
const { kept, dropped } = decimate(recorder.frames);
|
|
63
|
+
recorder.frames = kept;
|
|
64
|
+
recorder.droppedFrames += dropped;
|
|
65
|
+
bytes = kept.reduce((sum, f) => sum + f.bytes, 0);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
// Idempotent: collect() stops the recorder once the interesting part of the
|
|
69
|
+
// run is over and again in its `finally`, and Chrome errors on stopping a
|
|
70
|
+
// screencast that is already stopped.
|
|
71
|
+
let stopped = false;
|
|
72
|
+
recorder.stop = async () => {
|
|
73
|
+
if (stopped)
|
|
74
|
+
return;
|
|
75
|
+
stopped = true;
|
|
76
|
+
off();
|
|
77
|
+
await client.send('Page.stopScreencast', {}, sessionId).catch(() => { });
|
|
78
|
+
};
|
|
79
|
+
try {
|
|
80
|
+
await client.send('Page.startScreencast', {
|
|
81
|
+
format: 'jpeg',
|
|
82
|
+
quality: options.quality ?? 60,
|
|
83
|
+
everyNthFrame: options.everyNthFrame ?? 2,
|
|
84
|
+
maxWidth: options.maxWidth ?? 900,
|
|
85
|
+
maxHeight: options.maxHeight ?? 1600,
|
|
86
|
+
}, sessionId);
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
off();
|
|
90
|
+
throw err;
|
|
91
|
+
}
|
|
92
|
+
return recorder;
|
|
93
|
+
}
|
|
94
|
+
const DEFAULT_ATTACH_FRAMES = 12;
|
|
95
|
+
const DEFAULT_ATTACH_BYTES = 6 * 1024 * 1024;
|
|
96
|
+
const DEFAULT_WINDOW_MS = 4000;
|
|
97
|
+
/**
|
|
98
|
+
* Pick the frames to attach.
|
|
99
|
+
*
|
|
100
|
+
* With a focus offset, frames nearest it win — that is the moment being
|
|
101
|
+
* explained. Without one (a vitals-only breach has no instant), the run is
|
|
102
|
+
* sampled evenly so the reader still sees it load. The byte budget is applied
|
|
103
|
+
* last, evicting the least relevant frame first, so the cap is never reached
|
|
104
|
+
* by truncating the interesting end.
|
|
105
|
+
*/
|
|
106
|
+
export function selectFrames(frames, focusOffsetMs, options = {}) {
|
|
107
|
+
const maxFrames = Math.max(1, options.maxFrames ?? DEFAULT_ATTACH_FRAMES);
|
|
108
|
+
const maxBytes = options.maxBytes ?? DEFAULT_ATTACH_BYTES;
|
|
109
|
+
const windowMs = options.windowMs ?? DEFAULT_WINDOW_MS;
|
|
110
|
+
if (!frames.length)
|
|
111
|
+
return { frames: [], dropped: 0 };
|
|
112
|
+
let ranked;
|
|
113
|
+
if (focusOffsetMs === null) {
|
|
114
|
+
if (frames.length <= maxFrames) {
|
|
115
|
+
ranked = [...frames];
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
const step = (frames.length - 1) / (maxFrames - 1 || 1);
|
|
119
|
+
const picked = new Set();
|
|
120
|
+
for (let i = 0; i < maxFrames; i++)
|
|
121
|
+
picked.add(Math.round(i * step));
|
|
122
|
+
ranked = [...picked].sort((a, b) => a - b).map((i) => frames[i]);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
const distance = (f) => Math.abs(f.offsetMs - focusOffsetMs);
|
|
127
|
+
const inWindow = frames.filter((f) => distance(f) <= windowMs);
|
|
128
|
+
// Fall back to the nearest frames overall rather than attaching none: a
|
|
129
|
+
// failure whose frames all fell outside the window still deserves context.
|
|
130
|
+
const pool = inWindow.length ? inWindow : [...frames];
|
|
131
|
+
ranked = [...pool].sort((a, b) => distance(a) - distance(b)).slice(0, maxFrames);
|
|
132
|
+
}
|
|
133
|
+
let dropped = frames.length - ranked.length;
|
|
134
|
+
let total = ranked.reduce((sum, f) => sum + f.bytes, 0);
|
|
135
|
+
if (total > maxBytes) {
|
|
136
|
+
const order = focusOffsetMs === null
|
|
137
|
+
? [...ranked]
|
|
138
|
+
: [...ranked].sort((a, b) => Math.abs(a.offsetMs - focusOffsetMs) - Math.abs(b.offsetMs - focusOffsetMs));
|
|
139
|
+
const keep = new Set();
|
|
140
|
+
let used = 0;
|
|
141
|
+
for (const frame of order) {
|
|
142
|
+
if (used + frame.bytes > maxBytes)
|
|
143
|
+
continue;
|
|
144
|
+
keep.add(frame);
|
|
145
|
+
used += frame.bytes;
|
|
146
|
+
}
|
|
147
|
+
dropped += ranked.length - keep.size;
|
|
148
|
+
ranked = ranked.filter((f) => keep.has(f));
|
|
149
|
+
total = used;
|
|
150
|
+
}
|
|
151
|
+
return { frames: ranked.sort((a, b) => a.offsetMs - b.offsetMs), dropped };
|
|
152
|
+
}
|
|
153
|
+
const DEFAULT_MAX_EVENTS = 60;
|
|
154
|
+
function truncate(text, max = 200) {
|
|
155
|
+
const flat = String(text ?? '')
|
|
156
|
+
.replace(/\s+/g, ' ')
|
|
157
|
+
.trim();
|
|
158
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The instant the run went wrong: the earliest hard error we have a clock
|
|
162
|
+
* reading for. Returns null when the breach has no moment attached — a slow
|
|
163
|
+
* LCP is a property of the whole load, not an event.
|
|
164
|
+
*/
|
|
165
|
+
export function findFocusOffset(input) {
|
|
166
|
+
const candidates = [];
|
|
167
|
+
for (const e of input.console) {
|
|
168
|
+
if (e.type === 'error' && Number.isFinite(e.timestamp)) {
|
|
169
|
+
candidates.push(e.timestamp - input.startedAtMs);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
for (const e of input.pageErrors) {
|
|
173
|
+
if (Number.isFinite(e.timestamp))
|
|
174
|
+
candidates.push(e.timestamp - input.startedAtMs);
|
|
175
|
+
}
|
|
176
|
+
for (const r of input.requests) {
|
|
177
|
+
if (r.failed && r.startedAtMs !== undefined)
|
|
178
|
+
candidates.push(r.startedAtMs - input.startedAtMs);
|
|
179
|
+
}
|
|
180
|
+
const usable = candidates.filter((v) => Number.isFinite(v) && v >= 0);
|
|
181
|
+
return usable.length ? Math.min(...usable) : null;
|
|
182
|
+
}
|
|
183
|
+
export function buildEvidence(input) {
|
|
184
|
+
const notes = [];
|
|
185
|
+
const focusOffsetMs = findFocusOffset(input);
|
|
186
|
+
const picked = selectFrames(input.frames, focusOffsetMs, input.select);
|
|
187
|
+
const frames = picked.frames.map((f) => ({
|
|
188
|
+
offsetMs: f.offsetMs,
|
|
189
|
+
dataUrl: `data:image/jpeg;base64,${f.data}`,
|
|
190
|
+
bytes: f.bytes,
|
|
191
|
+
}));
|
|
192
|
+
const events = [];
|
|
193
|
+
for (const [i, frame] of picked.frames.entries()) {
|
|
194
|
+
events.push({
|
|
195
|
+
offsetMs: frame.offsetMs,
|
|
196
|
+
kind: 'frame',
|
|
197
|
+
label: 'frame',
|
|
198
|
+
severity: 'info',
|
|
199
|
+
frameIndex: i,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
for (const e of input.console) {
|
|
203
|
+
if (e.type !== 'error' && e.type !== 'warn' && e.type !== 'warning')
|
|
204
|
+
continue;
|
|
205
|
+
events.push({
|
|
206
|
+
offsetMs: e.timestamp - input.startedAtMs,
|
|
207
|
+
kind: 'console',
|
|
208
|
+
label: `console.${e.type}`,
|
|
209
|
+
detail: truncate(e.text),
|
|
210
|
+
severity: e.type === 'error' ? 'error' : 'warning',
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
for (const e of input.pageErrors) {
|
|
214
|
+
events.push({
|
|
215
|
+
offsetMs: e.timestamp - input.startedAtMs,
|
|
216
|
+
kind: 'pageerror',
|
|
217
|
+
label: 'uncaught exception',
|
|
218
|
+
detail: truncate(e.text),
|
|
219
|
+
severity: 'error',
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
for (const r of input.requests) {
|
|
223
|
+
if (!r.failed)
|
|
224
|
+
continue;
|
|
225
|
+
if (r.startedAtMs === undefined)
|
|
226
|
+
continue;
|
|
227
|
+
events.push({
|
|
228
|
+
offsetMs: r.startedAtMs - input.startedAtMs,
|
|
229
|
+
kind: 'request',
|
|
230
|
+
label: `${r.status ?? r.errorText ?? 'no response'} ${r.method}`,
|
|
231
|
+
detail: truncate(r.url),
|
|
232
|
+
severity: 'error',
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
if (focusOffsetMs !== null) {
|
|
236
|
+
events.push({
|
|
237
|
+
offsetMs: focusOffsetMs,
|
|
238
|
+
kind: 'marker',
|
|
239
|
+
label: 'first failure',
|
|
240
|
+
detail: input.failures.map((f) => `${f.budget} ${f.actual}`).join(', ') || undefined,
|
|
241
|
+
severity: 'error',
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
// Trim the non-frame rows worst-first, so a page with 400 console warnings
|
|
245
|
+
// still shows its one uncaught exception.
|
|
246
|
+
const frameRows = events.filter((e) => e.kind === 'frame');
|
|
247
|
+
let otherRows = events.filter((e) => e.kind !== 'frame');
|
|
248
|
+
const maxEvents = input.maxEvents ?? DEFAULT_MAX_EVENTS;
|
|
249
|
+
if (otherRows.length > maxEvents) {
|
|
250
|
+
const weight = { error: 0, warning: 1, info: 2 };
|
|
251
|
+
const kept = [...otherRows]
|
|
252
|
+
.sort((a, b) => weight[a.severity] - weight[b.severity] ||
|
|
253
|
+
Math.abs(a.offsetMs - (focusOffsetMs ?? 0)) - Math.abs(b.offsetMs - (focusOffsetMs ?? 0)))
|
|
254
|
+
.slice(0, maxEvents);
|
|
255
|
+
notes.push(`Timeline trimmed to the ${maxEvents} most relevant of ${otherRows.length} events.`);
|
|
256
|
+
otherRows = kept;
|
|
257
|
+
}
|
|
258
|
+
const timeline = [...frameRows, ...otherRows].sort((a, b) => a.offsetMs - b.offsetMs || a.kind.localeCompare(b.kind));
|
|
259
|
+
const droppedFrames = (input.bufferDropped ?? 0) + picked.dropped;
|
|
260
|
+
if (droppedFrames > 0) {
|
|
261
|
+
notes.push(`${droppedFrames} recorded frame(s) not attached — the report keeps the ${frames.length} nearest the failure to stay inside its size budget.`);
|
|
262
|
+
}
|
|
263
|
+
if (!frames.length && input.frames.length === 0) {
|
|
264
|
+
notes.push('No screencast frames were captured for this run.');
|
|
265
|
+
}
|
|
266
|
+
if (focusOffsetMs === null && input.failures.length) {
|
|
267
|
+
notes.push('No single moment of failure could be located — the breach is a load-wide metric, so the frames sample the whole run instead.');
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
reason: input.reason,
|
|
271
|
+
focusOffsetMs,
|
|
272
|
+
frames,
|
|
273
|
+
timeline,
|
|
274
|
+
droppedFrames,
|
|
275
|
+
totalBytes: frames.reduce((sum, f) => sum + f.bytes, 0),
|
|
276
|
+
notes,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=evidence.js.map
|