@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
package/src/cli/output.ts
CHANGED
|
@@ -17,20 +17,20 @@ class OutputFormatter {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
printSuccess(message: string): void {
|
|
20
|
-
console.log(this.c('green', '✓')
|
|
20
|
+
console.log(`${this.c('green', '✓')} ${message}`);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
printError(message: string, details?: string): void {
|
|
24
|
-
console.error(this.c('red', '✗')
|
|
25
|
-
if (details) console.error(this.c('red',
|
|
24
|
+
console.error(`${this.c('red', '✗')} ${message}`);
|
|
25
|
+
if (details) console.error(this.c('red', ` ${details}`));
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
printWarning(message: string): void {
|
|
29
|
-
console.warn(this.c('yellow', '⚠')
|
|
29
|
+
console.warn(`${this.c('yellow', '⚠')} ${message}`);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
printInfo(message: string): void {
|
|
33
|
-
console.log(this.c('cyan', 'ℹ')
|
|
33
|
+
console.log(`${this.c('cyan', 'ℹ')} ${message}`);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
package/src/cli/platform.ts
CHANGED
|
@@ -3,22 +3,22 @@
|
|
|
3
3
|
* Sessions stored as JSON at ~/.monomind/sessions.json
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { Command } from 'commander';
|
|
7
|
-
import { readFile, writeFile, mkdir, chmod } from 'node:fs/promises';
|
|
8
6
|
import { existsSync } from 'node:fs';
|
|
9
|
-
import {
|
|
7
|
+
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
10
8
|
import { homedir } from 'node:os';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { Command } from 'commander';
|
|
11
11
|
import { getAdapter } from '../index.js';
|
|
12
12
|
|
|
13
|
-
const CDP_PORT = Number(process.env
|
|
13
|
+
const CDP_PORT = Number(process.env.MONOMIND_CDP_PORT ?? 9222);
|
|
14
14
|
|
|
15
15
|
const SESSIONS_FILE = join(homedir(), '.monomind', 'sessions.json');
|
|
16
16
|
|
|
17
17
|
interface Session {
|
|
18
|
-
id: string;
|
|
18
|
+
id: string; // "platform:username"
|
|
19
19
|
platform: string;
|
|
20
20
|
username: string;
|
|
21
|
-
cookies: string;
|
|
21
|
+
cookies: string; // JSON-serialized
|
|
22
22
|
userAgent: string;
|
|
23
23
|
createdAt: number;
|
|
24
24
|
lastUsedAt: number;
|
|
@@ -46,7 +46,9 @@ export function createPlatformCommand(): Command {
|
|
|
46
46
|
|
|
47
47
|
cmd
|
|
48
48
|
.command('connect <platform>')
|
|
49
|
-
.description(
|
|
49
|
+
.description(
|
|
50
|
+
'Connect a platform account via browser — opens Chrome, waits for login, saves session',
|
|
51
|
+
)
|
|
50
52
|
.option('--port <number>', 'CDP port of running Chrome instance', String(CDP_PORT))
|
|
51
53
|
.option('--timeout <seconds>', 'Login timeout in seconds', '300')
|
|
52
54
|
.action(async (platform: string, opts: { port: string; timeout: string }) => {
|
|
@@ -57,7 +59,9 @@ export function createPlatformCommand(): Command {
|
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
let adapter: ReturnType<typeof getAdapter>;
|
|
60
|
-
try {
|
|
62
|
+
try {
|
|
63
|
+
adapter = getAdapter(platform);
|
|
64
|
+
} catch (err) {
|
|
61
65
|
console.error(err instanceof Error ? err.message : String(err));
|
|
62
66
|
process.exit(1);
|
|
63
67
|
}
|
|
@@ -67,7 +71,9 @@ export function createPlatformCommand(): Command {
|
|
|
67
71
|
|
|
68
72
|
// Lazy-load browser module
|
|
69
73
|
let browser: typeof import('@monoes/monobrowse');
|
|
70
|
-
try {
|
|
74
|
+
try {
|
|
75
|
+
browser = await import('@monoes/monobrowse');
|
|
76
|
+
} catch (err) {
|
|
71
77
|
console.error('Browser module unavailable:', err);
|
|
72
78
|
process.exit(1);
|
|
73
79
|
}
|
|
@@ -91,16 +97,18 @@ export function createPlatformCommand(): Command {
|
|
|
91
97
|
const { client, sessionId: sid } = conn;
|
|
92
98
|
|
|
93
99
|
try {
|
|
94
|
-
const
|
|
100
|
+
const _refs = new Map();
|
|
95
101
|
|
|
96
102
|
// Restore any existing cookies first so the user might already be logged in
|
|
97
103
|
const sessions = await loadSessions();
|
|
98
|
-
const existingSession = sessions.find(s => s.platform === platform);
|
|
104
|
+
const existingSession = sessions.find((s) => s.platform === platform);
|
|
99
105
|
if (existingSession?.cookies) {
|
|
100
106
|
try {
|
|
101
107
|
const cookies = JSON.parse(existingSession.cookies);
|
|
102
108
|
if (cookies.length > 0) await browser.setCookies(client, sid, cookies);
|
|
103
|
-
} catch {
|
|
109
|
+
} catch {
|
|
110
|
+
/* ignore */
|
|
111
|
+
}
|
|
104
112
|
}
|
|
105
113
|
|
|
106
114
|
// Navigate to login URL
|
|
@@ -120,25 +128,31 @@ export function createPlatformCommand(): Command {
|
|
|
120
128
|
console.log(`│ Please sign in to ${platform.padEnd(34)}│`);
|
|
121
129
|
console.log('│ │');
|
|
122
130
|
console.log('│ Complete the login in the browser window. │');
|
|
123
|
-
console.log(
|
|
131
|
+
console.log(
|
|
132
|
+
`│ Waiting up to ${Math.round(timeoutMs / 60000)} minutes for login… │`,
|
|
133
|
+
);
|
|
124
134
|
console.log('└──────────────────────────────────────────────────────┘');
|
|
125
135
|
console.log('');
|
|
126
136
|
|
|
127
137
|
const deadline = Date.now() + timeoutMs;
|
|
128
138
|
while (Date.now() < deadline) {
|
|
129
|
-
await new Promise(r => setTimeout(r, 2000));
|
|
139
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
130
140
|
isLoggedIn = await adapter.isLoggedIn(pageIface).catch(() => false);
|
|
131
141
|
if (isLoggedIn) break;
|
|
132
142
|
// Navigate to app URL if user manually browsed away from login
|
|
133
143
|
const currentUrl = await browser.getCurrentUrl(client, sid).catch(() => '');
|
|
134
|
-
if (
|
|
135
|
-
|
|
144
|
+
if (
|
|
145
|
+
!currentUrl.includes(new URL(adapter.loginURL()).hostname) &&
|
|
146
|
+
!currentUrl.includes(new URL(adapter.baseURL).hostname)
|
|
147
|
+
) {
|
|
136
148
|
await browser.openUrl(client, sid, adapter.loginURL());
|
|
137
149
|
}
|
|
138
150
|
}
|
|
139
151
|
|
|
140
152
|
if (!isLoggedIn) {
|
|
141
|
-
console.error(
|
|
153
|
+
console.error(
|
|
154
|
+
`\nLogin timeout after ${Math.round(timeoutMs / 1000)}s. Session not saved.`,
|
|
155
|
+
);
|
|
142
156
|
process.exit(1);
|
|
143
157
|
}
|
|
144
158
|
}
|
|
@@ -147,7 +161,9 @@ export function createPlatformCommand(): Command {
|
|
|
147
161
|
|
|
148
162
|
// Capture cookies and user agent
|
|
149
163
|
const liveCookies = await browser.getCookies(client, sid).catch(() => []);
|
|
150
|
-
const userAgent = await browser
|
|
164
|
+
const userAgent = (await browser
|
|
165
|
+
.evaluateJs(client, sid, 'navigator.userAgent')
|
|
166
|
+
.catch(() => 'unknown')) as string;
|
|
151
167
|
const username = await adapter.extractUsername(pageIface).catch(() => `${platform}-user`);
|
|
152
168
|
|
|
153
169
|
// Save to sessions file
|
|
@@ -160,8 +176,9 @@ export function createPlatformCommand(): Command {
|
|
|
160
176
|
createdAt: existingSession?.createdAt ?? Date.now(),
|
|
161
177
|
lastUsedAt: Date.now(),
|
|
162
178
|
};
|
|
163
|
-
const idx = sessions.findIndex(s => s.platform === platform);
|
|
164
|
-
if (idx >= 0) sessions[idx] = sessionEntry;
|
|
179
|
+
const idx = sessions.findIndex((s) => s.platform === platform);
|
|
180
|
+
if (idx >= 0) sessions[idx] = sessionEntry;
|
|
181
|
+
else sessions.push(sessionEntry);
|
|
165
182
|
await saveSessions(sessions);
|
|
166
183
|
|
|
167
184
|
console.log(`\n✓ Session saved: ${sessionEntry.id}`);
|
|
@@ -183,11 +200,15 @@ export function createPlatformCommand(): Command {
|
|
|
183
200
|
console.log('No connected accounts. Run: browse platform connect <platform>');
|
|
184
201
|
return;
|
|
185
202
|
}
|
|
186
|
-
console.log(
|
|
203
|
+
console.log(
|
|
204
|
+
`\n${'ID'.padEnd(40)} ${'PLATFORM'.padEnd(12)} ${'USERNAME'.padEnd(30)} LAST USED`,
|
|
205
|
+
);
|
|
187
206
|
console.log('─'.repeat(95));
|
|
188
207
|
for (const s of sessions) {
|
|
189
208
|
const lastUsed = new Date(s.lastUsedAt).toLocaleString();
|
|
190
|
-
console.log(
|
|
209
|
+
console.log(
|
|
210
|
+
`${s.id.padEnd(40)} ${s.platform.padEnd(12)} ${s.username.padEnd(30)} ${lastUsed}`,
|
|
211
|
+
);
|
|
191
212
|
}
|
|
192
213
|
});
|
|
193
214
|
|
|
@@ -196,7 +217,7 @@ export function createPlatformCommand(): Command {
|
|
|
196
217
|
.description('Remove a saved platform session')
|
|
197
218
|
.action(async (id: string) => {
|
|
198
219
|
const sessions = await loadSessions();
|
|
199
|
-
const idx = sessions.findIndex(s => s.id === id);
|
|
220
|
+
const idx = sessions.findIndex((s) => s.id === id);
|
|
200
221
|
if (idx < 0) {
|
|
201
222
|
console.error(`Session not found: ${id}`);
|
|
202
223
|
console.log('Run "browse platform list" to see available sessions.');
|
package/src/cli/types.ts
CHANGED
|
@@ -37,7 +37,7 @@ export interface CommandExample {
|
|
|
37
37
|
description: string;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export type CommandAction = (ctx: CommandContext) => Promise<CommandResult |
|
|
40
|
+
export type CommandAction = (ctx: CommandContext) => Promise<CommandResult | undefined>;
|
|
41
41
|
|
|
42
42
|
export interface CommandResult {
|
|
43
43
|
success: boolean;
|
package/src/cli.ts
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* Dispatches to the same command tree used by `monomind browse`
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { createRequire } from 'module';
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
8
|
import browseCommand from './cli/commands.js';
|
|
9
9
|
import { output } from './cli/output.js';
|
|
10
|
-
import type { Command, CommandContext,
|
|
10
|
+
import type { Command, CommandContext, ParsedFlags } from './cli/types.js';
|
|
11
11
|
|
|
12
12
|
const _require = createRequire(import.meta.url);
|
|
13
13
|
|
|
@@ -16,7 +16,7 @@ const _require = createRequire(import.meta.url);
|
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
17
17
|
|
|
18
18
|
function findSub(name: string, subs: Command[]): Command | undefined {
|
|
19
|
-
return subs.find(s => s.name === name || s.aliases?.includes(name));
|
|
19
|
+
return subs.find((s) => s.name === name || s.aliases?.includes(name));
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// ---------------------------------------------------------------------------
|
|
@@ -33,11 +33,14 @@ function buildShortMap(cmd: Command | undefined): Record<string, string> {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
function isBooleanOpt(name: string, cmd: Command | undefined): boolean {
|
|
36
|
-
const opt = cmd?.options?.find(o => o.name === name);
|
|
36
|
+
const opt = cmd?.options?.find((o) => o.name === name);
|
|
37
37
|
return opt?.type === 'boolean';
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
function parseArgv(
|
|
40
|
+
function parseArgv(
|
|
41
|
+
argv: string[],
|
|
42
|
+
sub: Command | undefined,
|
|
43
|
+
): { args: string[]; flags: ParsedFlags } {
|
|
41
44
|
const shortMap = buildShortMap(sub);
|
|
42
45
|
const flags: ParsedFlags = { _: [] };
|
|
43
46
|
const positional: string[] = [];
|
|
@@ -81,7 +84,7 @@ function parseArgv(argv: string[], sub: Command | undefined): { args: string[];
|
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
// Coerce number options
|
|
84
|
-
for (const opt of
|
|
87
|
+
for (const opt of sub?.options ?? []) {
|
|
85
88
|
if (opt.type === 'number' && typeof flags[opt.name] === 'string') {
|
|
86
89
|
flags[opt.name] = Number(flags[opt.name]);
|
|
87
90
|
}
|
|
@@ -95,11 +98,12 @@ function parseArgv(argv: string[], sub: Command | undefined): { args: string[];
|
|
|
95
98
|
// ---------------------------------------------------------------------------
|
|
96
99
|
|
|
97
100
|
function printHelp(cmd: Command, prefix = 'monobrowse'): void {
|
|
98
|
-
const usage =
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
const usage =
|
|
102
|
+
cmd.name === 'browse'
|
|
103
|
+
? `${prefix} <subcommand> [options]`
|
|
104
|
+
: `${prefix} ${cmd.name} [subcommand] [options]`;
|
|
101
105
|
console.log(`\nUsage: ${usage}\n`);
|
|
102
|
-
console.log(cmd.description
|
|
106
|
+
console.log(`${cmd.description}\n`);
|
|
103
107
|
|
|
104
108
|
if (cmd.subcommands?.length) {
|
|
105
109
|
console.log('Subcommands:');
|
|
@@ -160,7 +164,12 @@ async function dispatch(cmd: Command, argv: string[]): Promise<void> {
|
|
|
160
164
|
const nested = findSub(rest[0], sub.subcommands);
|
|
161
165
|
if (nested?.action) {
|
|
162
166
|
const { args, flags } = parseArgv(rest.slice(1), nested);
|
|
163
|
-
const ctx: CommandContext = {
|
|
167
|
+
const ctx: CommandContext = {
|
|
168
|
+
args,
|
|
169
|
+
flags,
|
|
170
|
+
cwd: process.cwd(),
|
|
171
|
+
interactive: !!process.stdout.isTTY,
|
|
172
|
+
};
|
|
164
173
|
const result = await nested.action(ctx);
|
|
165
174
|
if (result && !result.success) process.exitCode = result.exitCode ?? 1;
|
|
166
175
|
return;
|
|
@@ -174,7 +183,12 @@ async function dispatch(cmd: Command, argv: string[]): Promise<void> {
|
|
|
174
183
|
}
|
|
175
184
|
|
|
176
185
|
const { args, flags } = parseArgv(rest, sub);
|
|
177
|
-
const ctx: CommandContext = {
|
|
186
|
+
const ctx: CommandContext = {
|
|
187
|
+
args,
|
|
188
|
+
flags,
|
|
189
|
+
cwd: process.cwd(),
|
|
190
|
+
interactive: !!process.stdout.isTTY,
|
|
191
|
+
};
|
|
178
192
|
const result = await sub.action(ctx);
|
|
179
193
|
if (result && !result.success) process.exitCode = result.exitCode ?? 1;
|
|
180
194
|
}
|
|
@@ -200,9 +214,11 @@ async function main(): Promise<void> {
|
|
|
200
214
|
await dispatch(browseCommand, userArgs);
|
|
201
215
|
}
|
|
202
216
|
|
|
203
|
-
main()
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
217
|
+
main()
|
|
218
|
+
.then(() => {
|
|
219
|
+
process.exit(process.exitCode ?? 0);
|
|
220
|
+
})
|
|
221
|
+
.catch((err) => {
|
|
222
|
+
output.printError(err instanceof Error ? err.message : String(err));
|
|
223
|
+
process.exit(1);
|
|
224
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export * from './browser/index.js';
|
|
2
1
|
export * from './browser/action-builder/analyzer.js';
|
|
3
2
|
export * from './browser/action-builder/types.js';
|
|
4
3
|
export * from './browser/adapters/index.js';
|
|
5
|
-
export {
|
|
4
|
+
export { getDashboard, startDashboard } from './browser/dashboard/server.js';
|
|
5
|
+
export * from './browser/index.js';
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accessibility rules computed from the CDP accessibility tree.
|
|
3
|
+
*
|
|
4
|
+
* Deliberate scope limit: colour contrast is NOT computed here. Contrast
|
|
5
|
+
* needs resolved foreground/background pixels or computed styles, and the AX
|
|
6
|
+
* tree carries neither — guessing it would produce confident nonsense. The
|
|
7
|
+
* report says so explicitly instead.
|
|
8
|
+
*
|
|
9
|
+
* Every rule is a pure function of a node array, so the fixtures in the tests
|
|
10
|
+
* are just hand-written AX trees.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { A11yFinding, AxNode, FocusCandidate } from './types.js';
|
|
14
|
+
|
|
15
|
+
/** Roles whose whole purpose is to be activated — a nameless one is unusable. */
|
|
16
|
+
const COMMAND_ROLES = new Set([
|
|
17
|
+
'button',
|
|
18
|
+
'link',
|
|
19
|
+
'menuitem',
|
|
20
|
+
'menuitemcheckbox',
|
|
21
|
+
'menuitemradio',
|
|
22
|
+
'tab',
|
|
23
|
+
'switch',
|
|
24
|
+
'treeitem',
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
/** Roles that take user input and therefore need a programmatic label. */
|
|
28
|
+
const FORM_FIELD_ROLES = new Set([
|
|
29
|
+
'textbox',
|
|
30
|
+
'searchbox',
|
|
31
|
+
'combobox',
|
|
32
|
+
'listbox',
|
|
33
|
+
'checkbox',
|
|
34
|
+
'radio',
|
|
35
|
+
'slider',
|
|
36
|
+
'spinbutton',
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
const IMAGE_ROLES = new Set(['image', 'img']);
|
|
40
|
+
|
|
41
|
+
/** Natively focusable tags — a negative tabindex on these removes them from the tab order. */
|
|
42
|
+
const NATIVELY_FOCUSABLE = new Set(['a', 'button', 'input', 'select', 'textarea', 'summary']);
|
|
43
|
+
|
|
44
|
+
function strValue(v: unknown): string {
|
|
45
|
+
return typeof v === 'string' ? v : v == null ? '' : String(v);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function roleOf(node: AxNode): string {
|
|
49
|
+
return strValue(node.role?.value).toLowerCase();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function nameOf(node: AxNode): string {
|
|
53
|
+
return strValue(node.name?.value).trim();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function propOf(node: AxNode, name: string): unknown {
|
|
57
|
+
return node.properties?.find((p) => p.name === name)?.value?.value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function locatorFor(node: AxNode, locators?: Map<number, string>): string {
|
|
61
|
+
if (node.backendDOMNodeId !== undefined) {
|
|
62
|
+
const found = locators?.get(node.backendDOMNodeId);
|
|
63
|
+
if (found) return found;
|
|
64
|
+
}
|
|
65
|
+
return `ax-node:${node.nodeId}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Depth-first document order. `Accessibility.getFullAXTree` usually returns
|
|
70
|
+
* nodes pre-ordered already, but heading-order is the one rule where a
|
|
71
|
+
* reordered array would silently produce wrong findings, so we walk childIds
|
|
72
|
+
* ourselves. Nodes unreachable from a root are appended in array order rather
|
|
73
|
+
* than dropped.
|
|
74
|
+
*/
|
|
75
|
+
export function orderNodes(nodes: AxNode[]): AxNode[] {
|
|
76
|
+
const byId = new Map<string, AxNode>();
|
|
77
|
+
for (const n of nodes) byId.set(String(n.nodeId), n);
|
|
78
|
+
|
|
79
|
+
const ids = new Set(byId.keys());
|
|
80
|
+
const roots = nodes.filter((n) => n.parentId === undefined || !ids.has(String(n.parentId)));
|
|
81
|
+
|
|
82
|
+
const seen = new Set<string>();
|
|
83
|
+
const ordered: AxNode[] = [];
|
|
84
|
+
const visit = (node: AxNode): void => {
|
|
85
|
+
const id = String(node.nodeId);
|
|
86
|
+
if (seen.has(id)) return;
|
|
87
|
+
seen.add(id);
|
|
88
|
+
ordered.push(node);
|
|
89
|
+
for (const childId of node.childIds ?? []) {
|
|
90
|
+
const child = byId.get(String(childId));
|
|
91
|
+
if (child) visit(child);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
for (const root of roots) visit(root);
|
|
95
|
+
for (const node of nodes) if (!seen.has(String(node.nodeId))) ordered.push(node);
|
|
96
|
+
return ordered;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Nameless controls, images without alt text, and unlabelled form fields.
|
|
101
|
+
*
|
|
102
|
+
* `ignored` nodes are skipped: that is how `<img alt="">` correctly escapes
|
|
103
|
+
* the image rule — a deliberately decorative image is dropped from the AX
|
|
104
|
+
* tree, while `<img>` with no alt attribute at all stays in it with an empty
|
|
105
|
+
* name, which is the bug we want to report.
|
|
106
|
+
*/
|
|
107
|
+
export function findNamingIssues(nodes: AxNode[], locators?: Map<number, string>): A11yFinding[] {
|
|
108
|
+
const findings: A11yFinding[] = [];
|
|
109
|
+
for (const node of nodes) {
|
|
110
|
+
if (node.ignored) continue;
|
|
111
|
+
const role = roleOf(node);
|
|
112
|
+
if (!role) continue;
|
|
113
|
+
const name = nameOf(node);
|
|
114
|
+
if (name) continue;
|
|
115
|
+
|
|
116
|
+
if (FORM_FIELD_ROLES.has(role)) {
|
|
117
|
+
findings.push({
|
|
118
|
+
rule: 'form-field-no-label',
|
|
119
|
+
impact: 'error',
|
|
120
|
+
role,
|
|
121
|
+
name: null,
|
|
122
|
+
locator: locatorFor(node, locators),
|
|
123
|
+
detail: `${role} has no accessible name — no <label>, aria-label or aria-labelledby resolves to one`,
|
|
124
|
+
});
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (COMMAND_ROLES.has(role)) {
|
|
129
|
+
findings.push({
|
|
130
|
+
rule: 'unlabelled-control',
|
|
131
|
+
impact: 'error',
|
|
132
|
+
role,
|
|
133
|
+
name: null,
|
|
134
|
+
locator: locatorFor(node, locators),
|
|
135
|
+
detail: `${role} has no accessible name — a screen reader announces it as just "${role}"`,
|
|
136
|
+
});
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (IMAGE_ROLES.has(role)) {
|
|
141
|
+
findings.push({
|
|
142
|
+
rule: 'image-missing-alt',
|
|
143
|
+
impact: 'error',
|
|
144
|
+
role,
|
|
145
|
+
name: null,
|
|
146
|
+
locator: locatorFor(node, locators),
|
|
147
|
+
detail: 'image has no alt text (use alt="" if it is purely decorative)',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return findings;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Headings that skip a level (h1 -> h3), plus a document whose first heading
|
|
156
|
+
* is not h1. Both break the outline screen-reader users navigate by.
|
|
157
|
+
*/
|
|
158
|
+
export function findHeadingIssues(nodes: AxNode[], locators?: Map<number, string>): A11yFinding[] {
|
|
159
|
+
const findings: A11yFinding[] = [];
|
|
160
|
+
let previous: number | null = null;
|
|
161
|
+
|
|
162
|
+
for (const node of orderNodes(nodes)) {
|
|
163
|
+
if (node.ignored) continue;
|
|
164
|
+
if (roleOf(node) !== 'heading') continue;
|
|
165
|
+
const raw = propOf(node, 'level');
|
|
166
|
+
const level = typeof raw === 'number' ? raw : Number.parseInt(strValue(raw), 10);
|
|
167
|
+
if (!Number.isFinite(level) || level < 1) continue;
|
|
168
|
+
|
|
169
|
+
const name = nameOf(node);
|
|
170
|
+
if (previous === null) {
|
|
171
|
+
if (level > 1) {
|
|
172
|
+
findings.push({
|
|
173
|
+
rule: 'heading-order-jump',
|
|
174
|
+
impact: 'warning',
|
|
175
|
+
role: 'heading',
|
|
176
|
+
name: name || null,
|
|
177
|
+
locator: locatorFor(node, locators),
|
|
178
|
+
detail: `document outline starts at h${level} — the first heading should be h1`,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
} else if (level > previous + 1) {
|
|
182
|
+
findings.push({
|
|
183
|
+
rule: 'heading-order-jump',
|
|
184
|
+
impact: 'warning',
|
|
185
|
+
role: 'heading',
|
|
186
|
+
name: name || null,
|
|
187
|
+
locator: locatorFor(node, locators),
|
|
188
|
+
detail: `heading level jumps h${previous} -> h${level}`,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
previous = level;
|
|
192
|
+
}
|
|
193
|
+
return findings;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Interactive elements pulled out of the keyboard tab order by a negative
|
|
198
|
+
* tabindex. They stay clickable with a mouse and stay in the AX tree, so
|
|
199
|
+
* nothing else in this file can see them — the candidates come from a DOM
|
|
200
|
+
* sweep in collect.ts.
|
|
201
|
+
*/
|
|
202
|
+
export function findFocusOrderIssues(candidates: FocusCandidate[]): A11yFinding[] {
|
|
203
|
+
const findings: A11yFinding[] = [];
|
|
204
|
+
for (const c of candidates) {
|
|
205
|
+
if (c.tabindex >= 0) continue;
|
|
206
|
+
const interactive = NATIVELY_FOCUSABLE.has(c.tag.toLowerCase()) || c.role !== '';
|
|
207
|
+
if (!interactive) continue;
|
|
208
|
+
findings.push({
|
|
209
|
+
rule: 'negative-tabindex',
|
|
210
|
+
impact: 'warning',
|
|
211
|
+
role: c.role || c.tag.toLowerCase(),
|
|
212
|
+
name: c.name,
|
|
213
|
+
locator: c.locator,
|
|
214
|
+
detail: `<${c.tag.toLowerCase()}> has tabindex="${c.tabindex}" — mouse users can reach it, keyboard users cannot`,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return findings;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export interface A11yInput {
|
|
221
|
+
nodes: AxNode[];
|
|
222
|
+
focusCandidates?: FocusCandidate[];
|
|
223
|
+
locators?: Map<number, string>;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function runA11yRules({ nodes, focusCandidates = [], locators }: A11yInput): A11yFinding[] {
|
|
227
|
+
return [
|
|
228
|
+
...findNamingIssues(nodes, locators),
|
|
229
|
+
...findHeadingIssues(nodes, locators),
|
|
230
|
+
...findFocusOrderIssues(focusCandidates),
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function countA11y(findings: A11yFinding[]): { errors: number; warnings: number } {
|
|
235
|
+
let errors = 0;
|
|
236
|
+
let warnings = 0;
|
|
237
|
+
for (const f of findings) {
|
|
238
|
+
if (f.impact === 'error') errors++;
|
|
239
|
+
else warnings++;
|
|
240
|
+
}
|
|
241
|
+
return { errors, warnings };
|
|
242
|
+
}
|