@crvouga/atlas 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (444) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/README.md +191 -611
  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 +35 -1
  9. package/dist/core/src/config.d.ts +77 -4
  10. package/dist/core/src/config.js +110 -17
  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 +75 -16
  30. package/dist/core/src/run/execute.js +71 -18
  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 +85 -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 +56 -0
  120. package/dist/reports/src/http-store.js +148 -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 +369 -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/live-reporter.d.ts +34 -0
  147. package/dist/reports/src/live-reporter.js +122 -0
  148. package/dist/reports/src/media-types.d.ts +11 -0
  149. package/dist/reports/src/media-types.js +22 -0
  150. package/dist/reports/src/namespace-guard.d.ts +10 -1
  151. package/dist/reports/src/namespace-guard.js +10 -1
  152. package/dist/reports/src/navigation.d.ts +12 -0
  153. package/dist/reports/src/navigation.js +7 -0
  154. package/dist/reports/src/object-io.d.ts +11 -4
  155. package/dist/reports/src/object-io.js +4 -4
  156. package/dist/reports/src/object-store-conformance.d.ts +27 -0
  157. package/dist/reports/src/object-store-conformance.js +87 -0
  158. package/dist/reports/src/object-store.d.ts +33 -24
  159. package/dist/reports/src/object-store.js +17 -24
  160. package/dist/reports/src/parse-with.d.ts +16 -0
  161. package/dist/reports/src/parse-with.js +14 -0
  162. package/dist/reports/src/performance-part.d.ts +25 -1
  163. package/dist/reports/src/performance-part.js +15 -1
  164. package/dist/reports/src/performance.d.ts +22 -2
  165. package/dist/reports/src/performance.js +12 -2
  166. package/dist/reports/src/persist.d.ts +12 -3
  167. package/dist/reports/src/persist.js +12 -3
  168. package/dist/reports/src/phone.d.ts +18 -1
  169. package/dist/reports/src/phone.js +18 -1
  170. package/dist/reports/src/protocol.d.ts +26 -4
  171. package/dist/reports/src/protocol.js +15 -4
  172. package/dist/reports/src/publication.d.ts +21 -1
  173. package/dist/reports/src/publication.js +21 -1
  174. package/dist/reports/src/publish-checks.d.ts +35 -0
  175. package/dist/reports/src/publish-checks.js +122 -0
  176. package/dist/reports/src/publish.d.ts +42 -9
  177. package/dist/reports/src/publish.js +36 -7
  178. package/dist/reports/src/read.d.ts +46 -16
  179. package/dist/reports/src/read.js +38 -14
  180. package/dist/reports/src/recording-bundle.d.ts +13 -2
  181. package/dist/reports/src/recording-bundle.js +4 -2
  182. package/dist/reports/src/recording-keys.d.ts +15 -1
  183. package/dist/reports/src/recording-keys.js +15 -1
  184. package/dist/reports/src/remote-report.d.ts +28 -7
  185. package/dist/reports/src/remote-report.js +15 -7
  186. package/dist/reports/src/report-cli.d.ts +51 -0
  187. package/dist/reports/src/report-cli.js +486 -0
  188. package/dist/reports/src/report-index.d.ts +99 -0
  189. package/dist/reports/src/report-index.js +337 -0
  190. package/dist/reports/src/report-loader.d.ts +10 -0
  191. package/dist/reports/src/report-loader.js +6 -0
  192. package/dist/reports/src/report-parts.d.ts +29 -3
  193. package/dist/reports/src/report-parts.js +37 -3
  194. package/dist/reports/src/report-status.d.ts +48 -0
  195. package/dist/reports/src/report-status.js +156 -0
  196. package/dist/reports/src/report-types.d.ts +73 -0
  197. package/dist/reports/src/report-types.js +3 -0
  198. package/dist/reports/src/reporter.d.ts +67 -0
  199. package/dist/reports/src/reporter.js +128 -0
  200. package/dist/reports/src/reports-explorer-route.d.ts +541 -0
  201. package/dist/reports/src/reports-explorer-route.js +11 -0
  202. package/dist/reports/src/reports-list-route.d.ts +9 -0
  203. package/dist/reports/src/reports-list-route.js +9 -0
  204. package/dist/reports/src/reports-route.d.ts +561 -22
  205. package/dist/reports/src/reports-route.js +11 -3
  206. package/dist/reports/src/reports-store.d.ts +19 -2
  207. package/dist/reports/src/reports-store.js +10 -2
  208. package/dist/reports/src/resilient-cache.d.ts +14 -1
  209. package/dist/reports/src/resilient-cache.js +8 -1
  210. package/dist/reports/src/retention.d.ts +17 -1
  211. package/dist/reports/src/retention.js +5 -1
  212. package/dist/reports/src/run-bundle.d.ts +63 -0
  213. package/dist/reports/src/run-bundle.js +159 -0
  214. package/dist/reports/src/s3-store.d.ts +79 -0
  215. package/dist/reports/src/s3-store.js +295 -0
  216. package/dist/reports/src/scenario.d.ts +14 -4
  217. package/dist/reports/src/scenario.js +5 -4
  218. package/dist/reports/src/sigv4.d.ts +72 -0
  219. package/dist/reports/src/sigv4.js +96 -0
  220. package/dist/reports/src/snapshot-bundle.d.ts +24 -1
  221. package/dist/reports/src/snapshot-bundle.js +27 -2
  222. package/dist/reports/src/snapshot-surface.d.ts +20 -9
  223. package/dist/reports/src/snapshot-surface.js +32 -19
  224. package/dist/reports/src/source-content.d.ts +23 -0
  225. package/dist/reports/src/source-content.js +30 -1
  226. package/dist/reports/src/source-entries.d.ts +13 -0
  227. package/dist/reports/src/source-entries.js +6 -0
  228. package/dist/reports/src/source-part.d.ts +15 -0
  229. package/dist/reports/src/source-part.js +8 -0
  230. package/dist/reports/src/source-reader.d.ts +11 -0
  231. package/dist/reports/src/source-reader.js +5 -0
  232. package/dist/reports/src/spec-evidence.d.ts +12 -2
  233. package/dist/reports/src/spec-evidence.js +6 -2
  234. package/dist/reports/src/spec-inventory.d.ts +15 -1
  235. package/dist/reports/src/spec-inventory.js +22 -1
  236. package/dist/reports/src/spec-link.d.ts +28 -0
  237. package/dist/reports/src/spec-link.js +70 -0
  238. package/dist/reports/src/spec-set.d.ts +63 -0
  239. package/dist/reports/src/spec-set.js +177 -0
  240. package/dist/reports/src/status.d.ts +10 -0
  241. package/dist/reports/src/status.js +5 -0
  242. package/dist/reports/src/store-prefix.d.ts +28 -0
  243. package/dist/reports/src/store-prefix.js +22 -0
  244. package/dist/reports/src/store-url.d.ts +89 -0
  245. package/dist/reports/src/store-url.js +160 -0
  246. package/dist/reports/src/test-catalog-entity.d.ts +11 -0
  247. package/dist/reports/src/test-catalog-entity.js +6 -0
  248. package/dist/reports/src/types.d.ts +6 -1
  249. package/dist/reports/src/use-report-nav.d.ts +35 -0
  250. package/dist/reports/src/use-report-nav.js +12 -0
  251. package/dist/runners/src/codecept.d.ts +14 -0
  252. package/dist/runners/src/codecept.js +16 -0
  253. package/dist/runners/src/jest.d.ts +50 -0
  254. package/dist/runners/src/jest.js +52 -0
  255. package/dist/runners/src/mocha.d.ts +40 -0
  256. package/dist/runners/src/mocha.js +39 -0
  257. package/dist/runners/src/node-test.d.ts +28 -0
  258. package/dist/runners/src/node-test.js +44 -0
  259. package/dist/runners/src/publish.d.ts +30 -0
  260. package/dist/runners/src/publish.js +68 -0
  261. package/dist/schema/src/api.d.ts +800 -0
  262. package/dist/schema/src/api.js +201 -0
  263. package/dist/schema/src/cli-results.d.ts +210 -0
  264. package/dist/schema/src/cli-results.js +124 -0
  265. package/dist/schema/src/common.d.ts +53 -0
  266. package/dist/schema/src/common.js +42 -0
  267. package/dist/schema/src/index.d.ts +16 -0
  268. package/dist/schema/src/index.js +16 -0
  269. package/dist/schema/src/layer.d.ts +2273 -0
  270. package/dist/schema/src/layer.js +462 -0
  271. package/dist/schema/src/live-run.d.ts +123 -0
  272. package/dist/schema/src/live-run.js +30 -0
  273. package/dist/schema/src/manifest.d.ts +97 -2
  274. package/dist/schema/src/manifest.js +84 -0
  275. package/dist/schema/src/project.d.ts +20 -0
  276. package/dist/schema/src/project.js +10 -0
  277. package/dist/schema/src/report-sources.d.ts +29 -0
  278. package/dist/schema/src/report-sources.js +27 -1
  279. package/dist/schema/src/runs-index.d.ts +17 -1
  280. package/dist/schema/src/runs-index.js +15 -0
  281. package/dist/schema/src/server-config.d.ts +177 -0
  282. package/dist/schema/src/server-config.js +85 -0
  283. package/dist/schema/src/spec.d.ts +69 -0
  284. package/dist/schema/src/spec.js +58 -0
  285. package/dist/server/src/app.d.ts +72 -0
  286. package/dist/server/src/app.js +557 -0
  287. package/dist/server/src/auth.d.ts +41 -0
  288. package/dist/server/src/auth.js +59 -0
  289. package/dist/server/src/config.d.ts +83 -0
  290. package/dist/server/src/config.js +204 -0
  291. package/dist/server/src/dev.d.ts +12 -0
  292. package/dist/server/src/dev.js +89 -0
  293. package/dist/server/src/events.d.ts +29 -0
  294. package/dist/server/src/events.js +101 -0
  295. package/dist/server/src/host.d.ts +30 -0
  296. package/dist/server/src/host.js +115 -0
  297. package/dist/server/src/index.d.ts +18 -0
  298. package/dist/server/src/index.js +18 -0
  299. package/dist/server/src/live-runs.d.ts +52 -0
  300. package/dist/server/src/live-runs.js +229 -0
  301. package/dist/server/src/llms.d.ts +19 -0
  302. package/dist/server/src/llms.js +63 -0
  303. package/dist/server/src/main.d.ts +30 -0
  304. package/dist/server/src/main.js +114 -0
  305. package/dist/server/src/openapi.d.ts +56 -0
  306. package/dist/server/src/openapi.js +438 -0
  307. package/dist/server/src/problem.d.ts +8 -0
  308. package/dist/server/src/problem.js +39 -0
  309. package/dist/server/src/projects.d.ts +3 -0
  310. package/dist/server/src/projects.js +154 -0
  311. package/dist/server/src/settings-page.d.ts +9 -0
  312. package/dist/server/src/settings-page.js +93 -0
  313. package/dist/server/src/settings.d.ts +23 -0
  314. package/dist/server/src/settings.js +72 -0
  315. package/dist/server/src/site.d.ts +15 -0
  316. package/dist/server/src/site.js +78 -0
  317. package/dist/server/src/store-api.d.ts +28 -0
  318. package/dist/server/src/store-api.js +180 -0
  319. package/dist/server/src/validate.d.ts +71 -0
  320. package/dist/server/src/validate.js +197 -0
  321. package/dist/vitest/src/index.d.ts +27 -5
  322. package/dist/vitest/src/index.js +18 -5
  323. package/dist/webdriver/src/driver.d.ts +63 -11
  324. package/dist/webdriver/src/driver.js +57 -6
  325. package/dist/webdriver/src/index.d.ts +8 -0
  326. package/dist/webdriver/src/index.js +1 -0
  327. package/dist/webdriver/src/protocol.d.ts +75 -6
  328. package/dist/webdriver/src/protocol.js +79 -6
  329. package/docs/agents.md +137 -0
  330. package/docs/deploying.md +345 -0
  331. package/docs/dogfooding.md +175 -0
  332. package/docs/extending.md +242 -0
  333. package/docs/integrating.md +485 -0
  334. package/docs/reference/bun.md +141 -0
  335. package/docs/reference/cli.md +148 -0
  336. package/docs/reference/core.md +2432 -0
  337. package/docs/reference/detox.md +150 -0
  338. package/docs/reference/http-api.md +799 -0
  339. package/docs/reference/playwright.md +303 -0
  340. package/docs/reference/reports.md +7152 -0
  341. package/docs/reference/runners.md +169 -0
  342. package/docs/reference/schema.md +1762 -0
  343. package/docs/reference/server.md +825 -0
  344. package/docs/reference/vitest.md +45 -0
  345. package/docs/reference/webdriver.md +330 -0
  346. package/docs/report-backends.md +144 -0
  347. package/docs/report-contract.md +400 -0
  348. package/docs/reporting-issues.md +160 -0
  349. package/docs/runners.md +69 -0
  350. package/docs/server.md +321 -0
  351. package/docs/statecharts.md +643 -0
  352. package/llms-full.txt +4114 -0
  353. package/llms.txt +49 -0
  354. package/package.json +61 -11
  355. package/packages/core/README.md +2 -1
  356. package/packages/reports/README.md +65 -0
  357. package/packages/reports/STORAGE.md +4 -0
  358. package/packages/runners/README.md +6 -0
  359. package/packages/runners/compat/codecept.cjs +2 -0
  360. package/packages/runners/compat/jest.cjs +2 -0
  361. package/packages/runners/compat/mocha.cjs +2 -0
  362. package/packages/schema/README.md +14 -0
  363. package/packages/schema/schemas/atlas-change.schema.json +4 -0
  364. package/packages/schema/schemas/check-detail.schema.json +1032 -0
  365. package/packages/schema/schemas/checks-result.schema.json +26 -0
  366. package/packages/schema/schemas/command-error.schema.json +35 -0
  367. package/packages/schema/schemas/doctor-result.schema.json +60 -0
  368. package/packages/schema/schemas/effective-delta.schema.json +490 -0
  369. package/packages/schema/schemas/effective-evidence.schema.json +471 -0
  370. package/packages/schema/schemas/evidence-page.schema.json +271 -0
  371. package/packages/schema/schemas/hello-event.schema.json +26 -0
  372. package/packages/schema/schemas/init-plan.schema.json +347 -0
  373. package/packages/schema/schemas/layer-rows.schema.json +199 -0
  374. package/packages/schema/schemas/layer.schema.json +2888 -0
  375. package/packages/schema/schemas/live-run-start.schema.json +42 -0
  376. package/packages/schema/schemas/live-run-update.schema.json +93 -0
  377. package/packages/schema/schemas/live-run.schema.json +225 -0
  378. package/packages/schema/schemas/merge-result.schema.json +40 -0
  379. package/packages/schema/schemas/outline.schema.json +62 -0
  380. package/packages/schema/schemas/problem.schema.json +37 -0
  381. package/packages/schema/schemas/project-slug.schema.json +8 -0
  382. package/packages/schema/schemas/project.schema.json +22 -0
  383. package/packages/schema/schemas/projects.schema.json +33 -0
  384. package/packages/schema/schemas/publish-checks.schema.json +1413 -0
  385. package/packages/schema/schemas/publish-result.schema.json +168 -0
  386. package/packages/schema/schemas/ready.schema.json +68 -0
  387. package/packages/schema/schemas/report-event.schema.json +100 -0
  388. package/packages/schema/schemas/report-status.schema.json +370 -0
  389. package/packages/schema/schemas/server-config.schema.json +208 -0
  390. package/packages/schema/schemas/server-editable-settings.schema.json +132 -0
  391. package/packages/schema/schemas/server-info.schema.json +114 -0
  392. package/packages/schema/schemas/server-settings-snapshot.schema.json +285 -0
  393. package/packages/schema/schemas/spec-set.schema.json +103 -0
  394. package/packages/schema/schemas/store-listing.schema.json +29 -0
  395. package/packages/schema/schemas/store-write-result.schema.json +28 -0
  396. package/packages/schema/schemas/stream-result.schema.json +59 -0
  397. package/packages/server/README.md +43 -0
  398. package/packages/server/openapi.json +11021 -0
  399. package/packages/visualizer/README.md +32 -11
  400. package/packages/visualizer/bin/atlas-client.mjs +176 -0
  401. package/packages/visualizer/bin/atlas-visualizer.mjs +2 -175
  402. package/packages/visualizer/src/app/AppShell.tsx +32 -16
  403. package/packages/visualizer/src/app/report-sources.tsx +7 -4
  404. package/packages/visualizer/src/app/router.tsx +3 -3
  405. package/packages/visualizer/src/components/ActivityIndicator.module.css +4 -0
  406. package/packages/visualizer/src/components/ActivityIndicator.tsx +6 -0
  407. package/packages/visualizer/src/components/Header.module.css +101 -192
  408. package/packages/visualizer/src/components/Header.tsx +88 -13
  409. package/packages/visualizer/src/components/ProjectPicker.tsx +64 -0
  410. package/packages/visualizer/src/data/adapters/adapter.ts +5 -17
  411. package/packages/visualizer/src/data/adapters/index.ts +10 -1
  412. package/packages/visualizer/src/data/adapters/report-backend.test.ts +3 -2
  413. package/packages/visualizer/src/data/adapters/report-backend.ts +1 -1
  414. package/packages/visualizer/src/data/queries.ts +14 -7
  415. package/packages/visualizer/src/evidence/api.ts +16 -3
  416. package/packages/visualizer/src/evidence/artifacts.module.css +213 -0
  417. package/packages/visualizer/src/evidence/artifacts.tsx +272 -0
  418. package/packages/visualizer/src/evidence/check-detail.tsx +21 -4
  419. package/packages/visualizer/src/evidence/document-page.tsx +6 -2
  420. package/packages/visualizer/src/evidence/evidence-context.tsx +79 -12
  421. package/packages/visualizer/src/evidence/evidence.module.css +11 -1
  422. package/packages/visualizer/src/evidence/feature-page.tsx +7 -3
  423. package/packages/visualizer/src/evidence/model-page.tsx +6 -2
  424. package/packages/visualizer/src/evidence/project-home.tsx +45 -0
  425. package/packages/visualizer/src/evidence/recording-page.tsx +4 -1
  426. package/packages/visualizer/src/evidence/spec-index.tsx +2 -1
  427. package/packages/visualizer/src/evidence/spec-page.tsx +23 -16
  428. package/packages/visualizer/src/evidence/test-filter.tsx +21 -0
  429. package/packages/visualizer/src/find/Finder.tsx +1 -7
  430. package/packages/visualizer/src/find/open.ts +4 -0
  431. package/packages/visualizer/src/live/live-query.ts +21 -0
  432. package/packages/visualizer/src/live/live-runs.tsx +69 -0
  433. package/packages/visualizer/src/live/live-stream.ts +225 -0
  434. package/packages/visualizer/src/live/report-progress.ts +62 -0
  435. package/packages/visualizer/src/live/test-progress.tsx +34 -0
  436. package/packages/visualizer/src/runtime.ts +35 -0
  437. package/packages/visualizer/src/state/ui-store.ts +3 -2
  438. package/packages/visualizer/src/views/ChartPage.tsx +2 -0
  439. package/packages/visualizer/src/views/ChartTests.tsx +43 -0
  440. package/packages/visualizer/src/views/SidePanel.tsx +2 -0
  441. package/packages/visualizer/vite/atlas-files.test.ts +4 -4
  442. package/packages/visualizer/vite/atlas-files.ts +1 -5
  443. package/packages/visualizer/vite/atlas-server-proxy.ts +37 -0
  444. package/packages/visualizer/vite.config.ts +11 -3
@@ -0,0 +1,643 @@
1
+ # Executable statecharts
2
+
3
+ Write down how a product behaves as statecharts in open formats, then let Atlas lint them, plan
4
+ paths that cover every transition, run those paths against the real product through any driver (a
5
+ browser, a phone, a plain function), and write a manifest of what happened, with screenshots, video
6
+ clips, captions, business events and standard test reports. Each run publishes as a layer
7
+ (`atlas report publish --manifest`), so it combines with every other suite's evidence. This page covers
8
+ running them, from a runnable example to the Atlas client.
9
+
10
+ ```
11
+ specs/ atlas lint the charts are pure, reachable, free of silent dead ends,
12
+ app.scxml ──▶ and every journey replays
13
+ child.machine.yaml atlas plan curated journeys + shortest paths covering every transition
14
+ journeys.yaml atlas run each path against the product, through a driver
15
+ │
16
+ ▼
17
+ atlas-runs/<run>/manifest.json, junit.xml, ctrf.json, chart.mmd, media/
18
+ ```
19
+
20
+ The spec is the source of truth for the product's behaviour, not for the test code: a state says
21
+ what a person sees, an event says what happens in business language ("Adds a todo", "Sync fails"),
22
+ and the implementation says how a driver makes each event happen and recognises each state.
23
+
24
+ ## Quick start: the todo example
25
+
26
+ [`examples/todo`](https://github.com/crvouga/atlas/tree/main/examples/todo) is a dependency-free web app (static HTML and vanilla JavaScript
27
+ served by a small Node server) specified as two charts:
28
+
29
+ - `specs/todo-app.scxml`, the root chart in SCXML: a welcome screen, a "Setting up" state that
30
+ invokes the child chart, and a `<parallel>` "Working on the list" state whose "Sync" and "List"
31
+ regions change independently.
32
+ - `specs/getting-started.machine.yaml`, the child chart in XState YAML. Its two final states hand
33
+ back to the parent through `meta.doneEvent`.
34
+ - `specs/journeys.yaml`, four curated journeys.
35
+ - `specs/getting-started.feature` and `specs/working-the-list.feature`, the same stories in Gherkin.
36
+ - `specs/todo.qnt`, the list, its sync, and its actions as a Quint model.
37
+
38
+ The Atlas client lists the features under Gherkin and the model under Quint.
39
+
40
+ It has every kind of event: user events (taps and typing), a **system** event ("Sync fails", which
41
+ the test triggers through a real HTTP endpoint on the example server), a **time** event ("Retry
42
+ timer elapses", which jumps Playwright's clock 30 seconds) and **hand-off** events from the child
43
+ chart. The server reports business events as CloudEvents to an Atlas `cloudEventsHttpSource`, so
44
+ the states' `meta.events` are checked on every run.
45
+
46
+ This repository runs on [Bun](https://bun.sh). Video clips and WebP thumbnails need FFmpeg on the
47
+ `PATH`. From the repository root:
48
+
49
+ ```sh
50
+ bun install
51
+ bunx playwright install chromium
52
+
53
+ bun run example:lint # pure, reachable, no undocumented dead ends, journeys replay, everything implemented
54
+ bun run example:plan # 4 journeys + 1 generated path cover all 14 transitions
55
+ bun run example:fast # run every path quickly
56
+ bun run example:run # statechart showcase, then the Gherkin features and the Quint model
57
+ bun test examples # the same spec against an in-memory model, as a test suite
58
+
59
+ bun run example:clients # clients/: one chart across a phone, a desktop and the sync server at once
60
+ bun run example:clients:bun # the same, with the desktop in Bun's WebKit and the phone in Playwright's Chromium
61
+ ```
62
+
63
+ To run every chart of the example and browse the results, run these in separate terminals; the
64
+ visualizer updates when a run finishes.
65
+
66
+ ```sh
67
+ bun run atlas:run # example:clients, then example:run: every chart of the example, in showcase mode
68
+ bun atlas # the example's specs, chart runs, and layered reports
69
+ ```
70
+
71
+ Each config writes its own run and the Atlas client shows one run at a time: the latest is the
72
+ single-browser run, and the run across clients is the one before it in the run picker.
73
+
74
+ Gherkin, Quint, DMN, Alloy and OpenSLO documents sit beside the charts. Results compose across
75
+ environments, branches and backends within each project. **Sources** switches report sources on or off.
76
+
77
+ `bun atlas` runs the singleton Atlas server and the client together at `http://localhost:5180`.
78
+ It starts a project-agnostic node with no example data. Select a project using the top-right picker.
79
+ In another terminal run `bun run example:run`: statecharts, Gherkin scenarios and Quint traces stream running checks, completed results and current steps
80
+ to project `todo-app` on that server while testing. Producers renew a heartbeat every 5 seconds; 30 seconds of silence
81
+ marks an invocation interrupted. Final specs and recordings upload when their privacy scan completes.
82
+ Tests show inline running indicators and logs without waiting for the whole suite to finish. Report history
83
+ is retained in `atlas-data/projects/todo-app`; local run artifacts remain in `atlas-runs` and
84
+ `atlas-reports`. To upload a completed run again without rerunning tests,
85
+ use `bun run example:publish`. Set `ATLAS_SERVER_URL` for a different upload destination (and
86
+ `ATLAS_PUBLISH_TOKEN` when it requires one). Restart `bun atlas` when switching from the older
87
+ file-based example client; a busy port is an error so publishers cannot silently target another server.
88
+
89
+ A showcase run ends with:
90
+
91
+ ```
92
+ States 14/14 passed, 0 failed, 0 not reached
93
+ Transitions 14/14 passed, 0 failed, 0 not reached
94
+ Manifest: examples/todo/atlas-runs/<run-id>/manifest.json
95
+ ```
96
+
97
+ `example:run` then runs the Gherkin features in a browser and the Quint model, and streams every scenario and trace as it runs, including sharing and renaming. The final recordings are uploaded alongside the completed statechart run.
98
+
99
+ `bun test examples` runs [`model.test.ts`](https://github.com/crvouga/atlas/blob/main/examples/todo/model.test.ts): the same charts and journeys
100
+ against `TodoModel`, a plain class, through `functionDriver` and `describeAtlas`. Only the driver
101
+ and the implementation differ, which is the point: the spec belongs to the product, not to one
102
+ test tool.
103
+
104
+ ## Writing specs
105
+
106
+ ### Files
107
+
108
+ Atlas loads every chart and journeys file directly inside the specs directory:
109
+
110
+ - `*.scxml`, `*.machine.json`, `*.machine.yaml` (or `.yml`): one chart each;
111
+ - `journeys.yaml` (or `.yml`, `.json`): the curated journeys.
112
+
113
+ The **root** chart is the one no other chart invokes. A chart's id is the SCXML `name` attribute or
114
+ the XState `id`.
115
+
116
+ ### The pure subset
117
+
118
+ Atlas walks charts without running any code, so it executes only the parts of a statechart that
119
+ are pure structure:
120
+
121
+ - states, nesting (`initial`), `parallel` regions and `final` states;
122
+ - event transitions, written as a target (`Opens: Open` or `{ target: '#Open' }`);
123
+ - `invoke` that names another chart (`src`, `id`, `onDone`), composed statically;
124
+ - `meta`.
125
+
126
+ Guards, actions, `entry`/`exit`, `context`, `after` (delays), `always`, `output` and `onError` are
127
+ lint errors. A condition becomes a state or an event instead: "Payment declined" is an event the
128
+ system sends, not a guard on "Pays"; "30 seconds pass" is a time event, not an `after`.
129
+
130
+ State names are global across the composed product (transitions target `#<name>`), so they must be
131
+ unique across charts, and they cannot contain dots. Names are business language and may contain
132
+ spaces.
133
+
134
+ ### `meta`
135
+
136
+ | Field | Meaning |
137
+ | --- | --- |
138
+ | `description` | What is true in this state. Required on every state by default (`requiredMeta` changes that). |
139
+ | `snapshot` | What a person sees, in words. |
140
+ | `events` | Business events that happen on entering the state, checked against what event sources observed. |
141
+ | `checks` | Things to verify on screen, quoted from the spec; the implementation's `checks()` returns their results. |
142
+ | `confidence` | `confirmed` or `assumed`. |
143
+ | `source` | Where the behaviour is documented. |
144
+ | `deadEnd` | Why a state with no way out is meant to be one. Without it, a dead end is a lint error. |
145
+ | `doneEvent` | On a child chart's final state: the event its parent receives from that final. |
146
+ | `childMachine`, `childFinalEvents` | Legacy composition on the parent state. Prefer `invoke`. |
147
+
148
+ ### SCXML conventions
149
+
150
+ SCXML ids are XML IDs and event attributes are token lists, so business names travel in an
151
+ `atlas:name` attribute next to a token id. Metadata lives in the `atlas` namespace, as SCXML allows:
152
+
153
+ ```xml
154
+ <scxml xmlns="http://www.w3.org/2005/07/scxml" xmlns:atlas="https://github.com/crvouga/atlas/ns/1"
155
+ version="1.0" datamodel="null" name="Todo app" initial="welcome">
156
+ <state id="welcome" atlas:name="Welcome">
157
+ <atlas:meta>
158
+ <atlas:description>The first screen a person sees.</atlas:description>
159
+ <atlas:event>App opened</atlas:event>
160
+ </atlas:meta>
161
+ <transition event="starts-setup" atlas:name="Starts setup" target="setting-up"/>
162
+ </state>
163
+ <state id="setting-up" atlas:name="Setting up">
164
+ <atlas:meta><atlas:description>The child chart runs here.</atlas:description></atlas:meta>
165
+ <invoke id="getting-started" src="getting-started.machine.yaml" atlas:name="Getting started"/>
166
+ <transition event="done.invoke.getting-started" target="working"/>
167
+ </state>
168
+ ...
169
+ </scxml>
170
+ ```
171
+
172
+ `<atlas:event>`, `<atlas:check>` and `<atlas:source>` repeat to form lists; `<atlas:dead-end>` is
173
+ `meta.deadEnd`; a final state's `atlas:done-event` attribute is `meta.doneEvent`. `atlas export
174
+ --format scxml` writes any chart (or the composed product) back out in this form, and reading it
175
+ again gives the same chart.
176
+
177
+ ### Composition
178
+
179
+ A parent state runs a child chart with any of:
180
+
181
+ 1. XState `invoke: { src: 'Child', id: 'child', onDone: '#Next' }`, or SCXML `<invoke id="child">`
182
+ plus a `done.invoke.child` transition;
183
+ 2. `meta.doneEvent` on the child's final states, each naming the event the parent receives, which
184
+ the parent handles in its `on` (a final whose event the parent does not handle goes to
185
+ `onDone`);
186
+ 3. legacy `meta.childMachine: 'Child'` and `meta.childFinalEvents: { <final>: <event> }` on the
187
+ parent state.
188
+
189
+ Atlas inlines each child into the state that invokes it. The child's final states stay as states
190
+ (so they keep their screenshots and checks) but take the parent's transition for their hand-off
191
+ event. When a path reaches one, the hand-off happens by itself: journeys never list hand-off
192
+ events, and the planner inserts them. Give each hand-off event an implementation of kind
193
+ `hand-off` (usually a no-op); events without an implementation count as blocked.
194
+
195
+ ### Journeys
196
+
197
+ ```yaml
198
+ journeys:
199
+ Keeps working through a sync outage:
200
+ description: Adds todos while the sync is down, and the retry catches up.
201
+ events: [Starts setup, Skips naming the list, Adds a todo, Sync fails, Adds a todo, Retry timer elapses]
202
+ endsIn: [Some todos open, Synced]
203
+ ```
204
+
205
+ The linter replays each journey through the composed chart and checks it ends in every `endsIn`
206
+ state. Each journey runs as its own path; generated paths only cover what journeys leave out.
207
+
208
+ ## Implementing a spec
209
+
210
+ An implementation tells a driver how to make each event happen and how to recognise each state.
211
+ Events are keyed by their exact name in the spec.
212
+
213
+ ```ts
214
+ import { defineConfig } from '@crvouga/atlas';
215
+ import { playwrightDriver, role, screen, text } from '@crvouga/atlas/playwright';
216
+
217
+ export default defineConfig({
218
+ specs: './specs',
219
+ driver: () => playwrightDriver({ clock: true }),
220
+ targets: ['http://127.0.0.1:4317'],
221
+ implementation: {
222
+ async setup({ page }) {
223
+ await page.goto('http://127.0.0.1:4317');
224
+ },
225
+ events: {
226
+ 'Adds a todo': {
227
+ kind: 'user',
228
+ how: 'Types a todo and taps "Add".',
229
+ async run({ page, user }) {
230
+ await user.type(page.getByLabel('New todo'), 'Water the plants', 'New todo');
231
+ await user.tap(page.getByRole('button', { name: 'Add' }), 'Add');
232
+ }
233
+ },
234
+ 'Retry timer elapses': {
235
+ kind: 'time',
236
+ how: 'Jumps the page clock 30 seconds.',
237
+ run: ({ page }) => page.clock.fastForward(30_000)
238
+ }
239
+ },
240
+ states: {
241
+ 'No todos': screen({ all: [text('Nothing to do yet')] }),
242
+ Offline: screen({ all: [text('Offline')], none: [text('All changes synced')] })
243
+ }
244
+ }
245
+ });
246
+ ```
247
+
248
+ **Events** (`EventImplementation<C>`): `kind` is `user`, `system`, `time` or `hand-off`; `how` is a
249
+ plain-language note shown in reports; `run(ctx, tools)` makes it happen; `prepare(ctx, tools)`
250
+ creates test data before the path starts; `blocked: '<reason>'` marks an event that cannot run yet,
251
+ so paths stop before it and report why.
252
+
253
+ **States** (`StateImplementation<C>`): `recognize(ctx)` returns whether the state is showing and the
254
+ signals it looked at. Optional: `checks(ctx)` (results for `meta.checks`; a failed check fails the
255
+ state), `lookIn(ctx)` (where to look when the change happens off screen, such as reloading or
256
+ opening another tab), `settle(ctx)` (within-state actions after the screenshot, such as
257
+ dismissing a toast), `transient` (shown too briefly to wait for) and `sameAs` (a state the UI cannot
258
+ tell apart from this one).
259
+
260
+ **Setup**: `setup(ctx, { path, tools })` puts the product in the path's start configuration;
261
+ `canStart(active)` returns `true` or the reason a start configuration cannot be set up yet. It is
262
+ optional when seeds cover every start.
263
+
264
+ **Seeds** (`seeds: Record<string, Seed<C>>`): see [Seeds and parallel runs](#seeds-and-parallel-runs).
265
+
266
+ **Correlation**: `correlate(ctx)` returns ids (a user id, a trace id) that tell this attempt's
267
+ business events from those of attempts running beside it.
268
+
269
+ Only leaf states need recognisers. `atlas lint --require-implementations` lists every event and
270
+ leaf state in scope that has no implementation.
271
+
272
+ ## Seeds and parallel runs
273
+
274
+ A chart of a whole product is deep: a member reaches a results screen after signing up,
275
+ onboarding, paying and booking. Walking every path from the initial state repeats that prefix
276
+ in every path, so a run gets slower the more of the product it covers. Seeds remove the prefix.
277
+
278
+ A seed puts the system directly in one configuration of the chart, usually through an API or the
279
+ database: create a member who has paid, sign them in, open the screen.
280
+
281
+ ```ts
282
+ implementation: {
283
+ seeds: {
284
+ 'Paid member': {
285
+ at: 'Paid', // a state name, or a full state value
286
+ how: 'Creates a paid member through the admin API and signs in.',
287
+ async run({ page, api }) {
288
+ const member = await api.createMember({ plan: 'paid' });
289
+ await page.goto(`/sign-in?token=${member.token}`);
290
+ }
291
+ }
292
+ },
293
+ events: { /* … */ },
294
+ states: { /* … */ }
295
+ }
296
+ ```
297
+
298
+ With seeds, the plan changes in three ways:
299
+
300
+ - **Journeys are cut at seeded configurations.** A journey is replayed from the initial state, and
301
+ wherever it passes through a configuration a seed produces, it is cut. Each stretch becomes a path
302
+ that starts from that seed. A stretch that several journeys share becomes one path. The manifest's
303
+ `journeys` lists the paths each journey is made of. A journey passes when all of them pass, fails
304
+ when one fails, and is `not-reached` while any is blocked.
305
+ - **Generated paths start from the nearest seed.** A breadth-first search from every start point at
306
+ once finds, for each uncovered transition, the shortest way to take it from any seed (or from
307
+ `setup`'s start). Ties go to the seed declared first.
308
+ - **Redundant paths are dropped.** A generated path that is a prefix of another path from the same
309
+ start is not planned.
310
+
311
+ The plan is deterministic. The same chart, journeys and seeds give the same paths, ids and keys
312
+ (`key` hashes the start and the events) on every machine, so results can be composed:
313
+
314
+ - **Workers.** `--workers n` (or `workers` in the config) runs n path attempts at once. Each attempt
315
+ gets its own driver context. The longest paths go first, timed by earlier runs.
316
+ - The driver's `concurrency` caps the worker count. Playwright and Bun contexts and HTTP clients
317
+ run side by side. A WebDriver session or a Detox device runs one at a time unless you raise it.
318
+ - The manifest is a pure fold of the path outcomes in plan order, so the same outcomes give the
319
+ same report whatever order they finished in.
320
+ - **Shards.** `--shard i/n` runs one part of the plan, split by the plan alone, so every machine
321
+ agrees on the split. `atlas merge <run-dir>...` composes the shards into one run, with their media
322
+ copied in.
323
+ - **Reruns.** `--rerun-failed` keeps the passed paths of the newest run, matched by key, and runs
324
+ the rest. The result is still one complete run.
325
+ - **Areas.** `--seed <name>` (repeatable) runs only the paths that start from that seed (`start`
326
+ for paths `setup` starts). `--state <name>` scopes the run to one state of the chart. With these you can
327
+ run one area of the product on its own, then merge it into a full run.
328
+
329
+ Seeds are what make the composition sound: a seed must leave the system exactly as the steps that
330
+ lead there would. Atlas checks this both ways and reports it:
331
+ - A seeded path must first recognise its start state.
332
+ - The manifest's `seeds` lists, for each seed, the passed paths that reached the same states through
333
+ real steps (`verifiedBy`).
334
+
335
+ A seed nobody reaches through the steps is a claim that has not been checked yet. In the
336
+ visualizer, seeded states carry a "Seeded" tag.
337
+
338
+ Business events and parallel runs: a shared source, such as a log file, sees every attempt's events.
339
+ `EventSource.mark()` returns a position that `collect(mark)` reads from, so attempts don't consume
340
+ each other's marks.
341
+ - With `correlate(ctx)`, only events and log lines that mention one of the attempt's ids count.
342
+ - Without it, a run on several workers reports business events as "no signal" rather than guessing.
343
+
344
+ ## Drivers
345
+
346
+ | Driver | Package | Plays | Speed | Clips |
347
+ | --- | --- | --- | --- | --- |
348
+ | `functionDriver(create)` | `@crvouga/atlas` | A domain model, a CLI, anything reachable from code | Microseconds | No |
349
+ | `httpDriver(baseUrl)` | `@crvouga/atlas` | An API client with a cookie jar: a partner system, an operator API, a back office job | Milliseconds | No |
350
+ | `bunWebViewDriver()` | `@crvouga/atlas/bun` | A web app in Bun's built-in browser (WebKit on macOS, Chrome elsewhere) | A view in milliseconds, screenshots in about 10 ms | From screenshots |
351
+ | `playwrightDriver()` | `@crvouga/atlas/playwright` | A web app (or React Native on the web) in Chromium, with a fake clock | About a second per context | Screencast with a touch overlay |
352
+ | `webdriverDriver()` | `@crvouga/atlas/webdriver` | Safari, Chrome, Firefox, Edge; native iOS and Android apps through Appium | Depends on the server | From screenshots, or Appium's recorder |
353
+ | `detoxDriver()` | `@crvouga/atlas/detox` | A React Native app, inside a Detox test run | Depends on the device | Detox artifacts |
354
+
355
+ The Bun, Playwright, WebDriver and Detox contexts all provide a `SignalContext` (`goto`,
356
+ `isVisible`, `user.tap`, `user.type`), and they resolve the same signals (`testId`, `role`, `label`,
357
+ `text`, `css` from `@crvouga/atlas/signals`), each against its own UI tree: the DOM, or the
358
+ accessibility tree of a native app, where a test ID is the accessibility identifier (React
359
+ Native's `testID`). An implementation written
360
+ against `SignalContext` with `screen` from `@crvouga/atlas/signals` runs on any of them unchanged.
361
+
362
+ `bunWebViewDriver` needs the Bun runtime: run `bun --bun atlas run`. The `atlas` binary runs on
363
+ Node (through tsx) or Bun, and Playwright works under Bun too, so one run can mix Bun's WebKit and
364
+ Playwright's Chromium.
365
+
366
+ ## Several clients in one run
367
+
368
+ Product flows cross devices: a member books on an iOS app, an operator approves in a web
369
+ dashboard, a partner confirms over an API. `combineDrivers` runs several drivers as the named
370
+ clients of one run, and `combineImplementations` merges what each client does and shows:
371
+
372
+ ```ts
373
+ import { combineDrivers, combineImplementations, defineConfig, httpDriver, type HttpClient } from '@crvouga/atlas';
374
+ import { role, screen, text, type SignalContext } from '@crvouga/atlas/signals';
375
+ import { playwrightDriver } from '@crvouga/atlas/playwright';
376
+ import { accessibilityId, webdriverDriver } from '@crvouga/atlas/webdriver';
377
+
378
+ type Clients = { member: SignalContext; operator: SignalContext; partner: HttpClient };
379
+
380
+ export default defineConfig<Clients>({
381
+ specs: './specs',
382
+ driver: combineDrivers<Clients>(
383
+ {
384
+ member: webdriverDriver({
385
+ url: 'http://127.0.0.1:4723', // Appium
386
+ capabilities: { platformName: 'iOS', 'appium:automationName': 'XCUITest', 'appium:app': './build/App.app' }
387
+ }),
388
+ operator: playwrightDriver({ baseUrl: 'http://127.0.0.1:3000', device: { viewport: { width: 1280, height: 800 } } }),
389
+ partner: httpDriver('http://127.0.0.1:4000')
390
+ },
391
+ { screens: 'all' }
392
+ ),
393
+ implementation: combineImplementations<Clients>(
394
+ {
395
+ member: {
396
+ events: { 'Requests a visit': { kind: 'user', how: 'Taps "Request".', run: (m) => m.user.tap(role('button', 'Request'), 'Request') } },
397
+ states: { 'Visit confirmed': screen({ all: [text('Confirmed')] }) }
398
+ },
399
+ operator: {
400
+ setup: (o) => o.goto('/queue'),
401
+ events: { 'Operator approves': { kind: 'user', how: 'Approves it in the queue.', run: (o) => o.user.tap(role('button', 'Approve'), 'Approve') } },
402
+ states: { 'Waiting for approval': screen({ all: [role('row', 'New request')] }) }
403
+ },
404
+ partner: {
405
+ events: { 'Partner confirms': { kind: 'system', how: 'POSTs the confirmation webhook.', run: (p) => p.post('/webhooks/confirm', { ok: true }).then(() => undefined) } }
406
+ }
407
+ },
408
+ { setup: async ({ partner }) => void (await partner.post('/test/reset')) }
409
+ )
410
+ });
411
+ ```
412
+
413
+ - Each client's part is written against that client's own context, so every driver's helpers
414
+ work unchanged; `shared` (the second argument) holds setup and events that need several clients
415
+ at once. `shared.setup` runs first, then each client's setup in order.
416
+ - `onClient(name, impl)` lifts a single event or state implementation to a client.
417
+ - An event's client is recorded for its clip; a state's client is the screen it is recognised and
418
+ photographed on. With `screens: 'all'`, every other client's screen is captured at the same
419
+ moment (`screenshot.also`).
420
+ - Timeline entries, clips, states and transitions carry `client` in the manifest;
421
+ `run.environment.clients` names the driver each client ran on; a failed attempt keeps one trace
422
+ per client.
423
+ - If one client cannot open (no simulator booted), the others are closed and the attempt fails
424
+ naming that client.
425
+
426
+ [`examples/todo/clients`](https://github.com/crvouga/atlas/tree/main/examples/todo/clients) runs a chart (`specs/clients/shared-list.scxml`)
427
+ across a phone, a desktop and the sync server; pick each device's driver with `ATLAS_PHONE` and
428
+ `ATLAS_DESKTOP` (`playwright`, `bun` or `webdriver`).
429
+
430
+ ## Writing a driver
431
+
432
+ A driver owns the thing under test for one path attempt: a browser context, a device, a model or
433
+ an API client. Only `open` and `close` are required.
434
+
435
+ ```ts
436
+ type Focus = { client?: string };
437
+
438
+ type Driver<C> = {
439
+ name: string;
440
+ open(input: { path: PlannedPath; attempt: number; mode: RunMode; directory: string; timeline: Timeline }): Promise<C>;
441
+ close(ctx: C, input: { failed: boolean; traceFile: string }): Promise<{ trace?: string; traces?: Record<string, string> } | void>;
442
+ screenshot?(ctx: C, file: string, focus?: Focus): Promise<Image | null>;
443
+ record?(ctx: C, workDirectory: string, focus?: Focus): Promise<{ stop(output: string): Promise<Clip | null> }>;
444
+ text?(ctx: C, focus?: Focus): Promise<string>;
445
+ hold?(ctx: C, ms: number, focus?: Focus): Promise<void>;
446
+ pacing?: { before: number; after: number };
447
+ clients?: Record<string, string>;
448
+ concurrency?: number;
449
+ dispose?(): Promise<void>;
450
+ };
451
+ ```
452
+
453
+ `focus` says which client a multi-client run is looking at; single-client drivers ignore it.
454
+ A driver without a native screen recorder can encode clips from screenshots with
455
+ `recordFrames(capture, workDirectory)`.
456
+
457
+ - `open` returns a fresh context for each attempt; `close` may return the path of a trace file
458
+ (Playwright writes one for failed attempts).
459
+ - `screenshot` and `record` produce media; `record` is used only in showcase mode, one clip per
460
+ transition.
461
+ - `text` returns the visible text, for the privacy scan.
462
+ - `concurrency` is how many contexts can be open at once (1 when omitted). Workers never exceed
463
+ it, so a driver of one device stays sequential while browser contexts run side by side.
464
+ - `timeline.add({ kind, label, ... })` logs taps, typing, system calls and time jumps; they become
465
+ each clip's captions.
466
+
467
+ For anything you can reach from Node (a domain model, an API client, a CLI), `functionDriver(create)`
468
+ is a complete driver:
469
+
470
+ ```ts
471
+ import { functionDriver } from '@crvouga/atlas';
472
+
473
+ const driver = functionDriver(() => new TodoModel());
474
+ ```
475
+
476
+ ## CLI
477
+
478
+ ```
479
+ atlas lint [--config atlas.config.ts | --specs <dir>] [--require-implementations]
480
+ atlas plan [--config atlas.config.ts] [--state <name>]
481
+ atlas run [--config atlas.config.ts] [--mode fast|showcase] [--workers n] [--state <name>]
482
+ [--seed <name>]... [--paths id,id] [--shard i/n] [--rerun-failed | --reuse <run-dir>]
483
+ [--no-retry] [--output <dir>]
484
+ atlas merge [--config atlas.config.ts] [--output <dir>] <run-dir> <run-dir>...
485
+ atlas export --specs <dir> --format scxml|mermaid|markdown|json [--out <file>]
486
+ atlas report publish|stream|status|merge|doctor|policy … layered evidence for any kind of spec (below)
487
+ ```
488
+
489
+ - `lint` with `--specs` checks charts and journeys alone; with a config it also lists missing
490
+ implementations, and `--require-implementations` makes them errors.
491
+ - `plan` prints the seeds and each path with:
492
+ - its seed and step count;
493
+ - what blocks it;
494
+ - the paths each journey is made of;
495
+ - the total and longest path length, and how many transitions stay uncovered.
496
+ - `run` exits non-zero when a path fails or the privacy scan finds something. `--paths` runs only
497
+ the given path ids; `--no-retry` skips the retry of failed paths.
498
+ - `--workers`, `--shard`, `--seed`, `--state`, `--rerun-failed` and `--reuse` are described in
499
+ [Seeds and parallel runs](#seeds-and-parallel-runs).
500
+ - `merge` composes runs of the same plan (shards, or a rerun and its earlier run) into one run.
501
+ - `export` writes the composed product (every child inlined) as SCXML, Mermaid, Markdown or
502
+ XState JSON. The Markdown is a readable map for the people who own the product: one section per
503
+ area (each parallel region apart), every state with its description and what the user sees (a
504
+ `snapshot` written as an identifier is left out), a table of every event that can happen there
505
+ and where it leads, then the named journeys.
506
+
507
+ The config file is TypeScript (loaded with `tsx`) and defaults to `atlas.config.ts` in the current
508
+ directory. Relative paths in it resolve against the config file's directory.
509
+
510
+ ### `atlas report`
511
+
512
+ Evidence for specs of any kind, not only statecharts: `publish`, `status`, `merge`, `doctor`, `policy`
513
+ and `stream`. Run `atlas report --help` for every flag. [Integrating Atlas](integrating.md) is the guide,
514
+ [running Atlas for a project](deploying.md) covers the server, S3 and filters, and
515
+ [the report contract](report-contract.md) holds the rules. For a statechart run:
516
+
517
+ ```sh
518
+ atlas report publish --producer statecharts --environment desktop-web \
519
+ --manifest atlas-runs/<run>/manifest.json --specs ./specs --upload-run
520
+ ```
521
+
522
+ `--upload-run` also sends the run's screenshots and clips to the store (or the server) so the client can show them
523
+ (only when the run's privacy scan passed).
524
+
525
+ ## Config reference
526
+
527
+ `defineConfig<C>(config)` returns the config unchanged, typed for the driver's context `C`.
528
+
529
+ | Field | Meaning |
530
+ | --- | --- |
531
+ | `specs` | The specs directory. |
532
+ | `driver` | A `Driver<C>`, or a function returning one (called once per run). |
533
+ | `implementation` | `{ events, states, seeds?, setup?, canStart?, correlate? }`. |
534
+ | `chart` | Run one chart of the product; the root chart when omitted. |
535
+ | `start`, `startLabel` | Where generated paths start (an XState state value) and how to describe it; the initial state when omitted. |
536
+ | `entryEvents` | When running one chart or one state: the only events that enter it from outside. |
537
+ | `state` | Run one state of a chart and what is inside it, with the transitions into and out of it: one area of a chart that models the whole product. |
538
+ | `workers` | Path attempts that run at once, capped by the driver's `concurrency`; one per CPU when omitted. |
539
+ | `beforeRun` | Runs once before any path: prepare what every seed relies on (reference data, configuration). |
540
+ | `output` | Where runs are written; `atlas-runs` beside the config. |
541
+ | `eventSources` | `() => EventSource[]`: where business events come from. |
542
+ | `eventMatchers` | How each business event name is recognised: `{ type }`, `{ pattern }` or a function. |
543
+ | `privacy` | Extra `deny` patterns, `allowEmails` and `allowHosts`. |
544
+ | `targets` | URLs the run reaches, checked against `privacy.allowHosts` before anything runs. |
545
+ | `environment` | Recorded in the manifest. |
546
+ | `stepTimeoutMs` | How long to wait for a state to show up (default 45 000). |
547
+ | `requiredMeta` | `meta` fields every state needs (default `['description']`). |
548
+
549
+ The programmatic API mirrors the CLI: `prepare(config)` loads, lints and plans; `runConfig(config,
550
+ options)` runs; `loadSpecDirectory`, `lintBundle`, `chartScope`, `planChart` and `runPaths` are the
551
+ steps underneath.
552
+
553
+ ## How a run works
554
+
555
+ 1. **Plan.** Curated journeys come first.
556
+ - When running one chart of a larger product, each journey is cut to its stretch inside the
557
+ chart, starting at an entry transition.
558
+ - With seeds, journeys are also cut wherever they pass through a seeded configuration.
559
+ - Then, for each transition still uncovered, the plan adds the shortest path that takes it from
560
+ the nearest seed or start. The search is breadth-first, so loops stay bounded.
561
+ - Paths stop before blocked events. A blocked path whose runnable part other paths already cover
562
+ is reported but not run.
563
+ 2. **Run each path.** Paths run on a pool of workers, longest first. Each gets a fresh driver
564
+ context, then every event's `prepare`, then its seed's `run` (or `setup`). The start state must
565
+ be recognised. Then for each step: run the event, wait for the target state, run its checks,
566
+ collect business events, screenshot, `settle`.
567
+ 3. **Retry.** A failed path runs once more. Passing the second time makes it `flaky`.
568
+ 4. **Write.** The manifest and reports are rewritten each time a path finishes, from every outcome
569
+ so far in plan order, so a crash keeps what ran. `outcomes.json` keeps the raw outcomes, for
570
+ `atlas merge` and `--rerun-failed`.
571
+
572
+ Every path, state and transition ends as `passed`, `failed`, `flaky` or `not-reached`. Reaching a
573
+ state also reaches its ancestors. A failure records the recogniser's signals and which known states
574
+ the screen looked like instead.
575
+
576
+ ### Run modes
577
+
578
+ | Mode | Pacing | Media |
579
+ | --- | --- | --- |
580
+ | `fast` | None | Screenshots of every state (when the driver takes them) |
581
+ | `showcase` | Holds before and after each step; taps glide and type at human speed | Screenshots, plus an H.264 clip of every transition with a poster and WebVTT captions |
582
+
583
+ ## Manifest and reports
584
+
585
+ Each run writes `<output>/<timestamp>-<mode>/`:
586
+
587
+ | File | Contents |
588
+ | --- | --- |
589
+ | `manifest.json` | The run (commit, branch, environment, mode, spec version, scope), every chart, the composed map (nodes and edges), every state and transition with status, media, timeline and reasons, every path with its attempts, coverage and privacy results. Schema: `packages/schema/schemas/manifest.schema.json`. |
590
+ | `junit.xml` | One test case per path: failures, skips (with the blocking reason) and flaky passes. |
591
+ | `ctrf.json` | The same as CTRF. |
592
+ | `chart.mmd` | The composed chart as a Mermaid state diagram. |
593
+ | `outcomes.json` | Each path's raw outcome by path key, which the manifest is folded from: for merging shards and reusing passed paths. |
594
+ | `media/paths/<path>/NN.png`, `.webp` | Screenshots of each state a path reached; `failed-NN.png` where it failed. |
595
+ | `media/clips/<path>/NN.mp4`, `.poster.webp`, `.vtt` | Showcase clips of each transition, with captions. |
596
+ | `media/traces/<path>.zip` | Driver traces of failed attempts (Playwright traces with the Playwright driver). |
597
+
598
+ A run is also evidence for the report. `atlas report publish --manifest <run>/manifest.json` turns it
599
+ into a layer with one check per path, identified by the path's `key` (the same start and events give
600
+ the same key in every run). `atlas merge` composes the shards of one plan into one *run*; layers
601
+ compose a run with everything else: other suites, other environments, other branches.
602
+
603
+ ## Business events
604
+
605
+ States list the business events that happen on entering them in `meta.events`. An `EventSource`
606
+ says what actually happened. `mark()` returns a position before each event, and `collect(mark)`
607
+ returns what arrived after it, once the state is recognised:
608
+
609
+ - `cloudEventsHttpSource()` starts an HTTP endpoint that accepts CloudEvents in structured, binary
610
+ or batch mode. Point the product's event bus, webhook relay or collector at its `url`.
611
+ - `logFileSource(file)` collects lines appended to a log, for products whose only event stream is a
612
+ log; match them with `{ pattern }`.
613
+ - Any object with `name`, `mark` and `collect` works, such as an in-memory bus in a unit test.
614
+
615
+ With no matcher, a business event is observed when a CloudEvent's `type` equals its name. Events
616
+ with neither a matcher nor a CloudEvents source are reported as "no signal", never as missing.
617
+
618
+ ## Privacy
619
+
620
+ Runs produce media and manifests that get shared, so Atlas refuses to leak real data:
621
+
622
+ - Before running, every `targets` URL must be on `allowHosts` (loopback only by default).
623
+ - After every step, the driver's visible text is scanned, and the manifest is scanned before it is
624
+ written. By default it flags email addresses outside `example.com`/`.org`/`.net` and the `.test`,
625
+ `.example`, `.invalid`, `.localhost` and `.local` domains, anything shaped like a US Social Security
626
+ number, and card numbers other than the well-known test cards. Findings describe the match without
627
+ repeating it.
628
+ - Any finding fails the run. Add `deny` patterns and `allowEmails` in `privacy`.
629
+
630
+ ## Visualizer
631
+
632
+ The [`atlas client`](../packages/visualizer) command renders the charts from the specs directory and
633
+ overlays runs from the runs directory: screenshots on states, clips on transitions, statuses and
634
+ history. See its README for how to point it at a project.
635
+
636
+ Its open index lists every place, journey and spec beside one plate. Connect multiple local
637
+ directories, HTTP report stores and streaming
638
+ APIs using [report backends](report-backends.md). The runner publishes reached screens
639
+ and completed steps while paths are still running.
640
+
641
+ Layered reports for any spec appear beside the charts: each Gherkin, Quint, DMN, Alloy and OpenSLO
642
+ document shows the combined result of its checks per environment, with the layer behind each. Point
643
+ the client at your stores with `reportsHost` ([integrating Atlas](integrating.md#6-browse-it)).