@crvouga/atlas 0.4.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 +7 -1
- package/README.md +178 -617
- 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 +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 +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 +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/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 +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 +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/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 +32 -4
- package/packages/core/README.md +2 -1
- package/packages/reports/README.md +65 -0
- package/packages/reports/STORAGE.md +4 -0
- 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 +21 -0
- 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 +40 -3
- 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 +3 -1
- package/packages/visualizer/src/evidence/evidence-context.tsx +74 -12
- 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/vite/atlas-server-proxy.ts +36 -0
- package/packages/visualizer/vite.config.ts +11 -3
|
@@ -1,27 +1,38 @@
|
|
|
1
1
|
import type { ReportStepHit } from './report-types.js';
|
|
2
|
+
/** What drawing a press target found: `none` (no target to draw), `found` (target located, overlay drawn) or `missing` (the path does not resolve in the painted snapshot, overlay hidden). */
|
|
2
3
|
export type HitResult = 'none' | 'found' | 'missing';
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
5
|
+
* Rewrites snapshot CSS so it applies inside the phone surface: the selectors `html` and `:root` become `.sr-snap-doc`,
|
|
6
|
+
* `body` becomes `.sr-snap-body`, and the viewport units `100vh`, `100vw` (and their `d`, `s`, `l` variants) become
|
|
7
|
+
* `100%` of that box. A regex rewrite, so it is approximate for unusual selectors.
|
|
6
8
|
*/
|
|
7
9
|
export declare function scopeSnapshotCss(css: string): string;
|
|
8
|
-
/**
|
|
10
|
+
/** Makes `doc` the standards-mode shell with `frameCss` installed, replacing its content, unless it already is one. Does nothing when the shell's stylesheet is present, so a later different `frameCss` is not applied. */
|
|
9
11
|
export declare function ensureShell(doc: Document, frameCss: string): void;
|
|
10
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Finds the element a gesture targeted: the first `[data-testid]` match of `path[0]` inside `scope`, then the first
|
|
14
|
+
* match of `path[1]` inside that, and so on. Returns `null` as soon as a step has no match, and for an empty path
|
|
15
|
+
* returns `null` too. Mirrors how the UI driver resolves `get(a).get(b)`.
|
|
16
|
+
*/
|
|
11
17
|
export declare function resolveHitTarget(scope: ParentNode, path: readonly string[]): Element | null;
|
|
12
|
-
/**
|
|
18
|
+
/** Draws the one press overlay (a box and a dot at `hit.point`, default the centre) on `hit`'s target in a document, scrolling the target into view if it is outside the viewport. Returns what it found; with no hit it hides the overlay. */
|
|
13
19
|
export declare function drawHit(doc: Document, hit: ReportStepHit | null): HitResult;
|
|
14
20
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
21
|
+
* Paints one snapshot into `doc` (already a shell, see `ensureShell`) and draws `hit`. `key` identifies the snapshot for
|
|
22
|
+
* parse caching (256 entries, oldest dropped first): it must change whenever `html` does, or the earlier parse is
|
|
23
|
+
* reused.
|
|
17
24
|
*/
|
|
18
25
|
export declare function paintSnapshot(doc: Document, snap: {
|
|
19
26
|
readonly key: string;
|
|
20
27
|
readonly html: string;
|
|
21
28
|
}, hit: ReportStepHit | null): HitResult;
|
|
22
|
-
/**
|
|
29
|
+
/** Creates the in-page snapshot surface: a `div.sr-snap` with an open shadow tree, sized to the phone viewport, ready for `paintSnapshotHost`. No iframe. */
|
|
23
30
|
export declare function createSnapshotHost(): HTMLDivElement;
|
|
24
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Paints one snapshot into the shadow tree `createSnapshotHost` built, installs `snap.css` (the frame stylesheet) and
|
|
33
|
+
* draws `hit`. Painting is skipped, and only the overlay redrawn, when the same `key` and `html` were painted last.
|
|
34
|
+
* Returns `none` when `host` was not built by `createSnapshotHost`.
|
|
35
|
+
*/
|
|
25
36
|
export declare function paintSnapshotHost(host: HTMLElement, snap: {
|
|
26
37
|
readonly key: string;
|
|
27
38
|
readonly html: string;
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import { cleanSnapshotHtml, PHONE_H, PHONE_W, } from './phone.js';
|
|
2
1
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
2
|
+
* @module Snapshot surface
|
|
3
|
+
*
|
|
4
|
+
* Paints recorded page snapshots (HTML captured at each step of a test) into a phone-sized surface in the viewer and
|
|
5
|
+
* draws the press target of a gesture on top. One long-lived surface per client panel: frames are painted by swapping
|
|
6
|
+
* the parsed snapshot DOM into that surface synchronously, so scrubbing never re-parses `frameCss` or refetches fonts.
|
|
7
|
+
* The press target is resolved against the painted DOM with the driver's chained first-match semantics and drawn as a
|
|
8
|
+
* single overlay from real layout.
|
|
8
9
|
*
|
|
9
|
-
* The phone paints into an open shadow tree, not an iframe: embedded
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* The phone paints into an open shadow tree, not an iframe: embedded browsers drop iframe documents, and a shadow tree
|
|
11
|
+
* stays in the page. Snapshot styles sit on the shadow root, and the host is the 390x844 viewport the app shell fills.
|
|
12
|
+
* Browser-only: needs `document`, `DOMParser` and shadow DOM. Snapshot HTML comes from a test run and is untrusted;
|
|
13
|
+
* `<script>` elements are removed (`cleanSnapshotHtml`) but nothing else is sanitised, so show it only in a context you
|
|
14
|
+
* would show that run's pages in.
|
|
13
15
|
*/
|
|
16
|
+
import { cleanSnapshotHtml, PHONE_H, PHONE_W, } from './phone.js';
|
|
14
17
|
const FRAME_CSS_ID = 'sr-frame-css';
|
|
15
18
|
const OVERLAY_ID = 'sr-press';
|
|
16
19
|
const SNAP_ATTR = 'data-sr-snap';
|
|
@@ -18,8 +21,9 @@ const PARSE_CACHE_MAX = 256;
|
|
|
18
21
|
const OVERLAY_CSS = `#${OVERLAY_ID}{position:absolute;display:none;pointer-events:none;z-index:2147483647;box-sizing:border-box;border:2px solid #ff5a8a;border-radius:6px;box-shadow:0 0 0 4px rgba(255,90,138,.35)}#${OVERLAY_ID} i{position:absolute;width:14px;height:14px;margin:-7px 0 0 -7px;border-radius:50%;background:#ff5a8a;box-shadow:0 0 0 4px rgba(255,90,138,.35)}`;
|
|
19
22
|
const parseCache = new Map();
|
|
20
23
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
24
|
+
* Rewrites snapshot CSS so it applies inside the phone surface: the selectors `html` and `:root` become `.sr-snap-doc`,
|
|
25
|
+
* `body` becomes `.sr-snap-body`, and the viewport units `100vh`, `100vw` (and their `d`, `s`, `l` variants) become
|
|
26
|
+
* `100%` of that box. A regex rewrite, so it is approximate for unusual selectors.
|
|
23
27
|
*/
|
|
24
28
|
export function scopeSnapshotCss(css) {
|
|
25
29
|
const scoped = css.replace(/(^|[^\w-])(:root|html|body)(?![\w-])/g, (_match, prefix, token) => `${prefix}${token === 'body' ? '.sr-snap-body' : '.sr-snap-doc'}`);
|
|
@@ -56,7 +60,7 @@ function shellHtml(frameCss) {
|
|
|
56
60
|
const base = `html,body{margin:0}html{position:relative}${OVERLAY_CSS}`;
|
|
57
61
|
return `<!DOCTYPE html><html><head><meta charset="utf-8"><style id="${FRAME_CSS_ID}">${frameCss}${base}</style></head><body></body></html>`;
|
|
58
62
|
}
|
|
59
|
-
/**
|
|
63
|
+
/** Makes `doc` the standards-mode shell with `frameCss` installed, replacing its content, unless it already is one. Does nothing when the shell's stylesheet is present, so a later different `frameCss` is not applied. */
|
|
60
64
|
export function ensureShell(doc, frameCss) {
|
|
61
65
|
if (doc.getElementById(FRAME_CSS_ID))
|
|
62
66
|
return;
|
|
@@ -148,7 +152,11 @@ function syncHead(root, parsed) {
|
|
|
148
152
|
function testIdSelector(id) {
|
|
149
153
|
return `[data-testid="${id.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"]`;
|
|
150
154
|
}
|
|
151
|
-
/**
|
|
155
|
+
/**
|
|
156
|
+
* Finds the element a gesture targeted: the first `[data-testid]` match of `path[0]` inside `scope`, then the first
|
|
157
|
+
* match of `path[1]` inside that, and so on. Returns `null` as soon as a step has no match, and for an empty path
|
|
158
|
+
* returns `null` too. Mirrors how the UI driver resolves `get(a).get(b)`.
|
|
159
|
+
*/
|
|
152
160
|
export function resolveHitTarget(scope, path) {
|
|
153
161
|
let current = scope;
|
|
154
162
|
let found = null;
|
|
@@ -224,7 +232,7 @@ function drawOn(root, hit) {
|
|
|
224
232
|
placeOverlay(root, target, hit);
|
|
225
233
|
return 'found';
|
|
226
234
|
}
|
|
227
|
-
/**
|
|
235
|
+
/** Draws the one press overlay (a box and a dot at `hit.point`, default the centre) on `hit`'s target in a document, scrolling the target into view if it is outside the viewport. Returns what it found; with no hit it hides the overlay. */
|
|
228
236
|
export function drawHit(doc, hit) {
|
|
229
237
|
if (!doc.getElementById('sr-overlay-css')) {
|
|
230
238
|
const style = doc.createElement('style');
|
|
@@ -246,8 +254,9 @@ function paintRoot(root, snap, hit) {
|
|
|
246
254
|
return drawOn(root, hit);
|
|
247
255
|
}
|
|
248
256
|
/**
|
|
249
|
-
*
|
|
250
|
-
*
|
|
257
|
+
* Paints one snapshot into `doc` (already a shell, see `ensureShell`) and draws `hit`. `key` identifies the snapshot for
|
|
258
|
+
* parse caching (256 entries, oldest dropped first): it must change whenever `html` does, or the earlier parse is
|
|
259
|
+
* reused.
|
|
251
260
|
*/
|
|
252
261
|
export function paintSnapshot(doc, snap, hit) {
|
|
253
262
|
return paintRoot(documentRoot(doc), snap, hit);
|
|
@@ -272,7 +281,7 @@ const HOST_LAYOUT = [
|
|
|
272
281
|
'#root>.themeHost,.dom-root>.themeHost,.dom-root>*>.themeHost',
|
|
273
282
|
`{${HOST_FILL}}`,
|
|
274
283
|
].join('');
|
|
275
|
-
/**
|
|
284
|
+
/** Creates the in-page snapshot surface: a `div.sr-snap` with an open shadow tree, sized to the phone viewport, ready for `paintSnapshotHost`. No iframe. */
|
|
276
285
|
export function createSnapshotHost() {
|
|
277
286
|
const host = document.createElement('div');
|
|
278
287
|
host.className = 'sr-snap';
|
|
@@ -322,7 +331,11 @@ function installHostCss(root, frameCss) {
|
|
|
322
331
|
if (style.textContent !== next)
|
|
323
332
|
style.textContent = next;
|
|
324
333
|
}
|
|
325
|
-
/**
|
|
334
|
+
/**
|
|
335
|
+
* Paints one snapshot into the shadow tree `createSnapshotHost` built, installs `snap.css` (the frame stylesheet) and
|
|
336
|
+
* draws `hit`. Painting is skipped, and only the overlay redrawn, when the same `key` and `html` were painted last.
|
|
337
|
+
* Returns `none` when `host` was not built by `createSnapshotHost`.
|
|
338
|
+
*/
|
|
326
339
|
export function paintSnapshotHost(host, snap, hit) {
|
|
327
340
|
const root = hostRoot(host);
|
|
328
341
|
if (!root)
|
|
@@ -1,27 +1,50 @@
|
|
|
1
1
|
import type { ObjectStore } from './object-store.js';
|
|
2
2
|
import type { SourceRecord } from './protocol.js';
|
|
3
|
+
/** What makes a recorded source reusable: the exact task that produced it, within a retention window. */
|
|
3
4
|
export type SourceIdentity = {
|
|
5
|
+
/** The hash of the exact task that produced the source: 16 to 64 lowercase hex digits. */
|
|
4
6
|
readonly taskHash: string;
|
|
7
|
+
/** The retention window the source belongs to, from `reportGeneration`. A new generation never reuses an older copy, so old content can expire. */
|
|
5
8
|
readonly generation: number;
|
|
6
9
|
};
|
|
7
10
|
type SourceReference = {
|
|
8
11
|
readonly record: SourceRecord;
|
|
9
12
|
readonly contentPrefix: string;
|
|
10
13
|
};
|
|
14
|
+
/** The retention window index for a time: `floor(nowMs / (retentionDays * 86 400 000))`. Throws `Report time must be nonnegative` for a negative or non-finite time and `Report retention must be a positive number of days` for a non-integer or less than 1. */
|
|
11
15
|
export declare function reportGeneration(nowMs: number, retentionDays: number): number;
|
|
16
|
+
/**
|
|
17
|
+
* The key prefix of a reusable source's content: `content/v2/<generation>/<taskHash>/<source>/`. Throws
|
|
18
|
+
* `Report identity needs an exact task hash`, `Report generation must be a nonnegative integer` or `Invalid report
|
|
19
|
+
* source` when an input is malformed.
|
|
20
|
+
*/
|
|
12
21
|
export declare function sourceContentPrefix(identity: SourceIdentity, source: string): string;
|
|
22
|
+
/** Points a commit's source record at reusable content: writes `commits/<sha>/sources/<source>.json` as `{ record, contentPrefix }`. */
|
|
13
23
|
export declare function attachSource(store: ObjectStore, args: {
|
|
14
24
|
readonly sha: string;
|
|
15
25
|
readonly record: SourceRecord;
|
|
16
26
|
readonly contentPrefix: string;
|
|
17
27
|
}): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Reuses an earlier passing run of the same task: when the content for `identity` is complete in the store, attaches it
|
|
30
|
+
* to `sha` and returns its record; otherwise returns `null` and writes nothing. Returns `null` also when the stored
|
|
31
|
+
* record is for another package or bucket, or did not pass. Throws for a malformed identity or a stored record that is
|
|
32
|
+
* not JSON.
|
|
33
|
+
*/
|
|
18
34
|
export declare function reuseSource(store: ObjectStore, args: {
|
|
19
35
|
readonly sha: string;
|
|
20
36
|
readonly packageDir: string;
|
|
21
37
|
readonly bucket: string;
|
|
22
38
|
readonly identity: SourceIdentity;
|
|
23
39
|
}): Promise<SourceRecord | null>;
|
|
40
|
+
/**
|
|
41
|
+
* Reads a commit's record of one source and where its test files live, or `null` when the commit has no such source.
|
|
42
|
+
* A reference written by `attachSource` yields its content prefix; a plain record yields
|
|
43
|
+
* `commits/<sha>/tests/<source>/`. Throws `Invalid report content reference` when a reference's prefix does not match
|
|
44
|
+
* the expected pattern or names another source.
|
|
45
|
+
*/
|
|
24
46
|
export declare function readStoredSource(store: ObjectStore, sha: string, source: string): Promise<SourceReference | null>;
|
|
47
|
+
/** The key prefix of one test's files, `<content prefix><testId>/`, or `null` when the source is not in the commit. Throws `Invalid report test` when `testId` is not a safe key segment. */
|
|
25
48
|
export declare function sourceTestPrefix(store: ObjectStore, args: {
|
|
26
49
|
readonly sha: string;
|
|
27
50
|
readonly source: string;
|
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* @module Source content references
|
|
3
|
+
*
|
|
4
|
+
* A source (one package bucket of recorded tests in a commit) either owns commit-local files under
|
|
5
|
+
* `commits/<sha>/tests/<source>/`, or references a reusable, content-addressed copy under
|
|
6
|
+
* `content/v2/<generation>/<taskHash>/<source>/` that an identical earlier passing run already uploaded. This module
|
|
7
|
+
* names those prefixes, writes and resolves the reference, and decides when a copy is reusable. Only a passing source
|
|
8
|
+
* is ever reused, and a reference whose prefix does not fit the pattern is rejected, so a record cannot point a reader
|
|
9
|
+
* at another path.
|
|
10
|
+
*/
|
|
2
11
|
import { readUtf8, writeUtf8 } from './object-io.js';
|
|
3
12
|
import { commitPrefix, isKeySegment, sourceId, sourceKey } from './keys.js';
|
|
4
13
|
import { reportStore } from './publication.js';
|
|
14
|
+
/** The retention window index for a time: `floor(nowMs / (retentionDays * 86 400 000))`. Throws `Report time must be nonnegative` for a negative or non-finite time and `Report retention must be a positive number of days` for a non-integer or less than 1. */
|
|
5
15
|
export function reportGeneration(nowMs, retentionDays) {
|
|
6
16
|
if (!Number.isFinite(nowMs) || nowMs < 0)
|
|
7
17
|
throw new Error('Report time must be nonnegative');
|
|
@@ -9,6 +19,11 @@ export function reportGeneration(nowMs, retentionDays) {
|
|
|
9
19
|
throw new Error('Report retention must be a positive number of days');
|
|
10
20
|
return Math.floor(nowMs / (retentionDays * 86_400_000));
|
|
11
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* The key prefix of a reusable source's content: `content/v2/<generation>/<taskHash>/<source>/`. Throws
|
|
24
|
+
* `Report identity needs an exact task hash`, `Report generation must be a nonnegative integer` or `Invalid report
|
|
25
|
+
* source` when an input is malformed.
|
|
26
|
+
*/
|
|
12
27
|
export function sourceContentPrefix(identity, source) {
|
|
13
28
|
if (!/^[0-9a-f]{16,64}$/.test(identity.taskHash))
|
|
14
29
|
throw new Error('Report identity needs an exact task hash');
|
|
@@ -18,9 +33,16 @@ export function sourceContentPrefix(identity, source) {
|
|
|
18
33
|
throw new Error('Invalid report source');
|
|
19
34
|
return `content/v2/${identity.generation}/${identity.taskHash}/${source}/`;
|
|
20
35
|
}
|
|
36
|
+
/** Points a commit's source record at reusable content: writes `commits/<sha>/sources/<source>.json` as `{ record, contentPrefix }`. */
|
|
21
37
|
export async function attachSource(store, args) {
|
|
22
38
|
await writeUtf8(store, sourceKey(args.sha, args.record.source), JSON.stringify({ record: args.record, contentPrefix: args.contentPrefix }), 'application/json; charset=utf-8');
|
|
23
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Reuses an earlier passing run of the same task: when the content for `identity` is complete in the store, attaches it
|
|
42
|
+
* to `sha` and returns its record; otherwise returns `null` and writes nothing. Returns `null` also when the stored
|
|
43
|
+
* record is for another package or bucket, or did not pass. Throws for a malformed identity or a stored record that is
|
|
44
|
+
* not JSON.
|
|
45
|
+
*/
|
|
24
46
|
export async function reuseSource(store, args) {
|
|
25
47
|
const source = sourceId(args.packageDir, args.bucket);
|
|
26
48
|
const contentPrefix = sourceContentPrefix(args.identity, source);
|
|
@@ -36,6 +58,12 @@ export async function reuseSource(store, args) {
|
|
|
36
58
|
await attachSource(store, { sha: args.sha, record, contentPrefix });
|
|
37
59
|
return record;
|
|
38
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Reads a commit's record of one source and where its test files live, or `null` when the commit has no such source.
|
|
63
|
+
* A reference written by `attachSource` yields its content prefix; a plain record yields
|
|
64
|
+
* `commits/<sha>/tests/<source>/`. Throws `Invalid report content reference` when a reference's prefix does not match
|
|
65
|
+
* the expected pattern or names another source.
|
|
66
|
+
*/
|
|
39
67
|
export async function readStoredSource(store, sha, source) {
|
|
40
68
|
store = await reportStore(store, sha);
|
|
41
69
|
const text = await readUtf8(store, sourceKey(sha, source));
|
|
@@ -55,6 +83,7 @@ export async function readStoredSource(store, sha, source) {
|
|
|
55
83
|
contentPrefix: `${commitPrefix(sha)}tests/${source}/`,
|
|
56
84
|
};
|
|
57
85
|
}
|
|
86
|
+
/** The key prefix of one test's files, `<content prefix><testId>/`, or `null` when the source is not in the commit. Throws `Invalid report test` when `testId` is not a safe key segment. */
|
|
58
87
|
export async function sourceTestPrefix(store, args) {
|
|
59
88
|
if (!isKeySegment(args.testId))
|
|
60
89
|
throw new Error('Invalid report test');
|
|
@@ -1,4 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Source file entries
|
|
3
|
+
*
|
|
4
|
+
* Streams the files of a recorded source (each test's timeline and its captured frame files) as bundle entries ready to
|
|
5
|
+
* be packed into blobs. Entries come in a fixed order (tests by id, files by name) so the same source always packs into
|
|
6
|
+
* the same blobs.
|
|
7
|
+
*/
|
|
1
8
|
import type { HtmlReportTest } from './recording-bundle.js';
|
|
2
9
|
import type { BundleEntry } from './bundle.js';
|
|
3
10
|
import type { PublishSourceArgs } from './publish.js';
|
|
11
|
+
/**
|
|
12
|
+
* Yields, for each test of `tests` in `testId` order, its `report.json` and then each file the report names (sorted by
|
|
13
|
+
* name), keyed by the commit-relative layout `commits/<sha>/tests/<source>/<testId>/...`. Frame files are read from
|
|
14
|
+
* `args.local` under `runId` as the generator is consumed, one at a time. Throws `Missing report snapshot: <file>` when
|
|
15
|
+
* a named file is absent locally, and a `not a ...` error for an unsafe source, test id or file name.
|
|
16
|
+
*/
|
|
4
17
|
export declare function sourceEntries(args: PublishSourceArgs, source: string, tests: readonly HtmlReportTest[], runId: string): AsyncGenerator<BundleEntry>;
|
|
@@ -2,6 +2,12 @@ import { testFrameKey } from './recording-keys.js';
|
|
|
2
2
|
import { capturedFiles } from './frames.js';
|
|
3
3
|
import { remoteTestFileKey, remoteTestReportKey } from './keys.js';
|
|
4
4
|
const JSON_TYPE = 'application/json; charset=utf-8';
|
|
5
|
+
/**
|
|
6
|
+
* Yields, for each test of `tests` in `testId` order, its `report.json` and then each file the report names (sorted by
|
|
7
|
+
* name), keyed by the commit-relative layout `commits/<sha>/tests/<source>/<testId>/...`. Frame files are read from
|
|
8
|
+
* `args.local` under `runId` as the generator is consumed, one at a time. Throws `Missing report snapshot: <file>` when
|
|
9
|
+
* a named file is absent locally, and a `not a ...` error for an unsafe source, test id or file name.
|
|
10
|
+
*/
|
|
5
11
|
export async function* sourceEntries(args, source, tests, runId) {
|
|
6
12
|
for (const { report } of [...tests].sort((a, b) => a.report.meta.testId < b.report.meta.testId ? -1 : 1)) {
|
|
7
13
|
const { testId } = report.meta;
|
|
@@ -1,4 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Report parts of a recorded source
|
|
3
|
+
*
|
|
4
|
+
* Turns a recorded source (a package bucket's tests with their outcomes) into the report parts (checks grouped by kind)
|
|
5
|
+
* that become a layer's `parts`. Fail-closed: a source that failed as a whole always carries at least one failed check,
|
|
6
|
+
* so recorded passes cannot hide a producer that did not complete.
|
|
7
|
+
*/
|
|
1
8
|
import type { ReportPart } from './explorer-types.js';
|
|
2
9
|
import type { SourceRecord } from './protocol.js';
|
|
10
|
+
/**
|
|
11
|
+
* The main part of a source: its own evidence part (the first one that is not `performance`) re-keyed to the source,
|
|
12
|
+
* or else one check per test. Check ids become `<source>/<testId>` so two packages can name a test alike. The part's
|
|
13
|
+
* `kind` is `ui-quality` for the `ui-quality` bucket and `behavior` otherwise (unless its evidence says otherwise); a
|
|
14
|
+
* skipped test is `not-run`. When the source failed but no check did, a failing `<source>:producer-failure` check is
|
|
15
|
+
* added.
|
|
16
|
+
*/
|
|
3
17
|
export declare function sourcePart(sha: string, source: SourceRecord): ReportPart;
|
|
18
|
+
/** The parts of a source: `sourcePart`, then each `performance` evidence part as its own part with id `<source>--performance`, re-keyed to the source's time and check ids. */
|
|
4
19
|
export declare function sourceParts(sha: string, source: SourceRecord): readonly ReportPart[];
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { viewerTestId } from './keys.js';
|
|
2
|
+
/**
|
|
3
|
+
* The main part of a source: its own evidence part (the first one that is not `performance`) re-keyed to the source,
|
|
4
|
+
* or else one check per test. Check ids become `<source>/<testId>` so two packages can name a test alike. The part's
|
|
5
|
+
* `kind` is `ui-quality` for the `ui-quality` bucket and `behavior` otherwise (unless its evidence says otherwise); a
|
|
6
|
+
* skipped test is `not-run`. When the source failed but no check did, a failing `<source>:producer-failure` check is
|
|
7
|
+
* added.
|
|
8
|
+
*/
|
|
2
9
|
export function sourcePart(sha, source) {
|
|
3
10
|
const kind = source.bucket === 'ui-quality' ? 'ui-quality' : 'behavior';
|
|
4
11
|
const primary = source.evidence?.find((part) => part.kind !== 'performance');
|
|
@@ -47,6 +54,7 @@ function withProducerFailure(part, source) {
|
|
|
47
54
|
],
|
|
48
55
|
};
|
|
49
56
|
}
|
|
57
|
+
/** The parts of a source: `sourcePart`, then each `performance` evidence part as its own part with id `<source>--performance`, re-keyed to the source's time and check ids. */
|
|
50
58
|
export function sourceParts(sha, source) {
|
|
51
59
|
return [
|
|
52
60
|
sourcePart(sha, source),
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Reading a source's files
|
|
3
|
+
*
|
|
4
|
+
* Chooses how to read the files of one source of a commit: through its bundle index when it has one, otherwise as
|
|
5
|
+
* individual objects (stores written before bundles existed stay readable).
|
|
6
|
+
*/
|
|
1
7
|
import type { ObjectStore } from './object-store.js';
|
|
2
8
|
import type { BundleEntry } from './bundle.js';
|
|
9
|
+
/**
|
|
10
|
+
* Returns a function that reads one file of a source by key, resolving to its bytes and media type or `null` when it is
|
|
11
|
+
* absent. Reads through the bundle index at `commits/<sha>/bundles/<source>.json` when that has entries, otherwise reads
|
|
12
|
+
* the key directly from `store`. Throws when the index is present but not JSON.
|
|
13
|
+
*/
|
|
3
14
|
export declare function sourceReader(store: ObjectStore, args: {
|
|
4
15
|
sha: string;
|
|
5
16
|
source: string;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { bundleIndexKey, createBundleReader, readBundleIndex } from './bundle.js';
|
|
2
|
+
/**
|
|
3
|
+
* Returns a function that reads one file of a source by key, resolving to its bytes and media type or `null` when it is
|
|
4
|
+
* absent. Reads through the bundle index at `commits/<sha>/bundles/<source>.json` when that has entries, otherwise reads
|
|
5
|
+
* the key directly from `store`. Throws when the index is present but not JSON.
|
|
6
|
+
*/
|
|
2
7
|
export async function sourceReader(store, args) {
|
|
3
8
|
const index = await readBundleIndex(store, bundleIndexKey(args.sha, args.source));
|
|
4
9
|
if (Object.keys(index).length > 0)
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Matching recorded tests to specs
|
|
3
|
+
*
|
|
4
|
+
* Small lookups from a spec to the recorded tests that ran it, by the source file each test reports. Pure functions
|
|
5
|
+
* over `HtmlReportTest`.
|
|
6
|
+
*/
|
|
1
7
|
import type { HtmlReportTest } from './recording-bundle.js';
|
|
2
8
|
import type { SpecEntry } from './explorer-types.js';
|
|
3
|
-
/**
|
|
9
|
+
/** The tests whose reported `sourceFile` is the spec's path or ends with `/<path>` (so a runner's absolute path matches), after turning backslashes into `/`. A longer name that merely ends with the same letters does not match. */
|
|
4
10
|
export declare function testsForSpec(entry: SpecEntry, tests: readonly HtmlReportTest[]): readonly HtmlReportTest[];
|
|
5
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Counts recorded tests per spec path in one pass, so large suites never scan all tests per row. A test's path is its
|
|
13
|
+
* `sourceFile` (with backslashes turned into `/`) from the first occurrence of `specs/`, wherever that occurs, or the whole
|
|
14
|
+
* path when there is none; tests with no `sourceFile` are not counted.
|
|
15
|
+
*/
|
|
6
16
|
export declare function specEvidenceCounts(tests: readonly HtmlReportTest[]): ReadonlyMap<string, number>;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** The tests whose reported `sourceFile` is the spec's path or ends with `/<path>` (so a runner's absolute path matches), after turning backslashes into `/`. A longer name that merely ends with the same letters does not match. */
|
|
2
2
|
export function testsForSpec(entry, tests) {
|
|
3
3
|
return tests.filter((test) => {
|
|
4
4
|
const path = test.report.meta.sourceFile?.replaceAll('\\', '/') ?? '';
|
|
5
5
|
return path === entry.path || path.endsWith(`/${entry.path}`);
|
|
6
6
|
});
|
|
7
7
|
}
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Counts recorded tests per spec path in one pass, so large suites never scan all tests per row. A test's path is its
|
|
10
|
+
* `sourceFile` (with backslashes turned into `/`) from the first occurrence of `specs/`, wherever that occurs, or the whole
|
|
11
|
+
* path when there is none; tests with no `sourceFile` are not counted.
|
|
12
|
+
*/
|
|
9
13
|
export function specEvidenceCounts(tests) {
|
|
10
14
|
const counts = new Map();
|
|
11
15
|
for (const test of tests) {
|
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
import type { ObjectStore } from './object-store.js';
|
|
2
2
|
import type { SpecEntry, SpecInventory } from './explorer-types.js';
|
|
3
|
+
/** Whether `path` is a safe spec path: starts with `specs/` and no segment is empty, `.`, `..`, or contains a backslash or control character. */
|
|
3
4
|
export declare function isSpecPath(path: string): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Describes one spec document: its `path`, a text `fingerprint`, a `kind` (the file extension, `openslo` for
|
|
7
|
+
* `*.openslo.yaml`, `document` when there is none), a `title` (the first `Feature:` line, else the first `# ` heading,
|
|
8
|
+
* else the file name) and a `domain` (the folder right under `specs/`, or `Overview` for a file directly in it).
|
|
9
|
+
* Throws `Invalid spec path` unless `isSpecPath(path)`.
|
|
10
|
+
*/
|
|
4
11
|
export declare function specEntry(path: string, content: string): SpecEntry;
|
|
5
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Publishes a commit's spec documents and its inventory, and returns the inventory (entries sorted by path).
|
|
14
|
+
* Publish document bodies first; an inventory always names readable content. Throws `Duplicate spec` for a repeated
|
|
15
|
+
* path (before writing anything), `Invalid spec path` or `Invalid commit` for malformed input. Publishing the same
|
|
16
|
+
* documents again writes the same keys.
|
|
17
|
+
*/
|
|
6
18
|
export declare function publishInventory(store: ObjectStore, args: {
|
|
7
19
|
readonly sha: string;
|
|
8
20
|
readonly documents: readonly {
|
|
@@ -10,5 +22,7 @@ export declare function publishInventory(store: ObjectStore, args: {
|
|
|
10
22
|
readonly content: string;
|
|
11
23
|
}[];
|
|
12
24
|
}): Promise<SpecInventory>;
|
|
25
|
+
/** Reads a commit's inventory, or `null` when it has none. Throws `Invalid spec inventory` when the stored one is for another sha, malformed, or names an unsafe path, and `Invalid commit` for a bad sha. */
|
|
13
26
|
export declare function readInventory(store: ObjectStore, sha: string): Promise<SpecInventory | null>;
|
|
27
|
+
/** Reads one spec document's text, or `null` when the commit has no inventory or the inventory does not list `path`. Throws `Invalid spec path` for an unsafe path. */
|
|
14
28
|
export declare function readSpecDocument(store: ObjectStore, sha: string, path: string): Promise<string | null>;
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Spec inventory of a commit
|
|
3
|
+
*
|
|
4
|
+
* The older, commit-keyed record of which spec documents a commit has: `commits/<sha>/inventory.json` (the entries) and
|
|
5
|
+
* `commits/<sha>/specs/<path>` (their bodies). Document bodies are written first, so an inventory always names readable
|
|
6
|
+
* content. The newer form is the spec set (`spec-set.ts`).
|
|
7
|
+
*/
|
|
1
8
|
import { textFingerprint } from './layer-merge.js';
|
|
2
9
|
import { readUtf8, writeUtf8 } from './object-io.js';
|
|
3
10
|
import { eachLimited } from './completion-order.js';
|
|
4
11
|
import { recordExplorerVersion } from './explorer-versions.js';
|
|
5
12
|
import { isCommitSha } from './keys.js';
|
|
13
|
+
/** Whether `path` is a safe spec path: starts with `specs/` and no segment is empty, `.`, `..`, or contains a backslash or control character. */
|
|
6
14
|
export function isSpecPath(path) {
|
|
7
15
|
return (path.startsWith('specs/') &&
|
|
8
16
|
path
|
|
@@ -12,6 +20,12 @@ export function isSpecPath(path) {
|
|
|
12
20
|
part !== '..' &&
|
|
13
21
|
!/[\\\u0000-\u001f]/.test(part)));
|
|
14
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Describes one spec document: its `path`, a text `fingerprint`, a `kind` (the file extension, `openslo` for
|
|
25
|
+
* `*.openslo.yaml`, `document` when there is none), a `title` (the first `Feature:` line, else the first `# ` heading,
|
|
26
|
+
* else the file name) and a `domain` (the folder right under `specs/`, or `Overview` for a file directly in it).
|
|
27
|
+
* Throws `Invalid spec path` unless `isSpecPath(path)`.
|
|
28
|
+
*/
|
|
15
29
|
export function specEntry(path, content) {
|
|
16
30
|
if (!isSpecPath(path))
|
|
17
31
|
throw new Error('Invalid spec path');
|
|
@@ -41,7 +55,12 @@ function documentKey(sha, path) {
|
|
|
41
55
|
inventoryKey(sha);
|
|
42
56
|
return `commits/${sha}/specs/${encodeURIComponent(path)}`;
|
|
43
57
|
}
|
|
44
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Publishes a commit's spec documents and its inventory, and returns the inventory (entries sorted by path).
|
|
60
|
+
* Publish document bodies first; an inventory always names readable content. Throws `Duplicate spec` for a repeated
|
|
61
|
+
* path (before writing anything), `Invalid spec path` or `Invalid commit` for malformed input. Publishing the same
|
|
62
|
+
* documents again writes the same keys.
|
|
63
|
+
*/
|
|
45
64
|
export async function publishInventory(store, args) {
|
|
46
65
|
const entries = args.documents
|
|
47
66
|
.map((doc) => specEntry(doc.path, doc.content))
|
|
@@ -56,6 +75,7 @@ export async function publishInventory(store, args) {
|
|
|
56
75
|
await recordExplorerVersion(store, args.sha);
|
|
57
76
|
return inventory;
|
|
58
77
|
}
|
|
78
|
+
/** Reads a commit's inventory, or `null` when it has none. Throws `Invalid spec inventory` when the stored one is for another sha, malformed, or names an unsafe path, and `Invalid commit` for a bad sha. */
|
|
59
79
|
export async function readInventory(store, sha) {
|
|
60
80
|
const raw = await readUtf8(store, inventoryKey(sha));
|
|
61
81
|
if (raw === null)
|
|
@@ -67,6 +87,7 @@ export async function readInventory(store, sha) {
|
|
|
67
87
|
throw new Error('Invalid spec inventory');
|
|
68
88
|
return inventory;
|
|
69
89
|
}
|
|
90
|
+
/** Reads one spec document's text, or `null` when the commit has no inventory or the inventory does not list `path`. Throws `Invalid spec path` for an unsafe path. */
|
|
70
91
|
export async function readSpecDocument(store, sha, path) {
|
|
71
92
|
const inventory = await readInventory(store, sha);
|
|
72
93
|
if (!inventory?.entries.some((entry) => entry.path === path))
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { EvidenceCheck, SpecEntry } from './explorer-types.js';
|
|
2
|
+
import type { IngestedChecks } from './ingest.js';
|
|
3
|
+
/** Where specs are and how to resolve them for `linkChecksToSpecs`. */
|
|
4
|
+
export type SpecLinks = {
|
|
5
|
+
/** Every file under this directory is a spec named `specs/<its path>`. Relative to `base`. */
|
|
6
|
+
readonly specsDirectory?: string;
|
|
7
|
+
/** Every check in the report belongs to this spec. A path relative to `base`. */
|
|
8
|
+
readonly spec?: string;
|
|
9
|
+
/** The directory that relative paths (`specsDirectory`, `spec`) resolve against, usually the repository root. */
|
|
10
|
+
readonly base: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* A test report rarely says which spec a test checks. Say so here, in the order of how sure it is:
|
|
14
|
+
*
|
|
15
|
+
* 1. `spec`: the whole report is one spec's checks (a report per feature file).
|
|
16
|
+
* 2. A path the report gives that is one of the specs.
|
|
17
|
+
* 3. A name the report gives for the suite, class or file that is a spec's title or file name
|
|
18
|
+
* (Cucumber writes the feature's title as the suite), or the first part of a check's title before
|
|
19
|
+
* ` > `.
|
|
20
|
+
*
|
|
21
|
+
* What cannot be linked stays unlinked, and is reported under no spec rather than a wrong one.
|
|
22
|
+
* Returns the checks and the specs they now belong to, with fingerprints, so a later reader can warn
|
|
23
|
+
* when a spec changed after the layer was recorded.
|
|
24
|
+
*/
|
|
25
|
+
export declare function linkChecksToSpecs(ingested: IngestedChecks, links: SpecLinks): Promise<{
|
|
26
|
+
readonly checks: readonly EvidenceCheck[];
|
|
27
|
+
readonly specs: readonly SpecEntry[];
|
|
28
|
+
}>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Linking checks to specs
|
|
3
|
+
*
|
|
4
|
+
* Decides which spec each imported check belongs to, so results from a JUnit or CTRF file land under the right spec in
|
|
5
|
+
* the report. It reads only the specs directory and the one spec file the caller names explicitly; a report is data and
|
|
6
|
+
* does not choose which files Atlas opens. Uses Node's `fs`.
|
|
7
|
+
*/
|
|
8
|
+
import { readFile } from 'node:fs/promises';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { loadDirectoryOutline, specKindOf } from './directory-outline.js';
|
|
11
|
+
import { specPath, textFingerprint } from './layer-merge.js';
|
|
12
|
+
const words = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
|
|
13
|
+
/** A file's name without the extension of a spec: `user.profile.feature` is `user.profile`, `cart.openslo.yaml` is `cart`. */
|
|
14
|
+
const stem = (file) => path.posix.basename(file).replace(/(\.openslo|\.machine)?\.(feature|qnt|dmn|als|ya?ml|json|scxml|md|txt|xml)$/i, '');
|
|
15
|
+
/**
|
|
16
|
+
* A test report rarely says which spec a test checks. Say so here, in the order of how sure it is:
|
|
17
|
+
*
|
|
18
|
+
* 1. `spec`: the whole report is one spec's checks (a report per feature file).
|
|
19
|
+
* 2. A path the report gives that is one of the specs.
|
|
20
|
+
* 3. A name the report gives for the suite, class or file that is a spec's title or file name
|
|
21
|
+
* (Cucumber writes the feature's title as the suite), or the first part of a check's title before
|
|
22
|
+
* ` > `.
|
|
23
|
+
*
|
|
24
|
+
* What cannot be linked stays unlinked, and is reported under no spec rather than a wrong one.
|
|
25
|
+
* Returns the checks and the specs they now belong to, with fingerprints, so a later reader can warn
|
|
26
|
+
* when a spec changed after the layer was recorded.
|
|
27
|
+
*/
|
|
28
|
+
export async function linkChecksToSpecs(ingested, links) {
|
|
29
|
+
const outline = links.specsDirectory ? await loadDirectoryOutline(path.resolve(links.base, links.specsDirectory), { repoRoot: links.base }) : null;
|
|
30
|
+
const known = new Map((outline?.specs ?? []).map(entry => [entry.path, entry]));
|
|
31
|
+
const byName = new Map();
|
|
32
|
+
for (const entry of [...known.values()].sort((a, b) => (a.path < b.path ? -1 : 1))) {
|
|
33
|
+
for (const name of [words(entry.title), words(stem(entry.path))])
|
|
34
|
+
if (name && !byName.has(name))
|
|
35
|
+
byName.set(name, entry.path);
|
|
36
|
+
}
|
|
37
|
+
let explicit = null;
|
|
38
|
+
if (links.spec)
|
|
39
|
+
explicit = specPath(links.spec);
|
|
40
|
+
const checks = ingested.checks.map((check, index) => {
|
|
41
|
+
if (explicit)
|
|
42
|
+
return { ...check, specPath: explicit };
|
|
43
|
+
if (check.specPath && known.has(specPath(check.specPath)))
|
|
44
|
+
return { ...check, specPath: specPath(check.specPath) };
|
|
45
|
+
const first = check.title.split(/ [>›] /)[0] ?? '';
|
|
46
|
+
for (const hint of [...(ingested.hints?.[index] ?? []), first]) {
|
|
47
|
+
const found = byName.get(words(hint)) ?? byName.get(words(stem(hint)));
|
|
48
|
+
if (found)
|
|
49
|
+
return { ...check, specPath: found };
|
|
50
|
+
}
|
|
51
|
+
return check;
|
|
52
|
+
});
|
|
53
|
+
const used = [...new Set(checks.flatMap(check => (check.specPath ? [check.specPath] : [])))].sort();
|
|
54
|
+
const specs = [];
|
|
55
|
+
for (const used_ of used) {
|
|
56
|
+
const entry = known.get(used_);
|
|
57
|
+
if (entry) {
|
|
58
|
+
specs.push(entry);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
// An explicit spec outside a specs directory: read it where it is, if it is there. Nothing else
|
|
62
|
+
// a report names is read: a report is data, and does not choose which files Atlas opens.
|
|
63
|
+
if (used_ !== explicit || !links.spec)
|
|
64
|
+
continue;
|
|
65
|
+
const text = await readFile(path.resolve(links.base, links.spec), 'utf8').catch(() => null);
|
|
66
|
+
if (text !== null)
|
|
67
|
+
specs.push({ path: used_, domain: path.basename(links.base) || 'product', kind: specKindOf(used_), title: path.posix.basename(used_), fingerprint: textFingerprint(text) });
|
|
68
|
+
}
|
|
69
|
+
return { checks, specs };
|
|
70
|
+
}
|