@crvouga/atlas 0.3.0 → 0.5.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 +12 -0
- package/README.md +178 -604
- 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 +30 -1
- package/dist/core/src/config.d.ts +70 -4
- package/dist/core/src/config.js +62 -3
- 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 +69 -16
- package/dist/core/src/run/execute.js +67 -16
- 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 +83 -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 +40 -4
- package/dist/reports/src/evidence-live.js +52 -4
- package/dist/reports/src/evidence-types.d.ts +70 -1
- package/dist/reports/src/evidence-types.js +5 -1
- package/dist/reports/src/evidence-view.d.ts +58 -6
- package/dist/reports/src/evidence-view.js +44 -5
- 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 +52 -0
- package/dist/reports/src/http-store.js +145 -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 +364 -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 +48 -0
- package/dist/reports/src/layer-axes.js +71 -0
- package/dist/reports/src/layer-catalog.d.ts +28 -1
- package/dist/reports/src/layer-catalog.js +19 -2
- package/dist/reports/src/layer-merge.d.ts +98 -9
- package/dist/reports/src/layer-merge.js +154 -33
- package/dist/reports/src/layer-source.d.ts +18 -0
- package/dist/reports/src/layer-source.js +14 -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 +34 -1
- package/dist/reports/src/layers-route.d.ts +47 -4
- package/dist/reports/src/layers-route.js +88 -12
- 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 +30 -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 +44 -9
- package/dist/reports/src/publish.js +40 -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 +485 -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 +40 -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/schema/src/api.d.ts +799 -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 +14 -0
- package/dist/schema/src/index.js +14 -0
- package/dist/schema/src/layer.d.ts +2273 -0
- package/dist/schema/src/layer.js +462 -0
- package/dist/schema/src/manifest.d.ts +95 -0
- package/dist/schema/src/manifest.js +84 -0
- package/dist/schema/src/report-sources.d.ts +28 -0
- package/dist/schema/src/report-sources.js +25 -0
- package/dist/schema/src/runs-index.d.ts +16 -0
- package/dist/schema/src/runs-index.js +15 -0
- package/dist/schema/src/server-config.d.ts +93 -0
- package/dist/schema/src/server-config.js +73 -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 +62 -0
- package/dist/server/src/app.js +419 -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 +187 -0
- package/dist/server/src/dev.d.ts +12 -0
- package/dist/server/src/dev.js +80 -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 +16 -0
- package/dist/server/src/index.js +16 -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 +112 -0
- package/dist/server/src/openapi.d.ts +59 -0
- package/dist/server/src/openapi.js +374 -0
- package/dist/server/src/problem.d.ts +8 -0
- package/dist/server/src/problem.js +39 -0
- package/dist/server/src/site.d.ts +13 -0
- package/dist/server/src/site.js +43 -0
- package/dist/server/src/store-api.d.ts +28 -0
- package/dist/server/src/store-api.js +159 -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 +146 -0
- package/docs/reference/core.md +2426 -0
- package/docs/reference/detox.md +150 -0
- package/docs/reference/http-api.md +303 -0
- package/docs/reference/playwright.md +303 -0
- package/docs/reference/reports.md +7098 -0
- package/docs/reference/schema.md +1651 -0
- package/docs/reference/server.md +685 -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/server.md +285 -0
- package/docs/statecharts.md +641 -0
- package/llms-full.txt +3506 -0
- package/llms.txt +47 -0
- package/package.json +33 -5
- package/packages/core/README.md +2 -1
- package/packages/reports/README.md +69 -2
- package/packages/reports/STORAGE.md +5 -1
- package/packages/schema/README.md +14 -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 +22 -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/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/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 +194 -0
- package/packages/schema/schemas/server-info.schema.json +114 -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 +6699 -0
- package/packages/visualizer/README.md +27 -5
- package/packages/visualizer/example/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/example/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/example/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/example/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/example/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/example/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/scripts/make-fixtures.ts +2 -0
- package/packages/visualizer/scripts/publish-data.ts +1 -2
- package/packages/visualizer/src/app/AppShell.tsx +9 -2
- package/packages/visualizer/src/app/report-sources.tsx +6 -4
- package/packages/visualizer/src/app/router.tsx +3 -3
- package/packages/visualizer/src/components/Header.module.css +35 -0
- package/packages/visualizer/src/components/Header.tsx +41 -4
- package/packages/visualizer/src/data/adapters/adapter.ts +5 -17
- package/packages/visualizer/src/data/adapters/index.ts +10 -1
- package/packages/visualizer/src/evidence/api.ts +14 -2
- 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 +38 -4
- package/packages/visualizer/src/evidence/evidence-context.tsx +74 -12
- package/packages/visualizer/src/evidence/evidence.module.css +20 -0
- package/packages/visualizer/src/evidence/marks.tsx +2 -0
- package/packages/visualizer/src/evidence/overview.tsx +3 -3
- package/packages/visualizer/src/evidence/sources-panel.tsx +88 -34
- package/packages/visualizer/src/evidence/spec-page.tsx +23 -16
- package/packages/visualizer/src/find/Finder.tsx +1 -7
- package/packages/visualizer/src/find/open.ts +4 -0
- package/packages/visualizer/src/live/live-stream.ts +204 -0
- package/packages/visualizer/src/runtime.ts +19 -0
- package/packages/visualizer/src/state/ui-store.ts +6 -2
- package/packages/visualizer/vite/atlas-server-proxy.ts +36 -0
- package/packages/visualizer/vite/example-reports.ts +475 -0
- package/packages/visualizer/vite.config.ts +18 -2
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module The report gate
|
|
3
|
+
*
|
|
4
|
+
* Reduces a combined report to a pass or fail with reasons (`reportStatus`) and renders that result as text or
|
|
5
|
+
* markdown. The JSON of `ReportStatus` is a stable contract (`schemaVersion` 1); the text and markdown are for people
|
|
6
|
+
* and are not. The gate fails closed: no checks, a failed or still-running check, an unreadable layer or backend
|
|
7
|
+
* (a notice), a missing required environment, or (when asked) a spec with no result each make it fail.
|
|
8
|
+
*
|
|
9
|
+
* @see docs/report-contract.md#statuses-and-the-gate
|
|
10
|
+
*/
|
|
11
|
+
import { REPORT_STATUS_SCHEMA_VERSION } from '../../schema/src/layer.js';
|
|
12
|
+
import { countResults } from './compose.js';
|
|
13
|
+
import { normalizeEnvironment } from './layer-axes.js';
|
|
14
|
+
import { isDiagnosticKind } from './layer-merge.js';
|
|
15
|
+
/** Total string order by code unit. */
|
|
16
|
+
const compare = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
17
|
+
/**
|
|
18
|
+
* The gate. A report passes only when it has checks, none failed, none is still running, every
|
|
19
|
+
* layer and backend could be read, and every required environment is present. Unknown never passes.
|
|
20
|
+
*
|
|
21
|
+
* Returns a `ReportStatus` (schemaVersion 1): `ok` is true exactly when `reasons` is empty. Reasons name, in order: no
|
|
22
|
+
* checks (or none completed), failed checks, running checks, specs without results (only with `requireAllSpecs`),
|
|
23
|
+
* each required environment with no checks (the name is normalised, so `iOS` means `ios`), then every notice of the
|
|
24
|
+
* report (an unreadable layer or backend). `failing` lists each failed or running winner, in a fixed order that does
|
|
25
|
+
* not depend on read order; diagnostic `performance` checks are left out unless nothing else was recorded.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* const status = reportStatus(await combineBackends(backends), { requireEnvironments: ['ios'] });
|
|
29
|
+
* if (!status.ok) throw new Error(status.reasons.join('\n'));
|
|
30
|
+
*
|
|
31
|
+
* @see docs/report-contract.md#atlas-report-status
|
|
32
|
+
*/
|
|
33
|
+
export function reportStatus(report, options = {}) {
|
|
34
|
+
const results = [...report.combined.specs.flatMap(entry => entry.results), ...report.combined.unassigned];
|
|
35
|
+
const outcomes = results.some(result => !isDiagnosticKind(result.row.kind)) ? results.filter(result => !isDiagnosticKind(result.row.kind)) : results;
|
|
36
|
+
const failing = outcomes
|
|
37
|
+
.filter(result => result.row.status === 'failed' || result.row.status === 'running')
|
|
38
|
+
.map(result => ({
|
|
39
|
+
spec: result.row.specPath,
|
|
40
|
+
check: result.row.id,
|
|
41
|
+
title: result.row.title,
|
|
42
|
+
status: result.row.status,
|
|
43
|
+
environment: result.layer.environment,
|
|
44
|
+
producer: result.layer.producer,
|
|
45
|
+
branch: result.layer.branch,
|
|
46
|
+
sha: result.layer.sha,
|
|
47
|
+
layer: result.layer.id,
|
|
48
|
+
backend: result.layer.backend,
|
|
49
|
+
runUrl: result.layer.runUrl,
|
|
50
|
+
detail: result.row.detail
|
|
51
|
+
}))
|
|
52
|
+
// Total, so the output never depends on the order layers were read in.
|
|
53
|
+
.sort((a, b) => compare(a.spec ?? '', b.spec ?? '') || compare(a.check, b.check) || compare(a.environment ?? '', b.environment ?? '') || compare(a.producer, b.producer) || compare(a.layer, b.layer) || compare(a.backend, b.backend));
|
|
54
|
+
const reasons = [];
|
|
55
|
+
const { checks, byEnvironment } = report.summary;
|
|
56
|
+
if (checks.total === 0)
|
|
57
|
+
reasons.push('No checks were found in any layer');
|
|
58
|
+
else if (checks.passed + checks.failed === 0 && checks.running === 0)
|
|
59
|
+
reasons.push('No check has completed: everything was skipped or not run');
|
|
60
|
+
if (checks.failed)
|
|
61
|
+
reasons.push(`${checks.failed} check${checks.failed === 1 ? '' : 's'} failed`);
|
|
62
|
+
if (checks.running)
|
|
63
|
+
reasons.push(`${checks.running} check${checks.running === 1 ? ' is' : 's are'} still running`);
|
|
64
|
+
const missing = report.summary.specsWithoutEvidence;
|
|
65
|
+
if (options.requireAllSpecs && missing.length)
|
|
66
|
+
reasons.push(`${missing.length} spec${missing.length === 1 ? ' has' : 's have'} no results: ${missing.slice(0, 5).join(', ')}${missing.length > 5 ? ', …' : ''}`);
|
|
67
|
+
for (const required of options.requireEnvironments ?? []) {
|
|
68
|
+
// Layers carry normalized environments (`iOS` is `ios`), so a requirement is normalized the same way.
|
|
69
|
+
const environment = normalizeEnvironment(required) ?? required;
|
|
70
|
+
if (!byEnvironment[environment]?.total)
|
|
71
|
+
reasons.push(`No checks on required environment "${required}"`);
|
|
72
|
+
}
|
|
73
|
+
reasons.push(...report.notices);
|
|
74
|
+
return {
|
|
75
|
+
schemaVersion: REPORT_STATUS_SCHEMA_VERSION,
|
|
76
|
+
ok: reasons.length === 0,
|
|
77
|
+
reasons,
|
|
78
|
+
checks,
|
|
79
|
+
byEnvironment: { ...byEnvironment },
|
|
80
|
+
layers: report.layers.length,
|
|
81
|
+
backends: report.backends.map(({ id, label, available }) => ({ id, label, available })),
|
|
82
|
+
notices: [...report.notices],
|
|
83
|
+
specsWithoutEvidence: [...missing],
|
|
84
|
+
specs: [
|
|
85
|
+
...report.combined.specs.map(entry => specOutcome(entry.spec.path, entry.spec.title, entry.spec.kind, entry.results)),
|
|
86
|
+
...(report.combined.unassigned.length ? [specOutcome(null, 'No spec', 'unassigned', report.combined.unassigned)] : [])
|
|
87
|
+
],
|
|
88
|
+
failing
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/** `spec › check`, unless the check's id already starts with its spec. */
|
|
92
|
+
const label = (item) => (item.spec && !item.check.startsWith(item.spec) ? `${item.spec} › ${item.check}` : item.check);
|
|
93
|
+
function specOutcome(path, title, kind, results) {
|
|
94
|
+
const environments = new Map();
|
|
95
|
+
for (const result of results) {
|
|
96
|
+
const key = result.layer.environment ?? 'unspecified';
|
|
97
|
+
const group = environments.get(key);
|
|
98
|
+
if (group)
|
|
99
|
+
group.push(result);
|
|
100
|
+
else
|
|
101
|
+
environments.set(key, [result]);
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
path, title, kind,
|
|
105
|
+
checks: countResults(results),
|
|
106
|
+
byEnvironment: Object.fromEntries([...environments].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)).map(([key, value]) => [key, countResults(value)]))
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const row = (counts) => `${counts.passed} passed, ${counts.failed} failed, ${counts.running} running, ${counts['not-run']} not run`;
|
|
110
|
+
/**
|
|
111
|
+
* Renders a status as plain text for a terminal: a `PASS`/`FAIL` line with totals, one line per environment, each
|
|
112
|
+
* failing or running check with where it ran and the first line of its detail, notices, reasons and specs with no
|
|
113
|
+
* results. Ends with a newline. For people: the layout is not a contract; parse `--format json` instead.
|
|
114
|
+
*/
|
|
115
|
+
export function formatStatusText(status) {
|
|
116
|
+
const lines = [`${status.ok ? 'PASS' : 'FAIL'} ${row(status.checks)} — ${status.layers} layer${status.layers === 1 ? '' : 's'} from ${status.backends.length} backend${status.backends.length === 1 ? '' : 's'}`];
|
|
117
|
+
for (const [environment, counts] of Object.entries(status.byEnvironment))
|
|
118
|
+
lines.push(` ${environment.padEnd(14)} ${row(counts)}`);
|
|
119
|
+
for (const item of status.failing) {
|
|
120
|
+
const where = [item.environment, item.branch, item.sha?.slice(0, 7)].filter(Boolean).join(' · ');
|
|
121
|
+
lines.push(`✗ ${item.status === 'running' ? '[running] ' : ''}${label(item)}${where ? ` (${where})` : ''}${item.detail ? `\n ${item.detail.split('\n')[0]}` : ''}`);
|
|
122
|
+
}
|
|
123
|
+
for (const notice of status.notices)
|
|
124
|
+
lines.push(`! ${notice}`);
|
|
125
|
+
for (const reason of status.reasons)
|
|
126
|
+
if (!status.notices.includes(reason))
|
|
127
|
+
lines.push(`→ ${reason}`);
|
|
128
|
+
if (status.specsWithoutEvidence.length && !status.reasons.some(reason => reason.includes('no results')))
|
|
129
|
+
lines.push(`· ${status.specsWithoutEvidence.length} spec${status.specsWithoutEvidence.length === 1 ? '' : 's'} with no results yet: ${status.specsWithoutEvidence.slice(0, 5).join(', ')}${status.specsWithoutEvidence.length > 5 ? ', …' : ''}`);
|
|
130
|
+
return `${lines.join('\n')}\n`;
|
|
131
|
+
}
|
|
132
|
+
const cell = (value) => value.replace(/\|/g, '\\|').replace(/\n/g, ' ');
|
|
133
|
+
/**
|
|
134
|
+
* Renders a status as GitHub-flavoured markdown for a pull request comment or `$GITHUB_STEP_SUMMARY`: a heading with
|
|
135
|
+
* totals, a table per environment, a table of failing checks (`|` and newlines in cells are escaped) and the reasons.
|
|
136
|
+
* Ends with a newline. For people: the layout is not a contract.
|
|
137
|
+
*/
|
|
138
|
+
export function formatStatusMarkdown(status) {
|
|
139
|
+
const lines = [`### ${status.ok ? '✅' : '❌'} Atlas report: ${row(status.checks)}`, '', `${status.layers} layer${status.layers === 1 ? '' : 's'} from ${status.backends.length} backend${status.backends.length === 1 ? '' : 's'}.`, ''];
|
|
140
|
+
const environments = Object.entries(status.byEnvironment);
|
|
141
|
+
if (environments.length) {
|
|
142
|
+
lines.push('| Environment | Passed | Failed | Running | Not run |', '| --- | ---: | ---: | ---: | ---: |');
|
|
143
|
+
for (const [environment, counts] of environments)
|
|
144
|
+
lines.push(`| ${cell(environment)} | ${counts.passed} | ${counts.failed} | ${counts.running} | ${counts['not-run']} |`);
|
|
145
|
+
lines.push('');
|
|
146
|
+
}
|
|
147
|
+
if (status.failing.length) {
|
|
148
|
+
lines.push('| Check | Environment | Branch | Detail |', '| --- | --- | --- | --- |');
|
|
149
|
+
for (const item of status.failing)
|
|
150
|
+
lines.push(`| ${cell(label(item))} | ${cell(item.environment ?? '')} | ${cell(item.branch ?? '')} | ${cell(item.status === 'running' ? 'still running' : item.detail.split('\n')[0] ?? '')} |`);
|
|
151
|
+
lines.push('');
|
|
152
|
+
}
|
|
153
|
+
for (const reason of status.reasons)
|
|
154
|
+
lines.push(`- ${reason}`);
|
|
155
|
+
return `${lines.join('\n')}\n`;
|
|
156
|
+
}
|
|
@@ -1,19 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Recorded test timeline types
|
|
3
|
+
*
|
|
4
|
+
* The shapes of a recorded test run as the harness persists it and the viewer renders it: a timeline of steps, each
|
|
5
|
+
* with frames (captured snapshots), plus per-run metadata, the suite and run indexes, and the options of a recording.
|
|
6
|
+
* All times are epoch milliseconds unless a name says otherwise. Mostly types; the few functions are small id helpers
|
|
7
|
+
* that keep ids safe to use as key segments.
|
|
8
|
+
*/
|
|
1
9
|
import type { Clock } from './clock.js';
|
|
2
10
|
import type { ObjectStore } from './object-store.js';
|
|
3
11
|
/** Observed elapsed work, independent of the simulation clock. */
|
|
4
12
|
export type ReportStepPerformance = {
|
|
13
|
+
/** Observed elapsed time of the step, in milliseconds. */
|
|
5
14
|
readonly executionMs: number;
|
|
15
|
+
/** Time attributed to the step's own work, nested steps counted once (see `performanceHotspots`), in milliseconds. */
|
|
6
16
|
readonly selfMs: number;
|
|
17
|
+
/** Time spent capturing snapshots for the step, in milliseconds. */
|
|
7
18
|
readonly snapshotMs: number;
|
|
8
19
|
};
|
|
20
|
+
/** Timing totals of a whole run. */
|
|
9
21
|
export type ReportPerformanceSummary = {
|
|
22
|
+
/** Total observed elapsed work of the run, in milliseconds. */
|
|
10
23
|
readonly executionMs: number;
|
|
24
|
+
/** Part of that spent capturing snapshots, in milliseconds. */
|
|
11
25
|
readonly snapshotMs: number;
|
|
26
|
+
/** How many steps had a measurement. */
|
|
12
27
|
readonly measuredSteps: number;
|
|
28
|
+
/** How many measured steps were over `slowStepMs`. */
|
|
13
29
|
readonly slowSteps: number;
|
|
30
|
+
/** The allowance, in milliseconds, a step's own time is judged against. */
|
|
14
31
|
readonly slowStepMs: number;
|
|
15
32
|
};
|
|
33
|
+
/** How to measure elapsed work while recording. */
|
|
16
34
|
export type ReportPerformanceOptions = {
|
|
35
|
+
/** The monotonic clock to measure with; separate from the simulation clock. */
|
|
17
36
|
readonly clock: Clock;
|
|
18
37
|
/** Diagnostic threshold, not a portable CPU or paint-time assertion. */
|
|
19
38
|
readonly slowStepMs: number;
|
|
@@ -24,6 +43,7 @@ export type ReportPerformanceOptions = {
|
|
|
24
43
|
* A runner may record any mix. The viewer treats a still, a video, and a timeline of frames as the same kind of evidence.
|
|
25
44
|
*/
|
|
26
45
|
export type SnapshotMedia = 'json' | 'html' | 'png' | 'text' | 'bytes' | 'video';
|
|
46
|
+
/** The outcome of one step. */
|
|
27
47
|
export type ReportStepStatus = 'passed' | 'failed';
|
|
28
48
|
/** Test outcome after the harness (e.g. the test runner) has weighed in. */
|
|
29
49
|
export type ReportTestStatus = 'passed' | 'failed' | 'skipped';
|
|
@@ -32,17 +52,24 @@ export type ReportTestStatus = 'passed' | 'failed' | 'skipped';
|
|
|
32
52
|
* Multi-actor UI maps become one entry per nodeId.
|
|
33
53
|
*/
|
|
34
54
|
export type ReportSnapshotBlob = {
|
|
55
|
+
/** The blob's name as its snapshotter gave it. */
|
|
35
56
|
readonly name: string;
|
|
57
|
+
/** How `file` is encoded. */
|
|
36
58
|
readonly media: SnapshotMedia;
|
|
37
59
|
/** Relative path under the test dir, e.g. `frames/f-00001.html.alice.html`. */
|
|
38
60
|
readonly file: string;
|
|
61
|
+
/** Which client (actor) the blob is of, when the snapshot has one per client. */
|
|
39
62
|
readonly nodeId?: string;
|
|
40
63
|
};
|
|
41
64
|
/** Discrete visual/state capture within a report step. */
|
|
42
65
|
export type ReportFrame = {
|
|
66
|
+
/** The frame's ordinal as the recorder assigned it. */
|
|
43
67
|
readonly index: number;
|
|
68
|
+
/** When it was captured, epoch milliseconds. */
|
|
44
69
|
readonly capturedAtMs: number;
|
|
70
|
+
/** How long the capture took, in milliseconds. */
|
|
45
71
|
readonly durationMs?: number;
|
|
72
|
+
/** The captured blobs, one per snapshotter (and per client). */
|
|
46
73
|
readonly snapshots: readonly ReportSnapshotBlob[];
|
|
47
74
|
/**
|
|
48
75
|
* Gesture target, set only on the frame captured right before a UI step
|
|
@@ -56,7 +83,9 @@ export type ReportFrame = {
|
|
|
56
83
|
* Viewers resolve the path against the snapshot DOM for real geometry.
|
|
57
84
|
*/
|
|
58
85
|
export type ReportStepHit = {
|
|
86
|
+
/** Chain of `data-testid` values, each looked up inside the previous match. */
|
|
59
87
|
readonly path: readonly string[];
|
|
88
|
+
/** Where in the target box the gesture landed, as fractions from 0 to 1 of its width and height. */
|
|
60
89
|
readonly point?: {
|
|
61
90
|
readonly xRatio: number;
|
|
62
91
|
readonly yRatio: number;
|
|
@@ -67,43 +96,65 @@ export type ReportStepHit = {
|
|
|
67
96
|
* holds the UI ops recorded while its action ran.
|
|
68
97
|
*/
|
|
69
98
|
export type ReportStep = {
|
|
99
|
+
/** The step's ordinal as the recorder assigned it. */
|
|
70
100
|
readonly index: number;
|
|
101
|
+
/** An open category of step. Gherkin keywords (`given`, `when`, `then`, `and`, `but`) mark scenario lines. */
|
|
71
102
|
readonly kind: string;
|
|
103
|
+
/** Human title. */
|
|
72
104
|
readonly title: string;
|
|
73
105
|
readonly status: ReportStepStatus;
|
|
74
106
|
readonly startedAtMs: number;
|
|
75
107
|
readonly endedAtMs: number;
|
|
108
|
+
/** `endedAtMs - startedAtMs`, in milliseconds. */
|
|
76
109
|
readonly durationMs: number;
|
|
110
|
+
/** Observed elapsed work, when measurement was on. */
|
|
77
111
|
readonly performance?: ReportStepPerformance;
|
|
112
|
+
/** Which client (actor) took the step, in a multi-client run. */
|
|
78
113
|
readonly actorId?: string;
|
|
114
|
+
/** The UI selector the step acted on. */
|
|
79
115
|
readonly selector?: string;
|
|
80
116
|
readonly detail?: string;
|
|
117
|
+
/** The action performed, when the step is an applied action. */
|
|
81
118
|
readonly action?: string;
|
|
82
119
|
readonly args?: readonly unknown[];
|
|
120
|
+
/** The failure message when `status` is `failed`. */
|
|
83
121
|
readonly error?: string;
|
|
84
122
|
readonly errorStack?: string;
|
|
123
|
+
/** Captures taken during the step. */
|
|
85
124
|
readonly frames: readonly ReportFrame[];
|
|
125
|
+
/** Steps nested in this one. */
|
|
86
126
|
readonly steps: readonly ReportStep[];
|
|
87
127
|
};
|
|
128
|
+
/** Metadata of one recorded test run. */
|
|
88
129
|
export type RunReportMeta = {
|
|
130
|
+
/** The system under test the run exercised. */
|
|
89
131
|
readonly system: string;
|
|
132
|
+
/** The runtime it ran in. */
|
|
90
133
|
readonly runtime: string;
|
|
134
|
+
/** The seed of a randomised run. */
|
|
91
135
|
readonly seed: string;
|
|
92
136
|
readonly startedAtMs: number;
|
|
93
137
|
readonly endedAtMs: number;
|
|
138
|
+
/** `endedAtMs - startedAtMs`, in milliseconds. */
|
|
94
139
|
readonly durationMs: number;
|
|
140
|
+
/** Timing totals, when measurement was on. */
|
|
95
141
|
readonly performance?: ReportPerformanceSummary;
|
|
96
142
|
readonly status: ReportTestStatus;
|
|
143
|
+
/** The run the test belongs to; see `sanitizeRunId`. */
|
|
97
144
|
readonly runId: string;
|
|
145
|
+
/** The test's id within the run; see `sanitizeTestId`. */
|
|
98
146
|
readonly testId: string;
|
|
99
147
|
/** Human title (defaults to `testId`). */
|
|
100
148
|
readonly title: string;
|
|
149
|
+
/** The clients (actors) that took part. */
|
|
101
150
|
readonly actorIds: readonly string[];
|
|
151
|
+
/** Repo-relative path of the test's source file (a spec), when known. */
|
|
102
152
|
readonly sourceFile?: string;
|
|
103
153
|
/** 1-based line of the call that opened this run, when known. */
|
|
104
154
|
readonly sourceLine?: number;
|
|
105
155
|
/** First failure message (step, property, or harness). */
|
|
106
156
|
readonly error?: string;
|
|
157
|
+
/** The stack of that failure. */
|
|
107
158
|
readonly errorStack?: string;
|
|
108
159
|
};
|
|
109
160
|
/**
|
|
@@ -112,42 +163,59 @@ export type RunReportMeta = {
|
|
|
112
163
|
* is for what the runner can attach when it cannot: a video of the test, a few screenshots, a log.
|
|
113
164
|
*/
|
|
114
165
|
export type ReportArtifact = {
|
|
166
|
+
/** Display name. */
|
|
115
167
|
readonly name: string;
|
|
168
|
+
/** How `file` is encoded. */
|
|
116
169
|
readonly media: SnapshotMedia;
|
|
170
|
+
/** Path of the file relative to the test's directory. */
|
|
117
171
|
readonly file: string;
|
|
172
|
+
/** For a video: path of a still to show before playing. */
|
|
118
173
|
readonly poster?: string;
|
|
174
|
+
/** For a video: path of its captions (WebVTT). */
|
|
119
175
|
readonly captions?: string;
|
|
120
176
|
};
|
|
121
177
|
/** In-memory / persisted timeline for one test run. */
|
|
122
178
|
export type RunReport = {
|
|
123
179
|
readonly meta: RunReportMeta;
|
|
180
|
+
/** The top-level steps, in order; nested steps hang off each. */
|
|
124
181
|
readonly steps: readonly ReportStep[];
|
|
182
|
+
/** Whole-test files, for runners that cannot capture per step. */
|
|
125
183
|
readonly artifacts?: readonly ReportArtifact[];
|
|
126
184
|
};
|
|
185
|
+
/** One test as a suite manifest lists it: the metadata without the timeline. */
|
|
127
186
|
export type SuiteTestSummary = {
|
|
128
187
|
readonly testId: string;
|
|
129
188
|
readonly title: string;
|
|
130
189
|
readonly seed: string;
|
|
131
190
|
readonly status: ReportTestStatus;
|
|
132
191
|
readonly durationMs: number;
|
|
192
|
+
/** How many steps the test recorded. */
|
|
133
193
|
readonly stepCount: number;
|
|
194
|
+
/** How many frames the test captured. */
|
|
134
195
|
readonly frameCount: number;
|
|
135
196
|
readonly system: string;
|
|
136
197
|
readonly runtime: string;
|
|
137
198
|
readonly sourceFile?: string;
|
|
138
199
|
readonly error?: string;
|
|
139
200
|
};
|
|
201
|
+
/** A listing of a suite's tests, with the time it was last written. */
|
|
140
202
|
export type SuiteManifest = {
|
|
141
203
|
readonly tests: readonly SuiteTestSummary[];
|
|
204
|
+
/** When the listing was last written, epoch milliseconds. */
|
|
142
205
|
readonly updatedAtMs: number;
|
|
143
206
|
};
|
|
207
|
+
/** One run as `runs/index.json` lists it. */
|
|
144
208
|
export type RunSummary = {
|
|
209
|
+
/** The run id; see `sanitizeRunId`. */
|
|
145
210
|
readonly id: string;
|
|
211
|
+
/** When the run was last updated, epoch milliseconds. */
|
|
146
212
|
readonly updatedAtMs: number;
|
|
213
|
+
/** The run's overall outcome. */
|
|
147
214
|
readonly status: ReportTestStatus;
|
|
148
215
|
readonly testCount: number;
|
|
149
216
|
readonly frameCount: number;
|
|
150
217
|
};
|
|
218
|
+
/** The index of the runs in a report bucket (stored at `runs/index.json`). */
|
|
151
219
|
export type RunsIndex = {
|
|
152
220
|
readonly runs: readonly RunSummary[];
|
|
153
221
|
};
|
|
@@ -165,7 +233,9 @@ export type RecordStepMeta = {
|
|
|
165
233
|
/** Skip registered snapshotters for this step. */
|
|
166
234
|
readonly skipSnapshot?: boolean;
|
|
167
235
|
};
|
|
236
|
+
/** When a recording auto-opens a report step: on each applied action (`apply`) or never (`none`). */
|
|
168
237
|
export type ReportCaptureMode = 'apply' | 'none';
|
|
238
|
+
/** Options that turn on and shape the recording of a test run. */
|
|
169
239
|
export type RunReportOptions = {
|
|
170
240
|
/** Enable elapsed work measurement with a separate monotonic clock. */
|
|
171
241
|
readonly performance?: ReportPerformanceOptions;
|
|
@@ -204,8 +274,11 @@ export type RunReportOptions = {
|
|
|
204
274
|
/** 1-based source line of the call that opened the run, when known. */
|
|
205
275
|
readonly sourceLine?: number;
|
|
206
276
|
};
|
|
277
|
+
/** Makes a test id safe as a key segment: runs of characters outside `A-Za-z0-9._-` become one `_`, leading and trailing `_` are trimmed, and an empty result is `test`. */
|
|
207
278
|
export declare function sanitizeTestId(raw: string): string;
|
|
279
|
+
/** Makes a run id safe as a key segment: runs of characters outside `A-Za-z0-9._-` become one `_`, leading and trailing `_` are trimmed, and an empty result is `run`. */
|
|
208
280
|
export declare function sanitizeRunId(raw: string): string;
|
|
209
281
|
/** Mint `YYYYMMDDTHHmmss` UTC from epoch ms. */
|
|
210
282
|
export declare function mintRunId(nowMs: number): string;
|
|
283
|
+
/** Normalises the `report` option of a test: `undefined` or `false` is `null` (reporting off), `true` is `{ enabled: true }`, an options object with `enabled: false` is `null`, and any other object is returned with `enabled: true`. */
|
|
211
284
|
export declare function resolveReportOptions(report: boolean | RunReportOptions | undefined): RunReportOptions | null;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
/** Makes a test id safe as a key segment: runs of characters outside `A-Za-z0-9._-` become one `_`, leading and trailing `_` are trimmed, and an empty result is `test`. */
|
|
1
2
|
export function sanitizeTestId(raw) {
|
|
2
3
|
const cleaned = raw.replace(/[^a-zA-Z0-9._-]+/g, '_').replace(/^_+|_+$/g, '');
|
|
3
4
|
return cleaned.length > 0 ? cleaned : 'test';
|
|
4
5
|
}
|
|
6
|
+
/** Makes a run id safe as a key segment: runs of characters outside `A-Za-z0-9._-` become one `_`, leading and trailing `_` are trimmed, and an empty result is `run`. */
|
|
5
7
|
export function sanitizeRunId(raw) {
|
|
6
8
|
const cleaned = raw.replace(/[^a-zA-Z0-9._-]+/g, '_').replace(/^_+|_+$/g, '');
|
|
7
9
|
return cleaned.length > 0 ? cleaned : 'run';
|
|
@@ -17,6 +19,7 @@ export function mintRunId(nowMs) {
|
|
|
17
19
|
const s = String(d.getUTCSeconds()).padStart(2, '0');
|
|
18
20
|
return `${y}${mo}${day}T${h}${mi}${s}`;
|
|
19
21
|
}
|
|
22
|
+
/** Normalises the `report` option of a test: `undefined` or `false` is `null` (reporting off), `true` is `{ enabled: true }`, an options object with `enabled: false` is `null`, and any other object is returned with `enabled: true`. */
|
|
20
23
|
export function resolveReportOptions(report) {
|
|
21
24
|
if (report === undefined || report === false)
|
|
22
25
|
return null;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Streaming reporter
|
|
3
|
+
*
|
|
4
|
+
* `createReporter` lets a suite report checks as they finish: each flush is one immutable layer of the checks since the last,
|
|
5
|
+
* so a run is visible while it is still going, and a check reported `running` then `passed` is two layers whose newer,
|
|
6
|
+
* completed one wins. A layer that cannot be sent is kept and sent again byte for byte the same (never a second layer), a
|
|
7
|
+
* layer the server permanently refuses is counted as lost without blocking the ones behind it, and nothing is ever thrown
|
|
8
|
+
* into the suite being reported on.
|
|
9
|
+
*
|
|
10
|
+
* @see docs/server.md#streaming-a-run-as-it-happens
|
|
11
|
+
*/
|
|
12
|
+
import { type PublishChecksInput } from '../../schema/src/layer.js';
|
|
13
|
+
import type { ObjectStore } from './object-store.js';
|
|
14
|
+
type Check = PublishChecksInput['checks'][number];
|
|
15
|
+
/** What every layer of one stream shares: who is reporting, from where. Whatever `publishChecks` takes, except the checks and the time. */
|
|
16
|
+
export type ReporterIdentity = Pick<PublishChecksInput, 'producer' | 'kind' | 'title' | 'sha' | 'branch' | 'runUrl' | 'environment' | 'parameters' | 'freshness' | 'specs' | 'tags' | 'labels' | 'links' | 'attachments' | 'annotations' | 'extensions'>;
|
|
17
|
+
/** How a reporter sends: the `store` (a folder, S3, or `httpStore` for a server), how often (`flushMs`, `batchSize`), how hard it tries (`attempts`, `backoffMs`), what it tells (`onError`, `onPublished`), and the clock and sleep for tests. */
|
|
18
|
+
export type ReporterOptions = {
|
|
19
|
+
/** Where layers go: a directory, S3, or `httpStore` for an Atlas server. */
|
|
20
|
+
readonly store: ObjectStore;
|
|
21
|
+
/** Send what has been reported this often (ms). Default 1000. `0` sends only on `flush()` and `close()`. */
|
|
22
|
+
readonly flushMs?: number;
|
|
23
|
+
/** Send at once when this many checks are waiting. Default 100. */
|
|
24
|
+
readonly batchSize?: number;
|
|
25
|
+
/** Tries for one layer, in `close()`; the timer keeps trying on its own. Default 5. */
|
|
26
|
+
readonly attempts?: number;
|
|
27
|
+
/** First wait between tries (ms), doubled each time to at most 30 s. Default 500. */
|
|
28
|
+
readonly backoffMs?: number;
|
|
29
|
+
/** Told of each failed try. A reporter never throws into the test run it reports on. */
|
|
30
|
+
readonly onError?: (error: Error, willRetry: boolean) => void;
|
|
31
|
+
/** Told of each layer that arrived. */
|
|
32
|
+
readonly onPublished?: (layer: {
|
|
33
|
+
id: string;
|
|
34
|
+
checks: number;
|
|
35
|
+
}) => void;
|
|
36
|
+
readonly now?: () => number;
|
|
37
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
38
|
+
};
|
|
39
|
+
/** What `close()` says happened: layers and checks that arrived, and checks `lost` (never arrived). `lost` is zero when everything did. */
|
|
40
|
+
export type ReporterResult = {
|
|
41
|
+
/** Layers that arrived. */
|
|
42
|
+
readonly layers: number;
|
|
43
|
+
/** Checks that arrived. */
|
|
44
|
+
readonly checks: number;
|
|
45
|
+
/** Checks that could not be sent. Zero when everything arrived. */
|
|
46
|
+
readonly lost: number;
|
|
47
|
+
};
|
|
48
|
+
/** A running reporter. `check` adds a result, `flush` sends what is waiting, `close` sends the rest and stops. */
|
|
49
|
+
export type Reporter = {
|
|
50
|
+
/** Report one check, finished or still running. Reporting the same `id` again replaces it, whatever its state. */
|
|
51
|
+
check(check: Check): void;
|
|
52
|
+
/** Send everything reported so far, now. Resolves once it has arrived or failed; never rejects. */
|
|
53
|
+
flush(): Promise<void>;
|
|
54
|
+
/** Send the rest and stop. Retries each layer up to `attempts` times. */
|
|
55
|
+
close(): Promise<ReporterResult>;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* A suite that reports as it goes. Each flush is one immutable layer holding the checks reported since the
|
|
59
|
+
* last, so nothing is rewritten and a laptop, a CI job and a device lab can all be sending to the same
|
|
60
|
+
* server at once. A check reported as `running` and then as `passed` is two layers; the later, completed one
|
|
61
|
+
* wins, so the report follows the suite while it runs and agrees with it when it ends.
|
|
62
|
+
*
|
|
63
|
+
* A layer that cannot be sent is kept and sent again, unchanged (same time, so same content): a retry never
|
|
64
|
+
* makes a second layer. Time only moves forward within a reporter, so a later result always follows an earlier one.
|
|
65
|
+
*/
|
|
66
|
+
export declare function createReporter(identity: ReporterIdentity, options: ReporterOptions): Reporter;
|
|
67
|
+
export {};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Streaming reporter
|
|
3
|
+
*
|
|
4
|
+
* `createReporter` lets a suite report checks as they finish: each flush is one immutable layer of the checks since the last,
|
|
5
|
+
* so a run is visible while it is still going, and a check reported `running` then `passed` is two layers whose newer,
|
|
6
|
+
* completed one wins. A layer that cannot be sent is kept and sent again byte for byte the same (never a second layer), a
|
|
7
|
+
* layer the server permanently refuses is counted as lost without blocking the ones behind it, and nothing is ever thrown
|
|
8
|
+
* into the suite being reported on.
|
|
9
|
+
*
|
|
10
|
+
* @see docs/server.md#streaming-a-run-as-it-happens
|
|
11
|
+
*/
|
|
12
|
+
import { PublishChecksSchema } from '../../schema/src/layer.js';
|
|
13
|
+
import { parseWith } from './parse-with.js';
|
|
14
|
+
import { publishChecks } from './publish-checks.js';
|
|
15
|
+
/**
|
|
16
|
+
* Trying again cannot help a layer the server refused (too large, not valid) or that cannot be made: it would
|
|
17
|
+
* stand in front of every later one for ever.
|
|
18
|
+
*/
|
|
19
|
+
function permanent(error) {
|
|
20
|
+
return error.retryable === false || /^publishChecks:/.test(error.message);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A suite that reports as it goes. Each flush is one immutable layer holding the checks reported since the
|
|
24
|
+
* last, so nothing is rewritten and a laptop, a CI job and a device lab can all be sending to the same
|
|
25
|
+
* server at once. A check reported as `running` and then as `passed` is two layers; the later, completed one
|
|
26
|
+
* wins, so the report follows the suite while it runs and agrees with it when it ends.
|
|
27
|
+
*
|
|
28
|
+
* A layer that cannot be sent is kept and sent again, unchanged (same time, so same content): a retry never
|
|
29
|
+
* makes a second layer. Time only moves forward within a reporter, so a later result always follows an earlier one.
|
|
30
|
+
*/
|
|
31
|
+
export function createReporter(identity, options) {
|
|
32
|
+
const now = options.now ?? Date.now;
|
|
33
|
+
const sleep = options.sleep ?? ((ms) => new Promise(resolve => setTimeout(resolve, ms)));
|
|
34
|
+
const flushMs = options.flushMs ?? 1000;
|
|
35
|
+
const batchSize = Math.max(1, options.batchSize ?? 100);
|
|
36
|
+
const attempts = Math.max(1, options.attempts ?? 5);
|
|
37
|
+
const backoff = options.backoffMs ?? 500;
|
|
38
|
+
let pending = new Map();
|
|
39
|
+
const failed = [];
|
|
40
|
+
let lastTime = 0;
|
|
41
|
+
let layers = 0, sent = 0, lost = 0;
|
|
42
|
+
let queue = Promise.resolve();
|
|
43
|
+
let timer = null;
|
|
44
|
+
let closed = false;
|
|
45
|
+
let wait = backoff;
|
|
46
|
+
const nextTime = () => (lastTime = Math.max(now(), lastTime + 1));
|
|
47
|
+
async function send(batch) {
|
|
48
|
+
try {
|
|
49
|
+
const layer = await publishChecks(options.store, { ...identity, checks: batch.checks, recordedAtMs: batch.recordedAtMs });
|
|
50
|
+
layers += 1;
|
|
51
|
+
sent += batch.checks.length;
|
|
52
|
+
options.onPublished?.({ id: layer.id, checks: batch.checks.length });
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Everything waiting, oldest first. Returns once each batch has arrived, or failed `tries` times. */
|
|
60
|
+
async function drain(tries) {
|
|
61
|
+
if (pending.size) {
|
|
62
|
+
failed.push({ checks: [...pending.values()], recordedAtMs: nextTime() });
|
|
63
|
+
pending = new Map();
|
|
64
|
+
}
|
|
65
|
+
while (failed.length) {
|
|
66
|
+
const batch = failed[0];
|
|
67
|
+
let error = null;
|
|
68
|
+
for (let attempt = 1; attempt <= tries; attempt++) {
|
|
69
|
+
error = await send(batch);
|
|
70
|
+
if (!error)
|
|
71
|
+
break;
|
|
72
|
+
if (permanent(error))
|
|
73
|
+
break;
|
|
74
|
+
options.onError?.(error, attempt < tries || !closed);
|
|
75
|
+
if (attempt < tries)
|
|
76
|
+
await sleep(Math.min(wait * 2 ** (attempt - 1), 30_000));
|
|
77
|
+
}
|
|
78
|
+
if (error && permanent(error)) {
|
|
79
|
+
// Said once, counted as lost, and out of the way of the layers behind it.
|
|
80
|
+
options.onError?.(error, false);
|
|
81
|
+
lost += batch.checks.length;
|
|
82
|
+
failed.shift();
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (error) {
|
|
86
|
+
if (closed) {
|
|
87
|
+
lost += failed.reduce((sum, item) => sum + item.checks.length, 0);
|
|
88
|
+
failed.length = 0;
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
failed.shift();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const run = (tries) => (queue = queue.then(() => drain(tries)).catch(() => undefined));
|
|
96
|
+
function schedule() {
|
|
97
|
+
if (closed || timer || flushMs <= 0)
|
|
98
|
+
return;
|
|
99
|
+
timer = setTimeout(() => {
|
|
100
|
+
timer = null;
|
|
101
|
+
// One try per tick: a server that is down is asked again at the next one, not hammered.
|
|
102
|
+
void run(1).then(() => { if (pending.size || failed.length)
|
|
103
|
+
schedule(); });
|
|
104
|
+
}, failed.length ? Math.min(flushMs * 2 ** Math.min(failed.length, 5), 30_000) : flushMs);
|
|
105
|
+
timer.unref?.();
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
check(check) {
|
|
109
|
+
if (closed)
|
|
110
|
+
throw new Error('This reporter is closed. Create another to report more.');
|
|
111
|
+
// A check that cannot be a layer is a mistake of the caller, found here where it was made, and not as a layer that never arrives.
|
|
112
|
+
const valid = parseWith(PublishChecksSchema.shape.checks.element, check, 'reporter.check');
|
|
113
|
+
pending.set(valid.id, valid);
|
|
114
|
+
if (pending.size >= batchSize)
|
|
115
|
+
void run(1);
|
|
116
|
+
schedule();
|
|
117
|
+
},
|
|
118
|
+
flush: () => run(1),
|
|
119
|
+
async close() {
|
|
120
|
+
closed = true;
|
|
121
|
+
if (timer)
|
|
122
|
+
clearTimeout(timer);
|
|
123
|
+
timer = null;
|
|
124
|
+
await run(attempts);
|
|
125
|
+
return { layers, checks: sent, lost };
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|