@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
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* this checks that response before doing anything else.
|
|
8
8
|
*/
|
|
9
9
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
10
|
-
import type { CdpClient } from '../browser/cdp.js';
|
|
11
10
|
import { openUrl } from '../browser/browser.js';
|
|
11
|
+
import type { CdpClient } from '../browser/cdp.js';
|
|
12
12
|
|
|
13
13
|
afterEach(() => {
|
|
14
14
|
vi.restoreAllMocks();
|
|
@@ -43,9 +43,7 @@ describe('openUrl navigation-failure detection', () => {
|
|
|
43
43
|
await expect(openUrl(client, 'sess-1', 'http://127.0.0.1:1/')).rejects.toThrow(
|
|
44
44
|
/net::ERR_CONNECTION_REFUSED/,
|
|
45
45
|
);
|
|
46
|
-
expect(calls).toEqual([
|
|
47
|
-
{ method: 'Page.navigate', params: { url: 'http://127.0.0.1:1/' } },
|
|
48
|
-
]);
|
|
46
|
+
expect(calls).toEqual([{ method: 'Page.navigate', params: { url: 'http://127.0.0.1:1/' } }]);
|
|
49
47
|
});
|
|
50
48
|
|
|
51
49
|
it('rejects the 2 MB URL guard before ever calling Page.navigate', async () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
|
|
3
3
|
// The module exports a singleton `output` which reads process.stdout.isTTY at
|
|
4
4
|
// construction time. We need to control that, so we reset modules per test.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression cover for the 0-byte heap snapshot.
|
|
3
|
+
*
|
|
4
|
+
* Chrome emits `HeapProfiler.reportHeapSnapshotProgress` with finished:true
|
|
5
|
+
* BEFORE it streams a single `addHeapSnapshotChunk` (verified against Chrome
|
|
6
|
+
* 153). startHeapSnapshot used to resolve on that progress event, detach its
|
|
7
|
+
* chunk listener and write an empty file every time. The stub below replays
|
|
8
|
+
* that exact ordering, so a regression fails here rather than in a profiling
|
|
9
|
+
* session nobody notices until DevTools refuses to load the snapshot.
|
|
10
|
+
*/
|
|
11
|
+
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
12
|
+
import { tmpdir } from 'node:os';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
15
|
+
import type { CdpClient } from '../browser/cdp.js';
|
|
16
|
+
import { startHeapSnapshot } from '../browser/profiler.js';
|
|
17
|
+
|
|
18
|
+
type Listener = (params: Record<string, unknown>, sessionId?: string) => void;
|
|
19
|
+
|
|
20
|
+
interface StubOptions {
|
|
21
|
+
/** Chunks Chrome streams, in order. */
|
|
22
|
+
chunks?: string[];
|
|
23
|
+
/** sessionId Chrome tags the chunk events with (defaults to the caller's). */
|
|
24
|
+
chunkSessionId?: string;
|
|
25
|
+
/** Emit the misleading finished:true progress event before the chunks. */
|
|
26
|
+
emitProgressFirst?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A CdpClient that replays Chrome's real takeHeapSnapshot sequence: progress
|
|
31
|
+
* events first, then the chunks, and only then the command response.
|
|
32
|
+
*/
|
|
33
|
+
function heapClient(sessionId: string, options: StubOptions = {}) {
|
|
34
|
+
const { chunks = ['{"snapshot":', '{"node_count":2}', '}'], emitProgressFirst = true } = options;
|
|
35
|
+
const listeners = new Map<string, Set<Listener>>();
|
|
36
|
+
const calls: string[] = [];
|
|
37
|
+
|
|
38
|
+
const emit = (event: string, params: Record<string, unknown>, sid?: string) => {
|
|
39
|
+
for (const fn of listeners.get(event) ?? []) fn(params, sid);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Chrome delivers each of these in its own WebSocket frame, so the stub
|
|
43
|
+
// yields between them. Emitting synchronously would paper over the very
|
|
44
|
+
// bug this file exists to catch: the buggy implementation resolved on the
|
|
45
|
+
// progress event, and only lost the chunks because they arrived a turn
|
|
46
|
+
// later, after its `finally` had detached the listener.
|
|
47
|
+
const tick = () => new Promise<void>((r) => setTimeout(r, 0));
|
|
48
|
+
|
|
49
|
+
const client = {
|
|
50
|
+
on: vi.fn((event: string, fn: Listener) => {
|
|
51
|
+
if (!listeners.has(event)) listeners.set(event, new Set());
|
|
52
|
+
listeners.get(event)!.add(fn);
|
|
53
|
+
return () => listeners.get(event)?.delete(fn);
|
|
54
|
+
}),
|
|
55
|
+
send: vi.fn(async (method: string, _params?: unknown, _sid?: string) => {
|
|
56
|
+
calls.push(method);
|
|
57
|
+
if (method === 'HeapProfiler.takeHeapSnapshot') {
|
|
58
|
+
if (emitProgressFirst) {
|
|
59
|
+
emit(
|
|
60
|
+
'HeapProfiler.reportHeapSnapshotProgress',
|
|
61
|
+
{ done: 1, total: 1, finished: true },
|
|
62
|
+
sessionId,
|
|
63
|
+
);
|
|
64
|
+
await tick();
|
|
65
|
+
}
|
|
66
|
+
for (const chunk of chunks) {
|
|
67
|
+
emit('HeapProfiler.addHeapSnapshotChunk', { chunk }, options.chunkSessionId ?? sessionId);
|
|
68
|
+
await tick();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return {};
|
|
72
|
+
}),
|
|
73
|
+
} as unknown as CdpClient;
|
|
74
|
+
|
|
75
|
+
return { client, calls, listeners };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const dirs: string[] = [];
|
|
79
|
+
async function outPath(name = 'heap.heapsnapshot'): Promise<string> {
|
|
80
|
+
const dir = await mkdtemp(join(tmpdir(), 'monobrowse-heap-'));
|
|
81
|
+
dirs.push(dir);
|
|
82
|
+
return join(dir, name);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
afterEach(async () => {
|
|
86
|
+
await Promise.all(dirs.splice(0).map((d) => rm(d, { recursive: true, force: true })));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
let seq = 0;
|
|
90
|
+
const nextSession = () => `sid-${++seq}`;
|
|
91
|
+
|
|
92
|
+
describe('startHeapSnapshot', () => {
|
|
93
|
+
it('writes every streamed chunk, not an empty file', async () => {
|
|
94
|
+
const path = await outPath();
|
|
95
|
+
const sid = nextSession();
|
|
96
|
+
const { client } = heapClient(sid);
|
|
97
|
+
|
|
98
|
+
const written = await startHeapSnapshot(client, sid, path);
|
|
99
|
+
|
|
100
|
+
expect(written).toBe(path);
|
|
101
|
+
const contents = await readFile(path, 'utf8');
|
|
102
|
+
expect(contents).toBe('{"snapshot":{"node_count":2}}');
|
|
103
|
+
expect(contents.length).toBeGreaterThan(0);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('does not resolve on the finished progress event that precedes the chunks', async () => {
|
|
107
|
+
// The regression: with emitProgressFirst the old implementation resolved
|
|
108
|
+
// before any chunk arrived. Ordering is the whole point of this case.
|
|
109
|
+
const path = await outPath();
|
|
110
|
+
const sid = nextSession();
|
|
111
|
+
const { client } = heapClient(sid, {
|
|
112
|
+
emitProgressFirst: true,
|
|
113
|
+
chunks: ['a'.repeat(64), 'b'.repeat(64)],
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
await startHeapSnapshot(client, sid, path);
|
|
117
|
+
|
|
118
|
+
expect((await readFile(path, 'utf8')).length).toBe(128);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('works when Chrome sends no progress events at all', async () => {
|
|
122
|
+
const path = await outPath();
|
|
123
|
+
const sid = nextSession();
|
|
124
|
+
const { client } = heapClient(sid, { emitProgressFirst: false, chunks: ['{}'] });
|
|
125
|
+
await expect(startHeapSnapshot(client, sid, path)).resolves.toBe(path);
|
|
126
|
+
await expect(readFile(path, 'utf8')).resolves.toBe('{}');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('asks Chrome not to report progress, since nothing consumes it', async () => {
|
|
130
|
+
const path = await outPath();
|
|
131
|
+
const sid = nextSession();
|
|
132
|
+
const { client } = heapClient(sid);
|
|
133
|
+
await startHeapSnapshot(client, sid, path);
|
|
134
|
+
const take = (client.send as ReturnType<typeof vi.fn>).mock.calls.find(
|
|
135
|
+
(c) => c[0] === 'HeapProfiler.takeHeapSnapshot',
|
|
136
|
+
);
|
|
137
|
+
expect(take?.[1]).toEqual({ reportProgress: false });
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('enables and disables the heap profiler around the capture', async () => {
|
|
141
|
+
const path = await outPath();
|
|
142
|
+
const sid = nextSession();
|
|
143
|
+
const { client, calls } = heapClient(sid);
|
|
144
|
+
await startHeapSnapshot(client, sid, path);
|
|
145
|
+
expect(calls).toEqual([
|
|
146
|
+
'HeapProfiler.enable',
|
|
147
|
+
'HeapProfiler.takeHeapSnapshot',
|
|
148
|
+
'HeapProfiler.disable',
|
|
149
|
+
]);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('ignores chunks belonging to another session', async () => {
|
|
153
|
+
const path = await outPath();
|
|
154
|
+
const sid = nextSession();
|
|
155
|
+
const { client } = heapClient(sid, { chunkSessionId: 'other-session' });
|
|
156
|
+
await expect(startHeapSnapshot(client, sid, path)).rejects.toThrow(/no data/i);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('fails loudly instead of writing a 0-byte snapshot', async () => {
|
|
160
|
+
const path = await outPath();
|
|
161
|
+
const sid = nextSession();
|
|
162
|
+
const { client } = heapClient(sid, { chunks: [] });
|
|
163
|
+
await expect(startHeapSnapshot(client, sid, path)).rejects.toThrow(/produced no data/);
|
|
164
|
+
await expect(readFile(path, 'utf8')).rejects.toThrow();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('detaches its chunk listener once the snapshot is done', async () => {
|
|
168
|
+
const path = await outPath();
|
|
169
|
+
const sid = nextSession();
|
|
170
|
+
const { client, listeners } = heapClient(sid);
|
|
171
|
+
await startHeapSnapshot(client, sid, path);
|
|
172
|
+
expect(listeners.get('HeapProfiler.addHeapSnapshotChunk')?.size ?? 0).toBe(0);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('releases the in-progress guard after a failure, so a retry is possible', async () => {
|
|
176
|
+
const failing = {
|
|
177
|
+
on: vi.fn(() => () => {}),
|
|
178
|
+
send: vi.fn(async (method: string) => {
|
|
179
|
+
if (method === 'HeapProfiler.takeHeapSnapshot') throw new Error('target crashed');
|
|
180
|
+
return {};
|
|
181
|
+
}),
|
|
182
|
+
} as unknown as CdpClient;
|
|
183
|
+
|
|
184
|
+
const sid = nextSession();
|
|
185
|
+
await expect(startHeapSnapshot(failing, sid)).rejects.toThrow('target crashed');
|
|
186
|
+
|
|
187
|
+
const path = await outPath();
|
|
188
|
+
const { client } = heapClient(sid);
|
|
189
|
+
await expect(startHeapSnapshot(client, sid, path)).resolves.toBe(path);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #310: a launched Chrome outlives its session, holding its CDP port (9222 by
|
|
3
|
+
* convention, which other local tools want) with zero targets left to
|
|
4
|
+
* reattach to. reapIdleLaunchedBrowser() gives that port back.
|
|
5
|
+
*
|
|
6
|
+
* No real Chrome is spawned: the CDP HTTP endpoints (/json/version, /json/list)
|
|
7
|
+
* are served by a local http server that answers exactly like Chrome does
|
|
8
|
+
* (same approach as browser-launch.test.ts), the browser-level websocket is a
|
|
9
|
+
* fake `ws` socket (same approach as close-browser.test.ts), and process.kill
|
|
10
|
+
* is spied on rather than actually signaling anything.
|
|
11
|
+
*
|
|
12
|
+
* Fixed port range (23490-23499) chosen to avoid colliding with real services.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
16
|
+
import { createServer as createHttpServer, type Server as HttpServer } from 'node:http';
|
|
17
|
+
import { tmpdir } from 'node:os';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
20
|
+
import type { CdpTarget } from '../browser/types.js';
|
|
21
|
+
|
|
22
|
+
const BASE = 23490;
|
|
23
|
+
const MINUTE = 60_000;
|
|
24
|
+
|
|
25
|
+
let lastSocket: FakeWs | null = null;
|
|
26
|
+
|
|
27
|
+
/** Minimal `ws` stand-in: opens on its own and acknowledges every command,
|
|
28
|
+
* so `Browser.close` takes the graceful path without a real browser. */
|
|
29
|
+
class FakeWs {
|
|
30
|
+
handlers = new Map<string, Array<(...a: unknown[]) => void>>();
|
|
31
|
+
sent: string[] = [];
|
|
32
|
+
constructor(public url: string) {
|
|
33
|
+
lastSocket = this;
|
|
34
|
+
queueMicrotask(() => this.emit('open'));
|
|
35
|
+
}
|
|
36
|
+
on(event: string, fn: (...a: unknown[]) => void): void {
|
|
37
|
+
if (!this.handlers.has(event)) this.handlers.set(event, []);
|
|
38
|
+
this.handlers.get(event)!.push(fn);
|
|
39
|
+
}
|
|
40
|
+
emit(event: string, ...args: unknown[]): void {
|
|
41
|
+
for (const fn of [...(this.handlers.get(event) ?? [])]) fn(...args);
|
|
42
|
+
}
|
|
43
|
+
send(data: string, cb?: (err?: Error) => void): void {
|
|
44
|
+
void cb;
|
|
45
|
+
this.sent.push(data);
|
|
46
|
+
const { id } = JSON.parse(data) as { id: number };
|
|
47
|
+
queueMicrotask(() => this.emit('message', Buffer.from(JSON.stringify({ id, result: {} }))));
|
|
48
|
+
}
|
|
49
|
+
close(): void {}
|
|
50
|
+
/** Methods this socket was asked to run, e.g. ['Browser.close']. */
|
|
51
|
+
methods(): string[] {
|
|
52
|
+
return this.sent.map((s) => (JSON.parse(s) as { method: string }).method);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
vi.mock('ws', () => ({ WebSocket: FakeWs }));
|
|
57
|
+
|
|
58
|
+
let tempDir: string;
|
|
59
|
+
let servers: HttpServer[] = [];
|
|
60
|
+
let killSpy: ReturnType<typeof vi.spyOn>;
|
|
61
|
+
|
|
62
|
+
beforeEach(async () => {
|
|
63
|
+
lastSocket = null;
|
|
64
|
+
tempDir = await mkdtemp(join(tmpdir(), 'monobrowse-reap-test-'));
|
|
65
|
+
// ref-cache.ts resolves its cache dir from process.cwd() at module load, so
|
|
66
|
+
// this must be in place before the dynamic import in each test.
|
|
67
|
+
vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
|
|
68
|
+
// The browser is "already gone" by the time closeBrowser polls for its exit
|
|
69
|
+
// (ESRCH on the liveness probe) — the realistic outcome of Browser.close.
|
|
70
|
+
killSpy = vi.spyOn(process, 'kill').mockImplementation((_pid: unknown, signal?: unknown) => {
|
|
71
|
+
if (signal === 0) throw Object.assign(new Error('ESRCH'), { code: 'ESRCH' });
|
|
72
|
+
return true as never;
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
afterEach(async () => {
|
|
77
|
+
await Promise.all(servers.map((s) => new Promise<void>((resolve) => s.close(() => resolve()))));
|
|
78
|
+
servers = [];
|
|
79
|
+
vi.restoreAllMocks();
|
|
80
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
/** An http server answering /json/version and /json/list exactly like Chrome. */
|
|
84
|
+
function fakeChrome(port: number, targets: Partial<CdpTarget>[]): Promise<void> {
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
const s = createHttpServer((req, res) => {
|
|
87
|
+
if (req.url === '/json/version') {
|
|
88
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
89
|
+
res.end(
|
|
90
|
+
JSON.stringify({
|
|
91
|
+
Browser: 'Chrome/999.0.0.0',
|
|
92
|
+
webSocketDebuggerUrl: `ws://127.0.0.1:${port}/devtools/browser/fake-browser-id`,
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
} else if (req.url === '/json/list') {
|
|
96
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
97
|
+
res.end(JSON.stringify(targets));
|
|
98
|
+
} else {
|
|
99
|
+
res.writeHead(404);
|
|
100
|
+
res.end();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
s.once('error', reject);
|
|
104
|
+
s.listen(port, '127.0.0.1', () => {
|
|
105
|
+
servers.push(s);
|
|
106
|
+
resolve();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const PORT_FILE = () => join(tempDir, '.monomind', 'monobrowse', 'active-port.json');
|
|
112
|
+
|
|
113
|
+
async function writePersistedPort(record: {
|
|
114
|
+
port: number;
|
|
115
|
+
pid?: number;
|
|
116
|
+
launched: boolean;
|
|
117
|
+
savedAt: number;
|
|
118
|
+
}): Promise<void> {
|
|
119
|
+
await mkdir(join(tempDir, '.monomind', 'monobrowse'), { recursive: true });
|
|
120
|
+
await writeFile(PORT_FILE(), JSON.stringify(record), 'utf-8');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function portFileExists(): Promise<boolean> {
|
|
124
|
+
return readFile(PORT_FILE(), 'utf-8').then(
|
|
125
|
+
() => true,
|
|
126
|
+
() => false,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function loadBrowser() {
|
|
131
|
+
vi.resetModules();
|
|
132
|
+
return import('../browser/browser.js');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** A page target as Chrome reports it when NO debugger is attached to it. */
|
|
136
|
+
function idlePageTarget(id: string, port: number): Partial<CdpTarget> {
|
|
137
|
+
return {
|
|
138
|
+
id,
|
|
139
|
+
type: 'page',
|
|
140
|
+
title: 'Example',
|
|
141
|
+
url: 'https://example.com/',
|
|
142
|
+
webSocketDebuggerUrl: `ws://127.0.0.1:${port}/devtools/page/${id}`,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
describe('#310 reapIdleLaunchedBrowser — give back a CDP port nothing is using', () => {
|
|
147
|
+
it('reaps a launched instance that has had zero targets past the idle threshold', async () => {
|
|
148
|
+
const port = BASE + 0;
|
|
149
|
+
await fakeChrome(port, []);
|
|
150
|
+
await writePersistedPort({
|
|
151
|
+
port,
|
|
152
|
+
pid: 54321,
|
|
153
|
+
launched: true,
|
|
154
|
+
savedAt: Date.now() - 45 * MINUTE,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const { reapIdleLaunchedBrowser } = await loadBrowser();
|
|
158
|
+
await expect(reapIdleLaunchedBrowser()).resolves.toBe(port);
|
|
159
|
+
|
|
160
|
+
expect(lastSocket?.url).toBe(`ws://127.0.0.1:${port}/devtools/browser/fake-browser-id`);
|
|
161
|
+
expect(lastSocket?.methods()).toContain('Browser.close');
|
|
162
|
+
// The port file is the session handle — a reaped session must not leave
|
|
163
|
+
// one behind for the next command to chase.
|
|
164
|
+
expect(await portFileExists()).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('does NOT reap a launched instance that still has open page targets', async () => {
|
|
168
|
+
const port = BASE + 1;
|
|
169
|
+
await fakeChrome(port, [idlePageTarget('AAA', port)]);
|
|
170
|
+
await writePersistedPort({
|
|
171
|
+
port,
|
|
172
|
+
pid: 54322,
|
|
173
|
+
launched: true,
|
|
174
|
+
savedAt: Date.now() - 45 * MINUTE,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const { reapIdleLaunchedBrowser } = await loadBrowser();
|
|
178
|
+
await expect(reapIdleLaunchedBrowser()).resolves.toBeNull();
|
|
179
|
+
|
|
180
|
+
expect(lastSocket).toBeNull();
|
|
181
|
+
expect(killSpy).not.toHaveBeenCalled();
|
|
182
|
+
expect(await portFileExists()).toBe(true);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('does NOT reap a browser this tool never launched, however idle or old it is', async () => {
|
|
186
|
+
// `connect` records launched:false — that browser belongs to the user.
|
|
187
|
+
const port = BASE + 2;
|
|
188
|
+
await fakeChrome(port, []);
|
|
189
|
+
await writePersistedPort({
|
|
190
|
+
port,
|
|
191
|
+
pid: 54323,
|
|
192
|
+
launched: false,
|
|
193
|
+
savedAt: Date.now() - 48 * 60 * MINUTE,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const { reapIdleLaunchedBrowser } = await loadBrowser();
|
|
197
|
+
await expect(reapIdleLaunchedBrowser()).resolves.toBeNull();
|
|
198
|
+
|
|
199
|
+
expect(lastSocket).toBeNull();
|
|
200
|
+
expect(killSpy).not.toHaveBeenCalled();
|
|
201
|
+
expect(await portFileExists()).toBe(true);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('does NOT reap while a client is still attached to a target', async () => {
|
|
205
|
+
// Chrome omits webSocketDebuggerUrl from a target that already has a
|
|
206
|
+
// debugger attached — an in-flight session, not an abandoned browser.
|
|
207
|
+
const port = BASE + 3;
|
|
208
|
+
await fakeChrome(port, [
|
|
209
|
+
{ id: 'BBB', type: 'page', title: 'Example', url: 'https://example.com/' },
|
|
210
|
+
]);
|
|
211
|
+
await writePersistedPort({
|
|
212
|
+
port,
|
|
213
|
+
pid: 54324,
|
|
214
|
+
launched: true,
|
|
215
|
+
savedAt: Date.now() - 45 * MINUTE,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const { reapIdleLaunchedBrowser } = await loadBrowser();
|
|
219
|
+
await expect(reapIdleLaunchedBrowser()).resolves.toBeNull();
|
|
220
|
+
|
|
221
|
+
expect(lastSocket).toBeNull();
|
|
222
|
+
expect(await portFileExists()).toBe(true);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('does NOT reap an instance that is still inside the idle threshold', async () => {
|
|
226
|
+
// A live session can momentarily have no targets (last tab closed, next
|
|
227
|
+
// command about to open one) — reaping that would break reattach.
|
|
228
|
+
const port = BASE + 4;
|
|
229
|
+
await fakeChrome(port, []);
|
|
230
|
+
await writePersistedPort({ port, pid: 54325, launched: true, savedAt: Date.now() - MINUTE });
|
|
231
|
+
|
|
232
|
+
const { reapIdleLaunchedBrowser } = await loadBrowser();
|
|
233
|
+
await expect(reapIdleLaunchedBrowser()).resolves.toBeNull();
|
|
234
|
+
|
|
235
|
+
expect(lastSocket).toBeNull();
|
|
236
|
+
expect(await portFileExists()).toBe(true);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('does NOT reap when the persisted record has no savedAt (age unknowable)', async () => {
|
|
240
|
+
const port = BASE + 5;
|
|
241
|
+
await fakeChrome(port, []);
|
|
242
|
+
await mkdir(join(tempDir, '.monomind', 'monobrowse'), { recursive: true });
|
|
243
|
+
await writeFile(PORT_FILE(), JSON.stringify({ port, pid: 54326, launched: true }), 'utf-8');
|
|
244
|
+
|
|
245
|
+
const { reapIdleLaunchedBrowser } = await loadBrowser();
|
|
246
|
+
await expect(reapIdleLaunchedBrowser()).resolves.toBeNull();
|
|
247
|
+
expect(lastSocket).toBeNull();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('is a no-op with no persisted session at all', async () => {
|
|
251
|
+
const { reapIdleLaunchedBrowser } = await loadBrowser();
|
|
252
|
+
await expect(reapIdleLaunchedBrowser()).resolves.toBeNull();
|
|
253
|
+
expect(killSpy).not.toHaveBeenCalled();
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('launchBrowser reaps the stale instance before deciding what to do with the port', async () => {
|
|
257
|
+
// The wiring that makes the reaper run at all: every launch/attach is a
|
|
258
|
+
// chance to give back a port a previous session abandoned.
|
|
259
|
+
const port = BASE + 6;
|
|
260
|
+
await fakeChrome(port, []);
|
|
261
|
+
await writePersistedPort({
|
|
262
|
+
port,
|
|
263
|
+
pid: 54327,
|
|
264
|
+
launched: true,
|
|
265
|
+
savedAt: Date.now() - 45 * MINUTE,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const { launchBrowser } = await loadBrowser();
|
|
269
|
+
// The fake Chrome stays listening, so launchBrowser attaches to it after
|
|
270
|
+
// the reap rather than spawning a real browser.
|
|
271
|
+
await expect(launchBrowser({ port })).resolves.toBe(port);
|
|
272
|
+
|
|
273
|
+
expect(lastSocket?.methods()).toContain('Browser.close');
|
|
274
|
+
expect(await portFileExists()).toBe(false);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { join } from 'path';
|
|
4
|
-
import {
|
|
1
|
+
import { mkdtemp, readFile, rm, stat } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
5
|
|
|
6
6
|
// We need to mock process.cwd() before importing ref-cache so CACHE_DIR
|
|
7
7
|
// resolves to our temp directory.
|
|
@@ -26,7 +26,9 @@ async function importRefCache() {
|
|
|
26
26
|
return import('../browser/ref-cache.js');
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
function makeRefs(
|
|
29
|
+
function makeRefs(
|
|
30
|
+
count = 2,
|
|
31
|
+
): Map<string, { ref: string; role: string; name: string; nodeId: number }> {
|
|
30
32
|
const map = new Map();
|
|
31
33
|
for (let i = 1; i <= count; i++) {
|
|
32
34
|
map.set(`ref${i}`, { ref: `ref${i}`, role: 'button', name: `Button ${i}`, nodeId: i });
|
|
@@ -68,7 +70,7 @@ describe('ref-cache', () => {
|
|
|
68
70
|
|
|
69
71
|
// Now overwrite the file with invalid JSON
|
|
70
72
|
const cacheFile = join(tempDir, '.monomind', 'monobrowse', 'ax-snapshot.json');
|
|
71
|
-
const { writeFile: wf } = await import('fs/promises');
|
|
73
|
+
const { writeFile: wf } = await import('node:fs/promises');
|
|
72
74
|
await wf(cacheFile, '{{{not valid json!!!');
|
|
73
75
|
|
|
74
76
|
const loaded = await mod.loadRefCache('t');
|
|
@@ -117,7 +119,7 @@ describe('ref-cache', () => {
|
|
|
117
119
|
const cacheFile = join(tempDir, '.monomind', 'monobrowse', 'ax-snapshot.json');
|
|
118
120
|
const raw = JSON.parse(await readFile(cacheFile, 'utf8'));
|
|
119
121
|
raw.savedAt = Date.now() - mod.REF_CACHE_STALE_MS - 5000;
|
|
120
|
-
const { writeFile: wf } = await import('fs/promises');
|
|
122
|
+
const { writeFile: wf } = await import('node:fs/promises');
|
|
121
123
|
await wf(cacheFile, JSON.stringify(raw));
|
|
122
124
|
|
|
123
125
|
const loaded = await mod.loadRefCache('t1');
|
|
@@ -138,13 +140,21 @@ describe('active-port persistence', () => {
|
|
|
138
140
|
const mod = await importRefCache();
|
|
139
141
|
await mod.saveActivePort(9333);
|
|
140
142
|
expect(await mod.loadActivePort()).toBe(9333);
|
|
141
|
-
expect(await mod.loadActivePortInfo()).toEqual({
|
|
143
|
+
expect(await mod.loadActivePortInfo()).toEqual({
|
|
144
|
+
port: 9333,
|
|
145
|
+
launched: true,
|
|
146
|
+
savedAt: expect.any(Number),
|
|
147
|
+
});
|
|
142
148
|
});
|
|
143
149
|
|
|
144
150
|
it('connect provenance: launched:false survives the round-trip', async () => {
|
|
145
151
|
const mod = await importRefCache();
|
|
146
152
|
await mod.saveActivePort(9229, { launched: false });
|
|
147
|
-
expect(await mod.loadActivePortInfo()).toEqual({
|
|
153
|
+
expect(await mod.loadActivePortInfo()).toEqual({
|
|
154
|
+
port: 9229,
|
|
155
|
+
launched: false,
|
|
156
|
+
savedAt: expect.any(Number),
|
|
157
|
+
});
|
|
148
158
|
});
|
|
149
159
|
|
|
150
160
|
it('clear removes the file; load returns null afterwards', async () => {
|
|
@@ -196,11 +206,14 @@ describe('active-port persistence', () => {
|
|
|
196
206
|
|
|
197
207
|
it('#115: a non-numeric or non-positive persisted pid is dropped, not trusted', async () => {
|
|
198
208
|
const mod = await importRefCache();
|
|
199
|
-
const { writeFile, mkdir } = await import('fs/promises');
|
|
209
|
+
const { writeFile, mkdir } = await import('node:fs/promises');
|
|
200
210
|
const dir = join(process.cwd(), '.monomind', 'monobrowse');
|
|
201
211
|
await mkdir(dir, { recursive: true });
|
|
202
212
|
for (const badPid of ['54321', -1, 0, 1.5, null]) {
|
|
203
|
-
await writeFile(
|
|
213
|
+
await writeFile(
|
|
214
|
+
join(dir, 'active-port.json'),
|
|
215
|
+
JSON.stringify({ port: 9333, launched: true, pid: badPid }),
|
|
216
|
+
);
|
|
204
217
|
const info = await mod.loadActivePortInfo();
|
|
205
218
|
expect(info!.pid).toBeUndefined();
|
|
206
219
|
}
|
|
@@ -208,7 +221,7 @@ describe('active-port persistence', () => {
|
|
|
208
221
|
|
|
209
222
|
it('rejects out-of-range or non-integer persisted ports', async () => {
|
|
210
223
|
const mod = await importRefCache();
|
|
211
|
-
const { writeFile, mkdir } = await import('fs/promises');
|
|
224
|
+
const { writeFile, mkdir } = await import('node:fs/promises');
|
|
212
225
|
const dir = join(process.cwd(), '.monomind', 'monobrowse');
|
|
213
226
|
await mkdir(dir, { recursive: true });
|
|
214
227
|
for (const bad of [80, 70000, 1.5, '9222', null]) {
|