@jterrazz/test 16.1.1 → 17.0.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 (304) hide show
  1. package/README.md +45 -171
  2. package/dist/browser/index.js +2 -2980
  3. package/dist/browser/services.js +2 -0
  4. package/dist/browser/stubs.js +2 -0
  5. package/dist/checker.d.ts +6 -1
  6. package/dist/checker.js +4534 -2628
  7. package/dist/config.d.ts +40 -0
  8. package/dist/config.js +309 -0
  9. package/dist/in-place.global-setup.d.ts +11 -0
  10. package/dist/in-place.global-setup.js +21 -0
  11. package/dist/index.d.ts +1653 -3004
  12. package/dist/index.js +5226 -3845
  13. package/dist/mobile-session.global-setup.d.ts +14 -0
  14. package/dist/mobile-session.global-setup.js +31 -0
  15. package/dist/oxlint.cjs +6069 -5634
  16. package/dist/oxlint.d.cts +43 -7212
  17. package/dist/oxlint.d.ts +43 -7212
  18. package/dist/oxlint.js +6127 -5693
  19. package/dist/route-coverage.global-setup.d.ts +42 -0
  20. package/dist/route-coverage.global-setup.js +2 -0
  21. package/dist/services.d.ts +3 -0
  22. package/dist/services.global-setup.d.ts +43 -0
  23. package/dist/services.global-setup.js +2 -0
  24. package/dist/services.js +597 -0
  25. package/dist/shared-appium.adapter.js +428 -0
  26. package/dist/shared-artifacts.js +32 -0
  27. package/dist/shared-chapter.js +7 -0
  28. package/dist/shared-container.port.d.ts +31 -0
  29. package/dist/shared-derived-projects.js +58 -0
  30. package/dist/shared-device.port.d.ts +189 -0
  31. package/dist/shared-exchange.js +447 -0
  32. package/dist/shared-in-place.js +172 -0
  33. package/dist/shared-index.browser.js +3191 -0
  34. package/dist/shared-index.services.d.ts +346 -0
  35. package/dist/shared-index.stubs.d.ts +287 -0
  36. package/dist/shared-json-shape.js +17 -0
  37. package/dist/shared-landmarks.js +71 -0
  38. package/dist/shared-ledger.js +68 -0
  39. package/dist/shared-literate-plugin.js +183 -0
  40. package/dist/shared-manifest.js +1321 -0
  41. package/dist/shared-pause.js +11 -0
  42. package/dist/shared-peer.js +88 -0
  43. package/dist/shared-playwright.adapter.js +330 -0
  44. package/dist/shared-process.adapter.js +74 -0
  45. package/dist/shared-refusal.js +35 -0
  46. package/dist/shared-resolve.js +24 -0
  47. package/dist/shared-route-coverage.global-setup.js +187 -0
  48. package/dist/shared-scenario-failure.js +198 -0
  49. package/dist/shared-serve.js +365 -0
  50. package/dist/shared-service-images.js +10 -0
  51. package/dist/shared-services.global-setup.js +179 -0
  52. package/dist/shared-session-ledger.js +301 -0
  53. package/dist/shared-setup-entry.js +12 -0
  54. package/dist/shared-source-scan.js +133 -0
  55. package/dist/shared-spec-document.js +365 -0
  56. package/dist/shared-specifications.js +26 -0
  57. package/dist/shared-stream-body.js +130 -0
  58. package/dist/shared-stub-server.js +190 -0
  59. package/dist/shared-temp-root.global-setup.js +80 -0
  60. package/dist/shared-test-baseline.js +26 -0
  61. package/dist/shared-test-section.js +58 -0
  62. package/dist/shared-testcontainers.adapter.js +203 -0
  63. package/dist/shared-tokens.js +55 -0
  64. package/dist/shared-yaml-document.js +119 -0
  65. package/dist/stubs.d.ts +2 -0
  66. package/dist/stubs.js +657 -0
  67. package/dist/temp-root.global-setup.d.ts +7 -0
  68. package/dist/temp-root.global-setup.js +2 -0
  69. package/dist/vitest.d.ts +97 -163
  70. package/dist/vitest.js +373 -525
  71. package/docs/README.md +82 -36
  72. package/docs/concepts/architecture.md +118 -0
  73. package/docs/concepts/conventions.md +333 -0
  74. package/docs/guides/api-specs.md +374 -0
  75. package/docs/guides/cli-specs.md +564 -0
  76. package/docs/guides/component-specs.md +252 -0
  77. package/docs/guides/develop.md +94 -0
  78. package/docs/guides/integration-specs.md +181 -0
  79. package/docs/guides/job-specs.md +180 -0
  80. package/docs/guides/lint-specs.md +174 -0
  81. package/docs/guides/mobile-specs.md +319 -0
  82. package/docs/guides/module-tests.md +149 -0
  83. package/docs/guides/release.md +43 -0
  84. package/docs/guides/spec-documents.md +189 -0
  85. package/docs/guides/website-specs.md +413 -0
  86. package/docs/reference/assertions.md +345 -0
  87. package/docs/reference/glossary.md +20 -0
  88. package/docs/reference/linting.md +483 -0
  89. package/docs/reference/services.md +236 -0
  90. package/docs/reference/stubs.md +498 -0
  91. package/docs/reference/time-and-doubles.md +137 -0
  92. package/docs/reference/tokens.md +258 -0
  93. package/docs/reference/visitor.md +221 -0
  94. package/docs/start/getting-started.md +187 -0
  95. package/docs/start/set-up-a-project.md +214 -0
  96. package/package.json +50 -30
  97. package/schema/spec.schema.json +43 -17
  98. package/dist/appium.adapter.js +0 -489
  99. package/dist/artifacts.js +0 -32
  100. package/dist/fixtures.js +0 -110
  101. package/dist/literate.js +0 -1747
  102. package/dist/match.js +0 -804
  103. package/dist/playwright.adapter.js +0 -438
  104. package/dist/server.js +0 -160
  105. package/dist/stream.js +0 -321
  106. package/dist/substring-warning.js +0 -282
  107. package/docs/01-architecture.md +0 -95
  108. package/docs/02-developing.md +0 -351
  109. package/docs/03-testing.md +0 -495
  110. package/docs/04-operating.md +0 -101
  111. package/docs/05-module-tests.md +0 -152
  112. package/docs/06-integration.md +0 -180
  113. package/docs/07-component.md +0 -246
  114. package/docs/08-website.md +0 -404
  115. package/docs/09-mobile.md +0 -270
  116. package/docs/10-api.md +0 -264
  117. package/docs/11-jobs.md +0 -157
  118. package/docs/12-cli.md +0 -677
  119. package/docs/13-elements.md +0 -248
  120. package/docs/14-assertions.md +0 -314
  121. package/docs/15-tokens.md +0 -253
  122. package/docs/16-contracts.md +0 -438
  123. package/docs/17-services.md +0 -188
  124. package/docs/18-conventions.md +0 -332
  125. package/docs/19-linting.md +0 -512
  126. package/docs/decisions/001-v9-conventions-on-three-channels.md +0 -50
  127. package/docs/decisions/002-the-statique-channel-holds-only-what-upstream-does-not.md +0 -49
  128. package/docs/decisions/003-a-rendered-component-is-a-facet-on-vitest-browser-mode.md +0 -84
  129. package/docs/decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-facet.md +0 -81
  130. package/docs/decisions/005-the-doubles-ladder-is-closed.md +0 -86
  131. package/docs/decisions/006-the-catalogue-reaches-a-file-by-its-role.md +0 -245
  132. package/docs/decisions/007-compose-mode-leaves-the-package.md +0 -53
  133. package/docs/decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md +0 -75
  134. package/docs/decisions/_template.md +0 -22
  135. package/docs/reference/classes/Matcher.md +0 -120
  136. package/docs/reference/classes/ProcessHandle.md +0 -368
  137. package/docs/reference/functions/defineContract.md +0 -39
  138. package/docs/reference/functions/defineContracts.md +0 -36
  139. package/docs/reference/functions/disabled.md +0 -28
  140. package/docs/reference/functions/enabled.md +0 -20
  141. package/docs/reference/functions/focused.md +0 -26
  142. package/docs/reference/functions/mockOf.md +0 -79
  143. package/docs/reference/functions/postgres.md +0 -31
  144. package/docs/reference/functions/process.md +0 -33
  145. package/docs/reference/functions/redis.md +0 -28
  146. package/docs/reference/functions/required.md +0 -41
  147. package/docs/reference/functions/selected.md +0 -26
  148. package/docs/reference/functions/sqlite.md +0 -33
  149. package/docs/reference/functions/testId.md +0 -20
  150. package/docs/reference/functions/text.md +0 -40
  151. package/docs/reference/functions/valued.md +0 -28
  152. package/docs/reference/functions/waitUntil.md +0 -46
  153. package/docs/reference/functions/within.md +0 -29
  154. package/docs/reference/index.md +0 -196
  155. package/docs/reference/interfaces/BaseResult.md +0 -87
  156. package/docs/reference/interfaces/CallResult.md +0 -160
  157. package/docs/reference/interfaces/CaptureScope.md +0 -82
  158. package/docs/reference/interfaces/CliResult.md +0 -253
  159. package/docs/reference/interfaces/ContainerAccessor.md +0 -166
  160. package/docs/reference/interfaces/DirectoryAccessor.md +0 -33
  161. package/docs/reference/interfaces/FetchResult.md +0 -180
  162. package/docs/reference/interfaces/FilesystemAccessor.md +0 -46
  163. package/docs/reference/interfaces/HttpResult.md +0 -126
  164. package/docs/reference/interfaces/JsonAccessor.md +0 -46
  165. package/docs/reference/interfaces/PageResult.md +0 -356
  166. package/docs/reference/interfaces/RenderResult.md +0 -72
  167. package/docs/reference/interfaces/RenderedText.md +0 -110
  168. package/docs/reference/interfaces/ResponseAccessor.md +0 -47
  169. package/docs/reference/interfaces/ScreenResult.md +0 -128
  170. package/docs/reference/interfaces/TableAccessor.md +0 -26
  171. package/docs/reference/interfaces/TextAccessor.md +0 -96
  172. package/docs/reference/type-aliases/ApiHandle.md +0 -68
  173. package/docs/reference/type-aliases/ApiSpecification.md +0 -213
  174. package/docs/reference/type-aliases/ApiSpecificationOptions.md +0 -68
  175. package/docs/reference/type-aliases/BrowserConsoleMessage.md +0 -30
  176. package/docs/reference/type-aliases/BrowserLinkElement.md +0 -50
  177. package/docs/reference/type-aliases/BrowserMetaElement.md +0 -40
  178. package/docs/reference/type-aliases/BrowserOpenOptions.md +0 -87
  179. package/docs/reference/type-aliases/BrowserPage.md +0 -132
  180. package/docs/reference/type-aliases/BrowserPort.md +0 -51
  181. package/docs/reference/type-aliases/CliEnv.md +0 -11
  182. package/docs/reference/type-aliases/CliHandle.md +0 -80
  183. package/docs/reference/type-aliases/CliInput.md +0 -36
  184. package/docs/reference/type-aliases/CliOutput.md +0 -46
  185. package/docs/reference/type-aliases/CliPort.md +0 -58
  186. package/docs/reference/type-aliases/CliSpecification.md +0 -144
  187. package/docs/reference/type-aliases/CliSpecificationOptions.md +0 -134
  188. package/docs/reference/type-aliases/ComponentChain.md +0 -156
  189. package/docs/reference/type-aliases/ComponentScenario.md +0 -20
  190. package/docs/reference/type-aliases/ComponentUi.md +0 -47
  191. package/docs/reference/type-aliases/ComponentVisitor.md +0 -10
  192. package/docs/reference/type-aliases/ContainerPort.md +0 -113
  193. package/docs/reference/type-aliases/Contract.md +0 -68
  194. package/docs/reference/type-aliases/ContractInput.md +0 -13
  195. package/docs/reference/type-aliases/ContractRequest.md +0 -95
  196. package/docs/reference/type-aliases/ContractResponder.md +0 -23
  197. package/docs/reference/type-aliases/ContractResponse.md +0 -78
  198. package/docs/reference/type-aliases/ContractResponseValue.md +0 -13
  199. package/docs/reference/type-aliases/Contracts.md +0 -48
  200. package/docs/reference/type-aliases/DatabaseKeys.md +0 -16
  201. package/docs/reference/type-aliases/DatabasePort.md +0 -72
  202. package/docs/reference/type-aliases/DeviceOpenOptions.md +0 -46
  203. package/docs/reference/type-aliases/DevicePort.md +0 -51
  204. package/docs/reference/type-aliases/DeviceScreen.md +0 -32
  205. package/docs/reference/type-aliases/DeviceTimeouts.md +0 -39
  206. package/docs/reference/type-aliases/DockerSpecConfig.md +0 -43
  207. package/docs/reference/type-aliases/DomMount.md +0 -21
  208. package/docs/reference/type-aliases/ElementKind.md +0 -27
  209. package/docs/reference/type-aliases/ElementMatch.md +0 -74
  210. package/docs/reference/type-aliases/ElementOptions.md +0 -29
  211. package/docs/reference/type-aliases/ElementRef.md +0 -128
  212. package/docs/reference/type-aliases/ExecOptions.md +0 -37
  213. package/docs/reference/type-aliases/FileAccessor.md +0 -54
  214. package/docs/reference/type-aliases/HonoApp.md +0 -31
  215. package/docs/reference/type-aliases/HttpContractFilter.md +0 -49
  216. package/docs/reference/type-aliases/HttpResponseInit.md +0 -46
  217. package/docs/reference/type-aliases/HttpStreamInit.md +0 -58
  218. package/docs/reference/type-aliases/IntegrationHandle.md +0 -46
  219. package/docs/reference/type-aliases/IntegrationSpecification.md +0 -111
  220. package/docs/reference/type-aliases/IntegrationSpecificationOptions.md +0 -42
  221. package/docs/reference/type-aliases/Intercept.md +0 -19
  222. package/docs/reference/type-aliases/InterceptOptions.md +0 -28
  223. package/docs/reference/type-aliases/InterceptScope.md +0 -11
  224. package/docs/reference/type-aliases/IsolationStrategy.md +0 -75
  225. package/docs/reference/type-aliases/JobHandle.md +0 -34
  226. package/docs/reference/type-aliases/JobsHandle.md +0 -57
  227. package/docs/reference/type-aliases/JobsResult.md +0 -16
  228. package/docs/reference/type-aliases/JobsSpecification.md +0 -94
  229. package/docs/reference/type-aliases/JobsSpecificationOptions.md +0 -56
  230. package/docs/reference/type-aliases/LandmarkKind.md +0 -20
  231. package/docs/reference/type-aliases/LiterateRunFlags.md +0 -27
  232. package/docs/reference/type-aliases/LiterateServeRegistration.md +0 -70
  233. package/docs/reference/type-aliases/MatchFixtureOptions.md +0 -27
  234. package/docs/reference/type-aliases/MatchableRequest.md +0 -61
  235. package/docs/reference/type-aliases/MatcherKind.md +0 -34
  236. package/docs/reference/type-aliases/MobileBackendOptions.md +0 -28
  237. package/docs/reference/type-aliases/MobileElementKind.md +0 -14
  238. package/docs/reference/type-aliases/MobileElementMatch.md +0 -60
  239. package/docs/reference/type-aliases/MobileElementRef.md +0 -20
  240. package/docs/reference/type-aliases/MobileHandle.md +0 -65
  241. package/docs/reference/type-aliases/MobileScenario.md +0 -20
  242. package/docs/reference/type-aliases/MobileSpecification.md +0 -51
  243. package/docs/reference/type-aliases/MobileSpecificationOptions.md +0 -126
  244. package/docs/reference/type-aliases/MobileVisitor.md +0 -81
  245. package/docs/reference/type-aliases/MockOfOptions.md +0 -26
  246. package/docs/reference/type-aliases/MockPort.md +0 -10
  247. package/docs/reference/type-aliases/PinnedClock.md +0 -10
  248. package/docs/reference/type-aliases/PostgresOptions.md +0 -32
  249. package/docs/reference/type-aliases/ProcessOptions.md +0 -112
  250. package/docs/reference/type-aliases/RedisOptions.md +0 -20
  251. package/docs/reference/type-aliases/RenderSubject.md +0 -12
  252. package/docs/reference/type-aliases/ScreenNode.md +0 -67
  253. package/docs/reference/type-aliases/ServerPort.md +0 -36
  254. package/docs/reference/type-aliases/ServerResponse.md +0 -46
  255. package/docs/reference/type-aliases/ServerSpec.md +0 -21
  256. package/docs/reference/type-aliases/ServiceHandle.md +0 -216
  257. package/docs/reference/type-aliases/ServiceRecord.md +0 -12
  258. package/docs/reference/type-aliases/SpecDocument.md +0 -84
  259. package/docs/reference/type-aliases/SpecEnvToken.md +0 -21
  260. package/docs/reference/type-aliases/SpecFileAssertion.md +0 -27
  261. package/docs/reference/type-aliases/SpecFixture.md +0 -30
  262. package/docs/reference/type-aliases/SpecKind.md +0 -8
  263. package/docs/reference/type-aliases/SpecRun.md +0 -108
  264. package/docs/reference/type-aliases/SpecServeEntry.md +0 -40
  265. package/docs/reference/type-aliases/SpecStream.md +0 -32
  266. package/docs/reference/type-aliases/SpecificationConfig.md +0 -290
  267. package/docs/reference/type-aliases/SqliteOptions.md +0 -36
  268. package/docs/reference/type-aliases/SseEvent.md +0 -58
  269. package/docs/reference/type-aliases/StreamBody.md +0 -61
  270. package/docs/reference/type-aliases/TextFilter.md +0 -22
  271. package/docs/reference/type-aliases/VisitScenario.md +0 -20
  272. package/docs/reference/type-aliases/Visitor.md +0 -215
  273. package/docs/reference/type-aliases/WaitUntilOptions.md +0 -46
  274. package/docs/reference/type-aliases/WebsiteBackendOptions.md +0 -37
  275. package/docs/reference/type-aliases/WebsiteHandle.md +0 -51
  276. package/docs/reference/type-aliases/WebsiteSpecification.md +0 -118
  277. package/docs/reference/type-aliases/WebsiteSpecificationOptions.md +0 -40
  278. package/docs/reference/variables/anthropic.md +0 -19
  279. package/docs/reference/variables/banner.md +0 -10
  280. package/docs/reference/variables/button.md +0 -10
  281. package/docs/reference/variables/clock.md +0 -16
  282. package/docs/reference/variables/complementary.md +0 -10
  283. package/docs/reference/variables/component.md +0 -10
  284. package/docs/reference/variables/content.md +0 -10
  285. package/docs/reference/variables/contentinfo.md +0 -10
  286. package/docs/reference/variables/dialog.md +0 -10
  287. package/docs/reference/variables/field.md +0 -10
  288. package/docs/reference/variables/form.md +0 -10
  289. package/docs/reference/variables/heading.md +0 -10
  290. package/docs/reference/variables/http.md +0 -40
  291. package/docs/reference/variables/intercept.md +0 -11
  292. package/docs/reference/variables/link.md +0 -10
  293. package/docs/reference/variables/listitem.md +0 -10
  294. package/docs/reference/variables/main.md +0 -10
  295. package/docs/reference/variables/match.md +0 -8
  296. package/docs/reference/variables/navigation.md +0 -10
  297. package/docs/reference/variables/openai.md +0 -21
  298. package/docs/reference/variables/option.md +0 -17
  299. package/docs/reference/variables/region.md +0 -10
  300. package/docs/reference/variables/row.md +0 -10
  301. package/docs/reference/variables/search.md +0 -10
  302. package/docs/reference/variables/specification.md +0 -8
  303. package/docs/reference/variables/status.md +0 -10
  304. package/docs/reference/variables/table.md +0 -10
@@ -0,0 +1,11 @@
1
+ //#region src/chain/pause.ts
2
+ async function pause(ms) {
3
+ if (ms <= 0) return;
4
+ await new Promise((resolve) => {
5
+ setTimeout(resolve, ms);
6
+ });
7
+ }
8
+ //#endregion
9
+ export { pause as t };
10
+
11
+ //# sourceMappingURL=shared-pause.js.map
@@ -0,0 +1,88 @@
1
+ import { n as installRootOf } from "./shared-resolve.js";
2
+ import { n as refusal } from "./shared-refusal.js";
3
+ import { existsSync } from "node:fs";
4
+ import { resolve } from "node:path";
5
+ //#region src/services/peer.ts
6
+ const INSTALLERS = [
7
+ {
8
+ command: "bun add -d",
9
+ lockfile: "bun.lock",
10
+ name: "bun"
11
+ },
12
+ {
13
+ command: "bun add -d",
14
+ lockfile: "bun.lockb",
15
+ name: "bun"
16
+ },
17
+ {
18
+ command: "pnpm add -D",
19
+ lockfile: "pnpm-lock.yaml",
20
+ name: "pnpm"
21
+ },
22
+ {
23
+ command: "yarn add -D",
24
+ lockfile: "yarn.lock",
25
+ name: "yarn"
26
+ },
27
+ {
28
+ command: "npm install -D",
29
+ lockfile: "package-lock.json",
30
+ name: "npm"
31
+ }
32
+ ];
33
+ const DEFAULT_INSTALLER = {
34
+ command: "npm install -D",
35
+ name: "npm"
36
+ };
37
+ const BUILDERS = {
38
+ bun: (peer) => `bun pm trust ${peer}`,
39
+ npm: (peer) => `npm install-scripts approve ${peer} && npm rebuild ${peer}`,
40
+ pnpm: (peer) => `add "${peer}" to \`onlyBuiltDependencies\` in package.json (or \`only-built-dependencies\` in .npmrc), then \`pnpm rebuild ${peer}\``,
41
+ yarn: (peer) => `yarn rebuild ${peer}`
42
+ };
43
+ const UNBUILT = /could not locate the bindings file|\.node['"]?$|invalid elf header|was compiled against a different node/iu;
44
+ function isUnbuilt(error) {
45
+ const message = error instanceof Error ? error.message : String(error);
46
+ return UNBUILT.test(message);
47
+ }
48
+ const NATIVE_PEERS = /* @__PURE__ */ new Set(["better-sqlite3"]);
49
+ function installerAt(from) {
50
+ const root = installRootOf(from);
51
+ if (root === void 0) return DEFAULT_INSTALLER;
52
+ return INSTALLERS.find((candidate) => existsSync(resolve(root, candidate.lockfile))) ?? DEFAULT_INSTALLER;
53
+ }
54
+ function buildNote(peer, manager) {
55
+ if (!NATIVE_PEERS.has(peer) || manager !== "pnpm") return "";
56
+ return `; under pnpm the binding is not built unless the package is listed: add "${peer}" to \`onlyBuiltDependencies\` in package.json (or \`only-built-dependencies\` in .npmrc) and reinstall`;
57
+ }
58
+ /** A binding the package manager never built surfaces as a `bindings` stack trace; this turns it into a refusal naming the peer and the command. */
59
+ function requireBuiltPeer(peer, what, use, from = process.cwd()) {
60
+ try {
61
+ use();
62
+ } catch (error) {
63
+ if (!isUnbuilt(error)) throw error;
64
+ const { name } = installerAt(from);
65
+ throw new Error(refusal({
66
+ fix: BUILDERS[name](peer),
67
+ found: `${what} found \`${peer}\`, and its native binding is not built`,
68
+ read: "docs/start/set-up-a-project.md#what-to-install",
69
+ why: `A native peer is built by its install script, and ${name} runs a dependency's install script only once it is told to.`
70
+ }), { cause: error });
71
+ }
72
+ }
73
+ /** `load` is the caller's own `import('<peer>')`: the bundler and the resolver need to see the bare specifier. */
74
+ async function loadPeer(peer, what, load, from = process.cwd()) {
75
+ try {
76
+ return await load();
77
+ } catch (error) {
78
+ const { command, name } = installerAt(from);
79
+ throw new Error(refusal("seam-is-an-optional-peer", {
80
+ fix: `${command} ${peer}${buildNote(peer, name)}`,
81
+ found: `${what} requires \`${peer}\`, and it is not installed`
82
+ }), { cause: error });
83
+ }
84
+ }
85
+ //#endregion
86
+ export { requireBuiltPeer as n, loadPeer as t };
87
+
88
+ //# sourceMappingURL=shared-peer.js.map
@@ -0,0 +1,330 @@
1
+ import { n as refusal } from "./shared-refusal.js";
2
+ import { u as evidenceDirectory } from "./shared-artifacts.js";
3
+ import { c as scopeChain } from "./shared-landmarks.js";
4
+ import { n as CANDIDATE_LIMITS, o as ambiguousTargetError, r as candidatesOf, s as asWritten, t as scenarioFailure } from "./shared-scenario-failure.js";
5
+ import { mkdtempSync } from "node:fs";
6
+ import { resolve } from "node:path";
7
+ import { setTimeout } from "node:timers/promises";
8
+ //#region src/specifications/website/browser/page-scripts.ts
9
+ function extractPage() {
10
+ const links = [...document.querySelectorAll("link")].map((link) => ({
11
+ href: link.href,
12
+ hreflang: link.hreflang || void 0,
13
+ rel: link.rel,
14
+ type: link.type || void 0
15
+ }));
16
+ const metas = [...document.querySelectorAll("meta")].map((meta) => ({
17
+ content: meta.content,
18
+ name: meta.name || void 0,
19
+ property: meta.getAttribute("property") ?? void 0
20
+ }));
21
+ const jsonLdBlocks = [...document.querySelectorAll("script[type=\"application/ld+json\"]")].map((script) => script.textContent ?? "");
22
+ return {
23
+ html: document.documentElement.outerHTML,
24
+ jsonLdBlocks,
25
+ links,
26
+ metas,
27
+ text: document.body?.innerText ?? "",
28
+ title: document.title
29
+ };
30
+ }
31
+ function seedStorage(state) {
32
+ if (state === "throws") {
33
+ for (const store of ["localStorage", "sessionStorage"]) Object.defineProperty(globalThis, store, {
34
+ configurable: true,
35
+ get: () => {
36
+ throw new DOMException("The operation is insecure.", "SecurityError");
37
+ }
38
+ });
39
+ return;
40
+ }
41
+ for (const [store, entries] of [["localStorage", state.local], ["sessionStorage", state.session]]) for (const [key, value] of Object.entries(entries ?? {})) try {
42
+ if (globalThis[store].getItem(key) === null) globalThis[store].setItem(key, value);
43
+ } catch {}
44
+ }
45
+ //#endregion
46
+ //#region src/specifications/website/browser/shared-browser.ts
47
+ /** Launched on the first ask, again once disconnected; a failed launch is not kept. */
48
+ function createSharedBrowser(launch) {
49
+ let shared;
50
+ const attempt = async () => {
51
+ try {
52
+ const browser = await launch();
53
+ browser.on("disconnected", () => {
54
+ shared = void 0;
55
+ });
56
+ return browser;
57
+ } catch (error) {
58
+ shared = void 0;
59
+ throw error;
60
+ }
61
+ };
62
+ return async () => {
63
+ shared ??= attempt();
64
+ return await shared;
65
+ };
66
+ }
67
+ //#endregion
68
+ //#region src/specifications/website/browser/website-visitor.ts
69
+ const VALUE_TIMEOUT_MS = 3e4;
70
+ const VALUE_POLL_MS = 100;
71
+ const FIELD_ROLES = [
72
+ "checkbox",
73
+ "combobox",
74
+ "radio",
75
+ "searchbox",
76
+ "slider",
77
+ "spinbutton",
78
+ "switch",
79
+ "textbox"
80
+ ];
81
+ function locate(root, target) {
82
+ const scope = target.scope ? locate(root, target.scope) : root;
83
+ const exact = target.exact ?? true;
84
+ const name = target.name ?? "";
85
+ if (target.kind === "field") {
86
+ let field = scope.getByLabel(name, { exact });
87
+ for (const role of FIELD_ROLES) field = field.or(scope.getByRole(role, {
88
+ exact,
89
+ name
90
+ }));
91
+ return field;
92
+ }
93
+ if (target.kind === "testId") return scope.getByTestId(name);
94
+ if (target.kind === "text") return scope.getByText(name, { exact });
95
+ return scope.getByRole(target.kind, target.name === void 0 ? { exact } : {
96
+ exact,
97
+ name: target.name
98
+ });
99
+ }
100
+ function isStrictViolation(error) {
101
+ return error instanceof Error && error.message.includes("strict mode violation");
102
+ }
103
+ async function act(page, target, action) {
104
+ try {
105
+ return await action(locate(page, target));
106
+ } catch (error) {
107
+ if (!isStrictViolation(error)) throw error;
108
+ const culprit = await findAmbiguousLevel(page, target);
109
+ const matches = await locate(page, culprit).evaluateAll(candidatesOf, CANDIDATE_LIMITS);
110
+ throw ambiguousTargetError({
111
+ target: culprit,
112
+ matches,
113
+ url: page.url()
114
+ });
115
+ }
116
+ }
117
+ async function findAmbiguousLevel(page, target) {
118
+ for (const level of scopeChain(target).slice(0, -1)) if (await locate(page, level).count() > 1) return level;
119
+ return target;
120
+ }
121
+ function stateOnly(page, locator, target) {
122
+ if (target.focused === true) return locator.and(page.locator(":focus"));
123
+ if (target.disabled !== void 0) return locator.and(page.locator(target.disabled ? ":disabled" : ":enabled"));
124
+ if (target.selected !== void 0) return locator.and(page.locator(target.selected ? ":checked" : ":not(:checked)"));
125
+ return null;
126
+ }
127
+ function byPresence(target) {
128
+ return target.kind === "option";
129
+ }
130
+ async function currentValue(locator) {
131
+ try {
132
+ return await locator.inputValue({ timeout: VALUE_POLL_MS });
133
+ } catch (error) {
134
+ if (isStrictViolation(error)) throw error;
135
+ return null;
136
+ }
137
+ }
138
+ async function waitForValue(locator, target, wanted) {
139
+ const value = target.value ?? "";
140
+ const deadline = Date.now() + VALUE_TIMEOUT_MS;
141
+ for (;;) {
142
+ const actual = await currentValue(locator);
143
+ if (actual === null ? !wanted : actual === value === wanted) return;
144
+ if (Date.now() >= deadline) throw new Error(refusal({
145
+ fix: "correct the value the scenario states, or what the page puts in the field",
146
+ found: `${asWritten(target)} ${wanted ? "never held" : "still holds"} ${JSON.stringify(value)}${actual === null ? " (it is not on the page)" : `, it holds ${JSON.stringify(actual)}`}`,
147
+ read: "docs/reference/visitor.md#see-and-gone-are-the-synchronization",
148
+ why: "A verb waits for what it states to hold, within its budget, and never sleeps."
149
+ }));
150
+ await setTimeout(VALUE_POLL_MS);
151
+ }
152
+ }
153
+ async function settle(page, locator, target, present) {
154
+ if (target.value !== void 0) {
155
+ await waitForValue(locator, target, present);
156
+ return;
157
+ }
158
+ const narrowed = stateOnly(page, locator, target);
159
+ if (narrowed !== null) {
160
+ await narrowed.waitFor({ state: present ? "attached" : "detached" });
161
+ return;
162
+ }
163
+ const shown = present ? "visible" : "hidden";
164
+ await locator.waitFor({ state: byPresence(target) ? present ? "attached" : "detached" : shown });
165
+ }
166
+ function createVisitor(page, baseUrl) {
167
+ return {
168
+ check: async (target) => {
169
+ await act(page, target, async (locator) => {
170
+ await locator.check();
171
+ });
172
+ },
173
+ click: async (target) => {
174
+ await act(page, target, async (locator) => {
175
+ await locator.click();
176
+ });
177
+ },
178
+ fill: async (target, value) => {
179
+ await act(page, target, async (locator) => {
180
+ await locator.fill(value);
181
+ });
182
+ },
183
+ goto: async (path) => {
184
+ await page.goto(`${baseUrl}${path}`, { waitUntil: "load" });
185
+ },
186
+ hover: async (target) => {
187
+ await act(page, target, async (locator) => {
188
+ await locator.hover();
189
+ });
190
+ },
191
+ press: async (key) => {
192
+ await page.keyboard.press(key);
193
+ },
194
+ gone: async (target) => {
195
+ await act(page, target, async (locator) => {
196
+ await settle(page, locator, target, false);
197
+ });
198
+ },
199
+ see: async (target) => {
200
+ await act(page, target, async (locator) => {
201
+ await settle(page, locator, target, true);
202
+ });
203
+ },
204
+ select: async (target, option) => {
205
+ await act(page, target, async (locator) => await locator.selectOption(option));
206
+ }
207
+ };
208
+ }
209
+ //#endregion
210
+ //#region src/specifications/website/browser/playwright.adapter.ts
211
+ const LAUNCH_OPTIONS = {
212
+ channel: "chromium-headless-shell",
213
+ headless: true
214
+ };
215
+ async function launchChromium() {
216
+ let chromium;
217
+ try {
218
+ ({chromium} = await import("playwright"));
219
+ } catch {
220
+ throw new Error(refusal("seam-is-an-optional-peer", {
221
+ fix: "npm install -D playwright && npx playwright install chromium",
222
+ found: ".visit() requires `playwright`, and it is not installed"
223
+ }));
224
+ }
225
+ return await chromium.launch(LAUNCH_OPTIONS);
226
+ }
227
+ /** invariant: with `'block'`, requests leaving the site are aborted; the stub backend stays reachable through `allowedOrigins`. */
228
+ async function blockExternal(context, options) {
229
+ const aborted = /* @__PURE__ */ new Set();
230
+ if (options.external !== "block") return aborted;
231
+ const allowed = new Set([options.baseUrl, ...options.allowedOrigins ?? []].map((at) => new URL(at).origin));
232
+ await context.route("**/*", (route) => {
233
+ const url = route.request().url();
234
+ if (allowed.has(new URL(url).origin)) route.continue();
235
+ else {
236
+ aborted.add(url);
237
+ route.abort();
238
+ }
239
+ });
240
+ return aborted;
241
+ }
242
+ function recordConsole(page, aborted) {
243
+ const consoleMessages = [];
244
+ page.on("console", (message) => {
245
+ if (message.type() === "error" && aborted.has(message.location().url)) return;
246
+ consoleMessages.push({
247
+ text: message.text(),
248
+ type: message.type()
249
+ });
250
+ });
251
+ page.on("pageerror", (error) => {
252
+ consoleMessages.push({
253
+ text: `Uncaught ${error.name}: ${error.message}`,
254
+ type: "error"
255
+ });
256
+ });
257
+ return consoleMessages;
258
+ }
259
+ const workerBrowsers = /* @__PURE__ */ new Map();
260
+ function workerBrowser(launch) {
261
+ let shared = workerBrowsers.get(launch);
262
+ if (shared === void 0) {
263
+ shared = createSharedBrowser(launch);
264
+ workerBrowsers.set(launch, shared);
265
+ }
266
+ return shared;
267
+ }
268
+ /** One browser per worker, launched lazily; each visit gets a fresh `BrowserContext`. */
269
+ var PlaywrightAdapter = class {
270
+ browser;
271
+ contexts = /* @__PURE__ */ new Set();
272
+ constructor(options = {}) {
273
+ this.browser = workerBrowser(options.launch ?? launchChromium);
274
+ }
275
+ async close() {
276
+ const open = [...this.contexts];
277
+ this.contexts.clear();
278
+ await Promise.all(open.map(async (context) => {
279
+ await context.close();
280
+ }));
281
+ }
282
+ async open(url, options) {
283
+ const context = await (await this.browser()).newContext({ extraHTTPHeaders: options.headers ?? {} });
284
+ this.contexts.add(context);
285
+ try {
286
+ const aborted = await blockExternal(context, options);
287
+ if (options.storage !== void 0) await context.addInitScript(seedStorage, options.storage);
288
+ const page = await context.newPage();
289
+ if (options.clock !== void 0) await page.clock.setFixedTime(new Date(options.clock));
290
+ const consoleMessages = recordConsole(page, aborted);
291
+ const response = await page.goto(url, { waitUntil: "load" });
292
+ if (options.scenario) try {
293
+ await options.scenario(createVisitor(page, options.baseUrl));
294
+ } catch (error) {
295
+ const evidence = await this.captureEvidence(page);
296
+ throw scenarioFailure(error, evidence, "visit");
297
+ }
298
+ const extraction = await page.evaluate(extractPage);
299
+ const tree = await page.locator("body").ariaSnapshot();
300
+ return {
301
+ consoleMessages,
302
+ status: response?.status() ?? 0,
303
+ tree,
304
+ url: page.url(),
305
+ ...extraction
306
+ };
307
+ } finally {
308
+ this.contexts.delete(context);
309
+ await context.close();
310
+ }
311
+ }
312
+ /** Screenshot the failing state into the evidence folder; never masks the original error. */
313
+ async captureEvidence(page) {
314
+ try {
315
+ const dir = mkdtempSync(evidenceDirectory("website-"));
316
+ const path = resolve(dir, "failure.png");
317
+ await page.screenshot({
318
+ fullPage: true,
319
+ path
320
+ });
321
+ return path;
322
+ } catch {
323
+ return null;
324
+ }
325
+ }
326
+ };
327
+ //#endregion
328
+ export { PlaywrightAdapter };
329
+
330
+ //# sourceMappingURL=shared-playwright.adapter.js.map
@@ -0,0 +1,74 @@
1
+ import { t as ServedProcess } from "./shared-serve.js";
2
+ import { n as refusal } from "./shared-refusal.js";
3
+ //#region src/services/process/process.adapter.ts
4
+ async function nothingToIsolate() {}
5
+ var ProcessHandle = class {
6
+ /** Embedded: nothing to pull, nothing to run in a container. */
7
+ defaultImage = "";
8
+ defaultPort = 0;
9
+ environment = {};
10
+ type = "process";
11
+ serviceName = null;
12
+ connectionString = "";
13
+ started = false;
14
+ served = null;
15
+ options;
16
+ constructor(options) {
17
+ this.options = options;
18
+ }
19
+ buildConnectionString(host, port) {
20
+ return `http://${host}:${port}`;
21
+ }
22
+ createDatabaseAdapter() {
23
+ return null;
24
+ }
25
+ async healthcheck() {}
26
+ async initialize() {}
27
+ /** Isolation is the run's: one child per evaluation of the specification module, told apart by the minted id. */
28
+ isolation() {
29
+ return {
30
+ acquire: nothingToIsolate,
31
+ release: nothingToIsolate,
32
+ reset: nothingToIsolate
33
+ };
34
+ }
35
+ async reset() {}
36
+ /** Spawn it and wait until it answers; `services` is every sibling, already started. */
37
+ async spawnWith(root, services, runId) {
38
+ const stated = typeof this.options.env === "function" ? this.options.env(services) : this.options.env;
39
+ this.served = new ServedProcess(this.options, root, "process", {
40
+ ...stated,
41
+ TEST_RUN_ID: runId
42
+ });
43
+ this.connectionString = await this.served.start();
44
+ this.started = true;
45
+ }
46
+ /** Terminate the process group. Idempotent. */
47
+ async shutdown() {
48
+ await this.served?.stop();
49
+ this.served = null;
50
+ this.started = false;
51
+ }
52
+ /** What it was declared as: what a specification reads to start it in a slot of its own. */
53
+ get spec() {
54
+ return this.options;
55
+ }
56
+ /** The base URL it came up on, known only once it is started. */
57
+ get url() {
58
+ if (!this.started) throw new Error(refusal({
59
+ fix: "read it from the started services record (`env: (services) => ...` or `server: (services) => ...`), never at declaration time",
60
+ found: "process().url read before the process was ready",
61
+ read: "docs/reference/services.md#process-the-one-shape-an-external-process-takes",
62
+ why: "A process's URL is known once it is ready."
63
+ }));
64
+ return this.connectionString;
65
+ }
66
+ };
67
+ /** Declare an external process the framework owns for the life of the specification. */
68
+ function processService(options) {
69
+ return new ProcessHandle(options);
70
+ }
71
+ //#endregion
72
+ export { processService as n, ProcessHandle as t };
73
+
74
+ //# sourceMappingURL=shared-process.adapter.js.map
@@ -0,0 +1,35 @@
1
+ import { t as CATALOGUE_CHAPTER } from "./shared-chapter.js";
2
+ import { defineInvariant, refusalBlock } from "@jterrazz/typescript/rules";
3
+ //#region src/chain/refusals/rows.ts
4
+ const REFUSAL_WHY = {
5
+ "database-named-only-when-several-at-runtime": "With two SQL databases or more, a seed or a table names the `database:` it reads, and with exactly one it names none.",
6
+ "route-written-as-template-at-runtime": "A request written as its route template is handed exactly the parameters the template names.",
7
+ "disk-named-by-relative-path-at-runtime": "A verb that reads the disk takes a `file()` or a `directory()`, whose path starts with `./` or `../` and ends in `/` exactly when it names a directory.",
8
+ "one-verb-per-meaning": "`toMatch` on a result the framework produced compares the WHOLE result, to a value written in place or to a `file()`/`directory()` golden.",
9
+ "network-strict-after-first-stub": "From its first stub, a chain's network is strict: every outgoing request matches a declared stub.",
10
+ "target-matches-one-element": "A verb acts on exactly ONE element: a target that matches several is refused, never settled by the first.",
11
+ "seam-is-an-optional-peer": "Every seam is an optional peer of @jterrazz/test, installed by the project that specifies through it.",
12
+ "no-landmark-on-mobile": "An iOS screen has no ARIA landmark: a mobile verb is scoped with `within(testId(...), ...)`."
13
+ };
14
+ `${REFUSAL_WHY["route-written-as-template-at-runtime"]}`, `${REFUSAL_WHY["database-named-only-when-several-at-runtime"]}`, `${REFUSAL_WHY["disk-named-by-relative-path-at-runtime"]}`, `${REFUSAL_WHY["network-strict-after-first-stub"]}`, `${REFUSAL_WHY["one-verb-per-meaning"]}`, `${REFUSAL_WHY["target-matches-one-element"]}`, `${REFUSAL_WHY["no-landmark-on-mobile"]}`, `${REFUSAL_WHY["seam-is-an-optional-peer"]}`;
15
+ //#endregion
16
+ //#region src/chain/refusals/refusal.ts
17
+ const invariant = defineInvariant({
18
+ bugOf: "the framework",
19
+ notOf: "the test",
20
+ package: "@jterrazz/test"
21
+ });
22
+ function refusal$1(rowOrLesson, teaching) {
23
+ if (typeof rowOrLesson !== "string") return refusalBlock(rowOrLesson);
24
+ if (teaching === void 0) throw invariant(`refusal('${rowOrLesson}') was handed no found and no fix for its case`);
25
+ return refusalBlock({
26
+ fix: teaching.fix,
27
+ found: teaching.found,
28
+ read: `${CATALOGUE_CHAPTER}#${rowOrLesson}`,
29
+ why: REFUSAL_WHY[rowOrLesson]
30
+ });
31
+ }
32
+ //#endregion
33
+ export { refusal$1 as n, invariant as t };
34
+
35
+ //# sourceMappingURL=shared-refusal.js.map
@@ -0,0 +1,24 @@
1
+ import { holdsManifest, nearestRoot } from "@jterrazz/typescript/rules";
2
+ import { existsSync, realpathSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ //#region lint/kit/project/resolve.ts
5
+ const installRootOf = (from) => nearestRoot(from, (directory) => existsSync(join(directory, "package.json")));
6
+ function discoverRoot(startDir) {
7
+ return nearestRoot(startDir, holdsManifest) ?? startDir;
8
+ }
9
+ function resolveRoot(root, callerDir) {
10
+ if (root === void 0 || root === "") return discoverRoot(callerDir);
11
+ return resolve(callerDir, root);
12
+ }
13
+ /** What a child process prints as `$PWD` (macOS resolves the tmpdir symlink); the path itself when it cannot resolve. */
14
+ function safeRealpath(path) {
15
+ try {
16
+ return realpathSync(path);
17
+ } catch {
18
+ return path;
19
+ }
20
+ }
21
+ //#endregion
22
+ export { safeRealpath as i, installRootOf as n, resolveRoot as r, discoverRoot as t };
23
+
24
+ //# sourceMappingURL=shared-resolve.js.map