@crvouga/atlas 0.4.0 → 0.6.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/CHANGELOG.md +13 -1
- package/README.md +191 -611
- package/dist/bun/src/driver.d.ts +41 -7
- package/dist/bun/src/driver.js +47 -6
- package/dist/bun/src/index.d.ts +8 -0
- package/dist/bun/src/index.js +8 -0
- package/dist/core/src/cli.d.ts +14 -1
- package/dist/core/src/cli.js +35 -1
- package/dist/core/src/config.d.ts +77 -4
- package/dist/core/src/config.js +110 -17
- package/dist/core/src/events/cloudevents.d.ts +54 -9
- package/dist/core/src/events/cloudevents.js +60 -9
- package/dist/core/src/graph.d.ts +21 -0
- package/dist/core/src/graph.js +26 -0
- package/dist/core/src/index.d.ts +11 -0
- package/dist/core/src/index.js +11 -0
- package/dist/core/src/lint.d.ts +30 -0
- package/dist/core/src/lint.js +13 -0
- package/dist/core/src/plan.d.ts +39 -1
- package/dist/core/src/plan.js +28 -1
- package/dist/core/src/privacy.d.ts +29 -2
- package/dist/core/src/privacy.js +19 -2
- package/dist/core/src/report/formats.d.ts +34 -4
- package/dist/core/src/report/formats.js +24 -4
- package/dist/core/src/report/markdown.d.ts +15 -0
- package/dist/core/src/report/markdown.js +8 -0
- package/dist/core/src/run/clients.d.ts +44 -5
- package/dist/core/src/run/clients.js +46 -2
- package/dist/core/src/run/execute.d.ts +75 -16
- package/dist/core/src/run/execute.js +71 -18
- package/dist/core/src/run/frames.d.ts +13 -4
- package/dist/core/src/run/frames.js +19 -4
- package/dist/core/src/run/http.d.ts +34 -1
- package/dist/core/src/run/http.js +14 -1
- package/dist/core/src/run/types.d.ts +76 -3
- package/dist/core/src/run/types.js +19 -3
- package/dist/core/src/signals.d.ts +49 -5
- package/dist/core/src/signals.js +22 -5
- package/dist/core/src/spec/compose.d.ts +23 -1
- package/dist/core/src/spec/compose.js +10 -1
- package/dist/core/src/spec/index.d.ts +9 -1
- package/dist/core/src/spec/index.js +9 -1
- package/dist/core/src/spec/load.d.ts +22 -1
- package/dist/core/src/spec/load.js +31 -1
- package/dist/core/src/spec/parse.d.ts +16 -2
- package/dist/core/src/spec/parse.js +25 -2
- package/dist/core/src/spec/scxml.d.ts +25 -4
- package/dist/core/src/spec/scxml.js +36 -4
- package/dist/core/src/spec/types.d.ts +52 -1
- package/dist/core/src/spec/types.js +24 -0
- package/dist/detox/src/index.d.ts +54 -11
- package/dist/detox/src/index.js +45 -7
- package/dist/playwright/src/driver.d.ts +39 -3
- package/dist/playwright/src/driver.js +27 -3
- package/dist/playwright/src/index.d.ts +10 -0
- package/dist/playwright/src/index.js +10 -0
- package/dist/playwright/src/media.d.ts +50 -4
- package/dist/playwright/src/media.js +55 -4
- package/dist/playwright/src/signals.d.ts +44 -5
- package/dist/playwright/src/signals.js +29 -5
- package/dist/reports/src/attachment-files.d.ts +44 -0
- package/dist/reports/src/attachment-files.js +121 -0
- package/dist/reports/src/attachment-response.d.ts +31 -0
- package/dist/reports/src/attachment-response.js +55 -0
- package/dist/reports/src/aws-credentials.d.ts +48 -0
- package/dist/reports/src/aws-credentials.js +221 -0
- package/dist/reports/src/browser-navigation.d.ts +14 -0
- package/dist/reports/src/browser-navigation.js +7 -0
- package/dist/reports/src/bundle.d.ts +34 -2
- package/dist/reports/src/bundle.js +38 -2
- package/dist/reports/src/catalog.d.ts +34 -3
- package/dist/reports/src/catalog.js +17 -3
- package/dist/reports/src/cli-stores.d.ts +36 -0
- package/dist/reports/src/cli-stores.js +73 -0
- package/dist/reports/src/clock.d.ts +7 -2
- package/dist/reports/src/clock.js +5 -2
- package/dist/reports/src/completion-order.d.ts +18 -5
- package/dist/reports/src/completion-order.js +18 -5
- package/dist/reports/src/compose.d.ts +80 -0
- package/dist/reports/src/compose.js +117 -0
- package/dist/reports/src/copy-store.d.ts +29 -0
- package/dist/reports/src/copy-store.js +52 -0
- package/dist/reports/src/directory-outline.d.ts +38 -0
- package/dist/reports/src/directory-outline.js +103 -0
- package/dist/reports/src/directory-store.d.ts +20 -0
- package/dist/reports/src/directory-store.js +166 -0
- package/dist/reports/src/doctor.d.ts +29 -0
- package/dist/reports/src/doctor.js +85 -0
- package/dist/reports/src/effective-delta.d.ts +34 -0
- package/dist/reports/src/effective-delta.js +25 -0
- package/dist/reports/src/evidence-detail.d.ts +64 -0
- package/dist/reports/src/evidence-detail.js +92 -0
- package/dist/reports/src/evidence-live.d.ts +35 -5
- package/dist/reports/src/evidence-live.js +18 -2
- package/dist/reports/src/evidence-types.d.ts +68 -1
- package/dist/reports/src/evidence-types.js +5 -1
- package/dist/reports/src/evidence-view.d.ts +43 -4
- package/dist/reports/src/evidence-view.js +13 -2
- package/dist/reports/src/explorer-types.d.ts +40 -3
- package/dist/reports/src/explorer-types.js +8 -0
- package/dist/reports/src/explorer-versions.d.ts +9 -1
- package/dist/reports/src/explorer-versions.js +18 -1
- package/dist/reports/src/export-evidence.d.ts +16 -1
- package/dist/reports/src/export-evidence.js +52 -1
- package/dist/reports/src/fetcher.d.ts +27 -3
- package/dist/reports/src/fetcher.js +11 -3
- package/dist/reports/src/format-date.d.ts +6 -0
- package/dist/reports/src/format-date.js +6 -0
- package/dist/reports/src/format.d.ts +16 -0
- package/dist/reports/src/format.js +10 -0
- package/dist/reports/src/frames.d.ts +36 -3
- package/dist/reports/src/frames.js +22 -3
- package/dist/reports/src/hash.d.ts +18 -0
- package/dist/reports/src/hash.js +13 -0
- package/dist/reports/src/host.d.ts +29 -0
- package/dist/reports/src/host.js +64 -0
- package/dist/reports/src/html-frame.d.ts +5 -6
- package/dist/reports/src/html-frame.js +12 -1
- package/dist/reports/src/http-store.d.ts +56 -0
- package/dist/reports/src/http-store.js +148 -0
- package/dist/reports/src/http.d.ts +7 -0
- package/dist/reports/src/http.js +6 -0
- package/dist/reports/src/ingest.d.ts +54 -0
- package/dist/reports/src/ingest.js +210 -0
- package/dist/reports/src/init.d.ts +40 -0
- package/dist/reports/src/init.js +369 -0
- package/dist/reports/src/junit.d.ts +33 -6
- package/dist/reports/src/junit.js +87 -21
- package/dist/reports/src/keyboard.d.ts +20 -0
- package/dist/reports/src/keyboard.js +11 -0
- package/dist/reports/src/keys.d.ts +21 -3
- package/dist/reports/src/keys.js +21 -3
- package/dist/reports/src/layer-axes.d.ts +35 -5
- package/dist/reports/src/layer-axes.js +25 -5
- package/dist/reports/src/layer-catalog.d.ts +28 -1
- package/dist/reports/src/layer-catalog.js +18 -1
- package/dist/reports/src/layer-merge.d.ts +98 -9
- package/dist/reports/src/layer-merge.js +147 -31
- package/dist/reports/src/layer-source.d.ts +18 -0
- package/dist/reports/src/layer-source.js +11 -0
- package/dist/reports/src/layer-store.d.ts +42 -0
- package/dist/reports/src/layer-store.js +63 -5
- package/dist/reports/src/layer-types.d.ts +32 -1
- package/dist/reports/src/layers-route.d.ts +47 -4
- package/dist/reports/src/layers-route.js +86 -7
- package/dist/reports/src/live-reporter.d.ts +34 -0
- package/dist/reports/src/live-reporter.js +122 -0
- package/dist/reports/src/media-types.d.ts +11 -0
- package/dist/reports/src/media-types.js +22 -0
- package/dist/reports/src/namespace-guard.d.ts +10 -1
- package/dist/reports/src/namespace-guard.js +10 -1
- package/dist/reports/src/navigation.d.ts +12 -0
- package/dist/reports/src/navigation.js +7 -0
- package/dist/reports/src/object-io.d.ts +11 -4
- package/dist/reports/src/object-io.js +4 -4
- package/dist/reports/src/object-store-conformance.d.ts +27 -0
- package/dist/reports/src/object-store-conformance.js +87 -0
- package/dist/reports/src/object-store.d.ts +33 -24
- package/dist/reports/src/object-store.js +17 -24
- package/dist/reports/src/parse-with.d.ts +16 -0
- package/dist/reports/src/parse-with.js +14 -0
- package/dist/reports/src/performance-part.d.ts +25 -1
- package/dist/reports/src/performance-part.js +15 -1
- package/dist/reports/src/performance.d.ts +22 -2
- package/dist/reports/src/performance.js +12 -2
- package/dist/reports/src/persist.d.ts +12 -3
- package/dist/reports/src/persist.js +12 -3
- package/dist/reports/src/phone.d.ts +18 -1
- package/dist/reports/src/phone.js +18 -1
- package/dist/reports/src/protocol.d.ts +26 -4
- package/dist/reports/src/protocol.js +15 -4
- package/dist/reports/src/publication.d.ts +21 -1
- package/dist/reports/src/publication.js +21 -1
- package/dist/reports/src/publish-checks.d.ts +35 -0
- package/dist/reports/src/publish-checks.js +122 -0
- package/dist/reports/src/publish.d.ts +42 -9
- package/dist/reports/src/publish.js +36 -7
- package/dist/reports/src/read.d.ts +46 -16
- package/dist/reports/src/read.js +38 -14
- package/dist/reports/src/recording-bundle.d.ts +13 -2
- package/dist/reports/src/recording-bundle.js +4 -2
- package/dist/reports/src/recording-keys.d.ts +15 -1
- package/dist/reports/src/recording-keys.js +15 -1
- package/dist/reports/src/remote-report.d.ts +28 -7
- package/dist/reports/src/remote-report.js +15 -7
- package/dist/reports/src/report-cli.d.ts +51 -0
- package/dist/reports/src/report-cli.js +486 -0
- package/dist/reports/src/report-index.d.ts +99 -0
- package/dist/reports/src/report-index.js +337 -0
- package/dist/reports/src/report-loader.d.ts +10 -0
- package/dist/reports/src/report-loader.js +6 -0
- package/dist/reports/src/report-parts.d.ts +29 -3
- package/dist/reports/src/report-parts.js +37 -3
- package/dist/reports/src/report-status.d.ts +48 -0
- package/dist/reports/src/report-status.js +156 -0
- package/dist/reports/src/report-types.d.ts +73 -0
- package/dist/reports/src/report-types.js +3 -0
- package/dist/reports/src/reporter.d.ts +67 -0
- package/dist/reports/src/reporter.js +128 -0
- package/dist/reports/src/reports-explorer-route.d.ts +541 -0
- package/dist/reports/src/reports-explorer-route.js +11 -0
- package/dist/reports/src/reports-list-route.d.ts +9 -0
- package/dist/reports/src/reports-list-route.js +9 -0
- package/dist/reports/src/reports-route.d.ts +561 -22
- package/dist/reports/src/reports-route.js +11 -3
- package/dist/reports/src/reports-store.d.ts +19 -2
- package/dist/reports/src/reports-store.js +10 -2
- package/dist/reports/src/resilient-cache.d.ts +14 -1
- package/dist/reports/src/resilient-cache.js +8 -1
- package/dist/reports/src/retention.d.ts +17 -1
- package/dist/reports/src/retention.js +5 -1
- package/dist/reports/src/run-bundle.d.ts +63 -0
- package/dist/reports/src/run-bundle.js +159 -0
- package/dist/reports/src/s3-store.d.ts +79 -0
- package/dist/reports/src/s3-store.js +295 -0
- package/dist/reports/src/scenario.d.ts +14 -4
- package/dist/reports/src/scenario.js +5 -4
- package/dist/reports/src/sigv4.d.ts +72 -0
- package/dist/reports/src/sigv4.js +96 -0
- package/dist/reports/src/snapshot-bundle.d.ts +24 -1
- package/dist/reports/src/snapshot-bundle.js +27 -2
- package/dist/reports/src/snapshot-surface.d.ts +20 -9
- package/dist/reports/src/snapshot-surface.js +32 -19
- package/dist/reports/src/source-content.d.ts +23 -0
- package/dist/reports/src/source-content.js +30 -1
- package/dist/reports/src/source-entries.d.ts +13 -0
- package/dist/reports/src/source-entries.js +6 -0
- package/dist/reports/src/source-part.d.ts +15 -0
- package/dist/reports/src/source-part.js +8 -0
- package/dist/reports/src/source-reader.d.ts +11 -0
- package/dist/reports/src/source-reader.js +5 -0
- package/dist/reports/src/spec-evidence.d.ts +12 -2
- package/dist/reports/src/spec-evidence.js +6 -2
- package/dist/reports/src/spec-inventory.d.ts +15 -1
- package/dist/reports/src/spec-inventory.js +22 -1
- package/dist/reports/src/spec-link.d.ts +28 -0
- package/dist/reports/src/spec-link.js +70 -0
- package/dist/reports/src/spec-set.d.ts +63 -0
- package/dist/reports/src/spec-set.js +177 -0
- package/dist/reports/src/status.d.ts +10 -0
- package/dist/reports/src/status.js +5 -0
- package/dist/reports/src/store-prefix.d.ts +28 -0
- package/dist/reports/src/store-prefix.js +22 -0
- package/dist/reports/src/store-url.d.ts +89 -0
- package/dist/reports/src/store-url.js +160 -0
- package/dist/reports/src/test-catalog-entity.d.ts +11 -0
- package/dist/reports/src/test-catalog-entity.js +6 -0
- package/dist/reports/src/types.d.ts +6 -1
- package/dist/reports/src/use-report-nav.d.ts +35 -0
- package/dist/reports/src/use-report-nav.js +12 -0
- package/dist/runners/src/codecept.d.ts +14 -0
- package/dist/runners/src/codecept.js +16 -0
- package/dist/runners/src/jest.d.ts +50 -0
- package/dist/runners/src/jest.js +52 -0
- package/dist/runners/src/mocha.d.ts +40 -0
- package/dist/runners/src/mocha.js +39 -0
- package/dist/runners/src/node-test.d.ts +28 -0
- package/dist/runners/src/node-test.js +44 -0
- package/dist/runners/src/publish.d.ts +30 -0
- package/dist/runners/src/publish.js +68 -0
- package/dist/schema/src/api.d.ts +800 -0
- package/dist/schema/src/api.js +201 -0
- package/dist/schema/src/cli-results.d.ts +210 -0
- package/dist/schema/src/cli-results.js +124 -0
- package/dist/schema/src/common.d.ts +53 -0
- package/dist/schema/src/common.js +42 -0
- package/dist/schema/src/index.d.ts +16 -0
- package/dist/schema/src/index.js +16 -0
- package/dist/schema/src/layer.d.ts +2273 -0
- package/dist/schema/src/layer.js +462 -0
- package/dist/schema/src/live-run.d.ts +123 -0
- package/dist/schema/src/live-run.js +30 -0
- package/dist/schema/src/manifest.d.ts +97 -2
- package/dist/schema/src/manifest.js +84 -0
- package/dist/schema/src/project.d.ts +20 -0
- package/dist/schema/src/project.js +10 -0
- package/dist/schema/src/report-sources.d.ts +29 -0
- package/dist/schema/src/report-sources.js +27 -1
- package/dist/schema/src/runs-index.d.ts +17 -1
- package/dist/schema/src/runs-index.js +15 -0
- package/dist/schema/src/server-config.d.ts +177 -0
- package/dist/schema/src/server-config.js +85 -0
- package/dist/schema/src/spec.d.ts +69 -0
- package/dist/schema/src/spec.js +58 -0
- package/dist/server/src/app.d.ts +72 -0
- package/dist/server/src/app.js +557 -0
- package/dist/server/src/auth.d.ts +41 -0
- package/dist/server/src/auth.js +59 -0
- package/dist/server/src/config.d.ts +83 -0
- package/dist/server/src/config.js +204 -0
- package/dist/server/src/dev.d.ts +12 -0
- package/dist/server/src/dev.js +89 -0
- package/dist/server/src/events.d.ts +29 -0
- package/dist/server/src/events.js +101 -0
- package/dist/server/src/host.d.ts +30 -0
- package/dist/server/src/host.js +115 -0
- package/dist/server/src/index.d.ts +18 -0
- package/dist/server/src/index.js +18 -0
- package/dist/server/src/live-runs.d.ts +52 -0
- package/dist/server/src/live-runs.js +229 -0
- package/dist/server/src/llms.d.ts +19 -0
- package/dist/server/src/llms.js +63 -0
- package/dist/server/src/main.d.ts +30 -0
- package/dist/server/src/main.js +114 -0
- package/dist/server/src/openapi.d.ts +56 -0
- package/dist/server/src/openapi.js +438 -0
- package/dist/server/src/problem.d.ts +8 -0
- package/dist/server/src/problem.js +39 -0
- package/dist/server/src/projects.d.ts +3 -0
- package/dist/server/src/projects.js +154 -0
- package/dist/server/src/settings-page.d.ts +9 -0
- package/dist/server/src/settings-page.js +93 -0
- package/dist/server/src/settings.d.ts +23 -0
- package/dist/server/src/settings.js +72 -0
- package/dist/server/src/site.d.ts +15 -0
- package/dist/server/src/site.js +78 -0
- package/dist/server/src/store-api.d.ts +28 -0
- package/dist/server/src/store-api.js +180 -0
- package/dist/server/src/validate.d.ts +71 -0
- package/dist/server/src/validate.js +197 -0
- package/dist/vitest/src/index.d.ts +27 -5
- package/dist/vitest/src/index.js +18 -5
- package/dist/webdriver/src/driver.d.ts +63 -11
- package/dist/webdriver/src/driver.js +57 -6
- package/dist/webdriver/src/index.d.ts +8 -0
- package/dist/webdriver/src/index.js +1 -0
- package/dist/webdriver/src/protocol.d.ts +75 -6
- package/dist/webdriver/src/protocol.js +79 -6
- package/docs/agents.md +137 -0
- package/docs/deploying.md +345 -0
- package/docs/dogfooding.md +175 -0
- package/docs/extending.md +242 -0
- package/docs/integrating.md +485 -0
- package/docs/reference/bun.md +141 -0
- package/docs/reference/cli.md +148 -0
- package/docs/reference/core.md +2432 -0
- package/docs/reference/detox.md +150 -0
- package/docs/reference/http-api.md +799 -0
- package/docs/reference/playwright.md +303 -0
- package/docs/reference/reports.md +7152 -0
- package/docs/reference/runners.md +169 -0
- package/docs/reference/schema.md +1762 -0
- package/docs/reference/server.md +825 -0
- package/docs/reference/vitest.md +45 -0
- package/docs/reference/webdriver.md +330 -0
- package/docs/report-backends.md +144 -0
- package/docs/report-contract.md +400 -0
- package/docs/reporting-issues.md +160 -0
- package/docs/runners.md +69 -0
- package/docs/server.md +321 -0
- package/docs/statecharts.md +643 -0
- package/llms-full.txt +4114 -0
- package/llms.txt +49 -0
- package/package.json +61 -11
- package/packages/core/README.md +2 -1
- package/packages/reports/README.md +65 -0
- package/packages/reports/STORAGE.md +4 -0
- package/packages/runners/README.md +6 -0
- package/packages/runners/compat/codecept.cjs +2 -0
- package/packages/runners/compat/jest.cjs +2 -0
- package/packages/runners/compat/mocha.cjs +2 -0
- package/packages/schema/README.md +14 -0
- package/packages/schema/schemas/atlas-change.schema.json +4 -0
- package/packages/schema/schemas/check-detail.schema.json +1032 -0
- package/packages/schema/schemas/checks-result.schema.json +26 -0
- package/packages/schema/schemas/command-error.schema.json +35 -0
- package/packages/schema/schemas/doctor-result.schema.json +60 -0
- package/packages/schema/schemas/effective-delta.schema.json +490 -0
- package/packages/schema/schemas/effective-evidence.schema.json +471 -0
- package/packages/schema/schemas/evidence-page.schema.json +271 -0
- package/packages/schema/schemas/hello-event.schema.json +26 -0
- package/packages/schema/schemas/init-plan.schema.json +347 -0
- package/packages/schema/schemas/layer-rows.schema.json +199 -0
- package/packages/schema/schemas/layer.schema.json +2888 -0
- package/packages/schema/schemas/live-run-start.schema.json +42 -0
- package/packages/schema/schemas/live-run-update.schema.json +93 -0
- package/packages/schema/schemas/live-run.schema.json +225 -0
- package/packages/schema/schemas/merge-result.schema.json +40 -0
- package/packages/schema/schemas/outline.schema.json +62 -0
- package/packages/schema/schemas/problem.schema.json +37 -0
- package/packages/schema/schemas/project-slug.schema.json +8 -0
- package/packages/schema/schemas/project.schema.json +22 -0
- package/packages/schema/schemas/projects.schema.json +33 -0
- package/packages/schema/schemas/publish-checks.schema.json +1413 -0
- package/packages/schema/schemas/publish-result.schema.json +168 -0
- package/packages/schema/schemas/ready.schema.json +68 -0
- package/packages/schema/schemas/report-event.schema.json +100 -0
- package/packages/schema/schemas/report-status.schema.json +370 -0
- package/packages/schema/schemas/server-config.schema.json +208 -0
- package/packages/schema/schemas/server-editable-settings.schema.json +132 -0
- package/packages/schema/schemas/server-info.schema.json +114 -0
- package/packages/schema/schemas/server-settings-snapshot.schema.json +285 -0
- package/packages/schema/schemas/spec-set.schema.json +103 -0
- package/packages/schema/schemas/store-listing.schema.json +29 -0
- package/packages/schema/schemas/store-write-result.schema.json +28 -0
- package/packages/schema/schemas/stream-result.schema.json +59 -0
- package/packages/server/README.md +43 -0
- package/packages/server/openapi.json +11021 -0
- package/packages/visualizer/README.md +32 -11
- package/packages/visualizer/bin/atlas-client.mjs +176 -0
- package/packages/visualizer/bin/atlas-visualizer.mjs +2 -175
- package/packages/visualizer/src/app/AppShell.tsx +32 -16
- package/packages/visualizer/src/app/report-sources.tsx +7 -4
- package/packages/visualizer/src/app/router.tsx +3 -3
- package/packages/visualizer/src/components/ActivityIndicator.module.css +4 -0
- package/packages/visualizer/src/components/ActivityIndicator.tsx +6 -0
- package/packages/visualizer/src/components/Header.module.css +101 -192
- package/packages/visualizer/src/components/Header.tsx +88 -13
- package/packages/visualizer/src/components/ProjectPicker.tsx +64 -0
- package/packages/visualizer/src/data/adapters/adapter.ts +5 -17
- package/packages/visualizer/src/data/adapters/index.ts +10 -1
- package/packages/visualizer/src/data/adapters/report-backend.test.ts +3 -2
- package/packages/visualizer/src/data/adapters/report-backend.ts +1 -1
- package/packages/visualizer/src/data/queries.ts +14 -7
- package/packages/visualizer/src/evidence/api.ts +16 -3
- package/packages/visualizer/src/evidence/artifacts.module.css +213 -0
- package/packages/visualizer/src/evidence/artifacts.tsx +272 -0
- package/packages/visualizer/src/evidence/check-detail.tsx +21 -4
- package/packages/visualizer/src/evidence/document-page.tsx +6 -2
- package/packages/visualizer/src/evidence/evidence-context.tsx +79 -12
- package/packages/visualizer/src/evidence/evidence.module.css +11 -1
- package/packages/visualizer/src/evidence/feature-page.tsx +7 -3
- package/packages/visualizer/src/evidence/model-page.tsx +6 -2
- package/packages/visualizer/src/evidence/project-home.tsx +45 -0
- package/packages/visualizer/src/evidence/recording-page.tsx +4 -1
- package/packages/visualizer/src/evidence/spec-index.tsx +2 -1
- package/packages/visualizer/src/evidence/spec-page.tsx +23 -16
- package/packages/visualizer/src/evidence/test-filter.tsx +21 -0
- package/packages/visualizer/src/find/Finder.tsx +1 -7
- package/packages/visualizer/src/find/open.ts +4 -0
- package/packages/visualizer/src/live/live-query.ts +21 -0
- package/packages/visualizer/src/live/live-runs.tsx +69 -0
- package/packages/visualizer/src/live/live-stream.ts +225 -0
- package/packages/visualizer/src/live/report-progress.ts +62 -0
- package/packages/visualizer/src/live/test-progress.tsx +34 -0
- package/packages/visualizer/src/runtime.ts +35 -0
- package/packages/visualizer/src/state/ui-store.ts +3 -2
- package/packages/visualizer/src/views/ChartPage.tsx +2 -0
- package/packages/visualizer/src/views/ChartTests.tsx +43 -0
- package/packages/visualizer/src/views/SidePanel.tsx +2 -0
- package/packages/visualizer/vite/atlas-files.test.ts +4 -4
- package/packages/visualizer/vite/atlas-files.ts +1 -5
- package/packages/visualizer/vite/atlas-server-proxy.ts +37 -0
- package/packages/visualizer/vite.config.ts +11 -3
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Path runner and manifest
|
|
3
|
+
*
|
|
4
|
+
* Runs planned paths against the product through a driver and turns what happened into a run: a
|
|
5
|
+
* directory holding `manifest.json`, `junit.xml`, `ctrf.json`, `chart.mmd`, `outcomes.json` and `media/`.
|
|
6
|
+
* The invariants: the manifest is a pure fold of the plan and the per-path outcomes in plan order (so
|
|
7
|
+
* worker scheduling, sharding and reruns never change the report); it is rewritten atomically after
|
|
8
|
+
* every path so a crash keeps what ran; a failed path is retried once, and passing the second time is
|
|
9
|
+
* `flaky`. This module is the only one that touches the driver and the file system for a run. Its
|
|
10
|
+
* outputs follow the manifest schema in `packages/schema` (`schemaVersion: 1`).
|
|
11
|
+
*
|
|
12
|
+
* @see docs/statecharts.md
|
|
13
|
+
*/
|
|
1
14
|
import { execSync } from 'node:child_process';
|
|
2
15
|
import { createHash } from 'node:crypto';
|
|
3
16
|
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
@@ -25,7 +38,7 @@ function specVersion(bundle) {
|
|
|
25
38
|
const relative = (root, file) => path.relative(root, file).split(path.sep).join('/');
|
|
26
39
|
const pad = (n) => String(n).padStart(2, '0');
|
|
27
40
|
const sameStates = (a, b) => a.length === b.length && [...a].sort().join('|') === [...b].sort().join('|');
|
|
28
|
-
/** A rough cost of a path
|
|
41
|
+
/** A rough cost in milliseconds of a path that has never run (5 s to set up, 2.5 s a step); used to order and shard paths. */
|
|
29
42
|
export const estimateMs = (p) => 5_000 + 2_500 * p.steps.length;
|
|
30
43
|
/**
|
|
31
44
|
* The paths one shard runs: longest first onto the least loaded shard, by the plan alone, so
|
|
@@ -73,7 +86,10 @@ function copyMedia(outcome, from, to) {
|
|
|
73
86
|
cpSync(source, path.join(to, file));
|
|
74
87
|
}
|
|
75
88
|
}
|
|
76
|
-
/**
|
|
89
|
+
/**
|
|
90
|
+
* Reads the `outcomes.json` of a run directory: the run id and each finished path's outcome by key.
|
|
91
|
+
* Returns `null` when the file is missing or unreadable, never throws.
|
|
92
|
+
*/
|
|
77
93
|
export function readOutcomes(runDir) {
|
|
78
94
|
const file = path.join(runDir, 'outcomes.json');
|
|
79
95
|
if (!existsSync(file))
|
|
@@ -86,7 +102,11 @@ export function readOutcomes(runDir) {
|
|
|
86
102
|
return null;
|
|
87
103
|
}
|
|
88
104
|
}
|
|
89
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* Milliseconds each path took, by path key, taken from the newest of the last 10 runs under
|
|
107
|
+
* `outputRoot` that ran it (outcomes reused from elsewhere are ignored). Empty when the directory
|
|
108
|
+
* does not exist. `runPaths` uses it to start the slowest paths first.
|
|
109
|
+
*/
|
|
90
110
|
export function durationHistory(outputRoot) {
|
|
91
111
|
const history = {};
|
|
92
112
|
if (!existsSync(outputRoot))
|
|
@@ -104,7 +124,7 @@ export function durationHistory(outputRoot) {
|
|
|
104
124
|
}
|
|
105
125
|
return history;
|
|
106
126
|
}
|
|
107
|
-
/** The newest run under `outputRoot` that wrote outcomes. */
|
|
127
|
+
/** The directory of the newest run under `outputRoot` that wrote `outcomes.json` (run ids sort by time), or `null`. */
|
|
108
128
|
export function latestRun(outputRoot) {
|
|
109
129
|
if (!existsSync(outputRoot))
|
|
110
130
|
return null;
|
|
@@ -114,8 +134,20 @@ export function latestRun(outputRoot) {
|
|
|
114
134
|
return runs.length ? path.join(outputRoot, runs.at(-1)) : null;
|
|
115
135
|
}
|
|
116
136
|
/**
|
|
117
|
-
*
|
|
118
|
-
* the same manifest, however many workers ran them, in whatever order they finished.
|
|
137
|
+
* Folds path outcomes into the run's manifest object, in plan order: the same plan and outcomes
|
|
138
|
+
* always give the same manifest, however many workers ran them, in whatever order they finished.
|
|
139
|
+
*
|
|
140
|
+
* A state or transition is `passed` when a path reached it, `flaky` when only a flaky path did,
|
|
141
|
+
* `failed` when any failure touched it (a failed check fails its state), else `not-reached`. Reaching a
|
|
142
|
+
* state also marks its ancestors reached. The whole manifest is scanned for privacy findings, and any
|
|
143
|
+
* finding sets `privacy.passed` to false. Reads the chart files (for `specVersion`) and runs `git`
|
|
144
|
+
* in the current directory (for `commit` and `branch`, `null` when that fails). Apart from the
|
|
145
|
+
* wall-clock fields (`finishedAt`, `updatedAt`, `durationMs`) and those that depend on the working
|
|
146
|
+
* directory (`commit`, `branch`, `specDirectory`), two folds of the same outcomes are identical.
|
|
147
|
+
*
|
|
148
|
+
* @param outcomes finished or in-progress outcomes by path key; a path with none is `queued` or `running`.
|
|
149
|
+
* @param run.activePaths ids of the paths in flight; they are reported `running` and not counted complete.
|
|
150
|
+
* @param run.extra extra fields merged into `run` of the manifest (workers, shard, reused, mergedFrom).
|
|
119
151
|
*/
|
|
120
152
|
export function buildManifest(context, paths, outcomes, run) {
|
|
121
153
|
const { implementation: impl, graph, scope } = context;
|
|
@@ -406,7 +438,11 @@ function writeAtomic(file, text) {
|
|
|
406
438
|
writeFileSync(temporary, text);
|
|
407
439
|
renameSync(temporary, file);
|
|
408
440
|
}
|
|
409
|
-
/**
|
|
441
|
+
/**
|
|
442
|
+
* Writes a run's `junit.xml`, `ctrf.json`, `chart.mmd`, `outcomes.json` and `manifest.json` into
|
|
443
|
+
* `runDir` (each through a temporary file and a rename, so a reader never sees half a file) and returns
|
|
444
|
+
* the manifest. `outcomes.json` holds only finished paths, not those in `run.activePaths`.
|
|
445
|
+
*/
|
|
410
446
|
export function writeRun(runDir, context, paths, outcomes, run) {
|
|
411
447
|
const manifest = buildManifest(context, paths, outcomes, run);
|
|
412
448
|
writeAtomic(path.join(runDir, 'junit.xml'), toJUnit(manifest));
|
|
@@ -419,9 +455,14 @@ export function writeRun(runDir, context, paths, outcomes, run) {
|
|
|
419
455
|
}
|
|
420
456
|
const newRunId = (mode) => `${new Date().toISOString().replace(/[:.]/g, '-')}-${mode}`;
|
|
421
457
|
/**
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
* stands on its own. Later
|
|
458
|
+
* Composes the outcomes of several runs of the same plan (the shards of one run, or a rerun and
|
|
459
|
+
* the run before it) into one new run directory under `options.outputRoot`, folded in plan order. Media are
|
|
460
|
+
* copied in, so the merged run stands on its own. Later entries of `runDirs` win when two ran the same
|
|
461
|
+
* path; outcomes whose key is not in `paths` are ignored. The merged run's mode and driver name are
|
|
462
|
+
* taken from the run manifests unless `options` gives them.
|
|
463
|
+
*
|
|
464
|
+
* @returns the new `runDir`, its `manifest`, and `ok` (privacy passed and no path failed).
|
|
465
|
+
* @throws Error when a run directory has no `outcomes.json`.
|
|
425
466
|
*/
|
|
426
467
|
export function mergeRuns(context, paths, runDirs, options) {
|
|
427
468
|
const outcomes = new Map();
|
|
@@ -461,12 +502,22 @@ export function mergeRuns(context, paths, runDirs, options) {
|
|
|
461
502
|
return { runDir, manifest, ok: manifest.privacy.passed && manifest.paths.every((r) => r.status !== 'failed') };
|
|
462
503
|
}
|
|
463
504
|
/**
|
|
464
|
-
*
|
|
465
|
-
*
|
|
466
|
-
*
|
|
467
|
-
*
|
|
468
|
-
*
|
|
469
|
-
*
|
|
505
|
+
* Runs planned paths against a system through a driver and writes the run to
|
|
506
|
+
* `<outputRoot>/<timestamp>-<mode>/`. Paths start from their seed (or the implementation's
|
|
507
|
+
* `setup`) and run on a pool of workers, longest first (by earlier timings, else `estimateMs`),
|
|
508
|
+
* each attempt in its own driver context; every state is recognised from the outside after every step;
|
|
509
|
+
* a failed path is retried once to tell flaky from broken. The manifest is rewritten as each path
|
|
510
|
+
* finishes, from all outcomes so far in plan order, so a crash keeps what ran and the order paths
|
|
511
|
+
* finished in never changes the report.
|
|
512
|
+
*
|
|
513
|
+
* Workers are capped by `driver.concurrency` (1 if the driver gives none) and by the number of paths.
|
|
514
|
+
* Without `implementation.correlate`, a run on several workers reports business events as "no
|
|
515
|
+
* signal" rather than guess which attempt they belong to. While it runs it installs
|
|
516
|
+
* `uncaughtException` and `unhandledRejection` handlers that log and swallow stray errors from
|
|
517
|
+
* drivers, and removes them when it ends. Passed paths of `options.reuse` are kept instead of re-run.
|
|
518
|
+
*
|
|
519
|
+
* @returns the run directory, the final manifest, and `ok` (privacy passed and no path failed).
|
|
520
|
+
* @see docs/statecharts.md
|
|
470
521
|
*/
|
|
471
522
|
export async function runPaths(options) {
|
|
472
523
|
const { driver, implementation: impl, graph, scope } = options;
|
|
@@ -840,7 +891,8 @@ export async function runPaths(options) {
|
|
|
840
891
|
}
|
|
841
892
|
});
|
|
842
893
|
function publish() {
|
|
843
|
-
writeRun(runDir, options, options.paths, new Map([...outcomes, ...activeOutcomes]), runInfo(false));
|
|
894
|
+
const snapshot = writeRun(runDir, options, options.paths, new Map([...outcomes, ...activeOutcomes]), runInfo(false));
|
|
895
|
+
options.onProgress?.(snapshot);
|
|
844
896
|
}
|
|
845
897
|
const history = durationHistory(options.outputRoot);
|
|
846
898
|
const cost = (p) => history[p.key] ?? estimateMs(p);
|
|
@@ -848,7 +900,7 @@ export async function runPaths(options) {
|
|
|
848
900
|
if (reused.size)
|
|
849
901
|
log(`Reusing ${reused.size} passed path(s) from ${path.basename(options.reuse.runDir)}`);
|
|
850
902
|
log(`Running ${queue.length} path(s) on ${workers} worker(s)`);
|
|
851
|
-
writeRun(runDir, options, options.paths, outcomes, runInfo(false));
|
|
903
|
+
options.onProgress?.(writeRun(runDir, options, options.paths, outcomes, runInfo(false)));
|
|
852
904
|
// A driver can throw outside any awaited call (a message for a context that just closed); it
|
|
853
905
|
// belongs to one attempt, so it is logged and the run goes on instead of the process dying.
|
|
854
906
|
const stray = (error) => log(` (ignored an error thrown outside any step: ${(error instanceof Error ? error.message : String(error)).split('\n')[0]})`);
|
|
@@ -873,5 +925,6 @@ export async function runPaths(options) {
|
|
|
873
925
|
await driver.dispose?.();
|
|
874
926
|
await Promise.all(sources.map((s) => s.close?.()));
|
|
875
927
|
const manifest = writeRun(runDir, options, options.paths, outcomes, runInfo(true));
|
|
928
|
+
options.onProgress?.(manifest);
|
|
876
929
|
return { runDir, manifest, ok: manifest.privacy.passed && manifest.paths.every((r) => r.status !== 'failed') };
|
|
877
930
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Clip } from './types.js';
|
|
2
|
+
/** Options for `recordFrames`. */
|
|
2
3
|
export type FrameRecorderOptions = {
|
|
3
4
|
/** The image format `capture` returns (default png). */
|
|
4
5
|
extension?: 'png' | 'jpg';
|
|
@@ -6,10 +7,18 @@ export type FrameRecorderOptions = {
|
|
|
6
7
|
maxFps?: number;
|
|
7
8
|
};
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
10
|
-
* step runs, then encode them with FFmpeg as H.264 MP4 at their real timing. Drivers
|
|
11
|
-
* native screen recorder (Bun WebView, WebDriver) use it; it needs `ffmpeg` on the PATH
|
|
12
|
-
*
|
|
10
|
+
* Starts recording a clip from any driver that can take a screenshot: capture frames back to back
|
|
11
|
+
* while the step runs, then encode them with FFmpeg as H.264 MP4 at their real timing. Drivers
|
|
12
|
+
* without a native screen recorder (Bun WebView, WebDriver) use it; it needs `ffmpeg` on the PATH.
|
|
13
|
+
*
|
|
14
|
+
* Capturing starts immediately and continues until `stop` is called. A `capture` that throws or
|
|
15
|
+
* returns no bytes just skips that frame. The returned `stop(output)` ends capture, writes the MP4 to
|
|
16
|
+
* `output` and resolves to the clip (`durationMs` is wall-clock time since the call), or to `null`
|
|
17
|
+
* when no frame was captured or FFmpeg is missing or fails. It writes no poster. Frames and the
|
|
18
|
+
* FFmpeg frame list are left in `workDirectory`.
|
|
19
|
+
*
|
|
20
|
+
* @param capture returns one screenshot as encoded image bytes in `options.extension`'s format.
|
|
21
|
+
* @param workDirectory where the frames are written; created if missing.
|
|
13
22
|
*/
|
|
14
23
|
export declare function recordFrames(capture: () => Promise<Uint8Array>, workDirectory: string, options?: FrameRecorderOptions): Promise<{
|
|
15
24
|
stop(output: string): Promise<Clip | null>;
|
|
@@ -1,11 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Frame-based clip recorder
|
|
3
|
+
*
|
|
4
|
+
* Builds a video clip for drivers that can only take screenshots: frames are captured repeatedly while
|
|
5
|
+
* a step runs and encoded afterwards with FFmpeg at their real timing. It owns no driver logic; the
|
|
6
|
+
* caller supplies the capture function. A missing or failing `ffmpeg` produces no clip, never an error.
|
|
7
|
+
*/
|
|
1
8
|
import { spawnSync } from 'node:child_process';
|
|
2
9
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
3
10
|
import path from 'node:path';
|
|
4
11
|
/**
|
|
5
|
-
*
|
|
6
|
-
* step runs, then encode them with FFmpeg as H.264 MP4 at their real timing. Drivers
|
|
7
|
-
* native screen recorder (Bun WebView, WebDriver) use it; it needs `ffmpeg` on the PATH
|
|
8
|
-
*
|
|
12
|
+
* Starts recording a clip from any driver that can take a screenshot: capture frames back to back
|
|
13
|
+
* while the step runs, then encode them with FFmpeg as H.264 MP4 at their real timing. Drivers
|
|
14
|
+
* without a native screen recorder (Bun WebView, WebDriver) use it; it needs `ffmpeg` on the PATH.
|
|
15
|
+
*
|
|
16
|
+
* Capturing starts immediately and continues until `stop` is called. A `capture` that throws or
|
|
17
|
+
* returns no bytes just skips that frame. The returned `stop(output)` ends capture, writes the MP4 to
|
|
18
|
+
* `output` and resolves to the clip (`durationMs` is wall-clock time since the call), or to `null`
|
|
19
|
+
* when no frame was captured or FFmpeg is missing or fails. It writes no poster. Frames and the
|
|
20
|
+
* FFmpeg frame list are left in `workDirectory`.
|
|
21
|
+
*
|
|
22
|
+
* @param capture returns one screenshot as encoded image bytes in `options.extension`'s format.
|
|
23
|
+
* @param workDirectory where the frames are written; created if missing.
|
|
9
24
|
*/
|
|
10
25
|
export async function recordFrames(capture, workDirectory, options = {}) {
|
|
11
26
|
mkdirSync(workDirectory, { recursive: true });
|
|
@@ -1,23 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module HTTP driver
|
|
3
|
+
*
|
|
4
|
+
* A driver (and a bare client) for acting on or observing a system over HTTP, with no browser or
|
|
5
|
+
* device: a fetch with a base URL, a cookie jar and timeline logging. It is the fastest driver and
|
|
6
|
+
* runs any number of attempts side by side, each with its own cookie jar. Uses the global `fetch`.
|
|
7
|
+
*/
|
|
1
8
|
import type { Driver, Timeline } from './types.js';
|
|
9
|
+
/** The response of an `HttpClient` call; the body is read in full. */
|
|
2
10
|
export type HttpResponse = {
|
|
3
11
|
status: number;
|
|
12
|
+
/** True for a 2xx status. A non-2xx status does not throw. */
|
|
4
13
|
ok: boolean;
|
|
5
14
|
headers: Headers;
|
|
15
|
+
/** The body as text. */
|
|
6
16
|
text: string;
|
|
17
|
+
/** The body parsed as JSON when the content type is JSON and it parses; `null` otherwise (also for a literal JSON `null`). */
|
|
7
18
|
json: unknown;
|
|
8
19
|
};
|
|
20
|
+
/** Per-call options of an `HttpClient`. */
|
|
9
21
|
export type HttpRequestOptions = {
|
|
22
|
+
/** A string is sent as is; anything else is sent as JSON with a `content-type: application/json` header. */
|
|
10
23
|
body?: unknown;
|
|
24
|
+
/** Added to the client's headers for this call and overriding them (and the cookie header) on conflict. */
|
|
11
25
|
headers?: Record<string, string>;
|
|
12
26
|
/** What the timeline says this call was for; `METHOD /path` when omitted. */
|
|
13
27
|
label?: string;
|
|
14
28
|
};
|
|
15
29
|
/** What every event and recogniser receives from an HTTP client: a fetch with a base URL and a cookie jar. */
|
|
16
30
|
export type HttpClient = {
|
|
31
|
+
/** The URL that request paths are resolved against with `new URL(path, baseUrl)`: a path starting with `/` replaces any path in the base URL. */
|
|
17
32
|
baseUrl: string;
|
|
18
33
|
/** Sent with every request; set an `authorization` header here after signing in. */
|
|
19
34
|
headers: Record<string, string>;
|
|
35
|
+
/** Cookie name to value, stored from `Set-Cookie` responses and sent back on every request. Only the name and value are kept (no expiry, path or domain handling). */
|
|
20
36
|
cookies: Map<string, string>;
|
|
37
|
+
/** Sends a request. Rejects on a network error or timeout; resolves for any HTTP status. Each call adds a `system` entry (method, path, status, never bodies) to the timeline. */
|
|
21
38
|
request(method: string, path: string, options?: HttpRequestOptions): Promise<HttpResponse>;
|
|
22
39
|
get(path: string, options?: HttpRequestOptions): Promise<HttpResponse>;
|
|
23
40
|
post(path: string, body?: unknown, options?: HttpRequestOptions): Promise<HttpResponse>;
|
|
@@ -25,17 +42,33 @@ export type HttpClient = {
|
|
|
25
42
|
patch(path: string, body?: unknown, options?: HttpRequestOptions): Promise<HttpResponse>;
|
|
26
43
|
delete(path: string, options?: HttpRequestOptions): Promise<HttpResponse>;
|
|
27
44
|
};
|
|
45
|
+
/** Options for `httpDriver` and `httpClient`. */
|
|
28
46
|
export type HttpDriverOptions = {
|
|
47
|
+
/** The driver's name and the `source` recorded on timeline entries; `http` when omitted. */
|
|
29
48
|
name?: string;
|
|
49
|
+
/** Headers sent with every request. */
|
|
30
50
|
headers?: Record<string, string>;
|
|
31
51
|
/** Per request, in milliseconds (default 30 000). */
|
|
32
52
|
timeoutMs?: number;
|
|
33
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* Creates an `HttpClient` that logs each call to `timeline`. Use it directly when you need a client
|
|
56
|
+
* outside a driver; `httpDriver` creates one per path attempt.
|
|
57
|
+
*
|
|
58
|
+
* @param baseUrl the URL request paths are resolved against.
|
|
59
|
+
*/
|
|
34
60
|
export declare function httpClient(baseUrl: string, timeline: Timeline, options?: HttpDriverOptions): HttpClient;
|
|
35
61
|
/**
|
|
36
62
|
* The fastest driver: a client of an HTTP API, with no browser or device. Use it for actors that
|
|
37
63
|
* only talk to the system (a partner webhook, an operator API, a back office job) or to drive a
|
|
38
64
|
* whole chart at the API level. Each path attempt gets a fresh cookie jar; every call is logged to
|
|
39
|
-
* the step's timeline (method, path, status; never bodies).
|
|
65
|
+
* the step's timeline (method, path, status; never bodies). Has no screenshots or clips, and no limit on
|
|
66
|
+
* concurrent attempts.
|
|
67
|
+
*
|
|
68
|
+
* @param baseUrl the API's base URL, or a function returning it (called for each attempt).
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* const driver = httpDriver('http://127.0.0.1:4000', { headers: { 'x-test': '1' } });
|
|
72
|
+
* // events then receive an HttpClient: run: (api) => api.post('/orders', { sku: 'A1' })
|
|
40
73
|
*/
|
|
41
74
|
export declare function httpDriver(baseUrl: string | (() => string | Promise<string>), options?: HttpDriverOptions): Driver<HttpClient>;
|
|
@@ -8,6 +8,12 @@ function parseBody(text, contentType) {
|
|
|
8
8
|
return null;
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Creates an `HttpClient` that logs each call to `timeline`. Use it directly when you need a client
|
|
13
|
+
* outside a driver; `httpDriver` creates one per path attempt.
|
|
14
|
+
*
|
|
15
|
+
* @param baseUrl the URL request paths are resolved against.
|
|
16
|
+
*/
|
|
11
17
|
export function httpClient(baseUrl, timeline, options = {}) {
|
|
12
18
|
const source = options.name ?? 'http';
|
|
13
19
|
const client = {
|
|
@@ -55,7 +61,14 @@ export function httpClient(baseUrl, timeline, options = {}) {
|
|
|
55
61
|
* The fastest driver: a client of an HTTP API, with no browser or device. Use it for actors that
|
|
56
62
|
* only talk to the system (a partner webhook, an operator API, a back office job) or to drive a
|
|
57
63
|
* whole chart at the API level. Each path attempt gets a fresh cookie jar; every call is logged to
|
|
58
|
-
* the step's timeline (method, path, status; never bodies).
|
|
64
|
+
* the step's timeline (method, path, status; never bodies). Has no screenshots or clips, and no limit on
|
|
65
|
+
* concurrent attempts.
|
|
66
|
+
*
|
|
67
|
+
* @param baseUrl the API's base URL, or a function returning it (called for each attempt).
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* const driver = httpDriver('http://127.0.0.1:4000', { headers: { 'x-test': '1' } });
|
|
71
|
+
* // events then receive an HttpClient: run: (api) => api.post('/orders', { sku: 'A1' })
|
|
59
72
|
*/
|
|
60
73
|
export function httpDriver(baseUrl, options = {}) {
|
|
61
74
|
return {
|
|
@@ -1,29 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Run types and function driver
|
|
3
|
+
*
|
|
4
|
+
* The contracts between a run and the things it drives: `Driver` (owns the system under test for one
|
|
5
|
+
* path attempt), `Implementation` (how each event is made to happen and each state recognised), `Seed`,
|
|
6
|
+
* event sources and privacy rules, plus the status vocabulary and `Timeline`. These shapes are what
|
|
7
|
+
* integrators write against, so they change only by adding optional fields. Also holds
|
|
8
|
+
* `functionDriver`, the simplest complete driver.
|
|
9
|
+
*
|
|
10
|
+
* @see docs/statecharts.md
|
|
11
|
+
*/
|
|
1
12
|
import type { CloudEvent } from '../events/cloudevents.js';
|
|
2
13
|
import type { StateValue } from '../graph.js';
|
|
3
14
|
import type { PlannedPath } from '../plan.js';
|
|
15
|
+
/** `fast` runs without pacing and takes screenshots only; `showcase` paces steps like a person and records a clip of every transition. */
|
|
4
16
|
export type RunMode = 'fast' | 'showcase';
|
|
17
|
+
/** The result of a path, state, transition or journey: `flaky` passed only on the retry; `not-reached` was never run, was blocked, or has no covering path. */
|
|
5
18
|
export type Status = 'passed' | 'failed' | 'flaky' | 'not-reached';
|
|
19
|
+
/** Who or what makes an event happen: a `user` action, a `system` call, the passing of `time`, or a `hand-off` from a child chart (inserted by the planner, usually a no-op). */
|
|
6
20
|
export type EventKind = 'user' | 'system' | 'time' | 'hand-off';
|
|
7
21
|
/** `client` names which client's screen it is in a multi-client run; `also` holds the other clients' screens at the same moment. */
|
|
8
22
|
export type Image = {
|
|
23
|
+
/** Path to the PNG. A driver returns an absolute path; in a manifest it is relative to the run directory. */
|
|
9
24
|
png: string;
|
|
25
|
+
/** Path to a WebP thumbnail, when one was made. */
|
|
10
26
|
webp?: string;
|
|
11
27
|
client?: string;
|
|
12
28
|
also?: Image[];
|
|
13
29
|
};
|
|
30
|
+
/** A recorded video of one transition. */
|
|
14
31
|
export type Clip = {
|
|
32
|
+
/** Path to the MP4 (relative to the run directory in a manifest). */
|
|
15
33
|
video: string;
|
|
34
|
+
/** Path to a poster image, when one was made. */
|
|
16
35
|
poster?: string;
|
|
17
36
|
durationMs: number;
|
|
37
|
+
/** The client whose screen was recorded, in a multi-client run. */
|
|
18
38
|
client?: string;
|
|
19
39
|
};
|
|
40
|
+
/** One thing that happened during a step, which becomes a caption on the step's clip. */
|
|
20
41
|
export type TimelineEntry = {
|
|
42
|
+
/** Milliseconds since the step began (see `Timeline.restart`). */
|
|
21
43
|
atMs: number;
|
|
44
|
+
/** What kind of thing happened. */
|
|
22
45
|
kind: 'tap' | 'type' | 'system' | 'screen' | 'toast' | 'note' | 'time' | 'notification';
|
|
46
|
+
/** The caption. */
|
|
23
47
|
label: string;
|
|
48
|
+
/** For a tap, where it landed (page pixels for the Playwright driver). */
|
|
24
49
|
x?: number;
|
|
25
50
|
y?: number;
|
|
51
|
+
/** For typing, what was typed; shown in the caption. */
|
|
26
52
|
text?: string;
|
|
53
|
+
/** For a system call, what it was (never bodies). */
|
|
27
54
|
system?: {
|
|
28
55
|
source?: string;
|
|
29
56
|
endpoint?: string;
|
|
@@ -40,26 +67,37 @@ type TimelineState = {
|
|
|
40
67
|
};
|
|
41
68
|
/**
|
|
42
69
|
* What a step records while it runs: taps, typing, system calls, the screen change. `for(client)`
|
|
43
|
-
* returns a view onto the same timeline that tags every entry with that client.
|
|
70
|
+
* returns a view onto the same timeline that tags every entry with that client. The runner restarts
|
|
71
|
+
* it at the start of every step, so entries are relative to the step they belong to.
|
|
44
72
|
*/
|
|
45
73
|
export declare class Timeline {
|
|
46
74
|
private readonly state;
|
|
75
|
+
/** The client this view tags entries with; `undefined` on the root timeline. */
|
|
47
76
|
readonly client: string | undefined;
|
|
48
77
|
constructor(state?: TimelineState, client?: string);
|
|
78
|
+
/** Every entry added since the last `restart`, in order, shared by all views of the timeline. */
|
|
49
79
|
get entries(): TimelineEntry[];
|
|
80
|
+
/** Clears the entries and makes now the origin for `atMs`. */
|
|
50
81
|
restart(): void;
|
|
82
|
+
/** Appends an entry stamped with the time since the origin, and with this view's client when it has one. */
|
|
51
83
|
add(entry: Omit<TimelineEntry, 'atMs'>): void;
|
|
84
|
+
/** A view of the same timeline that tags what it adds with `client`. */
|
|
52
85
|
for(client: string): Timeline;
|
|
53
86
|
}
|
|
87
|
+
/** What a state recogniser found. */
|
|
54
88
|
export type RecognizerResult = {
|
|
89
|
+
/** Whether the state is showing. */
|
|
55
90
|
matched: boolean;
|
|
91
|
+
/** What was looked at, shown to explain a failure. `expected` is `visible` or `hidden`. */
|
|
56
92
|
signals: {
|
|
57
93
|
signal: string;
|
|
58
94
|
expected: 'visible' | 'hidden' | string;
|
|
59
95
|
visible: boolean;
|
|
60
96
|
}[];
|
|
61
97
|
};
|
|
98
|
+
/** The result of one of a state's `meta.checks`; a failed check fails the state. */
|
|
62
99
|
export type CheckResult = {
|
|
100
|
+
/** The check statement, quoted from the spec. */
|
|
63
101
|
check: string;
|
|
64
102
|
passed: boolean;
|
|
65
103
|
expected?: string;
|
|
@@ -87,7 +125,12 @@ export type Focus = {
|
|
|
87
125
|
* required; media hooks are used when present. `combineDrivers` runs several at once.
|
|
88
126
|
*/
|
|
89
127
|
export type Driver<C> = {
|
|
128
|
+
/** Shown in reports as the driver the run used. */
|
|
90
129
|
name: string;
|
|
130
|
+
/**
|
|
131
|
+
* Creates a fresh context for one attempt of one path. `attempt` is 1, or 2 for the retry;
|
|
132
|
+
* `directory` is a scratch directory for this attempt; the driver should log what it does to `timeline`.
|
|
133
|
+
*/
|
|
91
134
|
open(input: {
|
|
92
135
|
path: PlannedPath;
|
|
93
136
|
attempt: number;
|
|
@@ -95,6 +138,10 @@ export type Driver<C> = {
|
|
|
95
138
|
directory: string;
|
|
96
139
|
timeline: Timeline;
|
|
97
140
|
}): Promise<C>;
|
|
141
|
+
/**
|
|
142
|
+
* Releases the context. `traceFile` is where a trace may be written when `failed`; return the path
|
|
143
|
+
* actually written as `trace` (or one per client as `traces`) to attach it to the report.
|
|
144
|
+
*/
|
|
98
145
|
close(ctx: C, input: {
|
|
99
146
|
failed: boolean;
|
|
100
147
|
traceFile: string;
|
|
@@ -124,10 +171,12 @@ export type Driver<C> = {
|
|
|
124
171
|
* single device is not). Runs use at most this many workers; 1 when omitted.
|
|
125
172
|
*/
|
|
126
173
|
concurrency?: number;
|
|
174
|
+
/** Releases what is shared by all contexts (a browser, a server); called once after the last path. */
|
|
127
175
|
dispose?(): Promise<void>;
|
|
128
176
|
};
|
|
129
177
|
/** How one event is made to happen. Keyed by the event's exact name in the spec. */
|
|
130
178
|
export type EventImplementation<C> = {
|
|
179
|
+
/** Who makes the event happen; recorded with the event in the manifest. */
|
|
131
180
|
kind: EventKind;
|
|
132
181
|
/** The client that does it, in a multi-client run: its screen is recorded for the clip. */
|
|
133
182
|
client?: string;
|
|
@@ -135,12 +184,14 @@ export type EventImplementation<C> = {
|
|
|
135
184
|
how: string;
|
|
136
185
|
/** Test data the event needs before the path starts (a condition modelled as an event). */
|
|
137
186
|
prepare?(ctx: C, tools: StepTools): Promise<void>;
|
|
187
|
+
/** Makes the event happen. The runner then waits for the target state to be recognised, so `run` does not need to wait for it; it should throw only when the event cannot be done. */
|
|
138
188
|
run(ctx: C, tools: StepTools): Promise<void>;
|
|
139
189
|
/** Set when the event cannot be triggered reliably yet: the path stops before it. */
|
|
140
190
|
blocked?: string;
|
|
141
191
|
};
|
|
142
192
|
/** How one state is recognised from the outside, and what is checked there. */
|
|
143
193
|
export type StateImplementation<C> = {
|
|
194
|
+
/** Looks at the system from the outside and says whether the state is showing. Called repeatedly (with growing pauses, until `stepTimeoutMs`) while the runner waits for the state, so it must be cheap and side-effect free. */
|
|
144
195
|
recognize(ctx: C): Promise<RecognizerResult>;
|
|
145
196
|
/** The client whose screen shows it, in a multi-client run: that screen is the screenshot. */
|
|
146
197
|
client?: string;
|
|
@@ -152,6 +203,7 @@ export type StateImplementation<C> = {
|
|
|
152
203
|
lookIn?(ctx: C): Promise<void>;
|
|
153
204
|
/** Within-state actions after the screenshot (dismiss a success message). */
|
|
154
205
|
settle?(ctx: C): Promise<void>;
|
|
206
|
+
/** Results for the state's `meta.checks`, evaluated once the state is recognised. */
|
|
155
207
|
checks?(ctx: C): Promise<CheckResult[]>;
|
|
156
208
|
};
|
|
157
209
|
/**
|
|
@@ -184,8 +236,15 @@ export type Seed<C> = {
|
|
|
184
236
|
/** Set when the configuration cannot be seeded yet: paths start from another seed. */
|
|
185
237
|
blocked?: string;
|
|
186
238
|
};
|
|
239
|
+
/**
|
|
240
|
+
* How a driver makes each event of the spec happen and recognises each state: everything the charts
|
|
241
|
+
* do not say. Events and states are keyed by their exact names in the spec; only leaf states need a
|
|
242
|
+
* recogniser.
|
|
243
|
+
*/
|
|
187
244
|
export type Implementation<C> = {
|
|
245
|
+
/** By event name; an event with no entry is reported as blocked. */
|
|
188
246
|
events: Record<string, EventImplementation<C>>;
|
|
247
|
+
/** By state name; only leaf states need one. */
|
|
189
248
|
states: Record<string, StateImplementation<C>>;
|
|
190
249
|
/**
|
|
191
250
|
* Named seeds: configurations paths can start from. Journeys are cut at every seeded
|
|
@@ -207,13 +266,16 @@ export type Implementation<C> = {
|
|
|
207
266
|
};
|
|
208
267
|
/** Where business events come from: a CloudEvents endpoint, a log, a message queue. */
|
|
209
268
|
export type EventSource = {
|
|
269
|
+
/** Identifies the source; a name starting with `log:` marks a log file, anything else counts as a CloudEvents source. */
|
|
210
270
|
name: string;
|
|
211
271
|
/** Where "now" is in the source; `collect` returns what arrived after this mark. */
|
|
212
272
|
mark(): unknown;
|
|
273
|
+
/** Returns what arrived since `mark` (everything when omitted): CloudEvents and/or raw log text. */
|
|
213
274
|
collect(mark?: unknown): Promise<{
|
|
214
275
|
events: CloudEvent[];
|
|
215
276
|
text?: string;
|
|
216
277
|
}>;
|
|
278
|
+
/** Releases the source; called when the run ends. */
|
|
217
279
|
close?(): Promise<void>;
|
|
218
280
|
};
|
|
219
281
|
/** How a business event named in `meta.events` is recognised in what a source collected. */
|
|
@@ -222,6 +284,7 @@ export type EventMatcher = {
|
|
|
222
284
|
} | {
|
|
223
285
|
pattern: RegExp;
|
|
224
286
|
} | ((events: CloudEvent[], text: string) => boolean);
|
|
287
|
+
/** Rules for what may appear on screen and in a manifest. They extend the defaults (see `DEFAULT_PRIVACY`) except `allowHosts`, which replaces them. */
|
|
225
288
|
export type PrivacyRules = {
|
|
226
289
|
/** Patterns that must never appear on screen or in a manifest. */
|
|
227
290
|
deny?: {
|
|
@@ -234,8 +297,18 @@ export type PrivacyRules = {
|
|
|
234
297
|
allowHosts?: string[];
|
|
235
298
|
};
|
|
236
299
|
/**
|
|
237
|
-
* A driver for anything you can reach from
|
|
238
|
-
* returns a fresh context per path attempt; recognisers and events work on it directly.
|
|
300
|
+
* A driver for anything you can reach from code: a domain model, an API client, a CLI. `create`
|
|
301
|
+
* returns a fresh context per path attempt; recognisers and events work on it directly. It takes
|
|
302
|
+
* no screenshots or clips, and any number of attempts can run side by side unless `concurrency` says otherwise.
|
|
303
|
+
*
|
|
304
|
+
* @param create builds the context for one attempt; it may be async.
|
|
305
|
+
* @param options.name the driver's name in reports; `function` when omitted.
|
|
306
|
+
* @param options.dispose called with the context when the attempt ends.
|
|
307
|
+
* @param options.concurrency how many contexts may be open at once; unlimited when omitted.
|
|
308
|
+
* @see docs/statecharts.md
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* const driver = functionDriver(() => new TodoModel());
|
|
239
312
|
*/
|
|
240
313
|
export declare function functionDriver<C>(create: (input: {
|
|
241
314
|
path: PlannedPath;
|
|
@@ -1,31 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What a step records while it runs: taps, typing, system calls, the screen change. `for(client)`
|
|
3
|
-
* returns a view onto the same timeline that tags every entry with that client.
|
|
3
|
+
* returns a view onto the same timeline that tags every entry with that client. The runner restarts
|
|
4
|
+
* it at the start of every step, so entries are relative to the step they belong to.
|
|
4
5
|
*/
|
|
5
6
|
export class Timeline {
|
|
6
7
|
state;
|
|
8
|
+
/** The client this view tags entries with; `undefined` on the root timeline. */
|
|
7
9
|
client;
|
|
8
10
|
constructor(state = { origin: Date.now(), entries: [] }, client) {
|
|
9
11
|
this.state = state;
|
|
10
12
|
this.client = client;
|
|
11
13
|
}
|
|
14
|
+
/** Every entry added since the last `restart`, in order, shared by all views of the timeline. */
|
|
12
15
|
get entries() {
|
|
13
16
|
return this.state.entries;
|
|
14
17
|
}
|
|
18
|
+
/** Clears the entries and makes now the origin for `atMs`. */
|
|
15
19
|
restart() {
|
|
16
20
|
this.state.origin = Date.now();
|
|
17
21
|
this.state.entries = [];
|
|
18
22
|
}
|
|
23
|
+
/** Appends an entry stamped with the time since the origin, and with this view's client when it has one. */
|
|
19
24
|
add(entry) {
|
|
20
25
|
this.state.entries.push({ atMs: Date.now() - this.state.origin, ...(this.client ? { client: this.client } : {}), ...entry });
|
|
21
26
|
}
|
|
27
|
+
/** A view of the same timeline that tags what it adds with `client`. */
|
|
22
28
|
for(client) {
|
|
23
29
|
return new Timeline(this.state, client);
|
|
24
30
|
}
|
|
25
31
|
}
|
|
26
32
|
/**
|
|
27
|
-
* A driver for anything you can reach from
|
|
28
|
-
* returns a fresh context per path attempt; recognisers and events work on it directly.
|
|
33
|
+
* A driver for anything you can reach from code: a domain model, an API client, a CLI. `create`
|
|
34
|
+
* returns a fresh context per path attempt; recognisers and events work on it directly. It takes
|
|
35
|
+
* no screenshots or clips, and any number of attempts can run side by side unless `concurrency` says otherwise.
|
|
36
|
+
*
|
|
37
|
+
* @param create builds the context for one attempt; it may be async.
|
|
38
|
+
* @param options.name the driver's name in reports; `function` when omitted.
|
|
39
|
+
* @param options.dispose called with the context when the attempt ends.
|
|
40
|
+
* @param options.concurrency how many contexts may be open at once; unlimited when omitted.
|
|
41
|
+
* @see docs/statecharts.md
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const driver = functionDriver(() => new TodoModel());
|
|
29
45
|
*/
|
|
30
46
|
export function functionDriver(create, options = {}) {
|
|
31
47
|
return {
|