@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.
Files changed (379) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/README.md +178 -617
  3. package/dist/bun/src/driver.d.ts +41 -7
  4. package/dist/bun/src/driver.js +47 -6
  5. package/dist/bun/src/index.d.ts +8 -0
  6. package/dist/bun/src/index.js +8 -0
  7. package/dist/core/src/cli.d.ts +14 -1
  8. package/dist/core/src/cli.js +30 -1
  9. package/dist/core/src/config.d.ts +70 -4
  10. package/dist/core/src/config.js +62 -3
  11. package/dist/core/src/events/cloudevents.d.ts +54 -9
  12. package/dist/core/src/events/cloudevents.js +60 -9
  13. package/dist/core/src/graph.d.ts +21 -0
  14. package/dist/core/src/graph.js +26 -0
  15. package/dist/core/src/index.d.ts +11 -0
  16. package/dist/core/src/index.js +11 -0
  17. package/dist/core/src/lint.d.ts +30 -0
  18. package/dist/core/src/lint.js +13 -0
  19. package/dist/core/src/plan.d.ts +39 -1
  20. package/dist/core/src/plan.js +28 -1
  21. package/dist/core/src/privacy.d.ts +29 -2
  22. package/dist/core/src/privacy.js +19 -2
  23. package/dist/core/src/report/formats.d.ts +34 -4
  24. package/dist/core/src/report/formats.js +24 -4
  25. package/dist/core/src/report/markdown.d.ts +15 -0
  26. package/dist/core/src/report/markdown.js +8 -0
  27. package/dist/core/src/run/clients.d.ts +44 -5
  28. package/dist/core/src/run/clients.js +46 -2
  29. package/dist/core/src/run/execute.d.ts +69 -16
  30. package/dist/core/src/run/execute.js +67 -16
  31. package/dist/core/src/run/frames.d.ts +13 -4
  32. package/dist/core/src/run/frames.js +19 -4
  33. package/dist/core/src/run/http.d.ts +34 -1
  34. package/dist/core/src/run/http.js +14 -1
  35. package/dist/core/src/run/types.d.ts +76 -3
  36. package/dist/core/src/run/types.js +19 -3
  37. package/dist/core/src/signals.d.ts +49 -5
  38. package/dist/core/src/signals.js +22 -5
  39. package/dist/core/src/spec/compose.d.ts +23 -1
  40. package/dist/core/src/spec/compose.js +10 -1
  41. package/dist/core/src/spec/index.d.ts +9 -1
  42. package/dist/core/src/spec/index.js +9 -1
  43. package/dist/core/src/spec/load.d.ts +22 -1
  44. package/dist/core/src/spec/load.js +31 -1
  45. package/dist/core/src/spec/parse.d.ts +16 -2
  46. package/dist/core/src/spec/parse.js +25 -2
  47. package/dist/core/src/spec/scxml.d.ts +25 -4
  48. package/dist/core/src/spec/scxml.js +36 -4
  49. package/dist/core/src/spec/types.d.ts +52 -1
  50. package/dist/core/src/spec/types.js +24 -0
  51. package/dist/detox/src/index.d.ts +54 -11
  52. package/dist/detox/src/index.js +45 -7
  53. package/dist/playwright/src/driver.d.ts +39 -3
  54. package/dist/playwright/src/driver.js +27 -3
  55. package/dist/playwright/src/index.d.ts +10 -0
  56. package/dist/playwright/src/index.js +10 -0
  57. package/dist/playwright/src/media.d.ts +50 -4
  58. package/dist/playwright/src/media.js +55 -4
  59. package/dist/playwright/src/signals.d.ts +44 -5
  60. package/dist/playwright/src/signals.js +29 -5
  61. package/dist/reports/src/attachment-files.d.ts +44 -0
  62. package/dist/reports/src/attachment-files.js +121 -0
  63. package/dist/reports/src/attachment-response.d.ts +31 -0
  64. package/dist/reports/src/attachment-response.js +55 -0
  65. package/dist/reports/src/aws-credentials.d.ts +48 -0
  66. package/dist/reports/src/aws-credentials.js +221 -0
  67. package/dist/reports/src/browser-navigation.d.ts +14 -0
  68. package/dist/reports/src/browser-navigation.js +7 -0
  69. package/dist/reports/src/bundle.d.ts +34 -2
  70. package/dist/reports/src/bundle.js +38 -2
  71. package/dist/reports/src/catalog.d.ts +34 -3
  72. package/dist/reports/src/catalog.js +17 -3
  73. package/dist/reports/src/cli-stores.d.ts +36 -0
  74. package/dist/reports/src/cli-stores.js +73 -0
  75. package/dist/reports/src/clock.d.ts +7 -2
  76. package/dist/reports/src/clock.js +5 -2
  77. package/dist/reports/src/completion-order.d.ts +18 -5
  78. package/dist/reports/src/completion-order.js +18 -5
  79. package/dist/reports/src/compose.d.ts +80 -0
  80. package/dist/reports/src/compose.js +117 -0
  81. package/dist/reports/src/copy-store.d.ts +29 -0
  82. package/dist/reports/src/copy-store.js +52 -0
  83. package/dist/reports/src/directory-outline.d.ts +38 -0
  84. package/dist/reports/src/directory-outline.js +103 -0
  85. package/dist/reports/src/directory-store.d.ts +20 -0
  86. package/dist/reports/src/directory-store.js +166 -0
  87. package/dist/reports/src/doctor.d.ts +29 -0
  88. package/dist/reports/src/doctor.js +83 -0
  89. package/dist/reports/src/effective-delta.d.ts +34 -0
  90. package/dist/reports/src/effective-delta.js +25 -0
  91. package/dist/reports/src/evidence-detail.d.ts +64 -0
  92. package/dist/reports/src/evidence-detail.js +92 -0
  93. package/dist/reports/src/evidence-live.d.ts +35 -5
  94. package/dist/reports/src/evidence-live.js +18 -2
  95. package/dist/reports/src/evidence-types.d.ts +68 -1
  96. package/dist/reports/src/evidence-types.js +5 -1
  97. package/dist/reports/src/evidence-view.d.ts +43 -4
  98. package/dist/reports/src/evidence-view.js +13 -2
  99. package/dist/reports/src/explorer-types.d.ts +40 -3
  100. package/dist/reports/src/explorer-types.js +8 -0
  101. package/dist/reports/src/explorer-versions.d.ts +9 -1
  102. package/dist/reports/src/explorer-versions.js +18 -1
  103. package/dist/reports/src/export-evidence.d.ts +16 -1
  104. package/dist/reports/src/export-evidence.js +52 -1
  105. package/dist/reports/src/fetcher.d.ts +27 -3
  106. package/dist/reports/src/fetcher.js +11 -3
  107. package/dist/reports/src/format-date.d.ts +6 -0
  108. package/dist/reports/src/format-date.js +6 -0
  109. package/dist/reports/src/format.d.ts +16 -0
  110. package/dist/reports/src/format.js +10 -0
  111. package/dist/reports/src/frames.d.ts +36 -3
  112. package/dist/reports/src/frames.js +22 -3
  113. package/dist/reports/src/hash.d.ts +18 -0
  114. package/dist/reports/src/hash.js +13 -0
  115. package/dist/reports/src/host.d.ts +29 -0
  116. package/dist/reports/src/host.js +64 -0
  117. package/dist/reports/src/html-frame.d.ts +5 -6
  118. package/dist/reports/src/html-frame.js +12 -1
  119. package/dist/reports/src/http-store.d.ts +52 -0
  120. package/dist/reports/src/http-store.js +145 -0
  121. package/dist/reports/src/http.d.ts +7 -0
  122. package/dist/reports/src/http.js +6 -0
  123. package/dist/reports/src/ingest.d.ts +54 -0
  124. package/dist/reports/src/ingest.js +210 -0
  125. package/dist/reports/src/init.d.ts +40 -0
  126. package/dist/reports/src/init.js +364 -0
  127. package/dist/reports/src/junit.d.ts +33 -6
  128. package/dist/reports/src/junit.js +87 -21
  129. package/dist/reports/src/keyboard.d.ts +20 -0
  130. package/dist/reports/src/keyboard.js +11 -0
  131. package/dist/reports/src/keys.d.ts +21 -3
  132. package/dist/reports/src/keys.js +21 -3
  133. package/dist/reports/src/layer-axes.d.ts +35 -5
  134. package/dist/reports/src/layer-axes.js +25 -5
  135. package/dist/reports/src/layer-catalog.d.ts +28 -1
  136. package/dist/reports/src/layer-catalog.js +18 -1
  137. package/dist/reports/src/layer-merge.d.ts +98 -9
  138. package/dist/reports/src/layer-merge.js +147 -31
  139. package/dist/reports/src/layer-source.d.ts +18 -0
  140. package/dist/reports/src/layer-source.js +11 -0
  141. package/dist/reports/src/layer-store.d.ts +42 -0
  142. package/dist/reports/src/layer-store.js +63 -5
  143. package/dist/reports/src/layer-types.d.ts +32 -1
  144. package/dist/reports/src/layers-route.d.ts +47 -4
  145. package/dist/reports/src/layers-route.js +86 -7
  146. package/dist/reports/src/media-types.d.ts +11 -0
  147. package/dist/reports/src/media-types.js +22 -0
  148. package/dist/reports/src/namespace-guard.d.ts +10 -1
  149. package/dist/reports/src/namespace-guard.js +10 -1
  150. package/dist/reports/src/navigation.d.ts +12 -0
  151. package/dist/reports/src/navigation.js +7 -0
  152. package/dist/reports/src/object-io.d.ts +11 -4
  153. package/dist/reports/src/object-io.js +4 -4
  154. package/dist/reports/src/object-store-conformance.d.ts +27 -0
  155. package/dist/reports/src/object-store-conformance.js +87 -0
  156. package/dist/reports/src/object-store.d.ts +33 -24
  157. package/dist/reports/src/object-store.js +17 -24
  158. package/dist/reports/src/parse-with.d.ts +16 -0
  159. package/dist/reports/src/parse-with.js +14 -0
  160. package/dist/reports/src/performance-part.d.ts +25 -1
  161. package/dist/reports/src/performance-part.js +15 -1
  162. package/dist/reports/src/performance.d.ts +22 -2
  163. package/dist/reports/src/performance.js +12 -2
  164. package/dist/reports/src/persist.d.ts +12 -3
  165. package/dist/reports/src/persist.js +12 -3
  166. package/dist/reports/src/phone.d.ts +18 -1
  167. package/dist/reports/src/phone.js +18 -1
  168. package/dist/reports/src/protocol.d.ts +26 -4
  169. package/dist/reports/src/protocol.js +15 -4
  170. package/dist/reports/src/publication.d.ts +21 -1
  171. package/dist/reports/src/publication.js +21 -1
  172. package/dist/reports/src/publish-checks.d.ts +35 -0
  173. package/dist/reports/src/publish-checks.js +122 -0
  174. package/dist/reports/src/publish.d.ts +42 -9
  175. package/dist/reports/src/publish.js +36 -7
  176. package/dist/reports/src/read.d.ts +46 -16
  177. package/dist/reports/src/read.js +38 -14
  178. package/dist/reports/src/recording-bundle.d.ts +13 -2
  179. package/dist/reports/src/recording-bundle.js +4 -2
  180. package/dist/reports/src/recording-keys.d.ts +15 -1
  181. package/dist/reports/src/recording-keys.js +15 -1
  182. package/dist/reports/src/remote-report.d.ts +28 -7
  183. package/dist/reports/src/remote-report.js +15 -7
  184. package/dist/reports/src/report-cli.d.ts +51 -0
  185. package/dist/reports/src/report-cli.js +485 -0
  186. package/dist/reports/src/report-index.d.ts +99 -0
  187. package/dist/reports/src/report-index.js +337 -0
  188. package/dist/reports/src/report-loader.d.ts +10 -0
  189. package/dist/reports/src/report-loader.js +6 -0
  190. package/dist/reports/src/report-parts.d.ts +29 -3
  191. package/dist/reports/src/report-parts.js +37 -3
  192. package/dist/reports/src/report-status.d.ts +48 -0
  193. package/dist/reports/src/report-status.js +156 -0
  194. package/dist/reports/src/report-types.d.ts +73 -0
  195. package/dist/reports/src/report-types.js +3 -0
  196. package/dist/reports/src/reporter.d.ts +67 -0
  197. package/dist/reports/src/reporter.js +128 -0
  198. package/dist/reports/src/reports-explorer-route.d.ts +541 -0
  199. package/dist/reports/src/reports-explorer-route.js +11 -0
  200. package/dist/reports/src/reports-list-route.d.ts +9 -0
  201. package/dist/reports/src/reports-list-route.js +9 -0
  202. package/dist/reports/src/reports-route.d.ts +561 -22
  203. package/dist/reports/src/reports-route.js +11 -3
  204. package/dist/reports/src/reports-store.d.ts +19 -2
  205. package/dist/reports/src/reports-store.js +10 -2
  206. package/dist/reports/src/resilient-cache.d.ts +14 -1
  207. package/dist/reports/src/resilient-cache.js +8 -1
  208. package/dist/reports/src/retention.d.ts +17 -1
  209. package/dist/reports/src/retention.js +5 -1
  210. package/dist/reports/src/run-bundle.d.ts +63 -0
  211. package/dist/reports/src/run-bundle.js +159 -0
  212. package/dist/reports/src/s3-store.d.ts +79 -0
  213. package/dist/reports/src/s3-store.js +295 -0
  214. package/dist/reports/src/scenario.d.ts +14 -4
  215. package/dist/reports/src/scenario.js +5 -4
  216. package/dist/reports/src/sigv4.d.ts +72 -0
  217. package/dist/reports/src/sigv4.js +96 -0
  218. package/dist/reports/src/snapshot-bundle.d.ts +24 -1
  219. package/dist/reports/src/snapshot-bundle.js +27 -2
  220. package/dist/reports/src/snapshot-surface.d.ts +20 -9
  221. package/dist/reports/src/snapshot-surface.js +32 -19
  222. package/dist/reports/src/source-content.d.ts +23 -0
  223. package/dist/reports/src/source-content.js +30 -1
  224. package/dist/reports/src/source-entries.d.ts +13 -0
  225. package/dist/reports/src/source-entries.js +6 -0
  226. package/dist/reports/src/source-part.d.ts +15 -0
  227. package/dist/reports/src/source-part.js +8 -0
  228. package/dist/reports/src/source-reader.d.ts +11 -0
  229. package/dist/reports/src/source-reader.js +5 -0
  230. package/dist/reports/src/spec-evidence.d.ts +12 -2
  231. package/dist/reports/src/spec-evidence.js +6 -2
  232. package/dist/reports/src/spec-inventory.d.ts +15 -1
  233. package/dist/reports/src/spec-inventory.js +22 -1
  234. package/dist/reports/src/spec-link.d.ts +28 -0
  235. package/dist/reports/src/spec-link.js +70 -0
  236. package/dist/reports/src/spec-set.d.ts +63 -0
  237. package/dist/reports/src/spec-set.js +177 -0
  238. package/dist/reports/src/status.d.ts +10 -0
  239. package/dist/reports/src/status.js +5 -0
  240. package/dist/reports/src/store-prefix.d.ts +28 -0
  241. package/dist/reports/src/store-prefix.js +22 -0
  242. package/dist/reports/src/store-url.d.ts +89 -0
  243. package/dist/reports/src/store-url.js +160 -0
  244. package/dist/reports/src/test-catalog-entity.d.ts +11 -0
  245. package/dist/reports/src/test-catalog-entity.js +6 -0
  246. package/dist/reports/src/types.d.ts +6 -1
  247. package/dist/reports/src/use-report-nav.d.ts +35 -0
  248. package/dist/reports/src/use-report-nav.js +12 -0
  249. package/dist/schema/src/api.d.ts +799 -0
  250. package/dist/schema/src/api.js +201 -0
  251. package/dist/schema/src/cli-results.d.ts +210 -0
  252. package/dist/schema/src/cli-results.js +124 -0
  253. package/dist/schema/src/common.d.ts +53 -0
  254. package/dist/schema/src/common.js +42 -0
  255. package/dist/schema/src/index.d.ts +14 -0
  256. package/dist/schema/src/index.js +14 -0
  257. package/dist/schema/src/layer.d.ts +2273 -0
  258. package/dist/schema/src/layer.js +462 -0
  259. package/dist/schema/src/manifest.d.ts +95 -0
  260. package/dist/schema/src/manifest.js +84 -0
  261. package/dist/schema/src/report-sources.d.ts +28 -0
  262. package/dist/schema/src/report-sources.js +25 -0
  263. package/dist/schema/src/runs-index.d.ts +16 -0
  264. package/dist/schema/src/runs-index.js +15 -0
  265. package/dist/schema/src/server-config.d.ts +93 -0
  266. package/dist/schema/src/server-config.js +73 -0
  267. package/dist/schema/src/spec.d.ts +69 -0
  268. package/dist/schema/src/spec.js +58 -0
  269. package/dist/server/src/app.d.ts +62 -0
  270. package/dist/server/src/app.js +419 -0
  271. package/dist/server/src/auth.d.ts +41 -0
  272. package/dist/server/src/auth.js +59 -0
  273. package/dist/server/src/config.d.ts +83 -0
  274. package/dist/server/src/config.js +187 -0
  275. package/dist/server/src/dev.d.ts +12 -0
  276. package/dist/server/src/dev.js +80 -0
  277. package/dist/server/src/events.d.ts +29 -0
  278. package/dist/server/src/events.js +101 -0
  279. package/dist/server/src/host.d.ts +30 -0
  280. package/dist/server/src/host.js +115 -0
  281. package/dist/server/src/index.d.ts +16 -0
  282. package/dist/server/src/index.js +16 -0
  283. package/dist/server/src/llms.d.ts +19 -0
  284. package/dist/server/src/llms.js +63 -0
  285. package/dist/server/src/main.d.ts +30 -0
  286. package/dist/server/src/main.js +112 -0
  287. package/dist/server/src/openapi.d.ts +59 -0
  288. package/dist/server/src/openapi.js +374 -0
  289. package/dist/server/src/problem.d.ts +8 -0
  290. package/dist/server/src/problem.js +39 -0
  291. package/dist/server/src/site.d.ts +13 -0
  292. package/dist/server/src/site.js +43 -0
  293. package/dist/server/src/store-api.d.ts +28 -0
  294. package/dist/server/src/store-api.js +159 -0
  295. package/dist/server/src/validate.d.ts +71 -0
  296. package/dist/server/src/validate.js +197 -0
  297. package/dist/vitest/src/index.d.ts +27 -5
  298. package/dist/vitest/src/index.js +18 -5
  299. package/dist/webdriver/src/driver.d.ts +63 -11
  300. package/dist/webdriver/src/driver.js +57 -6
  301. package/dist/webdriver/src/index.d.ts +8 -0
  302. package/dist/webdriver/src/index.js +1 -0
  303. package/dist/webdriver/src/protocol.d.ts +75 -6
  304. package/dist/webdriver/src/protocol.js +79 -6
  305. package/docs/agents.md +137 -0
  306. package/docs/deploying.md +345 -0
  307. package/docs/dogfooding.md +175 -0
  308. package/docs/extending.md +242 -0
  309. package/docs/integrating.md +485 -0
  310. package/docs/reference/bun.md +141 -0
  311. package/docs/reference/cli.md +146 -0
  312. package/docs/reference/core.md +2426 -0
  313. package/docs/reference/detox.md +150 -0
  314. package/docs/reference/http-api.md +303 -0
  315. package/docs/reference/playwright.md +303 -0
  316. package/docs/reference/reports.md +7098 -0
  317. package/docs/reference/schema.md +1651 -0
  318. package/docs/reference/server.md +685 -0
  319. package/docs/reference/vitest.md +45 -0
  320. package/docs/reference/webdriver.md +330 -0
  321. package/docs/report-backends.md +144 -0
  322. package/docs/report-contract.md +400 -0
  323. package/docs/reporting-issues.md +160 -0
  324. package/docs/server.md +285 -0
  325. package/docs/statecharts.md +641 -0
  326. package/llms-full.txt +3506 -0
  327. package/llms.txt +47 -0
  328. package/package.json +32 -4
  329. package/packages/core/README.md +2 -1
  330. package/packages/reports/README.md +65 -0
  331. package/packages/reports/STORAGE.md +4 -0
  332. package/packages/schema/README.md +14 -0
  333. package/packages/schema/schemas/check-detail.schema.json +1032 -0
  334. package/packages/schema/schemas/checks-result.schema.json +26 -0
  335. package/packages/schema/schemas/command-error.schema.json +35 -0
  336. package/packages/schema/schemas/doctor-result.schema.json +60 -0
  337. package/packages/schema/schemas/effective-delta.schema.json +490 -0
  338. package/packages/schema/schemas/effective-evidence.schema.json +471 -0
  339. package/packages/schema/schemas/evidence-page.schema.json +271 -0
  340. package/packages/schema/schemas/hello-event.schema.json +22 -0
  341. package/packages/schema/schemas/init-plan.schema.json +347 -0
  342. package/packages/schema/schemas/layer-rows.schema.json +199 -0
  343. package/packages/schema/schemas/layer.schema.json +2888 -0
  344. package/packages/schema/schemas/merge-result.schema.json +40 -0
  345. package/packages/schema/schemas/outline.schema.json +62 -0
  346. package/packages/schema/schemas/problem.schema.json +37 -0
  347. package/packages/schema/schemas/publish-checks.schema.json +1413 -0
  348. package/packages/schema/schemas/publish-result.schema.json +168 -0
  349. package/packages/schema/schemas/ready.schema.json +68 -0
  350. package/packages/schema/schemas/report-event.schema.json +100 -0
  351. package/packages/schema/schemas/report-status.schema.json +370 -0
  352. package/packages/schema/schemas/server-config.schema.json +194 -0
  353. package/packages/schema/schemas/server-info.schema.json +114 -0
  354. package/packages/schema/schemas/spec-set.schema.json +103 -0
  355. package/packages/schema/schemas/store-listing.schema.json +29 -0
  356. package/packages/schema/schemas/store-write-result.schema.json +28 -0
  357. package/packages/schema/schemas/stream-result.schema.json +59 -0
  358. package/packages/server/README.md +43 -0
  359. package/packages/server/openapi.json +6699 -0
  360. package/packages/visualizer/README.md +21 -0
  361. package/packages/visualizer/src/app/AppShell.tsx +9 -2
  362. package/packages/visualizer/src/app/report-sources.tsx +6 -4
  363. package/packages/visualizer/src/app/router.tsx +3 -3
  364. package/packages/visualizer/src/components/Header.module.css +35 -0
  365. package/packages/visualizer/src/components/Header.tsx +40 -3
  366. package/packages/visualizer/src/data/adapters/adapter.ts +5 -17
  367. package/packages/visualizer/src/data/adapters/index.ts +10 -1
  368. package/packages/visualizer/src/evidence/api.ts +14 -2
  369. package/packages/visualizer/src/evidence/artifacts.module.css +213 -0
  370. package/packages/visualizer/src/evidence/artifacts.tsx +272 -0
  371. package/packages/visualizer/src/evidence/check-detail.tsx +3 -1
  372. package/packages/visualizer/src/evidence/evidence-context.tsx +74 -12
  373. package/packages/visualizer/src/evidence/spec-page.tsx +23 -16
  374. package/packages/visualizer/src/find/Finder.tsx +1 -7
  375. package/packages/visualizer/src/find/open.ts +4 -0
  376. package/packages/visualizer/src/live/live-stream.ts +204 -0
  377. package/packages/visualizer/src/runtime.ts +19 -0
  378. package/packages/visualizer/vite/atlas-server-proxy.ts +36 -0
  379. package/packages/visualizer/vite.config.ts +11 -3
@@ -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
- * A signal as a Detox matcher: test IDs are `by.id` (React Native's `testID`), labels `by.label`,
6
- * text `by.text`, a role with a name `by.label` (with the iOS `button` trait when Detox has it).
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
- * Drive a native app with Detox. Screenshots come from `device.takeScreenshot`. Detox records
30
- * video per test through its artifacts plugin rather than per call, so this driver does not
31
- * record clips; enable Detox's `--record-videos` to keep a video of each attempt.
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
- /** A state recognised by Detox matchers: every `all` visible and no `none` visible. */
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
- * Drive a web app (or a React Native app on the web) with Playwright. Screenshots are taken at the
41
- * device's physical resolution; clips are recorded at that resolution too and encoded with
42
- * FFmpeg as H.264 MP4 (FFmpeg on the PATH is needed for video and WebP thumbnails).
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
- * Drive a web app (or a React Native app on the web) with Playwright. Screenshots are taken at the
6
- * device's physical resolution; clips are recorded at that resolution too and encoded with
7
- * FFmpeg as H.264 MP4 (FFmpeg on the PATH is needed for video and WebP thumbnails).
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
- /** Taps and typing a person would do, paced for showcase videos and logged to the step timeline. */
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
- /** Tap a locator or a signal (resolved with `locate`). */
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
- /** A settled, deterministic capture at device resolution, with the touch layer hidden. */
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 the phone's physical resolution (1170×2532). Chrome's screencast, and
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
- /** Taps and typing a person would do, paced for showcase videos and logged to the step timeline. */
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
- /** Tap a locator or a signal (resolved with `locate`). */
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
- /** A settled, deterministic capture at device resolution, with the touch layer hidden. */
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 the phone's physical resolution (1170×2532). Chrome's screencast, and
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
- /** A signal as a Playwright locator: the first visible match, with Playwright's own role and text rules. */
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
- /** Recognise a state from its UI signature: every `all` visible, one `any` visible, no `none`. */
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
- /** Checks that a quoted piece of the spec is on screen; records expected and actual on failure. */
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
- * A state implementation from a UI signature, for a context that exposes `page`. Extra fields
21
- * (`lookIn`, `settle`, `transient`, `sameAs`, `checks`) pass through.
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
- /** A signal as a Playwright locator: the first visible match, with Playwright's own role and text rules. */
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
- /** Recognise a state from its UI signature: every `all` visible, one `any` visible, no `none`. */
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
- /** Checks that a quoted piece of the spec is on screen; records expected and actual on failure. */
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
- * A state implementation from a UI signature, for a context that exposes `page`. Extra fields
45
- * (`lookIn`, `settle`, `transient`, `sameAs`, `checks`) pass through.
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 {};