devharness 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +185 -0
- package/build/atomic-write.d.ts +28 -0
- package/build/atomic-write.d.ts.map +1 -0
- package/build/atomic-write.js +126 -0
- package/build/atomic-write.js.map +1 -0
- package/build/cdp-manager.d.ts +524 -0
- package/build/cdp-manager.d.ts.map +1 -0
- package/build/cdp-manager.js +1828 -0
- package/build/cdp-manager.js.map +1 -0
- package/build/cdp-manager.test.d.ts +6 -0
- package/build/cdp-manager.test.d.ts.map +1 -0
- package/build/cdp-manager.test.js +546 -0
- package/build/cdp-manager.test.js.map +1 -0
- package/build/chrome-launcher.d.ts +400 -0
- package/build/chrome-launcher.d.ts.map +1 -0
- package/build/chrome-launcher.js +1071 -0
- package/build/chrome-launcher.js.map +1 -0
- package/build/chrome-launcher.test.d.ts +11 -0
- package/build/chrome-launcher.test.d.ts.map +1 -0
- package/build/chrome-launcher.test.js +204 -0
- package/build/chrome-launcher.test.js.map +1 -0
- package/build/clickable-cache.d.ts +73 -0
- package/build/clickable-cache.d.ts.map +1 -0
- package/build/clickable-cache.js +90 -0
- package/build/clickable-cache.js.map +1 -0
- package/build/command-recorder.d.ts +401 -0
- package/build/command-recorder.d.ts.map +1 -0
- package/build/command-recorder.js +701 -0
- package/build/command-recorder.js.map +1 -0
- package/build/config.d.ts +390 -0
- package/build/config.d.ts.map +1 -0
- package/build/config.js +826 -0
- package/build/config.js.map +1 -0
- package/build/config.test.d.ts +2 -0
- package/build/config.test.d.ts.map +1 -0
- package/build/config.test.js +112 -0
- package/build/config.test.js.map +1 -0
- package/build/connection-manager.d.ts +159 -0
- package/build/connection-manager.d.ts.map +1 -0
- package/build/connection-manager.js +432 -0
- package/build/connection-manager.js.map +1 -0
- package/build/connection-manager.test.d.ts +11 -0
- package/build/connection-manager.test.d.ts.map +1 -0
- package/build/connection-manager.test.js +114 -0
- package/build/connection-manager.test.js.map +1 -0
- package/build/console-monitor.d.ts +150 -0
- package/build/console-monitor.d.ts.map +1 -0
- package/build/console-monitor.js +454 -0
- package/build/console-monitor.js.map +1 -0
- package/build/dashboard/bundle.js +1 -0
- package/build/dashboard/dashboard-client.d.ts +42 -0
- package/build/dashboard/dashboard-client.d.ts.map +1 -0
- package/build/dashboard/dashboard-client.js +184 -0
- package/build/dashboard/dashboard-client.js.map +1 -0
- package/build/dashboard/dashboard-hub.d.ts +50 -0
- package/build/dashboard/dashboard-hub.d.ts.map +1 -0
- package/build/dashboard/dashboard-hub.js +654 -0
- package/build/dashboard/dashboard-hub.js.map +1 -0
- package/build/dashboard/hub-lock.d.ts +16 -0
- package/build/dashboard/hub-lock.d.ts.map +1 -0
- package/build/dashboard/hub-lock.js +77 -0
- package/build/dashboard/hub-lock.js.map +1 -0
- package/build/dashboard/index.d.ts +25 -0
- package/build/dashboard/index.d.ts.map +1 -0
- package/build/dashboard/index.js +77 -0
- package/build/dashboard/index.js.map +1 -0
- package/build/dashboard/types.d.ts +83 -0
- package/build/dashboard/types.d.ts.map +1 -0
- package/build/dashboard/types.js +6 -0
- package/build/dashboard/types.js.map +1 -0
- package/build/debug-logger.d.ts +85 -0
- package/build/debug-logger.d.ts.map +1 -0
- package/build/debug-logger.js +195 -0
- package/build/debug-logger.js.map +1 -0
- package/build/debugger-aware-wrapper.d.ts +37 -0
- package/build/debugger-aware-wrapper.d.ts.map +1 -0
- package/build/debugger-aware-wrapper.js +128 -0
- package/build/debugger-aware-wrapper.js.map +1 -0
- package/build/dom-change-monitor.d.ts +92 -0
- package/build/dom-change-monitor.d.ts.map +1 -0
- package/build/dom-change-monitor.js +437 -0
- package/build/dom-change-monitor.js.map +1 -0
- package/build/element-collector.d.ts +16 -0
- package/build/element-collector.d.ts.map +1 -0
- package/build/element-collector.js +207 -0
- package/build/element-collector.js.map +1 -0
- package/build/error-helpers.d.ts +43 -0
- package/build/error-helpers.d.ts.map +1 -0
- package/build/error-helpers.js +143 -0
- package/build/error-helpers.js.map +1 -0
- package/build/formatters/console-formatter.d.ts +160 -0
- package/build/formatters/console-formatter.d.ts.map +1 -0
- package/build/formatters/console-formatter.js +338 -0
- package/build/formatters/console-formatter.js.map +1 -0
- package/build/helpers/parser-plugins.d.ts +44 -0
- package/build/helpers/parser-plugins.d.ts.map +1 -0
- package/build/helpers/parser-plugins.js +102 -0
- package/build/helpers/parser-plugins.js.map +1 -0
- package/build/helpers/paths.d.ts +79 -0
- package/build/helpers/paths.d.ts.map +1 -0
- package/build/helpers/paths.js +168 -0
- package/build/helpers/paths.js.map +1 -0
- package/build/helpers/process-liveness.d.ts +24 -0
- package/build/helpers/process-liveness.d.ts.map +1 -0
- package/build/helpers/process-liveness.js +36 -0
- package/build/helpers/process-liveness.js.map +1 -0
- package/build/helpers/process-liveness.test.d.ts +2 -0
- package/build/helpers/process-liveness.test.d.ts.map +1 -0
- package/build/helpers/process-liveness.test.js +40 -0
- package/build/helpers/process-liveness.test.js.map +1 -0
- package/build/helpers/sequence-tree.d.ts +27 -0
- package/build/helpers/sequence-tree.d.ts.map +1 -0
- package/build/helpers/sequence-tree.js +80 -0
- package/build/helpers/sequence-tree.js.map +1 -0
- package/build/helpers/sequence-tree.test.d.ts +2 -0
- package/build/helpers/sequence-tree.test.d.ts.map +1 -0
- package/build/helpers/sequence-tree.test.js +122 -0
- package/build/helpers/sequence-tree.test.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +2007 -0
- package/build/index.js.map +1 -0
- package/build/interaction-recorder.d.ts +214 -0
- package/build/interaction-recorder.d.ts.map +1 -0
- package/build/interaction-recorder.js +2007 -0
- package/build/interaction-recorder.js.map +1 -0
- package/build/issue-tracker.d.ts +107 -0
- package/build/issue-tracker.d.ts.map +1 -0
- package/build/issue-tracker.js +582 -0
- package/build/issue-tracker.js.map +1 -0
- package/build/issue-tracker.test.d.ts +2 -0
- package/build/issue-tracker.test.d.ts.map +1 -0
- package/build/issue-tracker.test.js +88 -0
- package/build/issue-tracker.test.js.map +1 -0
- package/build/launch-chrome-port.test.d.ts +14 -0
- package/build/launch-chrome-port.test.d.ts.map +1 -0
- package/build/launch-chrome-port.test.js +97 -0
- package/build/launch-chrome-port.test.js.map +1 -0
- package/build/log-processor/orchestrator.d.ts +32 -0
- package/build/log-processor/orchestrator.d.ts.map +1 -0
- package/build/log-processor/orchestrator.js +27 -0
- package/build/log-processor/orchestrator.js.map +1 -0
- package/build/logpoint-execution-tracker.d.ts +57 -0
- package/build/logpoint-execution-tracker.d.ts.map +1 -0
- package/build/logpoint-execution-tracker.js +112 -0
- package/build/logpoint-execution-tracker.js.map +1 -0
- package/build/mcp-supervisor.d.ts +3 -0
- package/build/mcp-supervisor.d.ts.map +1 -0
- package/build/mcp-supervisor.js +211 -0
- package/build/mcp-supervisor.js.map +1 -0
- package/build/messages.d.ts +165 -0
- package/build/messages.d.ts.map +1 -0
- package/build/messages.js +574 -0
- package/build/messages.js.map +1 -0
- package/build/network-monitor.d.ts +172 -0
- package/build/network-monitor.d.ts.map +1 -0
- package/build/network-monitor.js +350 -0
- package/build/network-monitor.js.map +1 -0
- package/build/overlays.d.ts +49 -0
- package/build/overlays.d.ts.map +1 -0
- package/build/overlays.js +409 -0
- package/build/overlays.js.map +1 -0
- package/build/persistent-profiles.test.d.ts +15 -0
- package/build/persistent-profiles.test.d.ts.map +1 -0
- package/build/persistent-profiles.test.js +470 -0
- package/build/persistent-profiles.test.js.map +1 -0
- package/build/port-reserver.d.ts +32 -0
- package/build/port-reserver.d.ts.map +1 -0
- package/build/port-reserver.js +118 -0
- package/build/port-reserver.js.map +1 -0
- package/build/puppeteer-manager.d.ts +47 -0
- package/build/puppeteer-manager.d.ts.map +1 -0
- package/build/puppeteer-manager.js +116 -0
- package/build/puppeteer-manager.js.map +1 -0
- package/build/recording-coverage.test.d.ts +12 -0
- package/build/recording-coverage.test.d.ts.map +1 -0
- package/build/recording-coverage.test.js +72 -0
- package/build/recording-coverage.test.js.map +1 -0
- package/build/reference-validator.d.ts +47 -0
- package/build/reference-validator.d.ts.map +1 -0
- package/build/reference-validator.js +86 -0
- package/build/reference-validator.js.map +1 -0
- package/build/reference-validator.test.d.ts +5 -0
- package/build/reference-validator.test.d.ts.map +1 -0
- package/build/reference-validator.test.js +243 -0
- package/build/reference-validator.test.js.map +1 -0
- package/build/replay-cursor.d.ts +27 -0
- package/build/replay-cursor.d.ts.map +1 -0
- package/build/replay-cursor.js +240 -0
- package/build/replay-cursor.js.map +1 -0
- package/build/runners/docker-compose-runner.d.ts +69 -0
- package/build/runners/docker-compose-runner.d.ts.map +1 -0
- package/build/runners/docker-compose-runner.js +438 -0
- package/build/runners/docker-compose-runner.js.map +1 -0
- package/build/runners/docker-runner.d.ts +65 -0
- package/build/runners/docker-runner.d.ts.map +1 -0
- package/build/runners/docker-runner.js +414 -0
- package/build/runners/docker-runner.js.map +1 -0
- package/build/runners/index.d.ts +19 -0
- package/build/runners/index.d.ts.map +1 -0
- package/build/runners/index.js +35 -0
- package/build/runners/index.js.map +1 -0
- package/build/runners/native-runner.d.ts +95 -0
- package/build/runners/native-runner.d.ts.map +1 -0
- package/build/runners/native-runner.js +370 -0
- package/build/runners/native-runner.js.map +1 -0
- package/build/runners/types.d.ts +147 -0
- package/build/runners/types.d.ts.map +1 -0
- package/build/runners/types.js +25 -0
- package/build/runners/types.js.map +1 -0
- package/build/self-restart.d.ts +12 -0
- package/build/self-restart.d.ts.map +1 -0
- package/build/self-restart.js +41 -0
- package/build/self-restart.js.map +1 -0
- package/build/self-restart.test.d.ts +2 -0
- package/build/self-restart.test.d.ts.map +1 -0
- package/build/self-restart.test.js +34 -0
- package/build/self-restart.test.js.map +1 -0
- package/build/server-claims.d.ts +161 -0
- package/build/server-claims.d.ts.map +1 -0
- package/build/server-claims.js +382 -0
- package/build/server-claims.js.map +1 -0
- package/build/server-claims.test.d.ts +2 -0
- package/build/server-claims.test.d.ts.map +1 -0
- package/build/server-claims.test.js +197 -0
- package/build/server-claims.test.js.map +1 -0
- package/build/server-manager.d.ts +438 -0
- package/build/server-manager.d.ts.map +1 -0
- package/build/server-manager.js +1674 -0
- package/build/server-manager.js.map +1 -0
- package/build/server-manager.test.d.ts +17 -0
- package/build/server-manager.test.d.ts.map +1 -0
- package/build/server-manager.test.js +122 -0
- package/build/server-manager.test.js.map +1 -0
- package/build/server-ownership.test.d.ts +2 -0
- package/build/server-ownership.test.d.ts.map +1 -0
- package/build/server-ownership.test.js +238 -0
- package/build/server-ownership.test.js.map +1 -0
- package/build/server-watcher.d.ts +21 -0
- package/build/server-watcher.d.ts.map +1 -0
- package/build/server-watcher.js +105 -0
- package/build/server-watcher.js.map +1 -0
- package/build/session-detector.d.ts +47 -0
- package/build/session-detector.d.ts.map +1 -0
- package/build/session-detector.js +229 -0
- package/build/session-detector.js.map +1 -0
- package/build/session-detector.test.d.ts +9 -0
- package/build/session-detector.test.d.ts.map +1 -0
- package/build/session-detector.test.js +45 -0
- package/build/session-detector.test.js.map +1 -0
- package/build/sourcemap-handler.d.ts +114 -0
- package/build/sourcemap-handler.d.ts.map +1 -0
- package/build/sourcemap-handler.js +479 -0
- package/build/sourcemap-handler.js.map +1 -0
- package/build/supervisor/child-manager.d.ts +43 -0
- package/build/supervisor/child-manager.d.ts.map +1 -0
- package/build/supervisor/child-manager.js +148 -0
- package/build/supervisor/child-manager.js.map +1 -0
- package/build/supervisor/child-manager.test.d.ts +2 -0
- package/build/supervisor/child-manager.test.d.ts.map +1 -0
- package/build/supervisor/child-manager.test.js +107 -0
- package/build/supervisor/child-manager.test.js.map +1 -0
- package/build/supervisor/client-watcher.d.ts +48 -0
- package/build/supervisor/client-watcher.d.ts.map +1 -0
- package/build/supervisor/client-watcher.js +186 -0
- package/build/supervisor/client-watcher.js.map +1 -0
- package/build/supervisor/client-watcher.test.d.ts +2 -0
- package/build/supervisor/client-watcher.test.d.ts.map +1 -0
- package/build/supervisor/client-watcher.test.js +121 -0
- package/build/supervisor/client-watcher.test.js.map +1 -0
- package/build/supervisor/idle-config.d.ts +30 -0
- package/build/supervisor/idle-config.d.ts.map +1 -0
- package/build/supervisor/idle-config.js +76 -0
- package/build/supervisor/idle-config.js.map +1 -0
- package/build/supervisor/idle-config.test.d.ts +2 -0
- package/build/supervisor/idle-config.test.d.ts.map +1 -0
- package/build/supervisor/idle-config.test.js +119 -0
- package/build/supervisor/idle-config.test.js.map +1 -0
- package/build/supervisor/ndjson-reader.d.ts +47 -0
- package/build/supervisor/ndjson-reader.d.ts.map +1 -0
- package/build/supervisor/ndjson-reader.js +75 -0
- package/build/supervisor/ndjson-reader.js.map +1 -0
- package/build/supervisor/ndjson-reader.test.d.ts +2 -0
- package/build/supervisor/ndjson-reader.test.d.ts.map +1 -0
- package/build/supervisor/ndjson-reader.test.js +92 -0
- package/build/supervisor/ndjson-reader.test.js.map +1 -0
- package/build/supervisor/pidfile.d.ts +11 -0
- package/build/supervisor/pidfile.d.ts.map +1 -0
- package/build/supervisor/pidfile.js +29 -0
- package/build/supervisor/pidfile.js.map +1 -0
- package/build/supervisor/pidfile.test.d.ts +8 -0
- package/build/supervisor/pidfile.test.d.ts.map +1 -0
- package/build/supervisor/pidfile.test.js +46 -0
- package/build/supervisor/pidfile.test.js.map +1 -0
- package/build/supervisor/restart-coordinator.d.ts +73 -0
- package/build/supervisor/restart-coordinator.d.ts.map +1 -0
- package/build/supervisor/restart-coordinator.js +300 -0
- package/build/supervisor/restart-coordinator.js.map +1 -0
- package/build/supervisor/restart-coordinator.test.d.ts +2 -0
- package/build/supervisor/restart-coordinator.test.d.ts.map +1 -0
- package/build/supervisor/restart-coordinator.test.js +434 -0
- package/build/supervisor/restart-coordinator.test.js.map +1 -0
- package/build/test-support/fake-execute-tool-call.d.ts +11 -0
- package/build/test-support/fake-execute-tool-call.d.ts.map +1 -0
- package/build/test-support/fake-execute-tool-call.js +19 -0
- package/build/test-support/fake-execute-tool-call.js.map +1 -0
- package/build/tool-error.d.ts +14 -0
- package/build/tool-error.d.ts.map +1 -0
- package/build/tool-error.js +18 -0
- package/build/tool-error.js.map +1 -0
- package/build/tool-response.d.ts +293 -0
- package/build/tool-response.d.ts.map +1 -0
- package/build/tool-response.js +365 -0
- package/build/tool-response.js.map +1 -0
- package/build/tools/assert-tools.d.ts +62 -0
- package/build/tools/assert-tools.d.ts.map +1 -0
- package/build/tools/assert-tools.js +330 -0
- package/build/tools/assert-tools.js.map +1 -0
- package/build/tools/breakpoint-await-abort.test.d.ts +2 -0
- package/build/tools/breakpoint-await-abort.test.d.ts.map +1 -0
- package/build/tools/breakpoint-await-abort.test.js +124 -0
- package/build/tools/breakpoint-await-abort.test.js.map +1 -0
- package/build/tools/breakpoint-tools.d.ts +99 -0
- package/build/tools/breakpoint-tools.d.ts.map +1 -0
- package/build/tools/breakpoint-tools.js +1197 -0
- package/build/tools/breakpoint-tools.js.map +1 -0
- package/build/tools/config-tools.d.ts +58 -0
- package/build/tools/config-tools.d.ts.map +1 -0
- package/build/tools/config-tools.js +180 -0
- package/build/tools/config-tools.js.map +1 -0
- package/build/tools/connection-routing.test.d.ts +12 -0
- package/build/tools/connection-routing.test.d.ts.map +1 -0
- package/build/tools/connection-routing.test.js +206 -0
- package/build/tools/connection-routing.test.js.map +1 -0
- package/build/tools/console-tools.d.ts +84 -0
- package/build/tools/console-tools.d.ts.map +1 -0
- package/build/tools/console-tools.js +241 -0
- package/build/tools/console-tools.js.map +1 -0
- package/build/tools/content-tools.d.ts +74 -0
- package/build/tools/content-tools.d.ts.map +1 -0
- package/build/tools/content-tools.js +582 -0
- package/build/tools/content-tools.js.map +1 -0
- package/build/tools/dashboard-tools.d.ts +38 -0
- package/build/tools/dashboard-tools.d.ts.map +1 -0
- package/build/tools/dashboard-tools.js +123 -0
- package/build/tools/dashboard-tools.js.map +1 -0
- package/build/tools/dom-tools.d.ts +41 -0
- package/build/tools/dom-tools.d.ts.map +1 -0
- package/build/tools/dom-tools.js +277 -0
- package/build/tools/dom-tools.js.map +1 -0
- package/build/tools/download-tools.d.ts +55 -0
- package/build/tools/download-tools.d.ts.map +1 -0
- package/build/tools/download-tools.js +280 -0
- package/build/tools/download-tools.js.map +1 -0
- package/build/tools/execution-tools.d.ts +40 -0
- package/build/tools/execution-tools.d.ts.map +1 -0
- package/build/tools/execution-tools.js +121 -0
- package/build/tools/execution-tools.js.map +1 -0
- package/build/tools/input-abort.test.d.ts +2 -0
- package/build/tools/input-abort.test.d.ts.map +1 -0
- package/build/tools/input-abort.test.js +227 -0
- package/build/tools/input-abort.test.js.map +1 -0
- package/build/tools/input-ambiguous-selector.test.d.ts +2 -0
- package/build/tools/input-ambiguous-selector.test.d.ts.map +1 -0
- package/build/tools/input-ambiguous-selector.test.js +34 -0
- package/build/tools/input-ambiguous-selector.test.js.map +1 -0
- package/build/tools/input-tools.d.ts +168 -0
- package/build/tools/input-tools.d.ts.map +1 -0
- package/build/tools/input-tools.js +1397 -0
- package/build/tools/input-tools.js.map +1 -0
- package/build/tools/inspection-tools.d.ts +112 -0
- package/build/tools/inspection-tools.d.ts.map +1 -0
- package/build/tools/inspection-tools.js +637 -0
- package/build/tools/inspection-tools.js.map +1 -0
- package/build/tools/interpolation.d.ts +30 -0
- package/build/tools/interpolation.d.ts.map +1 -0
- package/build/tools/interpolation.js +143 -0
- package/build/tools/interpolation.js.map +1 -0
- package/build/tools/issues-abort-hygiene.test.d.ts +2 -0
- package/build/tools/issues-abort-hygiene.test.d.ts.map +1 -0
- package/build/tools/issues-abort-hygiene.test.js +79 -0
- package/build/tools/issues-abort-hygiene.test.js.map +1 -0
- package/build/tools/issues-tools.d.ts +98 -0
- package/build/tools/issues-tools.d.ts.map +1 -0
- package/build/tools/issues-tools.js +821 -0
- package/build/tools/issues-tools.js.map +1 -0
- package/build/tools/issues-tools.test.d.ts +8 -0
- package/build/tools/issues-tools.test.d.ts.map +1 -0
- package/build/tools/issues-tools.test.js +184 -0
- package/build/tools/issues-tools.test.js.map +1 -0
- package/build/tools/modal-tools.d.ts +73 -0
- package/build/tools/modal-tools.d.ts.map +1 -0
- package/build/tools/modal-tools.js +161 -0
- package/build/tools/modal-tools.js.map +1 -0
- package/build/tools/network-tools.d.ts +72 -0
- package/build/tools/network-tools.d.ts.map +1 -0
- package/build/tools/network-tools.js +432 -0
- package/build/tools/network-tools.js.map +1 -0
- package/build/tools/page-tools.d.ts +78 -0
- package/build/tools/page-tools.d.ts.map +1 -0
- package/build/tools/page-tools.js +255 -0
- package/build/tools/page-tools.js.map +1 -0
- package/build/tools/plugin-tools.d.ts +16 -0
- package/build/tools/plugin-tools.d.ts.map +1 -0
- package/build/tools/plugin-tools.js +17 -0
- package/build/tools/plugin-tools.js.map +1 -0
- package/build/tools/replay-abort.test.d.ts +2 -0
- package/build/tools/replay-abort.test.d.ts.map +1 -0
- package/build/tools/replay-abort.test.js +259 -0
- package/build/tools/replay-abort.test.js.map +1 -0
- package/build/tools/replay-add-conditional.test.d.ts +2 -0
- package/build/tools/replay-add-conditional.test.d.ts.map +1 -0
- package/build/tools/replay-add-conditional.test.js +205 -0
- package/build/tools/replay-add-conditional.test.js.map +1 -0
- package/build/tools/replay-background-run.test.d.ts +2 -0
- package/build/tools/replay-background-run.test.d.ts.map +1 -0
- package/build/tools/replay-background-run.test.js +176 -0
- package/build/tools/replay-background-run.test.js.map +1 -0
- package/build/tools/replay-capture-variables.test.d.ts +2 -0
- package/build/tools/replay-capture-variables.test.d.ts.map +1 -0
- package/build/tools/replay-capture-variables.test.js +331 -0
- package/build/tools/replay-capture-variables.test.js.map +1 -0
- package/build/tools/replay-conditional-idb-and-launch.test.d.ts +11 -0
- package/build/tools/replay-conditional-idb-and-launch.test.d.ts.map +1 -0
- package/build/tools/replay-conditional-idb-and-launch.test.js +429 -0
- package/build/tools/replay-conditional-idb-and-launch.test.js.map +1 -0
- package/build/tools/replay-conditional-timeout.test.d.ts +14 -0
- package/build/tools/replay-conditional-timeout.test.d.ts.map +1 -0
- package/build/tools/replay-conditional-timeout.test.js +126 -0
- package/build/tools/replay-conditional-timeout.test.js.map +1 -0
- package/build/tools/replay-declare.test.d.ts +2 -0
- package/build/tools/replay-declare.test.d.ts.map +1 -0
- package/build/tools/replay-declare.test.js +106 -0
- package/build/tools/replay-declare.test.js.map +1 -0
- package/build/tools/replay-declared-browser-cleanup.test.d.ts +2 -0
- package/build/tools/replay-declared-browser-cleanup.test.d.ts.map +1 -0
- package/build/tools/replay-declared-browser-cleanup.test.js +103 -0
- package/build/tools/replay-declared-browser-cleanup.test.js.map +1 -0
- package/build/tools/replay-declared-profile.test.d.ts +2 -0
- package/build/tools/replay-declared-profile.test.d.ts.map +1 -0
- package/build/tools/replay-declared-profile.test.js +116 -0
- package/build/tools/replay-declared-profile.test.js.map +1 -0
- package/build/tools/replay-executor.d.ts +556 -0
- package/build/tools/replay-executor.d.ts.map +1 -0
- package/build/tools/replay-executor.js +2347 -0
- package/build/tools/replay-executor.js.map +1 -0
- package/build/tools/replay-folder-save.test.d.ts +2 -0
- package/build/tools/replay-folder-save.test.d.ts.map +1 -0
- package/build/tools/replay-folder-save.test.js +63 -0
- package/build/tools/replay-folder-save.test.js.map +1 -0
- package/build/tools/replay-formatters.d.ts +148 -0
- package/build/tools/replay-formatters.d.ts.map +1 -0
- package/build/tools/replay-formatters.js +856 -0
- package/build/tools/replay-formatters.js.map +1 -0
- package/build/tools/replay-kill-chrome-on-finish.test.d.ts +2 -0
- package/build/tools/replay-kill-chrome-on-finish.test.d.ts.map +1 -0
- package/build/tools/replay-kill-chrome-on-finish.test.js +175 -0
- package/build/tools/replay-kill-chrome-on-finish.test.js.map +1 -0
- package/build/tools/replay-multi-connection.test.d.ts +2 -0
- package/build/tools/replay-multi-connection.test.d.ts.map +1 -0
- package/build/tools/replay-multi-connection.test.js +547 -0
- package/build/tools/replay-multi-connection.test.js.map +1 -0
- package/build/tools/replay-output-and-record-options.test.d.ts +2 -0
- package/build/tools/replay-output-and-record-options.test.d.ts.map +1 -0
- package/build/tools/replay-output-and-record-options.test.js +240 -0
- package/build/tools/replay-output-and-record-options.test.js.map +1 -0
- package/build/tools/replay-production-error-shape.test.d.ts +7 -0
- package/build/tools/replay-production-error-shape.test.d.ts.map +1 -0
- package/build/tools/replay-production-error-shape.test.js +93 -0
- package/build/tools/replay-production-error-shape.test.js.map +1 -0
- package/build/tools/replay-rebase.test.d.ts +2 -0
- package/build/tools/replay-rebase.test.d.ts.map +1 -0
- package/build/tools/replay-rebase.test.js +58 -0
- package/build/tools/replay-rebase.test.js.map +1 -0
- package/build/tools/replay-retry-classification.test.d.ts +9 -0
- package/build/tools/replay-retry-classification.test.d.ts.map +1 -0
- package/build/tools/replay-retry-classification.test.js +57 -0
- package/build/tools/replay-retry-classification.test.js.map +1 -0
- package/build/tools/replay-run-registry.d.ts +55 -0
- package/build/tools/replay-run-registry.d.ts.map +1 -0
- package/build/tools/replay-run-registry.js +65 -0
- package/build/tools/replay-run-registry.js.map +1 -0
- package/build/tools/replay-sequence-reload.test.d.ts +2 -0
- package/build/tools/replay-sequence-reload.test.d.ts.map +1 -0
- package/build/tools/replay-sequence-reload.test.js +82 -0
- package/build/tools/replay-sequence-reload.test.js.map +1 -0
- package/build/tools/replay-socket-scope.test.d.ts +2 -0
- package/build/tools/replay-socket-scope.test.d.ts.map +1 -0
- package/build/tools/replay-socket-scope.test.js +75 -0
- package/build/tools/replay-socket-scope.test.js.map +1 -0
- package/build/tools/replay-step-connection.test.d.ts +2 -0
- package/build/tools/replay-step-connection.test.d.ts.map +1 -0
- package/build/tools/replay-step-connection.test.js +292 -0
- package/build/tools/replay-step-connection.test.js.map +1 -0
- package/build/tools/replay-step-timeout.test.d.ts +2 -0
- package/build/tools/replay-step-timeout.test.d.ts.map +1 -0
- package/build/tools/replay-step-timeout.test.js +204 -0
- package/build/tools/replay-step-timeout.test.js.map +1 -0
- package/build/tools/replay-tags.test.d.ts +2 -0
- package/build/tools/replay-tags.test.d.ts.map +1 -0
- package/build/tools/replay-tags.test.js +99 -0
- package/build/tools/replay-tags.test.js.map +1 -0
- package/build/tools/replay-teardown-and-foreach.test.d.ts +13 -0
- package/build/tools/replay-teardown-and-foreach.test.d.ts.map +1 -0
- package/build/tools/replay-teardown-and-foreach.test.js +318 -0
- package/build/tools/replay-teardown-and-foreach.test.js.map +1 -0
- package/build/tools/replay-tool-name-validation.test.d.ts +2 -0
- package/build/tools/replay-tool-name-validation.test.d.ts.map +1 -0
- package/build/tools/replay-tool-name-validation.test.js +280 -0
- package/build/tools/replay-tool-name-validation.test.js.map +1 -0
- package/build/tools/replay-tools.d.ts +288 -0
- package/build/tools/replay-tools.d.ts.map +1 -0
- package/build/tools/replay-tools.js +2994 -0
- package/build/tools/replay-tools.js.map +1 -0
- package/build/tools/request-abort.test.d.ts +2 -0
- package/build/tools/request-abort.test.d.ts.map +1 -0
- package/build/tools/request-abort.test.js +130 -0
- package/build/tools/request-abort.test.js.map +1 -0
- package/build/tools/request-tools.d.ts +56 -0
- package/build/tools/request-tools.d.ts.map +1 -0
- package/build/tools/request-tools.js +174 -0
- package/build/tools/request-tools.js.map +1 -0
- package/build/tools/screenshot-tools.d.ts +135 -0
- package/build/tools/screenshot-tools.d.ts.map +1 -0
- package/build/tools/screenshot-tools.js +636 -0
- package/build/tools/screenshot-tools.js.map +1 -0
- package/build/tools/server-tools.d.ts +88 -0
- package/build/tools/server-tools.d.ts.map +1 -0
- package/build/tools/server-tools.js +485 -0
- package/build/tools/server-tools.js.map +1 -0
- package/build/tools/source-tools.d.ts +46 -0
- package/build/tools/source-tools.d.ts.map +1 -0
- package/build/tools/source-tools.js +47 -0
- package/build/tools/source-tools.js.map +1 -0
- package/build/tools/storage-tools.d.ts +141 -0
- package/build/tools/storage-tools.d.ts.map +1 -0
- package/build/tools/storage-tools.js +894 -0
- package/build/tools/storage-tools.js.map +1 -0
- package/build/tools/storage-tools.test.d.ts +13 -0
- package/build/tools/storage-tools.test.d.ts.map +1 -0
- package/build/tools/storage-tools.test.js +707 -0
- package/build/tools/storage-tools.test.js.map +1 -0
- package/build/tools/tab-tools.d.ts +42 -0
- package/build/tools/tab-tools.d.ts.map +1 -0
- package/build/tools/tab-tools.js +296 -0
- package/build/tools/tab-tools.js.map +1 -0
- package/build/tools/wait-tools.d.ts +85 -0
- package/build/tools/wait-tools.d.ts.map +1 -0
- package/build/tools/wait-tools.js +236 -0
- package/build/tools/wait-tools.js.map +1 -0
- package/build/tools/wait-tools.test.d.ts +9 -0
- package/build/tools/wait-tools.test.d.ts.map +1 -0
- package/build/tools/wait-tools.test.js +193 -0
- package/build/tools/wait-tools.test.js.map +1 -0
- package/build/types.d.ts +133 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +5 -0
- package/build/types.js.map +1 -0
- package/build/ui-verifier.d.ts +88 -0
- package/build/ui-verifier.d.ts.map +1 -0
- package/build/ui-verifier.js +540 -0
- package/build/ui-verifier.js.map +1 -0
- package/build/utils/abort.d.ts +68 -0
- package/build/utils/abort.d.ts.map +1 -0
- package/build/utils/abort.js +159 -0
- package/build/utils/abort.js.map +1 -0
- package/build/utils/abort.test.d.ts +2 -0
- package/build/utils/abort.test.d.ts.map +1 -0
- package/build/utils/abort.test.js +174 -0
- package/build/utils/abort.test.js.map +1 -0
- package/build/utils/modal-detection-core.d.ts +150 -0
- package/build/utils/modal-detection-core.d.ts.map +1 -0
- package/build/utils/modal-detection-core.js +332 -0
- package/build/utils/modal-detection-core.js.map +1 -0
- package/build/utils/modal-detection-core.test.d.ts +5 -0
- package/build/utils/modal-detection-core.test.d.ts.map +1 -0
- package/build/utils/modal-detection-core.test.js +492 -0
- package/build/utils/modal-detection-core.test.js.map +1 -0
- package/build/utils/modal-detector.d.ts +29 -0
- package/build/utils/modal-detector.d.ts.map +1 -0
- package/build/utils/modal-detector.js +281 -0
- package/build/utils/modal-detector.js.map +1 -0
- package/build/utils/modal-dismissal.d.ts +32 -0
- package/build/utils/modal-dismissal.d.ts.map +1 -0
- package/build/utils/modal-dismissal.js +190 -0
- package/build/utils/modal-dismissal.js.map +1 -0
- package/build/utils/port-check.d.ts +31 -0
- package/build/utils/port-check.d.ts.map +1 -0
- package/build/utils/port-check.js +87 -0
- package/build/utils/port-check.js.map +1 -0
- package/build/utils/port-check.test.d.ts +2 -0
- package/build/utils/port-check.test.d.ts.map +1 -0
- package/build/utils/port-check.test.js +66 -0
- package/build/utils/port-check.test.js.map +1 -0
- package/build/utils/selector-resolver.d.ts +66 -0
- package/build/utils/selector-resolver.d.ts.map +1 -0
- package/build/utils/selector-resolver.js +200 -0
- package/build/utils/selector-resolver.js.map +1 -0
- package/build/validation-helpers.d.ts +41 -0
- package/build/validation-helpers.d.ts.map +1 -0
- package/build/validation-helpers.js +236 -0
- package/build/validation-helpers.js.map +1 -0
- package/build/window-sizing.d.ts +26 -0
- package/build/window-sizing.d.ts.map +1 -0
- package/build/window-sizing.js +87 -0
- package/build/window-sizing.js.map +1 -0
- package/build/window-sizing.test.d.ts +8 -0
- package/build/window-sizing.test.d.ts.map +1 -0
- package/build/window-sizing.test.js +113 -0
- package/build/window-sizing.test.js.map +1 -0
- package/docs/README.md +74 -0
- package/docs/automation.md +562 -0
- package/docs/debugging.md +229 -0
- package/docs/installation.md +133 -0
- package/docs/instructions.md +227 -0
- package/docs/mcp-instructions.md +15 -0
- package/docs/message-system.md +327 -0
- package/docs/messages.md +3480 -0
- package/docs/parser-plugins.md +132 -0
- package/docs/replay.md +1411 -0
- package/docs/troubleshooting.md +272 -0
- package/docs/working_with_llm_notes.md +5 -0
- package/package.json +87 -0
- package/skills/devharness/SKILL.md +195 -0
- package/skills/devharness/references/sequences.md +613 -0
- package/skills/devharness/references/tool-categories.md +99 -0
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
# Replay Sequences
|
|
2
|
+
|
|
3
|
+
A sequence is an ordered list of tool calls you can re-run. It's how cdp-tools
|
|
4
|
+
turns "I clicked around and hit the bug" into something repeatable - a
|
|
5
|
+
regression test, a repro attached to an issue, or a multi-step automation.
|
|
6
|
+
|
|
7
|
+
Everything below is the `replay` tool: `replay({ action: '...' })`.
|
|
8
|
+
|
|
9
|
+
## Folders
|
|
10
|
+
|
|
11
|
+
Sequences may live in subfolders of the sequences dir:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
.cdp-tools/sequences/
|
|
15
|
+
_helpers/ preamble guards, forEach bodies - loaded, never run on their own
|
|
16
|
+
spine/
|
|
17
|
+
story/
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Filenames are relative to that root (`spine/spine-01.json`), and `load` still
|
|
21
|
+
accepts the bare basename, so moving a file into a folder does not break calls
|
|
22
|
+
that name it.
|
|
23
|
+
|
|
24
|
+
`replay({ action: 'runAll', folder: 'spine' })` loads the WHOLE tree, then runs
|
|
25
|
+
only that folder. Loading everything matters: `conditional`'s `then` and
|
|
26
|
+
`forEach`'s `do` resolve by sequence NAME, not by path, so a spine sequence can
|
|
27
|
+
call a helper in `_helpers/` only if that helper was loaded too.
|
|
28
|
+
|
|
29
|
+
**Tags are the other axis.** `replay({ action: 'declare', name: '...', tags:
|
|
30
|
+
['ui'] })` labels a sequence; `replay({ action: 'runAll', tags: ['ui'] })` runs
|
|
31
|
+
only those, and composes with `folder`. Several tags mean *any of*. Tags are
|
|
32
|
+
lowercased and de-duplicated (a tag is matched, not displayed) and may not
|
|
33
|
+
contain spaces.
|
|
34
|
+
|
|
35
|
+
Every `runAll` reports the split whether or not you filtered - `3 passed (1
|
|
36
|
+
contract, 1 ui, 1 untagged)`. That is the point: a suite reporting "36 passed"
|
|
37
|
+
reads as interface coverage even when a third of it never issues an `input`
|
|
38
|
+
step, and folders cannot carry the distinction because they already carry
|
|
39
|
+
scenario shape.
|
|
40
|
+
|
|
41
|
+
A folder whose name starts with `_` is skipped by a bare `runAll` - those
|
|
42
|
+
sequences fail in isolation by design (unbound `{{var:}}`, an unmet
|
|
43
|
+
precondition). Naming one explicitly runs it anyway.
|
|
44
|
+
|
|
45
|
+
Failures are recorded and the suite continues unless `continueOnFailure: false`.
|
|
46
|
+
A sequence that only PROMPTS (recorded variables, none supplied) or that PAUSES
|
|
47
|
+
is reported as a failure, not a pass - it did not run.
|
|
48
|
+
|
|
49
|
+
## Rules for building one
|
|
50
|
+
|
|
51
|
+
These hold however you build a sequence - by hand, as a subagent, or from a
|
|
52
|
+
slash command. They are here, once, rather than restated by each of those.
|
|
53
|
+
|
|
54
|
+
- **Never hand-write sequence JSON.** Sequences come from recorded tool calls.
|
|
55
|
+
Hand-edited JSON skips the validation the tools apply and does not port.
|
|
56
|
+
The things that cannot be recorded have their own actions rather than being
|
|
57
|
+
an exception to this: `addConditional` for a guarded branch, `declare` for
|
|
58
|
+
the browsers and sockets a sequence needs.
|
|
59
|
+
- **Do the work with the tools; don't describe it.** Every call you make is
|
|
60
|
+
recorded, and the sequence is assembled from that history afterwards.
|
|
61
|
+
- **Pass `connectionReason` on every browser call** - including the connection
|
|
62
|
+
that is already active, and including tools where it is optional (`inspect`,
|
|
63
|
+
`execution`, `storage`, `network`, `breakpoint`, `request`). A call without it
|
|
64
|
+
records nothing about which browser it ran in, so on replay it lands wherever
|
|
65
|
+
the run-level connection points - silently, and the run still passes. This is
|
|
66
|
+
the most common way to produce a sequence that tests nothing.
|
|
67
|
+
- **Check `listSaved` first.** Auth and setup flows often already exist; a
|
|
68
|
+
`conditional` step can reuse one instead of re-recording it.
|
|
69
|
+
- **Keep the path minimal.** Skip exploratory calls (source searches, unrelated
|
|
70
|
+
navigation); include only what is needed to reproduce.
|
|
71
|
+
- **Write a specific `expectedOutcome`** - file:line, variable names, expected
|
|
72
|
+
vs actual values. "It works" is not an expected outcome.
|
|
73
|
+
|
|
74
|
+
## Getting a sequence
|
|
75
|
+
|
|
76
|
+
**Record what a human does** - `recordInteraction`
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
replay({ action: 'recordInteraction', connectionReason: 'signup-flow' })
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Opens the page with a recording overlay and captures real mouse, keyboard and
|
|
83
|
+
navigation events. **This call blocks until the person finishes in the
|
|
84
|
+
browser** - there is no separate stop action. It returns the created sequence.
|
|
85
|
+
Pass `issueId` to name and link the recording to an issue (`bug-7-repro`).
|
|
86
|
+
|
|
87
|
+
Because it waits on a human, don't call it unattended - the same rule as
|
|
88
|
+
`issues({ action: 'resolve' })`.
|
|
89
|
+
|
|
90
|
+
Tune how events become commands with `simplifyEvents` (default true),
|
|
91
|
+
`includeHovers` (false), `preferCoordinates` (false - `x,y` clicks for
|
|
92
|
+
canvas/3D) and `preferSelectors` (false - selector clicks even for canvas;
|
|
93
|
+
wins if both preference flags are set). Add `outputFormat: 'events'` or
|
|
94
|
+
`'commands'` to get the raw captured events / converted commands as JSON
|
|
95
|
+
alongside the summary, or `'review'` for a readable walkthrough of the captured
|
|
96
|
+
events (coordinates, element and selector per interaction, plus navigations,
|
|
97
|
+
pastes and comments). All three are only available here - raw events are not
|
|
98
|
+
stored with the sequence.
|
|
99
|
+
|
|
100
|
+
**Build one from calls you already made** - `create`
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
replay({ action: 'create', name: 'login-check', indices: [3, 4, 5] })
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Every tool response footer shows its history index (`**Repeat:**` hint).
|
|
107
|
+
`replay({ action: 'history' })` lists them. This is usually faster than
|
|
108
|
+
recording when you've just done the steps yourself.
|
|
109
|
+
|
|
110
|
+
**Re-run calls you already made, without building a sequence** - `repeat`
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
replay({ action: 'repeat', indices: [12] }) // one call
|
|
114
|
+
replay({ action: 'repeat', indices: [58, 59, 60, 61] }) // a whole stretch, in order
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`indices` takes a list, so this replays a run of work in one call - and that is
|
|
118
|
+
usually the point. Whenever you are about to redo something you already did
|
|
119
|
+
(relaunch the browser, log in again, retype a form, get back to the screen
|
|
120
|
+
where the bug shows), repeat those indices instead of re-issuing the calls by
|
|
121
|
+
hand: it is faster, and retyped arguments drift from what actually ran.
|
|
122
|
+
|
|
123
|
+
Every tool response carries its own index in the footer, so the numbers are
|
|
124
|
+
already in front of you. `replay({ action: 'history' })` lists them when they
|
|
125
|
+
have scrolled away. If the stretch turns out to be worth keeping, hand the same
|
|
126
|
+
indices to `create`.
|
|
127
|
+
|
|
128
|
+
## Managing them
|
|
129
|
+
|
|
130
|
+
- `list` / `get` / `delete` - sequences in memory. `get` takes
|
|
131
|
+
`outputFormat: 'commands' | 'playwright' | 'puppeteer'` to return the raw
|
|
132
|
+
command JSON or generated test code instead of the detail view (`'events'`
|
|
133
|
+
and `'review'` are recordInteraction-only - a stored sequence has no raw
|
|
134
|
+
events, and `get` says so rather than ignoring them)
|
|
135
|
+
- `load` / `listSaved` / `deleteSaved` - sequences on disk
|
|
136
|
+
- `export` - write to a file as `sequence`, `playwright`, or `puppeteer`
|
|
137
|
+
- `global: true` on `export` saves to `~/.cdp-tools/sequences/` instead of the
|
|
138
|
+
working directory
|
|
139
|
+
|
|
140
|
+
`load` and `create` reject a sequence naming a tool that doesn't exist, listing
|
|
141
|
+
the offending step, rather than failing halfway through a run after earlier
|
|
142
|
+
steps already changed state.
|
|
143
|
+
|
|
144
|
+
**Editing a sequence file mid-session just works.** The sequences directories
|
|
145
|
+
are watched like a managed dev server's sources, and a run re-checks the file's
|
|
146
|
+
timestamp anyway - so the version you just saved is the version that runs. No
|
|
147
|
+
`load` needed to pick up an edit. A file caught mid-write leaves the loaded copy
|
|
148
|
+
in place rather than replacing a good sequence with a half-saved one, and a
|
|
149
|
+
sequence built from history has no file to reload from.
|
|
150
|
+
|
|
151
|
+
## Running
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
replay({ action: 'run', sequenceId: 'seq-login', connectionReason: 'my-app' })
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**`run` does not block** (changed in 0.7): it returns a run id immediately and
|
|
158
|
+
executes in the background.
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
replay({ action: 'status', runId: 'run-3-...' }) // progress; full result once settled
|
|
162
|
+
replay({ action: 'cancel', runId: 'run-3-...' }) // stop it
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`cancel` reaches the step that is in flight (including inside nested
|
|
166
|
+
`conditional` sequences), but what it can do there differs by tool - three
|
|
167
|
+
levels, and the difference matters:
|
|
168
|
+
|
|
169
|
+
- **Genuinely cancelled:** `wait` (all forms, mid-poll) and `request` with
|
|
170
|
+
`destination: 'node'` (the socket is closed - the server sees it aborted).
|
|
171
|
+
- **Stops waiting, work continues:** `navigate` (deliberately no
|
|
172
|
+
`Page.stopLoading` - a half-loaded page is worse than a loaded one),
|
|
173
|
+
`inspect({ action: 'evaluateExpression' })`, `content({ action: 'parse' })`.
|
|
174
|
+
- **Checkpoint only:** `input` - an input event on the wire cannot be
|
|
175
|
+
recalled, so cancelling stops events that had not gone out yet and undoes
|
|
176
|
+
nothing already dispatched (a cancelled drag does still release the button).
|
|
177
|
+
Same for `request` with `destination: 'browser'`, `screenshot`, and the
|
|
178
|
+
non-waiting `content`/`inspect` actions.
|
|
179
|
+
|
|
180
|
+
`breakpoint({ action: 'await' })` is cancellable and now **fails** the step
|
|
181
|
+
(it used to report success). `dom`, `network` and everything else have no real
|
|
182
|
+
wait to interrupt, so they stop at the next step boundary. In every case, work
|
|
183
|
+
already dispatched to the browser may still take effect. Full table:
|
|
184
|
+
`docs/replay.md`.
|
|
185
|
+
|
|
186
|
+
Several runs can execute concurrently - even of the same sequence - and the
|
|
187
|
+
run id is what tells them apart. Settled runs and their results are kept in
|
|
188
|
+
memory for 30 minutes (max 50); after that, or after a server restart (which
|
|
189
|
+
kills in-flight runs), the id returns `REPLAY_RUN_NOT_FOUND`. Nested sequences
|
|
190
|
+
(`conditional` flows, `replay run` steps) are part of their parent run, never
|
|
191
|
+
separate runs. Pass `wait: true` to block until completion and get the full
|
|
192
|
+
result in one call (the pre-0.7 behaviour).
|
|
193
|
+
|
|
194
|
+
Useful `run` parameters:
|
|
195
|
+
|
|
196
|
+
- `startUrl` - override the stored start URL for this run only (e.g. a
|
|
197
|
+
freshly minted magic link)
|
|
198
|
+
- `baseUrl` - retarget every absolute URL at another origin, keeping paths and
|
|
199
|
+
queries. Point a staging-recorded sequence at local
|
|
200
|
+
- `startFrom` - begin at step N (1-indexed)
|
|
201
|
+
- `stepTimeout` / `totalTimeout` - each step is bounded by
|
|
202
|
+
`min(stepTimeout, remaining totalTimeout)` (defaults 30s / 5min); a step that
|
|
203
|
+
exceeds it fails the run at that step. `wait` steps are exempt from
|
|
204
|
+
`stepTimeout` (they have their own `timeoutMs`) but still capped by
|
|
205
|
+
`totalTimeout`
|
|
206
|
+
- `variables` - substitute recorded typed text (see below)
|
|
207
|
+
- `killChromeOnFinish` - tears down the browsers this run OWNS: its own
|
|
208
|
+
run-level connection, plus any browser a `launchChrome` step actually
|
|
209
|
+
created. A step that reached an already-bound reference only borrowed that
|
|
210
|
+
browser, so it is left running and a sequence can read from a long-lived
|
|
211
|
+
instance you launched yourself without it being killed underneath you.
|
|
212
|
+
Skipped for any browser whose port another live connection shares (a
|
|
213
|
+
`launchChrome` step usually opens a tab in the same instance) - the run says
|
|
214
|
+
which connection kept it alive
|
|
215
|
+
|
|
216
|
+
Step through interactively with `step`, `finish`, `insert`, `status`, `cancel`
|
|
217
|
+
(`run` with `stepTo: N` pauses after step N; the run's status becomes `paused`
|
|
218
|
+
and you drive it from there). A bare `cancel` prefers the paused session;
|
|
219
|
+
use `runId` to address a specific background run.
|
|
220
|
+
|
|
221
|
+
## Two different "variables" - don't confuse them
|
|
222
|
+
|
|
223
|
+
**1. `variables` on `run` replaces recorded typed text.** Keyed by the recorded
|
|
224
|
+
input, for replaying a signup with a fresh email:
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
replay({ action: 'run', sequenceId: 'seq-signup',
|
|
228
|
+
variables: { 'var_2_#email': 'new@example.com' } })
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**2. `saveAs` captures a value mid-run for later steps.** Supported on
|
|
232
|
+
`request` and on `inspect({ action: 'evaluateExpression' })`. Later steps read
|
|
233
|
+
it with `{{var:name}}` or `{{var:name.path}}`:
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
request({ url: '...', saveAs: 'login' }) // stores the whole response
|
|
237
|
+
inspect({ action: 'evaluateExpression',
|
|
238
|
+
expression: 'document.querySelector("#pair").href',
|
|
239
|
+
saveAs: 'pairingUrl' }) // stores the value itself
|
|
240
|
+
navigate({ action: 'goto', url: '{{var:pairingUrl}}' })
|
|
241
|
+
assert({ left: '{{var:login.body.token}}', operator: 'exists' })
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Note the asymmetry: `request` stores the response object (so you index into
|
|
245
|
+
`.body`), `inspect` stores the evaluated value directly. A `saveAs` that can't
|
|
246
|
+
be honoured fails the step rather than silently capturing nothing.
|
|
247
|
+
|
|
248
|
+
Values that only render as a description (`[HTMLDivElement]`, `Array(3)`) come
|
|
249
|
+
back as strings - capture a specific field rather than a whole DOM object.
|
|
250
|
+
|
|
251
|
+
## Waiting for async work
|
|
252
|
+
|
|
253
|
+
Recording by hand hides races: driving tools interactively puts seconds
|
|
254
|
+
between calls, so async work always looks settled. Replayed back-to-back, a
|
|
255
|
+
step after a navigation or an async kick-off reads state that isn't there
|
|
256
|
+
yet. `wait` is the sequence step for that:
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
{ tool: 'wait', params: { selector: 'button:has-text("Join")' } } // appears
|
|
260
|
+
{ tool: 'wait', params: { selectorGone: '.spinner' } } // disappears
|
|
261
|
+
{ tool: 'wait', params: { expression: 'window.__probe !== "PENDING"' } }
|
|
262
|
+
{ tool: 'wait', params: { ms: 500 } } // last resort
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Exactly one form per step. Condition forms poll a **synchronous** check from
|
|
266
|
+
the MCP side (default: every 100ms, up to `timeoutMs` 15000), so they survive
|
|
267
|
+
a navigation mid-wait and don't depend on in-page timers or promises. On
|
|
268
|
+
timeout the step fails and stops the run - a `wait` never hangs. For async
|
|
269
|
+
in-page work, kick it off in one step, store the result in a global, then
|
|
270
|
+
`wait({ expression: 'window.__result !== undefined' })` and read it with
|
|
271
|
+
`inspect` + `saveAs`.
|
|
272
|
+
|
|
273
|
+
Historical note: sequences in the wild use a marker-div + hover-on-
|
|
274
|
+
`:has-text()` idiom (an `input({ action: 'hover' })` on an element that only
|
|
275
|
+
exists once async work settles). That was never stylistic - hover's short
|
|
276
|
+
implicit element-wait was the *only* step that waited at all before `wait`
|
|
277
|
+
existed. Don't copy the pattern into new sequences; use `wait` and `assert`.
|
|
278
|
+
|
|
279
|
+
## Multi-device / multi-browser sequences
|
|
280
|
+
|
|
281
|
+
Any step may carry its own `connectionReason`, and it is honoured for
|
|
282
|
+
validation and pause handling, not just dispatch. That's what makes
|
|
283
|
+
"device A scans, device B confirms" sequences work in one run:
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
{ tool: 'input', params: { action: 'click', selector: '#pair',
|
|
287
|
+
connectionReason: 'device-a' } }
|
|
288
|
+
{ tool: 'inspect', params: { action: 'evaluateExpression',
|
|
289
|
+
expression: '...', saveAs: 'code',
|
|
290
|
+
connectionReason: 'device-a' } }
|
|
291
|
+
{ tool: 'navigate', params: { action: 'goto', url: '{{var:code}}',
|
|
292
|
+
connectionReason: 'device-b' } }
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Steps without an explicit `connectionReason` use the run-level one.
|
|
296
|
+
|
|
297
|
+
**Recording one.** Pass `connectionReason` explicitly on **every** call while you
|
|
298
|
+
drive the browsers - including the one that happens to be active. Recording
|
|
299
|
+
preserves it, and `create` decides what to do with it:
|
|
300
|
+
|
|
301
|
+
- all steps on one connection - hoisted off the steps, so the sequence stays
|
|
302
|
+
portable and `run({ connectionReason })` still retargets it
|
|
303
|
+
- genuinely spanning connections - kept per step
|
|
304
|
+
- **mixed** (some steps named, some driven implicitly through the active
|
|
305
|
+
connection) - kept as-is with a warning, because nothing can tell which
|
|
306
|
+
browser the bare steps belonged to. `create` says so; re-record naming every
|
|
307
|
+
step rather than shipping it
|
|
308
|
+
|
|
309
|
+
"Bare" covers the tools whose `connectionReason` is *optional* (`inspect`,
|
|
310
|
+
`execution`, `storage`, `network`, `breakpoint`, `request`), not just the
|
|
311
|
+
browser-only ones - those are the ones actually left off. A sequence can be both
|
|
312
|
+
multi-connection and mixed, and that is the worst case: the bare steps land in a
|
|
313
|
+
different browser depending on the run-level `connectionReason`, green either
|
|
314
|
+
way. `create` warns about both.
|
|
315
|
+
|
|
316
|
+
**Inserting into one.** `insert` re-stamps the connection `create` hoisted off
|
|
317
|
+
(kept on the sequence as `recordedConnection`) before merging, so a same-browser
|
|
318
|
+
insert re-hoists and stays portable, while a cross-browser insert makes every
|
|
319
|
+
step explicit and becomes a real multi-connection sequence.
|
|
320
|
+
|
|
321
|
+
**Declaring the browsers it needs.** A sequence can bring up its own browsers
|
|
322
|
+
instead of expecting the caller to have launched them. Set it with `declare`:
|
|
323
|
+
|
|
324
|
+
```js
|
|
325
|
+
replay({ action: 'declare', name: 'duo-stock-propagation',
|
|
326
|
+
requiredConnections: [
|
|
327
|
+
{ reference: 'duo-member-two', role: 'the member who draws stock',
|
|
328
|
+
url: 'http://localhost:5173/login' }
|
|
329
|
+
] })
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Each list replaces its field and `[]` clears it; passing one leaves the other
|
|
333
|
+
untouched. The sequence is written back to its file (a memory-only one waits
|
|
334
|
+
for `export`), and a declaration that cannot mean what it says is refused here
|
|
335
|
+
rather than on the next run. It lands on the sequence next to `commands`:
|
|
336
|
+
|
|
337
|
+
```json
|
|
338
|
+
"requiredConnections": [
|
|
339
|
+
{ "reference": "duo-member-two", "role": "the member who draws stock",
|
|
340
|
+
"url": "http://localhost:5173/login" }
|
|
341
|
+
]
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
`url` defaults to the sequence's `startUrl`; `forceNewInstance` defaults to
|
|
345
|
+
**true** (a separate process, not a tab - two identities in one browser share
|
|
346
|
+
its storage); `role` shows up in the run summary. A reference already bound to
|
|
347
|
+
a live browser is reused, and a `connections` mapping wins over the
|
|
348
|
+
declaration. A browser that will not launch fails the run before step 1.
|
|
349
|
+
|
|
350
|
+
**`profile` makes the device durable.** Add the persistent profile the browser
|
|
351
|
+
should come up on - the same ones `launchChrome({ profile })` creates:
|
|
352
|
+
|
|
353
|
+
```json
|
|
354
|
+
{ "reference": "device-a", "profile": "device-a", "role": "the enrolled device" }
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Storage (cookies, localStorage, IndexedDB, non-extractable CryptoKeys) survives
|
|
358
|
+
between runs, so a device enrolled once stays enrolled; the reference is just
|
|
359
|
+
this session's name for it. Steps still address browsers by `connectionReason` -
|
|
360
|
+
there is no per-step `profile`.
|
|
361
|
+
|
|
362
|
+
Two rules follow. `forceNewInstance` defaults to **false** when a profile is
|
|
363
|
+
named, because only one live Chrome may hold a profile and the one already
|
|
364
|
+
running it is the browser you asked for. And a profile-bearing reference may
|
|
365
|
+
**not** be rebound through `connections`, nor may two declarations share one
|
|
366
|
+
profile: a profile is an identity claim, not a default, and pointing it
|
|
367
|
+
elsewhere would run device-a's steps in a browser that is not device-a and pass.
|
|
368
|
+
Teardown kills the browser but never the profile directory.
|
|
369
|
+
|
|
370
|
+
The run closes what it launched on every terminal outcome - completed, failed,
|
|
371
|
+
cancelled - and reports *"Browsers closed (declared and launched): ..."*. A
|
|
372
|
+
pause keeps them (that is the state you stopped to inspect); whatever ends the
|
|
373
|
+
pause (`cancel`, `finish`, stepping off the end) closes them then. Browsers
|
|
374
|
+
that were already up, or that share a port with another live connection, are
|
|
375
|
+
left alone.
|
|
376
|
+
|
|
377
|
+
**Declaring the sockets it depends on.** `requiredSockets` is the same idea for
|
|
378
|
+
transports: URL substrings of the WebSockets the assertions ride on, set by the
|
|
379
|
+
same action (`replay({ action: 'declare', name: '...', requiredSockets:
|
|
380
|
+
['/api/sync/socket'] })`). A sequence that declares them is
|
|
381
|
+
checked without the caller asking - `requireSockets: true` is only for a
|
|
382
|
+
sequence that declares none. Per entry the run fails when a matching socket
|
|
383
|
+
closed or hit frame errors mid-run, or when none is open at the end (including
|
|
384
|
+
one that never opened - invisible to any final "is it up" assertion). Closes
|
|
385
|
+
the run did not cause are not blamed on it: a socket torn down with its target
|
|
386
|
+
by a navigation, or hung up by the page, is normal. Match the app's own path,
|
|
387
|
+
not the origin, so the declaration survives `baseUrl`; dev-server sockets (Vite
|
|
388
|
+
HMR) go undeclared and are ignored.
|
|
389
|
+
|
|
390
|
+
**Replaying one in a different session.** Recorded references are per-session,
|
|
391
|
+
so rebind them:
|
|
392
|
+
|
|
393
|
+
```
|
|
394
|
+
replay({ action: 'run', sequenceId: 'duo',
|
|
395
|
+
connections: { 'duo-member-two': 'my-second-browser' } })
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
Recorded name on the left, a reference from this session on the right. A key
|
|
399
|
+
that matches nothing in the sequence is rejected up front, listing the real
|
|
400
|
+
ones, rather than being ignored - "the sequence" includes the sequences its
|
|
401
|
+
`conditional` steps pull in, so a setup sequence behind a conditional is
|
|
402
|
+
rebindable too. Mapping two recorded references onto one browser is rejected as
|
|
403
|
+
well - that would collapse the sequence into a single browser and pass.
|
|
404
|
+
`issues({ action: 'workOn' | 'resolve' })` takes `connections` too.
|
|
405
|
+
|
|
406
|
+
Any step naming a connection other than the run's is checked against the live
|
|
407
|
+
session first, so a missing browser fails as *"step 3 needs connection
|
|
408
|
+
duo-member-two, which does not exist in this session"* rather than as a generic
|
|
409
|
+
"not connected to browser" from somewhere inside the tool.
|
|
410
|
+
|
|
411
|
+
**repeat / runFromLog.** Each command replays against the connection it was
|
|
412
|
+
recorded with. An explicit `connectionReason` retargets a single-connection
|
|
413
|
+
batch and is refused for a multi-connection one.
|
|
414
|
+
|
|
415
|
+
**Exported code.** `outputFormat: 'playwright' | 'puppeteer'` gives each recorded
|
|
416
|
+
connection its own page rather than merging them into one. Only `navigate` and
|
|
417
|
+
`input` steps have equivalents; everything else (`conditional`, `launchChrome`,
|
|
418
|
+
`inspect`, `storage`, `wait`) becomes a `// [not generated]` comment, and a
|
|
419
|
+
sequence where nothing could be generated exports a test that **throws** instead
|
|
420
|
+
of an empty one that passes. Setup sequences are for `run`, not for export.
|
|
421
|
+
|
|
422
|
+
Two things that deliberately do not happen: a run-level `connectionReason` does
|
|
423
|
+
**not** override a step's own, and a per-step reference that doesn't exist in
|
|
424
|
+
this session **fails the step** - it never falls back to the run-level
|
|
425
|
+
connection. Falling back is what made a two-browser sequence silently replay in
|
|
426
|
+
one browser and report success.
|
|
427
|
+
|
|
428
|
+
## Conditional steps
|
|
429
|
+
|
|
430
|
+
`conditional` is a virtual step tool - it runs another sequence inline when a
|
|
431
|
+
condition holds. It's handled inside the executor and never appears in the tool
|
|
432
|
+
list, which is why it's exempt from tool-name validation.
|
|
433
|
+
|
|
434
|
+
Not being a tool, it is never recorded, so `create`/`insert` cannot produce
|
|
435
|
+
one. `addConditional` is its authoring route:
|
|
436
|
+
|
|
437
|
+
```javascript
|
|
438
|
+
replay({ action: 'addConditional',
|
|
439
|
+
name: 'checkout-flow', // or sequenceId
|
|
440
|
+
condition: '{{selector:.login-button}}',
|
|
441
|
+
thenSequence: 'perform-login', // name of another sequence
|
|
442
|
+
insertAfterStep: 0 }) // omit to append
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
which stores `{ tool: 'conditional', params: { if, then } }`. Use it for state
|
|
446
|
+
that varies between runs - "log in first, but only if logged out".
|
|
447
|
+
|
|
448
|
+
Condition syntax and the branch target are checked before the sequence is
|
|
449
|
+
touched. A sequence already saved on disk is rewritten in place; otherwise it
|
|
450
|
+
waits for `export`. The response says which.
|
|
451
|
+
|
|
452
|
+
| Condition | True when |
|
|
453
|
+
|---|---|
|
|
454
|
+
| `{{selector:CSS}}` / `{{!selector:CSS}}` | element exists / doesn't |
|
|
455
|
+
| `{{url:contains:STRING}}` | current URL contains the string |
|
|
456
|
+
| `{{url:matches:REGEX}}` | current URL matches the regex |
|
|
457
|
+
| `{{url:EXACT}}` | current URL equals the value |
|
|
458
|
+
| `{{cookie:NAME}}` / `{{!cookie:NAME}}` | cookie exists / doesn't |
|
|
459
|
+
| `{{localStorage:KEY}}` / `{{!localStorage:KEY}}` | key exists / doesn't |
|
|
460
|
+
| `{{indexedDB:DB/STORE/KEY}}` / `{{!indexedDB:...}}` | that record exists / doesn't |
|
|
461
|
+
| `{{indexedDB:DB/STORE}}` | the object store holds at least one record |
|
|
462
|
+
|
|
463
|
+
A database or store that doesn't exist yet counts as **absent**, not as an
|
|
464
|
+
evaluation error - that's the state a wiped profile is in, and the state a
|
|
465
|
+
healing setup sequence exists to fix.
|
|
466
|
+
| `{{indexedDB:DB/STORE/KEY}}` / `{{!indexedDB:...}}` | that record exists / doesn't |
|
|
467
|
+
| `{{indexedDB:DB/STORE}}` | the object store holds at least one record |
|
|
468
|
+
|
|
469
|
+
A database or store that doesn't exist yet is **absent**, not an error - that's
|
|
470
|
+
the state a wiped profile is in. A value JSON can't represent (a
|
|
471
|
+
non-extractable `CryptoKey`, a `Blob`) still counts as present, so a device
|
|
472
|
+
identity is probeable directly instead of through some UI proxy. An all-digits
|
|
473
|
+
key is tried as a string and then as a number, since IndexedDB keys `42` and
|
|
474
|
+
`"42"` differ.
|
|
475
|
+
|
|
476
|
+
Conditions are interpolated like any other parameter, so a captured variable can
|
|
477
|
+
drive one: `{{indexedDB:identity/keys/{{var:deviceId}}}}`.
|
|
478
|
+
|
|
479
|
+
**Not met and cannot-evaluate are different outcomes.** A condition that is
|
|
480
|
+
legitimately false skips the nested sequence and the step counts as a
|
|
481
|
+
**success**. A condition that can't be evaluated at all - bad format, unknown
|
|
482
|
+
type, invalid or over-long regex, tool error - **fails the run**. Don't write a
|
|
483
|
+
conditional expecting a malformed condition to fall through quietly.
|
|
484
|
+
|
|
485
|
+
The nested sequence shares the parent run's captured variables (`saveAs` values
|
|
486
|
+
flow both ways) and inherits its remaining timeout budget. A `launchChrome` step
|
|
487
|
+
inside it is skipped when that reference is already connected and run when it
|
|
488
|
+
isn't, so a setup sequence spanning two browsers can create the second one
|
|
489
|
+
itself.
|
|
490
|
+
|
|
491
|
+
**Which browser its bare steps run in** follows from that: if the nested launch
|
|
492
|
+
actually ran, they run in the browser it created (a setup sequence is a launch
|
|
493
|
+
plus bare steps, since `create` hoists the connection off them - leaving them on
|
|
494
|
+
the caller would open a browser and then do the work in the wrong one); if the
|
|
495
|
+
launch was skipped or absent, they run in the calling run's connection, so a
|
|
496
|
+
nested login sequence still works wherever it's called from. Steps naming their
|
|
497
|
+
own `connectionReason` are unaffected.
|
|
498
|
+
|
|
499
|
+
**Two connections are not two devices.** A plain `launchChrome` opens a tab in
|
|
500
|
+
the running instance, so both references share one profile - one cookie jar, one
|
|
501
|
+
localStorage, one IndexedDB. A duo test built that way has ONE device identity
|
|
502
|
+
under two names, and a cross-user propagation check passes without a second
|
|
503
|
+
device existing. When the two sides must be genuinely separate, launch the
|
|
504
|
+
second with its own profile:
|
|
505
|
+
`launchChrome({ reference: 'duo-member-two', profile: 'member', forceNewInstance: true })`.
|
|
506
|
+
Same `port` in `listConnections` means same instance, so shared storage.
|
|
507
|
+
|
|
508
|
+
Nesting depth is capped by `replay.maxConditionalDepth` (default 10) and regexes
|
|
509
|
+
by `replay.maxRegexLength` (default 500), both in `.cdp-tools/config.json`.
|
|
510
|
+
Oscillating chains (A->B->A) are allowed up to the depth cap. Full detail:
|
|
511
|
+
`docs/replay.md`.
|
|
512
|
+
|
|
513
|
+
## `forEach` steps
|
|
514
|
+
|
|
515
|
+
A condition asks whether ONE named thing exists, so `conditional` can express
|
|
516
|
+
"add it if it's missing" but never "remove everything that shouldn't be here".
|
|
517
|
+
`forEach` is the other half: enumerate a source, run a sequence per item.
|
|
518
|
+
|
|
519
|
+
```javascript
|
|
520
|
+
{ tool: 'forEach', params: {
|
|
521
|
+
in: '{{var:shares}}', // an array a previous saveAs captured
|
|
522
|
+
as: 'share', // bound per iteration
|
|
523
|
+
do: 'revoke-one-share', // sequence name, run once per item
|
|
524
|
+
where: 'item.name !== "Employees"', // optional filter
|
|
525
|
+
maxItems: 50 } } // optional cap (default 100)
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
**`in` takes two forms.** `{{var:name}}` reads an array a previous `saveAs`
|
|
529
|
+
captured - which is how anything non-DOM is enumerated, since
|
|
530
|
+
`inspect({ action: 'evaluateExpression', saveAs: 'shares' })` can return exactly
|
|
531
|
+
the list you want and is a recordable step. `{{selectorAll:CSS}}` enumerates the
|
|
532
|
+
DOM, yielding `{ index, text, id, className, href, value }` per element -
|
|
533
|
+
elements themselves cannot cross the CDP boundary, so `index` is what the body
|
|
534
|
+
uses to address one again.
|
|
535
|
+
|
|
536
|
+
**`as` binds the item**, readable in the body as `{{var:share.id}}` like any
|
|
537
|
+
captured variable, with its position in `{{var:shareIndex}}`. The binding is
|
|
538
|
+
replaced per iteration, not scoped - the variable store is shared by reference
|
|
539
|
+
across nested runs, so a body's own `saveAs` captures also survive into the next
|
|
540
|
+
iteration.
|
|
541
|
+
|
|
542
|
+
**`where` is JavaScript, not the `{{...}}` condition grammar**, evaluated in the
|
|
543
|
+
page with `item` and `index` in scope. Conditions probe the browser for one named
|
|
544
|
+
thing; a filter has to read fields off an arbitrary object, which that grammar
|
|
545
|
+
cannot express. A `where` that cannot be evaluated **fails the run** - the same
|
|
546
|
+
rule a malformed condition follows, because silently excluding every item makes a
|
|
547
|
+
typo look like an empty result set.
|
|
548
|
+
|
|
549
|
+
An empty source is a **success**, and the run output says how many items were
|
|
550
|
+
found - a converge loop with nothing left to clean up would otherwise be
|
|
551
|
+
indistinguishable from a broken selector. A body failure stops the run and names
|
|
552
|
+
which item it was on. Depth shares `maxConditionalDepth` with `conditional`.
|
|
553
|
+
|
|
554
|
+
## `teardown` - steps that always run
|
|
555
|
+
|
|
556
|
+
A sequence can carry a `teardown` array beside its `commands`:
|
|
557
|
+
|
|
558
|
+
```json
|
|
559
|
+
{ "name": "mint-and-check",
|
|
560
|
+
"commands": [ ... ],
|
|
561
|
+
"teardown": [
|
|
562
|
+
{ "tool": "request",
|
|
563
|
+
"params": { "url": "/api/share/revoke/{{var:mint.body.id}}", "method": "POST" } }
|
|
564
|
+
] }
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
They run once the main steps reach a terminal state - success, a failed step, an
|
|
568
|
+
abort, or the total timeout - which is what makes cleanup survive the cases that
|
|
569
|
+
need it. Three properties, each deliberate:
|
|
570
|
+
|
|
571
|
+
- **Their own timeout budget** (`teardownTimeout`, default 60s), not drawn from
|
|
572
|
+
the run's `totalTimeout`. The commonest reason a run needs cleaning up after is
|
|
573
|
+
that it timed out; sharing the budget would skip teardown exactly then.
|
|
574
|
+
- **The run's abort signal is NOT passed down**, so `replay cancel` stops the
|
|
575
|
+
work and not the cleanup. A cancelled run is precisely one that left something
|
|
576
|
+
behind.
|
|
577
|
+
- **The variable store is shared**, so teardown can revoke what setup minted even
|
|
578
|
+
though the capturing step ran long before the failure.
|
|
579
|
+
|
|
580
|
+
They do **not** run when a run *pauses* - `stepTo`, a breakpoint, a click
|
|
581
|
+
validation failure. A paused run is not over, and its state is what you stopped
|
|
582
|
+
to look at.
|
|
583
|
+
|
|
584
|
+
A failing teardown step never changes the run's verdict; it is reported in its
|
|
585
|
+
own section. Otherwise a broken cleanup would mask the failure it was cleaning up
|
|
586
|
+
after.
|
|
587
|
+
|
|
588
|
+
**Teardown is always best-effort.** A killed cdp-tools process takes any pending
|
|
589
|
+
teardown with it, so it reduces accumulation and cannot guarantee a clean world.
|
|
590
|
+
An assertion that depends on nothing being left over ("No assets yet") stays
|
|
591
|
+
order-dependent whether or not teardown exists - mint your own fixture and assert
|
|
592
|
+
on that instead, and teardown becomes hygiene rather than correctness.
|
|
593
|
+
|
|
594
|
+
## When a sequence is flaky
|
|
595
|
+
|
|
596
|
+
Name the symptom rather than adding sleeps - each of these has a real mechanism
|
|
597
|
+
behind it, documented in `docs/replay.md`:
|
|
598
|
+
|
|
599
|
+
| Symptom | What to reach for |
|
|
600
|
+
|---|---|
|
|
601
|
+
| Clicks land before the element exists | Click/type/hover already retry; add an explicit `wait({ selector })` step for work the previous step kicked off |
|
|
602
|
+
| Consent banners or dialogs block interaction | `handleModals: true` on the input action, with a `dismissStrategy` |
|
|
603
|
+
| Stale content while requests are in flight | `wait({ expression })` on a flag the app sets, not a fixed sleep |
|
|
604
|
+
| localhost URL fails because nothing is running | The port check fails fast - start the server (`server({ action: 'start' })`) |
|
|
605
|
+
| A run hangs or takes far too long | `stepTimeout` / `totalTimeout`; a step exceeding its budget fails the run at that step |
|
|
606
|
+
| A step ran against the wrong browser | See the multi-device section - almost always a bare `connectionReason` |
|
|
607
|
+
|
|
608
|
+
## Verifying a fix
|
|
609
|
+
|
|
610
|
+
`issues({ action: 'workOn', id: N })` replays an issue's linked sequence so you
|
|
611
|
+
can see the bug reproduce, fix it, then replay again. Closing the issue is
|
|
612
|
+
`resolve`, which is human-gated - an agent should record findings with
|
|
613
|
+
`comment` and leave the closing to a person.
|