@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
package/dist/detox/src/index.js
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Detox driver
|
|
3
|
+
*
|
|
4
|
+
* The Atlas driver for native iOS and Android apps tested with Detox (https://wix.github.io/Detox/).
|
|
5
|
+
* It owns launching and terminating the app, mapping signals to Detox matchers, screenshots, and
|
|
6
|
+
* logging taps and typing to the step timeline; it does not record video (Detox does that through its
|
|
7
|
+
* artifacts plugin). It must run inside a Detox test, where Detox's `device`, `element`, `by` and
|
|
8
|
+
* `waitFor` globals exist, or be given them as `api`. Detox is typed structurally here, so importing
|
|
9
|
+
* this module needs no Detox install.
|
|
10
|
+
*/
|
|
1
11
|
import { copyFileSync, mkdirSync } from 'node:fs';
|
|
2
12
|
import path from 'node:path';
|
|
3
13
|
import { describeSignal } from '../../core/src/signals.js';
|
|
4
14
|
/**
|
|
5
|
-
*
|
|
6
|
-
* text `by.text`, a role with a name `by.label` (with the iOS `button` trait when Detox
|
|
7
|
-
* Detox matches exact strings, so regular expressions and CSS selectors are refused.
|
|
15
|
+
* Translates a signal into a Detox matcher: test IDs are `by.id` (React Native's `testID`), labels
|
|
16
|
+
* `by.label`, text `by.text`, a role with a name `by.label` (with the iOS `button` trait when Detox
|
|
17
|
+
* has it). Detox matches exact strings, so regular expressions and CSS selectors are refused.
|
|
18
|
+
*
|
|
19
|
+
* @param api the Detox API whose `by` builds the matcher.
|
|
20
|
+
* @throws Error for a `css` signal, a regular expression, or a role without a name.
|
|
8
21
|
*/
|
|
9
22
|
export function detoxMatcher(api, signal) {
|
|
10
23
|
if ('testId' in signal)
|
|
@@ -26,9 +39,25 @@ export function detoxMatcher(api, signal) {
|
|
|
26
39
|
return byLabel;
|
|
27
40
|
}
|
|
28
41
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
42
|
+
* Creates a driver for a native app tested with Detox. Every path attempt relaunches the app
|
|
43
|
+
* (`device.launchApp({ newInstance: true })`) and terminates it afterwards. Screenshots come from
|
|
44
|
+
* `device.takeScreenshot`. Detox records video per test through its artifacts plugin rather than per
|
|
45
|
+
* call, so this driver does not record clips; enable Detox's `--record-videos` to keep a video of
|
|
46
|
+
* each attempt.
|
|
47
|
+
*
|
|
48
|
+
* The Detox globals are read when an attempt opens, so call `runConfig` from inside a Detox test.
|
|
49
|
+
* Signals are resolved with `detoxMatcher`, so regular expressions and CSS selectors are refused.
|
|
50
|
+
*
|
|
51
|
+
* @param options `api` to use instead of the globals, and `launchArgs` for the app.
|
|
52
|
+
* @returns a driver whose context is a `DetoxContext`.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* const config = defineConfig<DetoxContext>({
|
|
56
|
+
* specs: './specs',
|
|
57
|
+
* driver: detoxDriver({ launchArgs: { resetData: true } }),
|
|
58
|
+
* implementation
|
|
59
|
+
* });
|
|
60
|
+
* await runConfig(config, { mode: 'fast' });
|
|
32
61
|
*/
|
|
33
62
|
export function detoxDriver(options = {}) {
|
|
34
63
|
const api = () => options.api ?? globalThis;
|
|
@@ -74,6 +103,7 @@ export function detoxDriver(options = {}) {
|
|
|
74
103
|
}
|
|
75
104
|
};
|
|
76
105
|
}
|
|
106
|
+
/** Whether the matcher's element is visible right now: asks Detox with a zero timeout and counts any failure as not visible. */
|
|
77
107
|
async function visible(ctx, matcher) {
|
|
78
108
|
try {
|
|
79
109
|
await ctx.waitFor(ctx.element(matcher)).toBeVisible().withTimeout(0);
|
|
@@ -83,7 +113,15 @@ async function visible(ctx, matcher) {
|
|
|
83
113
|
return false;
|
|
84
114
|
}
|
|
85
115
|
}
|
|
86
|
-
/**
|
|
116
|
+
/**
|
|
117
|
+
* Builds a state implementation recognised by Detox matchers: matched when every `all` matcher is
|
|
118
|
+
* visible and no `none` matcher is. `checks` pair a statement from the state's `meta.checks` with
|
|
119
|
+
* the matcher that proves it. Each entry's `name` appears in failure reports. Unlike
|
|
120
|
+
* `screen(...)` from the signals module it takes matchers, not signals, and has no `any`.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* states: { 'No todos': detoxScreen({ all: [{ matcher: by.text('Nothing to do yet'), name: 'empty message' }] }) }
|
|
124
|
+
*/
|
|
87
125
|
export function detoxScreen(spec) {
|
|
88
126
|
return {
|
|
89
127
|
async recognize(ctx) {
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Playwright driver
|
|
3
|
+
*
|
|
4
|
+
* `playwrightDriver()` is the core `Driver` that runs a statechart's paths against a web app (or a React Native app
|
|
5
|
+
* on the web) in Chromium. Each path attempt gets its own browser context and page; the `PlaywrightContext` it passes
|
|
6
|
+
* to event and state implementations carries the page and paced user actions. Screens are recognised by signals
|
|
7
|
+
* (`signals.ts`), and screenshots, clips and touch overlay come from `media.ts`.
|
|
8
|
+
*
|
|
9
|
+
* @see docs/statecharts.md
|
|
10
|
+
*/
|
|
1
11
|
import type { Driver } from '../../core/src/index.js';
|
|
2
12
|
import type { SignalContext } from '../../core/src/signals.js';
|
|
3
13
|
import type { Browser, BrowserContext, BrowserContextOptions, LaunchOptions, Page } from 'playwright';
|
|
@@ -14,11 +24,13 @@ export type PlaywrightContext = SignalContext & {
|
|
|
14
24
|
/** Paced taps and typing, logged to the step's timeline and shown by the touch overlay in videos. */
|
|
15
25
|
user: UserActions;
|
|
16
26
|
};
|
|
27
|
+
/** Options of `playwrightDriver`; every field is optional. */
|
|
17
28
|
export type PlaywrightDriverOptions = {
|
|
18
29
|
/** Where `goto` paths resolve; the implementation's setup decides where to open. */
|
|
19
30
|
baseUrl?: string | (() => string | Promise<string>);
|
|
20
31
|
/** Browser context options; a 390×844 phone at device scale factor 3 with touch by default. */
|
|
21
32
|
device?: BrowserContextOptions;
|
|
33
|
+
/** Options for `chromium.launch`. */
|
|
22
34
|
launch?: LaunchOptions;
|
|
23
35
|
/** Override the pacing used in showcase mode. */
|
|
24
36
|
pacing?: Partial<Pacing>;
|
|
@@ -37,8 +49,32 @@ export type PlaywrightDriverOptions = {
|
|
|
37
49
|
concurrency?: number;
|
|
38
50
|
};
|
|
39
51
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
52
|
+
* Drives a web app (or a React Native app on the web) in Chromium with Playwright, for `atlas run`. Pass it in the
|
|
53
|
+
* config as `driver: () => playwrightDriver(options)`. The browser launches on first use (and again if it was
|
|
54
|
+
* disconnected) and is shared by every context; each path attempt opens a fresh context from `options.device`
|
|
55
|
+
* (default `PHONE`). Screenshots are taken at the device's physical resolution; clips are recorded at that
|
|
56
|
+
* resolution too and encoded with FFmpeg as H.264 MP4. FFmpeg must be on the PATH: each screenshot also writes a
|
|
57
|
+
* WebP thumbnail with it, so without FFmpeg no screenshot is recorded (the run goes on). A Playwright trace is
|
|
58
|
+
* kept only for a failed attempt that was traced (see `trace`), and `dispose` closes the browser.
|
|
59
|
+
*
|
|
60
|
+
* @see docs/statecharts.md
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* import { defineConfig } from '@crvouga/atlas';
|
|
65
|
+
* import { playwrightDriver, screen, text } from '@crvouga/atlas/playwright';
|
|
66
|
+
*
|
|
67
|
+
* export default defineConfig({
|
|
68
|
+
* specs: './specs',
|
|
69
|
+
* driver: () => playwrightDriver({ clock: true }),
|
|
70
|
+
* implementation: {
|
|
71
|
+
* async setup({ page }) { await page.goto('http://127.0.0.1:4317'); },
|
|
72
|
+
* events: {
|
|
73
|
+
* // one entry per event of the spec
|
|
74
|
+
* },
|
|
75
|
+
* states: { 'No todos': screen({ all: [text('Nothing to do yet')] }) }
|
|
76
|
+
* }
|
|
77
|
+
* });
|
|
78
|
+
* ```
|
|
43
79
|
*/
|
|
44
80
|
export declare function playwrightDriver(options?: PlaywrightDriverOptions): Driver<PlaywrightContext>;
|
|
@@ -2,9 +2,33 @@ import { chromium } from 'playwright';
|
|
|
2
2
|
import { captureScreen, FAST_PACING, PHONE, ScreencastRecorder, SHOWCASE_PACING, UserActions } from './media.js';
|
|
3
3
|
import { locate } from './signals.js';
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Drives a web app (or a React Native app on the web) in Chromium with Playwright, for `atlas run`. Pass it in the
|
|
6
|
+
* config as `driver: () => playwrightDriver(options)`. The browser launches on first use (and again if it was
|
|
7
|
+
* disconnected) and is shared by every context; each path attempt opens a fresh context from `options.device`
|
|
8
|
+
* (default `PHONE`). Screenshots are taken at the device's physical resolution; clips are recorded at that
|
|
9
|
+
* resolution too and encoded with FFmpeg as H.264 MP4. FFmpeg must be on the PATH: each screenshot also writes a
|
|
10
|
+
* WebP thumbnail with it, so without FFmpeg no screenshot is recorded (the run goes on). A Playwright trace is
|
|
11
|
+
* kept only for a failed attempt that was traced (see `trace`), and `dispose` closes the browser.
|
|
12
|
+
*
|
|
13
|
+
* @see docs/statecharts.md
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { defineConfig } from '@crvouga/atlas';
|
|
18
|
+
* import { playwrightDriver, screen, text } from '@crvouga/atlas/playwright';
|
|
19
|
+
*
|
|
20
|
+
* export default defineConfig({
|
|
21
|
+
* specs: './specs',
|
|
22
|
+
* driver: () => playwrightDriver({ clock: true }),
|
|
23
|
+
* implementation: {
|
|
24
|
+
* async setup({ page }) { await page.goto('http://127.0.0.1:4317'); },
|
|
25
|
+
* events: {
|
|
26
|
+
* // one entry per event of the spec
|
|
27
|
+
* },
|
|
28
|
+
* states: { 'No todos': screen({ all: [text('Nothing to do yet')] }) }
|
|
29
|
+
* }
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
8
32
|
*/
|
|
9
33
|
export function playwrightDriver(options = {}) {
|
|
10
34
|
let browser = null;
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Playwright driver entry point (`@crvouga/atlas/playwright`)
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the Playwright driver for statechart runs (`playwrightDriver`), the helpers an implementation uses to
|
|
5
|
+
* recognise screens and tap or type (`screen`, `recognize`, `locate`, the signal constructors and `UserActions`), and
|
|
6
|
+
* the media tools (`captureScreen`, `ScreencastRecorder`, `ffmpeg`, `PHONE` and the pacing presets). Needs the
|
|
7
|
+
* `playwright` package; clips and thumbnails need FFmpeg on the PATH.
|
|
8
|
+
*
|
|
9
|
+
* @see docs/statecharts.md
|
|
10
|
+
*/
|
|
1
11
|
export { playwrightDriver, type PlaywrightContext, type PlaywrightDriverOptions } from './driver.js';
|
|
2
12
|
export { captureScreen, ScreencastRecorder, UserActions, PHONE, SHOWCASE_PACING, FAST_PACING, ffmpeg, type Pacing } from './media.js';
|
|
3
13
|
export { recognize, checkSignals, screen, locate, describeSignal, testId, text, role, label, css, type Signal, type SignatureSpec } from './signals.js';
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Playwright driver entry point (`@crvouga/atlas/playwright`)
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the Playwright driver for statechart runs (`playwrightDriver`), the helpers an implementation uses to
|
|
5
|
+
* recognise screens and tap or type (`screen`, `recognize`, `locate`, the signal constructors and `UserActions`), and
|
|
6
|
+
* the media tools (`captureScreen`, `ScreencastRecorder`, `ffmpeg`, `PHONE` and the pacing presets). Needs the
|
|
7
|
+
* `playwright` package; clips and thumbnails need FFmpeg on the PATH.
|
|
8
|
+
*
|
|
9
|
+
* @see docs/statecharts.md
|
|
10
|
+
*/
|
|
1
11
|
export { playwrightDriver } from './driver.js';
|
|
2
12
|
export { captureScreen, ScreencastRecorder, UserActions, PHONE, SHOWCASE_PACING, FAST_PACING, ffmpeg } from './media.js';
|
|
3
13
|
export { recognize, checkSignals, screen, locate, describeSignal, testId, text, role, label, css } from './signals.js';
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { Locator, Page } from 'playwright';
|
|
2
2
|
import type { Timeline } from '../../core/src/index.js';
|
|
3
3
|
import type { Signal } from '../../core/src/signals.js';
|
|
4
|
+
/**
|
|
5
|
+
* Pauses, in milliseconds, that make a showcase video readable: how long the touch indicator takes to glide to a tap
|
|
6
|
+
* target, the pause before the tap, the delay between typed characters, and the holds after and before a step.
|
|
7
|
+
*/
|
|
4
8
|
export type Pacing = {
|
|
5
9
|
moveMs: number;
|
|
6
10
|
pauseBeforeTapMs: number;
|
|
@@ -8,8 +12,14 @@ export type Pacing = {
|
|
|
8
12
|
holdAfterMs: number;
|
|
9
13
|
holdBeforeMs: number;
|
|
10
14
|
};
|
|
15
|
+
/** The default pacing of showcase mode: a visible glide and pauses, so a person can follow the clip. */
|
|
11
16
|
export declare const SHOWCASE_PACING: Pacing;
|
|
17
|
+
/** No pauses at all; the pacing of fast mode. */
|
|
12
18
|
export declare const FAST_PACING: Pacing;
|
|
19
|
+
/**
|
|
20
|
+
* The default `device` of `playwrightDriver`: a 390 by 844 CSS pixel viewport at device scale factor 3 (screenshots
|
|
21
|
+
* are 1170 by 2532 pixels), a mobile user agent and touch.
|
|
22
|
+
*/
|
|
13
23
|
export declare const PHONE: {
|
|
14
24
|
readonly viewport: {
|
|
15
25
|
readonly width: 390;
|
|
@@ -19,31 +29,61 @@ export declare const PHONE: {
|
|
|
19
29
|
readonly isMobile: true;
|
|
20
30
|
readonly hasTouch: true;
|
|
21
31
|
};
|
|
22
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Taps and typing a person would do, paced for showcase videos and logged to the step timeline. Provided to event
|
|
34
|
+
* implementations as `user` on the `PlaywrightContext`. `showTouches` turns on the touch indicator (showcase mode).
|
|
35
|
+
*/
|
|
23
36
|
export declare class UserActions {
|
|
24
37
|
readonly page: Page;
|
|
25
38
|
readonly pacing: Pacing;
|
|
26
39
|
timeline: Timeline;
|
|
27
40
|
readonly showTouches: boolean;
|
|
28
41
|
constructor(page: Page, pacing: Pacing, timeline: Timeline, showTouches: boolean);
|
|
42
|
+
/**
|
|
43
|
+
* Registers the touch indicator overlay to be added to every page load, so it exists before the app's scripts run.
|
|
44
|
+
* Call it once per page, before navigating.
|
|
45
|
+
*/
|
|
29
46
|
static install(page: Page): Promise<void>;
|
|
30
47
|
private overlay;
|
|
31
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Taps the centre of a locator or a signal (resolved with `locate`). Waits up to 15 s for it to be visible, scrolls
|
|
50
|
+
* it
|
|
51
|
+
* into view, logs a `tap` entry labelled `label` to the timeline, then clicks (10 s timeout). In showcase mode the
|
|
52
|
+
* touch
|
|
53
|
+
* indicator glides there first. Throws when the target has no position on screen.
|
|
54
|
+
*/
|
|
32
55
|
tap(target: Locator | Signal, label: string): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Taps `target`, clears it, then types `text` one character at a time, `pacing.typeDelayMs` apart. Logs a `tap` and
|
|
58
|
+
* a `type` entry labelled `label` to the timeline.
|
|
59
|
+
*/
|
|
33
60
|
type(target: Locator | Signal, text: string, label: string): Promise<void>;
|
|
61
|
+
/** Waits `ms` milliseconds; does nothing when `ms` is zero or less. */
|
|
34
62
|
hold(ms: number): Promise<void>;
|
|
63
|
+
/** Hides the touch indicator, so a screenshot does not show it. */
|
|
35
64
|
hideTouches(): Promise<void>;
|
|
65
|
+
/** Shows the touch indicator again after `hideTouches`. */
|
|
36
66
|
showTouchLayer(): Promise<void>;
|
|
37
67
|
}
|
|
68
|
+
/** The files `captureScreen` wrote: the full-size PNG and its WebP thumbnail. */
|
|
38
69
|
export type ScreenshotFiles = {
|
|
39
70
|
png: string;
|
|
40
71
|
webp?: string;
|
|
41
72
|
};
|
|
42
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* Takes a settled, deterministic screenshot at device resolution, with the touch layer hidden: waits for the network to
|
|
75
|
+
* go quiet (up to 5 s) and for fonts, disables animations and hides the caret. Writes the PNG to `file` (creating its
|
|
76
|
+
* folder) and a 400 px wide WebP thumbnail beside it with FFmpeg. `mask` lists locators to paint over. Rejects when
|
|
77
|
+
* FFmpeg is missing or fails.
|
|
78
|
+
*/
|
|
43
79
|
export declare function captureScreen(page: Page, driver: UserActions, file: string, mask?: Locator[]): Promise<ScreenshotFiles>;
|
|
80
|
+
/**
|
|
81
|
+
* Runs `ffmpeg` with `args` and resolves when it exits with code 0. Rejects with the spawn error when `ffmpeg` is not
|
|
82
|
+
* on the PATH, or with `ffmpeg <code>: <stderr>` when it fails.
|
|
83
|
+
*/
|
|
44
84
|
export declare function ffmpeg(args: string[]): Promise<void>;
|
|
45
85
|
/**
|
|
46
|
-
* Records a clip at
|
|
86
|
+
* Records a clip of a page at its physical resolution (1170×2532 for the default `PHONE`). Chrome's screencast, and
|
|
47
87
|
* Playwright's built-in video that rides on it, only deliver CSS-pixel frames (390×844) and
|
|
48
88
|
* upscale them, so text goes soft. Instead this loops device-scale JPEG captures (about 20 fps
|
|
49
89
|
* here), keeps each frame's own timestamp, and encodes H.264 at 30 fps, holding each frame for as
|
|
@@ -59,7 +99,13 @@ export declare class ScreencastRecorder {
|
|
|
59
99
|
private running;
|
|
60
100
|
private loop;
|
|
61
101
|
constructor(page: Page, scale?: number);
|
|
102
|
+
/** Begins capturing frames of the page into `workDirectory`, which is emptied first. Call `stop` to encode them. */
|
|
62
103
|
start(workDirectory: string): Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* Stops capturing and encodes the frames to the MP4 file `output` (the poster is written beside it, with `.mp4`
|
|
106
|
+
* replaced by `.poster.webp`), then deletes the work directory. Resolves to `{ video, poster, durationMs }`, or
|
|
107
|
+
* `null` when no frame was captured.
|
|
108
|
+
*/
|
|
63
109
|
stop(output: string): Promise<{
|
|
64
110
|
video: string;
|
|
65
111
|
poster: string;
|
|
@@ -1,8 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Playwright screenshots, clips and paced actions
|
|
3
|
+
*
|
|
4
|
+
* The media side of the Playwright driver: `UserActions` taps and types like a person (pausing, and drawing a touch
|
|
5
|
+
* indicator in showcase videos) while logging each action to the step's timeline; `captureScreen` takes a settled,
|
|
6
|
+
* deterministic screenshot at device resolution plus a WebP thumbnail; `ScreencastRecorder` records an H.264 clip at
|
|
7
|
+
* device resolution; `ffmpeg` runs the `ffmpeg` binary, which must be on the PATH for thumbnails and clips.
|
|
8
|
+
*
|
|
9
|
+
* @see docs/statecharts.md
|
|
10
|
+
*/
|
|
1
11
|
import { spawn } from 'node:child_process';
|
|
2
12
|
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
13
|
import path from 'node:path';
|
|
4
14
|
import { locate } from './signals.js';
|
|
5
15
|
const isLocator = (target) => 'waitFor' in target;
|
|
16
|
+
/** The default pacing of showcase mode: a visible glide and pauses, so a person can follow the clip. */
|
|
6
17
|
export const SHOWCASE_PACING = {
|
|
7
18
|
moveMs: 600,
|
|
8
19
|
pauseBeforeTapMs: 300,
|
|
@@ -10,6 +21,7 @@ export const SHOWCASE_PACING = {
|
|
|
10
21
|
holdAfterMs: 1000,
|
|
11
22
|
holdBeforeMs: 500
|
|
12
23
|
};
|
|
24
|
+
/** No pauses at all; the pacing of fast mode. */
|
|
13
25
|
export const FAST_PACING = {
|
|
14
26
|
moveMs: 0,
|
|
15
27
|
pauseBeforeTapMs: 0,
|
|
@@ -17,6 +29,10 @@ export const FAST_PACING = {
|
|
|
17
29
|
holdAfterMs: 0,
|
|
18
30
|
holdBeforeMs: 0
|
|
19
31
|
};
|
|
32
|
+
/**
|
|
33
|
+
* The default `device` of `playwrightDriver`: a 390 by 844 CSS pixel viewport at device scale factor 3 (screenshots
|
|
34
|
+
* are 1170 by 2532 pixels), a mobile user agent and touch.
|
|
35
|
+
*/
|
|
20
36
|
export const PHONE = {
|
|
21
37
|
viewport: { width: 390, height: 844 },
|
|
22
38
|
deviceScaleFactor: 3,
|
|
@@ -66,7 +82,10 @@ const TOUCH_OVERLAY = `(() => {
|
|
|
66
82
|
};
|
|
67
83
|
if (document.documentElement) install(); else document.addEventListener('DOMContentLoaded', install);
|
|
68
84
|
})();`;
|
|
69
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* Taps and typing a person would do, paced for showcase videos and logged to the step timeline. Provided to event
|
|
87
|
+
* implementations as `user` on the `PlaywrightContext`. `showTouches` turns on the touch indicator (showcase mode).
|
|
88
|
+
*/
|
|
70
89
|
export class UserActions {
|
|
71
90
|
page;
|
|
72
91
|
pacing;
|
|
@@ -78,6 +97,10 @@ export class UserActions {
|
|
|
78
97
|
this.timeline = timeline;
|
|
79
98
|
this.showTouches = showTouches;
|
|
80
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Registers the touch indicator overlay to be added to every page load, so it exists before the app's scripts run.
|
|
102
|
+
* Call it once per page, before navigating.
|
|
103
|
+
*/
|
|
81
104
|
static async install(page) {
|
|
82
105
|
await page.addInitScript(TOUCH_OVERLAY);
|
|
83
106
|
}
|
|
@@ -91,7 +114,13 @@ export class UserActions {
|
|
|
91
114
|
}, { fn, args })
|
|
92
115
|
.catch(() => undefined);
|
|
93
116
|
}
|
|
94
|
-
/**
|
|
117
|
+
/**
|
|
118
|
+
* Taps the centre of a locator or a signal (resolved with `locate`). Waits up to 15 s for it to be visible, scrolls
|
|
119
|
+
* it
|
|
120
|
+
* into view, logs a `tap` entry labelled `label` to the timeline, then clicks (10 s timeout). In showcase mode the
|
|
121
|
+
* touch
|
|
122
|
+
* indicator glides there first. Throws when the target has no position on screen.
|
|
123
|
+
*/
|
|
95
124
|
async tap(target, label) {
|
|
96
125
|
const locator = isLocator(target) ? target : locate(this.page, target);
|
|
97
126
|
await locator.waitFor({ state: 'visible', timeout: 15_000 });
|
|
@@ -113,6 +142,10 @@ export class UserActions {
|
|
|
113
142
|
await this.overlay('press', false);
|
|
114
143
|
}
|
|
115
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Taps `target`, clears it, then types `text` one character at a time, `pacing.typeDelayMs` apart. Logs a `tap` and
|
|
147
|
+
* a `type` entry labelled `label` to the timeline.
|
|
148
|
+
*/
|
|
116
149
|
async type(target, text, label) {
|
|
117
150
|
const locator = isLocator(target) ? target : locate(this.page, target);
|
|
118
151
|
await this.tap(locator, label);
|
|
@@ -120,18 +153,26 @@ export class UserActions {
|
|
|
120
153
|
await locator.fill('');
|
|
121
154
|
await locator.pressSequentially(text, { delay: this.pacing.typeDelayMs });
|
|
122
155
|
}
|
|
156
|
+
/** Waits `ms` milliseconds; does nothing when `ms` is zero or less. */
|
|
123
157
|
async hold(ms) {
|
|
124
158
|
if (ms > 0)
|
|
125
159
|
await this.page.waitForTimeout(ms);
|
|
126
160
|
}
|
|
161
|
+
/** Hides the touch indicator, so a screenshot does not show it. */
|
|
127
162
|
async hideTouches() {
|
|
128
163
|
await this.overlay('visible', false);
|
|
129
164
|
}
|
|
165
|
+
/** Shows the touch indicator again after `hideTouches`. */
|
|
130
166
|
async showTouchLayer() {
|
|
131
167
|
await this.overlay('visible', true);
|
|
132
168
|
}
|
|
133
169
|
}
|
|
134
|
-
/**
|
|
170
|
+
/**
|
|
171
|
+
* Takes a settled, deterministic screenshot at device resolution, with the touch layer hidden: waits for the network to
|
|
172
|
+
* go quiet (up to 5 s) and for fonts, disables animations and hides the caret. Writes the PNG to `file` (creating its
|
|
173
|
+
* folder) and a 400 px wide WebP thumbnail beside it with FFmpeg. `mask` lists locators to paint over. Rejects when
|
|
174
|
+
* FFmpeg is missing or fails.
|
|
175
|
+
*/
|
|
135
176
|
export async function captureScreen(page, driver, file, mask = []) {
|
|
136
177
|
mkdirSync(path.dirname(file), { recursive: true });
|
|
137
178
|
await page.waitForLoadState('networkidle', { timeout: 5_000 }).catch(() => undefined);
|
|
@@ -160,6 +201,10 @@ export async function captureScreen(page, driver, file, mask = []) {
|
|
|
160
201
|
]);
|
|
161
202
|
return { png: file, webp };
|
|
162
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Runs `ffmpeg` with `args` and resolves when it exits with code 0. Rejects with the spawn error when `ffmpeg` is not
|
|
206
|
+
* on the PATH, or with `ffmpeg <code>: <stderr>` when it fails.
|
|
207
|
+
*/
|
|
163
208
|
export function ffmpeg(args) {
|
|
164
209
|
return new Promise((resolve, reject) => {
|
|
165
210
|
const child = spawn('ffmpeg', args, { stdio: ['ignore', 'ignore', 'pipe'] });
|
|
@@ -170,7 +215,7 @@ export function ffmpeg(args) {
|
|
|
170
215
|
});
|
|
171
216
|
}
|
|
172
217
|
/**
|
|
173
|
-
* Records a clip at
|
|
218
|
+
* Records a clip of a page at its physical resolution (1170×2532 for the default `PHONE`). Chrome's screencast, and
|
|
174
219
|
* Playwright's built-in video that rides on it, only deliver CSS-pixel frames (390×844) and
|
|
175
220
|
* upscale them, so text goes soft. Instead this loops device-scale JPEG captures (about 20 fps
|
|
176
221
|
* here), keeps each frame's own timestamp, and encodes H.264 at 30 fps, holding each frame for as
|
|
@@ -189,6 +234,7 @@ export class ScreencastRecorder {
|
|
|
189
234
|
this.page = page;
|
|
190
235
|
this.scale = scale;
|
|
191
236
|
}
|
|
237
|
+
/** Begins capturing frames of the page into `workDirectory`, which is emptied first. Call `stop` to encode them. */
|
|
192
238
|
async start(workDirectory) {
|
|
193
239
|
this.directory = workDirectory;
|
|
194
240
|
rmSync(workDirectory, { recursive: true, force: true });
|
|
@@ -220,6 +266,11 @@ export class ScreencastRecorder {
|
|
|
220
266
|
}
|
|
221
267
|
})();
|
|
222
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Stops capturing and encodes the frames to the MP4 file `output` (the poster is written beside it, with `.mp4`
|
|
271
|
+
* replaced by `.poster.webp`), then deletes the work directory. Resolves to `{ video, poster, durationMs }`, or
|
|
272
|
+
* `null` when no frame was captured.
|
|
273
|
+
*/
|
|
223
274
|
async stop(output) {
|
|
224
275
|
this.stoppedAt = Date.now();
|
|
225
276
|
this.running = false;
|
|
@@ -1,24 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Playwright signal helpers
|
|
3
|
+
*
|
|
4
|
+
* Maps the driver-neutral `Signal` values (test ID, role, label, text, CSS) to Playwright locators, and builds state
|
|
5
|
+
* recognisers from them: `recognize` evaluates a UI signature, `checkSignals` verifies quoted spec text is on screen,
|
|
6
|
+
* and `screen` wraps both into a `StateImplementation` for any context that exposes `page`. Also re-exports the
|
|
7
|
+
* signal constructors so an implementation imports everything from `@crvouga/atlas/playwright`. Visibility always
|
|
8
|
+
* means the first visible match.
|
|
9
|
+
*
|
|
10
|
+
* @see docs/statecharts.md
|
|
11
|
+
*/
|
|
1
12
|
import type { CheckResult, RecognizerResult, StateImplementation } from '../../core/src/index.js';
|
|
2
13
|
import type { Signal } from '../../core/src/signals.js';
|
|
3
14
|
import type { Locator, Page } from 'playwright';
|
|
4
15
|
export { css, describeSignal, label, role, testId, text, type Signal } from '../../core/src/signals.js';
|
|
5
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Resolves a `Signal` to a Playwright locator for the first visible match, using Playwright's own role, label and
|
|
18
|
+
* text rules (text matches are substrings unless `exact`).
|
|
19
|
+
*/
|
|
6
20
|
export declare function locate(page: Page, signal: Signal): Locator;
|
|
21
|
+
/**
|
|
22
|
+
* A state's UI signature: every `all` signal visible, at least one `any` signal visible (when `any` is given), and no
|
|
23
|
+
* `none` signal visible.
|
|
24
|
+
*/
|
|
7
25
|
export type SignatureSpec = {
|
|
8
26
|
all?: Signal[];
|
|
9
27
|
any?: Signal[];
|
|
10
28
|
none?: Signal[];
|
|
11
29
|
};
|
|
12
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Recognises a state from its UI signature on `page`: every `all` signal visible, one `any` signal visible (when `any`
|
|
32
|
+
* is given), no `none` signal visible. Returns `matched` plus one entry per signal with what was expected and whether
|
|
33
|
+
* it
|
|
34
|
+
* was visible, which the report shows when the state is not recognised. A signal that cannot be evaluated counts as not
|
|
35
|
+
* visible.
|
|
36
|
+
*/
|
|
13
37
|
export declare function recognize(page: Page, spec: SignatureSpec): Promise<RecognizerResult>;
|
|
14
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Checks that each quoted piece of the spec (`check`) is visible on screen; returns one result per check, with
|
|
40
|
+
* `expected` and `actual` filled in.
|
|
41
|
+
*/
|
|
15
42
|
export declare function checkSignals(page: Page, checks: {
|
|
16
43
|
check: string;
|
|
17
44
|
signal: Signal;
|
|
18
45
|
}[]): Promise<CheckResult[]>;
|
|
19
46
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
47
|
+
* Builds a state implementation from a UI signature, for a context that exposes `page` (the `PlaywrightContext`). The
|
|
48
|
+
* state is recognised as `recognize` describes; `checks` become the state's `meta.checks` results. The other fields
|
|
49
|
+
* (`lookIn`, `settle`, `transient`, `sameAs`) pass through to the `StateImplementation`.
|
|
50
|
+
*
|
|
51
|
+
* @see docs/statecharts.md
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* import { role, screen, text } from '@crvouga/atlas/playwright';
|
|
56
|
+
*
|
|
57
|
+
* const states = {
|
|
58
|
+
* 'No todos': screen({ all: [text('Nothing to do yet')], none: [role('listitem')] })
|
|
59
|
+
* };
|
|
60
|
+
* ```
|
|
22
61
|
*/
|
|
23
62
|
export declare function screen<C extends {
|
|
24
63
|
page: Page;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { describeSignal } from '../../core/src/signals.js';
|
|
2
2
|
export { css, describeSignal, label, role, testId, text } from '../../core/src/signals.js';
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Resolves a `Signal` to a Playwright locator for the first visible match, using Playwright's own role, label and
|
|
5
|
+
* text rules (text matches are substrings unless `exact`).
|
|
6
|
+
*/
|
|
4
7
|
export function locate(page, signal) {
|
|
5
8
|
if ('testId' in signal)
|
|
6
9
|
return page.getByTestId(signal.testId).filter({ visible: true }).first();
|
|
@@ -15,7 +18,13 @@ export function locate(page, signal) {
|
|
|
15
18
|
return page.getByText(signal.text, signal.exact ? { exact: true } : {}).filter({ visible: true }).first();
|
|
16
19
|
}
|
|
17
20
|
const isVisible = (page, signal) => locate(page, signal).isVisible().catch(() => false);
|
|
18
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* Recognises a state from its UI signature on `page`: every `all` signal visible, one `any` signal visible (when `any`
|
|
23
|
+
* is given), no `none` signal visible. Returns `matched` plus one entry per signal with what was expected and whether
|
|
24
|
+
* it
|
|
25
|
+
* was visible, which the report shows when the state is not recognised. A signal that cannot be evaluated counts as not
|
|
26
|
+
* visible.
|
|
27
|
+
*/
|
|
19
28
|
export async function recognize(page, spec) {
|
|
20
29
|
const signals = [];
|
|
21
30
|
for (const s of spec.all ?? [])
|
|
@@ -31,7 +40,10 @@ export async function recognize(page, spec) {
|
|
|
31
40
|
signals.push({ signal: describeSignal(s), expected: 'hidden', visible: await isVisible(page, s) });
|
|
32
41
|
return { matched: signals.every((x) => (x.expected === 'visible' ? x.visible : !x.visible)), signals };
|
|
33
42
|
}
|
|
34
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Checks that each quoted piece of the spec (`check`) is visible on screen; returns one result per check, with
|
|
45
|
+
* `expected` and `actual` filled in.
|
|
46
|
+
*/
|
|
35
47
|
export async function checkSignals(page, checks) {
|
|
36
48
|
const results = [];
|
|
37
49
|
for (const { check, signal } of checks) {
|
|
@@ -41,8 +53,20 @@ export async function checkSignals(page, checks) {
|
|
|
41
53
|
return results;
|
|
42
54
|
}
|
|
43
55
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
56
|
+
* Builds a state implementation from a UI signature, for a context that exposes `page` (the `PlaywrightContext`). The
|
|
57
|
+
* state is recognised as `recognize` describes; `checks` become the state's `meta.checks` results. The other fields
|
|
58
|
+
* (`lookIn`, `settle`, `transient`, `sameAs`) pass through to the `StateImplementation`.
|
|
59
|
+
*
|
|
60
|
+
* @see docs/statecharts.md
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* import { role, screen, text } from '@crvouga/atlas/playwright';
|
|
65
|
+
*
|
|
66
|
+
* const states = {
|
|
67
|
+
* 'No todos': screen({ all: [text('Nothing to do yet')], none: [role('listitem')] })
|
|
68
|
+
* };
|
|
69
|
+
* ```
|
|
46
70
|
*/
|
|
47
71
|
export function screen(spec) {
|
|
48
72
|
return {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
type Loose = Record<string, unknown>;
|
|
2
|
+
/** What {@link readAttachmentFiles} found. */
|
|
3
|
+
export type AttachmentFileResult = {
|
|
4
|
+
/** A copy of the holder in which every readable `path` attachment became a `data` attachment (and `path` is gone). The input is not mutated. */
|
|
5
|
+
readonly value: Loose;
|
|
6
|
+
/** How many files were read. */
|
|
7
|
+
readonly attached: number;
|
|
8
|
+
/** The total size of those files, in bytes. */
|
|
9
|
+
readonly bytes: number;
|
|
10
|
+
/** One line per attachment that was left out (missing, not allowed, too large, over budget), prefixed with its name. */
|
|
11
|
+
readonly warnings: readonly string[];
|
|
12
|
+
};
|
|
13
|
+
/** A budget shared by every call of one publish, so many files cannot add up to more than can be held. */
|
|
14
|
+
export type AttachmentBudget = {
|
|
15
|
+
/** Bytes still available; each file read subtracts its size. A file larger than this is left out, not truncated. */
|
|
16
|
+
remaining: number;
|
|
17
|
+
};
|
|
18
|
+
/** Options of {@link readAttachmentFiles}. */
|
|
19
|
+
export type ReadOptions = {
|
|
20
|
+
/** Largest single file, in bytes. Default 512 MiB. */
|
|
21
|
+
readonly maxBytes?: number;
|
|
22
|
+
/** Shared total-size budget; mutated as files are read. Omit for no total limit. */
|
|
23
|
+
readonly budget?: AttachmentBudget;
|
|
24
|
+
/**
|
|
25
|
+
* A path the person running the command typed (`--attach`) is theirs to choose. A path inside a *report* is
|
|
26
|
+
* data from a test run, which is not: it may name only files inside `roots`, never a hidden file, and never a
|
|
27
|
+
* link that leads out.
|
|
28
|
+
*/
|
|
29
|
+
readonly trusted?: boolean;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Reads the files that attachments name (`path`), at every level (the holder, its `steps` and its `attempts`,
|
|
33
|
+
* recursively), and hands their bytes over as `data` with a `contentType` guessed from the file name when the
|
|
34
|
+
* attachment gave none.
|
|
35
|
+
*
|
|
36
|
+
* A file that is missing, not allowed or too large is left out with a warning: evidence that could not be
|
|
37
|
+
* found never stops the results from being published. Attachments without a `path` are kept untouched.
|
|
38
|
+
*
|
|
39
|
+
* @param holder A check, step, attempt or any object that may have `attachments`, `steps` and `attempts`.
|
|
40
|
+
* @param roots Folders a relative path is resolved against (in order), and the only folders an untrusted path may name.
|
|
41
|
+
* @returns The rewritten copy, counts and warnings; never throws for a bad file.
|
|
42
|
+
*/
|
|
43
|
+
export declare function readAttachmentFiles(holder: Loose, roots: readonly string[], options?: ReadOptions): Promise<AttachmentFileResult>;
|
|
44
|
+
export {};
|