@jterrazz/test 16.1.1 → 18.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 (309) hide show
  1. package/README.md +45 -171
  2. package/dist/browser/index.js +1 -2980
  3. package/dist/browser/services.js +1 -0
  4. package/dist/browser/stubs.js +1 -0
  5. package/dist/browser-shared-index.browser.js +3233 -0
  6. package/dist/checker.d.ts +6 -1
  7. package/dist/checker.js +3721 -2624
  8. package/dist/config.d.ts +40 -0
  9. package/dist/config.js +309 -0
  10. package/dist/in-place.global-setup.d.ts +12 -0
  11. package/dist/in-place.global-setup.js +21 -0
  12. package/dist/index.d.ts +1641 -3004
  13. package/dist/index.js +5095 -3835
  14. package/dist/mobile-session.global-setup.d.ts +15 -0
  15. package/dist/mobile-session.global-setup.js +31 -0
  16. package/dist/oxlint.cjs +6069 -5634
  17. package/dist/oxlint.d.cts +43 -7212
  18. package/dist/oxlint.d.ts +43 -7212
  19. package/dist/oxlint.js +3979 -5799
  20. package/dist/route-coverage.global-setup.d.ts +43 -0
  21. package/dist/route-coverage.global-setup.js +1 -0
  22. package/dist/services.d.ts +3 -0
  23. package/dist/services.global-setup.d.ts +43 -0
  24. package/dist/services.global-setup.js +1 -0
  25. package/dist/services.js +597 -0
  26. package/dist/shared-appium.adapter.js +428 -0
  27. package/dist/shared-artifacts.js +34 -0
  28. package/dist/shared-chapter.js +7 -0
  29. package/dist/shared-container.port.d.ts +31 -0
  30. package/dist/shared-derived-projects.js +73 -0
  31. package/dist/shared-device.port.d.ts +189 -0
  32. package/dist/shared-exchange.js +447 -0
  33. package/dist/shared-in-place.js +172 -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-listened-port.js +19 -0
  40. package/dist/shared-literate-plugin.js +184 -0
  41. package/dist/shared-manifest.js +1321 -0
  42. package/dist/shared-pause.js +22 -0
  43. package/dist/shared-peer.js +88 -0
  44. package/dist/shared-playwright.adapter.js +328 -0
  45. package/dist/shared-process-alive.js +13 -0
  46. package/dist/shared-process.adapter.js +74 -0
  47. package/dist/shared-record-key.js +13 -0
  48. package/dist/shared-refusal.js +35 -0
  49. package/dist/shared-resolve.js +24 -0
  50. package/dist/shared-route-coverage.global-setup.js +187 -0
  51. package/dist/shared-scenario-failure.js +198 -0
  52. package/dist/shared-serve.js +407 -0
  53. package/dist/shared-service-images.js +10 -0
  54. package/dist/shared-services.global-setup.js +174 -0
  55. package/dist/shared-session-ledger.js +305 -0
  56. package/dist/shared-setup-entry.js +12 -0
  57. package/dist/shared-source-scan.js +133 -0
  58. package/dist/shared-spec-document.js +303 -0
  59. package/dist/shared-spec-grammar.js +66 -0
  60. package/dist/shared-specifications.js +26 -0
  61. package/dist/shared-specs-modules.js +1033 -0
  62. package/dist/shared-stub-reply.js +167 -0
  63. package/dist/shared-stub-server.js +142 -0
  64. package/dist/shared-temp-root.global-setup.js +73 -0
  65. package/dist/shared-test-baseline.js +26 -0
  66. package/dist/shared-test-section.js +58 -0
  67. package/dist/shared-testcontainers.adapter.js +306 -0
  68. package/dist/shared-tokens.js +55 -0
  69. package/dist/shared-yaml-document.js +119 -0
  70. package/dist/stubs.d.ts +2 -0
  71. package/dist/stubs.js +657 -0
  72. package/dist/temp-root.global-setup.d.ts +7 -0
  73. package/dist/temp-root.global-setup.js +1 -0
  74. package/dist/vitest.d.ts +93 -164
  75. package/dist/vitest.js +382 -550
  76. package/docs/README.md +82 -36
  77. package/docs/concepts/architecture.md +118 -0
  78. package/docs/concepts/conventions.md +333 -0
  79. package/docs/guides/api-specs.md +374 -0
  80. package/docs/guides/cli-specs.md +566 -0
  81. package/docs/guides/component-specs.md +252 -0
  82. package/docs/guides/develop.md +97 -0
  83. package/docs/guides/integration-specs.md +181 -0
  84. package/docs/guides/job-specs.md +180 -0
  85. package/docs/guides/lint-specs.md +174 -0
  86. package/docs/guides/mobile-specs.md +319 -0
  87. package/docs/guides/module-tests.md +149 -0
  88. package/docs/guides/release.md +44 -0
  89. package/docs/guides/spec-documents.md +189 -0
  90. package/docs/guides/website-specs.md +413 -0
  91. package/docs/reference/assertions.md +345 -0
  92. package/docs/reference/glossary.md +20 -0
  93. package/docs/reference/linting.md +483 -0
  94. package/docs/reference/services.md +236 -0
  95. package/docs/reference/stubs.md +500 -0
  96. package/docs/reference/time-and-doubles.md +137 -0
  97. package/docs/reference/tokens.md +258 -0
  98. package/docs/reference/visitor.md +221 -0
  99. package/docs/start/getting-started.md +187 -0
  100. package/docs/start/set-up-a-project.md +228 -0
  101. package/package.json +56 -36
  102. package/schema/spec.schema.json +43 -17
  103. package/dist/appium.adapter.js +0 -489
  104. package/dist/artifacts.js +0 -32
  105. package/dist/fixtures.js +0 -110
  106. package/dist/literate.js +0 -1747
  107. package/dist/match.js +0 -804
  108. package/dist/playwright.adapter.js +0 -438
  109. package/dist/server.js +0 -160
  110. package/dist/stream.js +0 -321
  111. package/dist/substring-warning.js +0 -282
  112. package/docs/01-architecture.md +0 -95
  113. package/docs/02-developing.md +0 -351
  114. package/docs/03-testing.md +0 -495
  115. package/docs/04-operating.md +0 -101
  116. package/docs/05-module-tests.md +0 -152
  117. package/docs/06-integration.md +0 -180
  118. package/docs/07-component.md +0 -246
  119. package/docs/08-website.md +0 -404
  120. package/docs/09-mobile.md +0 -270
  121. package/docs/10-api.md +0 -264
  122. package/docs/11-jobs.md +0 -157
  123. package/docs/12-cli.md +0 -677
  124. package/docs/13-elements.md +0 -248
  125. package/docs/14-assertions.md +0 -314
  126. package/docs/15-tokens.md +0 -253
  127. package/docs/16-contracts.md +0 -438
  128. package/docs/17-services.md +0 -188
  129. package/docs/18-conventions.md +0 -332
  130. package/docs/19-linting.md +0 -512
  131. package/docs/decisions/001-v9-conventions-on-three-channels.md +0 -50
  132. package/docs/decisions/002-the-statique-channel-holds-only-what-upstream-does-not.md +0 -49
  133. package/docs/decisions/003-a-rendered-component-is-a-facet-on-vitest-browser-mode.md +0 -84
  134. package/docs/decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-facet.md +0 -81
  135. package/docs/decisions/005-the-doubles-ladder-is-closed.md +0 -86
  136. package/docs/decisions/006-the-catalogue-reaches-a-file-by-its-role.md +0 -245
  137. package/docs/decisions/007-compose-mode-leaves-the-package.md +0 -53
  138. package/docs/decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md +0 -75
  139. package/docs/decisions/_template.md +0 -22
  140. package/docs/reference/classes/Matcher.md +0 -120
  141. package/docs/reference/classes/ProcessHandle.md +0 -368
  142. package/docs/reference/functions/defineContract.md +0 -39
  143. package/docs/reference/functions/defineContracts.md +0 -36
  144. package/docs/reference/functions/disabled.md +0 -28
  145. package/docs/reference/functions/enabled.md +0 -20
  146. package/docs/reference/functions/focused.md +0 -26
  147. package/docs/reference/functions/mockOf.md +0 -79
  148. package/docs/reference/functions/postgres.md +0 -31
  149. package/docs/reference/functions/process.md +0 -33
  150. package/docs/reference/functions/redis.md +0 -28
  151. package/docs/reference/functions/required.md +0 -41
  152. package/docs/reference/functions/selected.md +0 -26
  153. package/docs/reference/functions/sqlite.md +0 -33
  154. package/docs/reference/functions/testId.md +0 -20
  155. package/docs/reference/functions/text.md +0 -40
  156. package/docs/reference/functions/valued.md +0 -28
  157. package/docs/reference/functions/waitUntil.md +0 -46
  158. package/docs/reference/functions/within.md +0 -29
  159. package/docs/reference/index.md +0 -196
  160. package/docs/reference/interfaces/BaseResult.md +0 -87
  161. package/docs/reference/interfaces/CallResult.md +0 -160
  162. package/docs/reference/interfaces/CaptureScope.md +0 -82
  163. package/docs/reference/interfaces/CliResult.md +0 -253
  164. package/docs/reference/interfaces/ContainerAccessor.md +0 -166
  165. package/docs/reference/interfaces/DirectoryAccessor.md +0 -33
  166. package/docs/reference/interfaces/FetchResult.md +0 -180
  167. package/docs/reference/interfaces/FilesystemAccessor.md +0 -46
  168. package/docs/reference/interfaces/HttpResult.md +0 -126
  169. package/docs/reference/interfaces/JsonAccessor.md +0 -46
  170. package/docs/reference/interfaces/PageResult.md +0 -356
  171. package/docs/reference/interfaces/RenderResult.md +0 -72
  172. package/docs/reference/interfaces/RenderedText.md +0 -110
  173. package/docs/reference/interfaces/ResponseAccessor.md +0 -47
  174. package/docs/reference/interfaces/ScreenResult.md +0 -128
  175. package/docs/reference/interfaces/TableAccessor.md +0 -26
  176. package/docs/reference/interfaces/TextAccessor.md +0 -96
  177. package/docs/reference/type-aliases/ApiHandle.md +0 -68
  178. package/docs/reference/type-aliases/ApiSpecification.md +0 -213
  179. package/docs/reference/type-aliases/ApiSpecificationOptions.md +0 -68
  180. package/docs/reference/type-aliases/BrowserConsoleMessage.md +0 -30
  181. package/docs/reference/type-aliases/BrowserLinkElement.md +0 -50
  182. package/docs/reference/type-aliases/BrowserMetaElement.md +0 -40
  183. package/docs/reference/type-aliases/BrowserOpenOptions.md +0 -87
  184. package/docs/reference/type-aliases/BrowserPage.md +0 -132
  185. package/docs/reference/type-aliases/BrowserPort.md +0 -51
  186. package/docs/reference/type-aliases/CliEnv.md +0 -11
  187. package/docs/reference/type-aliases/CliHandle.md +0 -80
  188. package/docs/reference/type-aliases/CliInput.md +0 -36
  189. package/docs/reference/type-aliases/CliOutput.md +0 -46
  190. package/docs/reference/type-aliases/CliPort.md +0 -58
  191. package/docs/reference/type-aliases/CliSpecification.md +0 -144
  192. package/docs/reference/type-aliases/CliSpecificationOptions.md +0 -134
  193. package/docs/reference/type-aliases/ComponentChain.md +0 -156
  194. package/docs/reference/type-aliases/ComponentScenario.md +0 -20
  195. package/docs/reference/type-aliases/ComponentUi.md +0 -47
  196. package/docs/reference/type-aliases/ComponentVisitor.md +0 -10
  197. package/docs/reference/type-aliases/ContainerPort.md +0 -113
  198. package/docs/reference/type-aliases/Contract.md +0 -68
  199. package/docs/reference/type-aliases/ContractInput.md +0 -13
  200. package/docs/reference/type-aliases/ContractRequest.md +0 -95
  201. package/docs/reference/type-aliases/ContractResponder.md +0 -23
  202. package/docs/reference/type-aliases/ContractResponse.md +0 -78
  203. package/docs/reference/type-aliases/ContractResponseValue.md +0 -13
  204. package/docs/reference/type-aliases/Contracts.md +0 -48
  205. package/docs/reference/type-aliases/DatabaseKeys.md +0 -16
  206. package/docs/reference/type-aliases/DatabasePort.md +0 -72
  207. package/docs/reference/type-aliases/DeviceOpenOptions.md +0 -46
  208. package/docs/reference/type-aliases/DevicePort.md +0 -51
  209. package/docs/reference/type-aliases/DeviceScreen.md +0 -32
  210. package/docs/reference/type-aliases/DeviceTimeouts.md +0 -39
  211. package/docs/reference/type-aliases/DockerSpecConfig.md +0 -43
  212. package/docs/reference/type-aliases/DomMount.md +0 -21
  213. package/docs/reference/type-aliases/ElementKind.md +0 -27
  214. package/docs/reference/type-aliases/ElementMatch.md +0 -74
  215. package/docs/reference/type-aliases/ElementOptions.md +0 -29
  216. package/docs/reference/type-aliases/ElementRef.md +0 -128
  217. package/docs/reference/type-aliases/ExecOptions.md +0 -37
  218. package/docs/reference/type-aliases/FileAccessor.md +0 -54
  219. package/docs/reference/type-aliases/HonoApp.md +0 -31
  220. package/docs/reference/type-aliases/HttpContractFilter.md +0 -49
  221. package/docs/reference/type-aliases/HttpResponseInit.md +0 -46
  222. package/docs/reference/type-aliases/HttpStreamInit.md +0 -58
  223. package/docs/reference/type-aliases/IntegrationHandle.md +0 -46
  224. package/docs/reference/type-aliases/IntegrationSpecification.md +0 -111
  225. package/docs/reference/type-aliases/IntegrationSpecificationOptions.md +0 -42
  226. package/docs/reference/type-aliases/Intercept.md +0 -19
  227. package/docs/reference/type-aliases/InterceptOptions.md +0 -28
  228. package/docs/reference/type-aliases/InterceptScope.md +0 -11
  229. package/docs/reference/type-aliases/IsolationStrategy.md +0 -75
  230. package/docs/reference/type-aliases/JobHandle.md +0 -34
  231. package/docs/reference/type-aliases/JobsHandle.md +0 -57
  232. package/docs/reference/type-aliases/JobsResult.md +0 -16
  233. package/docs/reference/type-aliases/JobsSpecification.md +0 -94
  234. package/docs/reference/type-aliases/JobsSpecificationOptions.md +0 -56
  235. package/docs/reference/type-aliases/LandmarkKind.md +0 -20
  236. package/docs/reference/type-aliases/LiterateRunFlags.md +0 -27
  237. package/docs/reference/type-aliases/LiterateServeRegistration.md +0 -70
  238. package/docs/reference/type-aliases/MatchFixtureOptions.md +0 -27
  239. package/docs/reference/type-aliases/MatchableRequest.md +0 -61
  240. package/docs/reference/type-aliases/MatcherKind.md +0 -34
  241. package/docs/reference/type-aliases/MobileBackendOptions.md +0 -28
  242. package/docs/reference/type-aliases/MobileElementKind.md +0 -14
  243. package/docs/reference/type-aliases/MobileElementMatch.md +0 -60
  244. package/docs/reference/type-aliases/MobileElementRef.md +0 -20
  245. package/docs/reference/type-aliases/MobileHandle.md +0 -65
  246. package/docs/reference/type-aliases/MobileScenario.md +0 -20
  247. package/docs/reference/type-aliases/MobileSpecification.md +0 -51
  248. package/docs/reference/type-aliases/MobileSpecificationOptions.md +0 -126
  249. package/docs/reference/type-aliases/MobileVisitor.md +0 -81
  250. package/docs/reference/type-aliases/MockOfOptions.md +0 -26
  251. package/docs/reference/type-aliases/MockPort.md +0 -10
  252. package/docs/reference/type-aliases/PinnedClock.md +0 -10
  253. package/docs/reference/type-aliases/PostgresOptions.md +0 -32
  254. package/docs/reference/type-aliases/ProcessOptions.md +0 -112
  255. package/docs/reference/type-aliases/RedisOptions.md +0 -20
  256. package/docs/reference/type-aliases/RenderSubject.md +0 -12
  257. package/docs/reference/type-aliases/ScreenNode.md +0 -67
  258. package/docs/reference/type-aliases/ServerPort.md +0 -36
  259. package/docs/reference/type-aliases/ServerResponse.md +0 -46
  260. package/docs/reference/type-aliases/ServerSpec.md +0 -21
  261. package/docs/reference/type-aliases/ServiceHandle.md +0 -216
  262. package/docs/reference/type-aliases/ServiceRecord.md +0 -12
  263. package/docs/reference/type-aliases/SpecDocument.md +0 -84
  264. package/docs/reference/type-aliases/SpecEnvToken.md +0 -21
  265. package/docs/reference/type-aliases/SpecFileAssertion.md +0 -27
  266. package/docs/reference/type-aliases/SpecFixture.md +0 -30
  267. package/docs/reference/type-aliases/SpecKind.md +0 -8
  268. package/docs/reference/type-aliases/SpecRun.md +0 -108
  269. package/docs/reference/type-aliases/SpecServeEntry.md +0 -40
  270. package/docs/reference/type-aliases/SpecStream.md +0 -32
  271. package/docs/reference/type-aliases/SpecificationConfig.md +0 -290
  272. package/docs/reference/type-aliases/SqliteOptions.md +0 -36
  273. package/docs/reference/type-aliases/SseEvent.md +0 -58
  274. package/docs/reference/type-aliases/StreamBody.md +0 -61
  275. package/docs/reference/type-aliases/TextFilter.md +0 -22
  276. package/docs/reference/type-aliases/VisitScenario.md +0 -20
  277. package/docs/reference/type-aliases/Visitor.md +0 -215
  278. package/docs/reference/type-aliases/WaitUntilOptions.md +0 -46
  279. package/docs/reference/type-aliases/WebsiteBackendOptions.md +0 -37
  280. package/docs/reference/type-aliases/WebsiteHandle.md +0 -51
  281. package/docs/reference/type-aliases/WebsiteSpecification.md +0 -118
  282. package/docs/reference/type-aliases/WebsiteSpecificationOptions.md +0 -40
  283. package/docs/reference/variables/anthropic.md +0 -19
  284. package/docs/reference/variables/banner.md +0 -10
  285. package/docs/reference/variables/button.md +0 -10
  286. package/docs/reference/variables/clock.md +0 -16
  287. package/docs/reference/variables/complementary.md +0 -10
  288. package/docs/reference/variables/component.md +0 -10
  289. package/docs/reference/variables/content.md +0 -10
  290. package/docs/reference/variables/contentinfo.md +0 -10
  291. package/docs/reference/variables/dialog.md +0 -10
  292. package/docs/reference/variables/field.md +0 -10
  293. package/docs/reference/variables/form.md +0 -10
  294. package/docs/reference/variables/heading.md +0 -10
  295. package/docs/reference/variables/http.md +0 -40
  296. package/docs/reference/variables/intercept.md +0 -11
  297. package/docs/reference/variables/link.md +0 -10
  298. package/docs/reference/variables/listitem.md +0 -10
  299. package/docs/reference/variables/main.md +0 -10
  300. package/docs/reference/variables/match.md +0 -8
  301. package/docs/reference/variables/navigation.md +0 -10
  302. package/docs/reference/variables/openai.md +0 -21
  303. package/docs/reference/variables/option.md +0 -17
  304. package/docs/reference/variables/region.md +0 -10
  305. package/docs/reference/variables/row.md +0 -10
  306. package/docs/reference/variables/search.md +0 -10
  307. package/docs/reference/variables/specification.md +0 -8
  308. package/docs/reference/variables/status.md +0 -10
  309. package/docs/reference/variables/table.md +0 -10
@@ -1,438 +0,0 @@
1
- import { a as describeAmbiguity, i as AmbiguousElementError, o as formatElement, r as widenedForWindow, t as WINDOW_SETTLE_MS } from "./substring-warning.js";
2
- import { mkdtempSync } from "node:fs";
3
- import { resolve } from "node:path";
4
- import { tmpdir } from "node:os";
5
- import { setTimeout } from "node:timers/promises";
6
- //#region src/model/elements/aria-name.ts
7
- /**
8
- * The accessible name an ARIA snapshot states.
9
- *
10
- * A descriptor matches the name the BROWSER computes, and that name is not the
11
- * element's text: `<button><span>Experiments</span><span>9</span></button>`
12
- * reads as `Experiments9` through `textContent` and is named "Experiments 9",
13
- * a field's name comes from its label and is nowhere in its text at all, and a
14
- * name cut at eighty characters resolves to nothing. Reading the name off the
15
- * snapshot is what makes the spelling the window prints a spelling that can be
16
- * written back into a spec.
17
- *
18
- * The dialect is the one both surfaces already speak — playwright's, on the
19
- * page directly and through `server.commands` for a mounted component — so the
20
- * projection is stated once, here, and neither adapter computes a name itself.
21
- */
22
- /**
23
- * The name of the FIRST node a snapshot describes: `- button "Experiments 9"`,
24
- * optionally followed by `:` and its children.
25
- *
26
- * A quote must follow the role across a SPACE. `- paragraph: "…"` is a node
27
- * with text and no name, and reading that text as a name would hand the author
28
- * a spelling no role descriptor matches on.
29
- */
30
- const NAMED = /^-\s+[\w-]+\s+"(?<name>(?:[^"\\]|\\.)*)"/u;
31
- /** Undo the escaping the snapshot applies to a quote or a backslash inside a name. */
32
- function unescaped(name) {
33
- return name.replaceAll(/\\(?<char>.)/gu, "$<char>");
34
- }
35
- /**
36
- * The accessible name the snapshot gives the node it describes, or `undefined`
37
- * when that node has none — an unnamed landmark, a paragraph of text.
38
- */
39
- function accessibleNameIn(snapshot) {
40
- const [first] = snapshot.split("\n");
41
- const name = (first === void 0 ? null : NAMED.exec(first.trim()))?.groups?.name;
42
- return name === void 0 ? void 0 : unescaped(name);
43
- }
44
- //#endregion
45
- //#region src/seams/playwright/playwright.adapter.ts
46
- /** How many candidates the ambiguity error enumerates before truncating. */
47
- const MAX_REPORTED_MATCHES = 10;
48
- const VALUE_TIMEOUT_MS = 3e4;
49
- const VALUE_POLL_MS = 100;
50
- /**
51
- * The roles a FIELD can carry — what `field('Name')` designates.
52
- *
53
- * A field is named by its ACCESSIBLE name, not by the text of the label
54
- * element that happens to wrap it: `<label><span>Channel</span><select>…` has
55
- * a label whose text carries every option of the select, while the control's
56
- * accessible name is `Channel` — which is what the ARIA tree shows and what a
57
- * screen reader reads. Resolving by label text alone made the ordinary
58
- * wrapping-label form unreachable under the 16.0 whole-name default.
59
- *
60
- * The label-text locator stays in the union for the controls ARIA gives no
61
- * role at all (a date or colour input), which nothing else would find.
62
- */
63
- const FIELD_ROLES = [
64
- "checkbox",
65
- "combobox",
66
- "radio",
67
- "searchbox",
68
- "slider",
69
- "spinbutton",
70
- "switch",
71
- "textbox"
72
- ];
73
- /**
74
- * Translate a user-facing descriptor into a playwright locator, resolving
75
- * `scope` outside-in so `within(navigation(), link('X'))` searches the nav
76
- * subtree. No `.first()` anywhere: playwright's strict mode is the mechanism
77
- * behind CONVENTIONS W3, and swallowing it would reintroduce the silent
78
- * wrong-element bug the rule exists to prevent.
79
- */
80
- function locate(root, element) {
81
- const scope = element.scope ? locate(root, element.scope) : root;
82
- const exact = element.exact ?? true;
83
- const name = element.name ?? "";
84
- if (element.kind === "field") {
85
- let field = scope.getByLabel(name, { exact });
86
- for (const role of FIELD_ROLES) field = field.or(scope.getByRole(role, {
87
- exact,
88
- name
89
- }));
90
- return field;
91
- }
92
- if (element.kind === "testId") return scope.getByTestId(name);
93
- if (element.kind === "text") return scope.getByText(name, { exact });
94
- return scope.getByRole(element.kind, element.name === void 0 ? { exact } : {
95
- exact,
96
- name: element.name
97
- });
98
- }
99
- /** Playwright signals "more than one match" through this error text. */
100
- function isStrictViolation(error) {
101
- return error instanceof Error && error.message.includes("strict mode violation");
102
- }
103
- /** Capture the candidates in-page — the evidence the refusal enumerates. */
104
- async function captureMatches(locator) {
105
- return await locator.evaluateAll((nodes, limit) => {
106
- const LANDMARKS = "nav, header, footer, main, aside, section, form, [role]";
107
- return nodes.slice(0, limit).map((node) => {
108
- const element = node;
109
- const landmark = element.parentElement?.closest(LANDMARKS);
110
- const context = landmark ? landmark.getAttribute("role") ?? landmark.tagName.toLowerCase() : null;
111
- const detail = element.getAttribute("href") ?? element.getAttribute("name");
112
- const text = (element.textContent ?? "").replaceAll(/\s+/gu, " ").trim().slice(0, 80);
113
- const label = element.getAttribute("aria-label")?.trim();
114
- return {
115
- accessibleName: label && label !== text ? label : void 0,
116
- context: context ?? void 0,
117
- detail: detail ?? void 0,
118
- tag: element.tagName.toLowerCase(),
119
- text
120
- };
121
- });
122
- }, MAX_REPORTED_MATCHES);
123
- }
124
- /**
125
- * Run one visitor action, converting a strict-mode violation into the W3
126
- * refusal. The ambiguous level is identified before reporting: a scope that
127
- * matches several landmarks is the real fault, and naming the target instead
128
- * would send the author to fix the wrong descriptor.
129
- */
130
- async function act(page, element, action) {
131
- const target = await widenedForWindow(element, page.url(), probeOf(page)) ?? element;
132
- try {
133
- return await action(locate(page, target));
134
- } catch (error) {
135
- if (!isStrictViolation(error)) throw error;
136
- const culprit = await findAmbiguousLevel(page, target);
137
- const matches = await captureMatches(locate(page, culprit));
138
- throw new AmbiguousElementError(describeAmbiguity({
139
- element: culprit,
140
- matches,
141
- url: page.url()
142
- }));
143
- }
144
- }
145
- /** What the transitional window may ask of the page. */
146
- function probeOf(page) {
147
- return {
148
- count: async (element) => await locate(page, element).count(),
149
- nameOf: async (element) => await wholeNameOf(page, element),
150
- settles: async (element) => await settles(page, element)
151
- };
152
- }
153
- /**
154
- * Does the descriptor arrive if the page is given the settle budget?
155
- *
156
- * A poll rather than `waitFor`, for the reason the rest of this adapter has no
157
- * `.first()`: waiting on the locator itself would have to narrow a strict
158
- * locator to one element, and a count answers the same question without
159
- * touching the mechanism W3 rests on. The locator is rebuilt every round, so a
160
- * navigation that replaced the document is read on the new one.
161
- */
162
- async function settles(page, element) {
163
- const deadline = Date.now() + WINDOW_SETTLE_MS;
164
- for (;;) {
165
- if (await countOrZero(page, element) > 0) return true;
166
- if (Date.now() >= deadline) return false;
167
- await setTimeout(VALUE_POLL_MS);
168
- }
169
- }
170
- /** A count taken mid-navigation throws; the page is simply not answering yet. */
171
- async function countOrZero(page, element) {
172
- try {
173
- return await locate(page, element).count();
174
- } catch {
175
- return 0;
176
- }
177
- }
178
- /**
179
- * The name to WRITE for this descriptor — the whole one, never truncated.
180
- *
181
- * A role descriptor matches the accessible name the browser computed, so the
182
- * name is read off the ARIA snapshot, which is that same computation: the text
183
- * content glues inline children with no separator (`Experiments9` for a button
184
- * named "Experiments 9") and a field's name is its label, which its text never
185
- * carries at all. A `text` descriptor matches text, so text is what it is told
186
- * to write.
187
- */
188
- async function wholeNameOf(page, element) {
189
- const locator = locate(page, element);
190
- if (element.kind !== "text") try {
191
- const named = accessibleNameIn(await locator.ariaSnapshot());
192
- if (named !== void 0) return named;
193
- } catch {}
194
- return await wholeTextOf(locator);
195
- }
196
- /** The element's text, whitespace-collapsed and WHOLE — a name cut mid-word resolves to nothing. */
197
- async function wholeTextOf(locator) {
198
- try {
199
- const collapsed = (await locator.evaluate((node) => node.textContent ?? "")).replaceAll(/\s+/gu, " ").trim();
200
- return collapsed === "" ? void 0 : collapsed;
201
- } catch {
202
- return;
203
- }
204
- }
205
- /** Walk the scope chain outside-in; the outermost ambiguous level is the fault. */
206
- async function findAmbiguousLevel(page, element) {
207
- const chain = [];
208
- for (let current = element; current; current = current.scope) chain.unshift(current);
209
- for (const level of chain.slice(0, -1)) if (await locate(page, level).count() > 1) return level;
210
- return element;
211
- }
212
- /**
213
- * The same element, narrowed by whatever STATE the descriptor carries — focus,
214
- * or accepting input — or `null` when it carries none.
215
- *
216
- * A second condition on the locator rather than a one-shot probe: the locator
217
- * keeps playwright's own retry, so `see(focused(x))` waits for the keyboard to
218
- * arrive instead of asking once and failing on a frame of animation. It also
219
- * needs nothing but `playwright` — `expect(...).toBeFocused()` lives in
220
- * `@playwright/test`, which this package deliberately does not carry.
221
- */
222
- function stateOnly(page, locator, element) {
223
- if (element.focused === true) return locator.and(page.locator(":focus"));
224
- if (element.disabled !== void 0) return locator.and(page.locator(element.disabled ? ":disabled" : ":enabled"));
225
- if (element.selected !== void 0) return locator.and(page.locator(element.selected ? ":checked" : ":not(:checked)"));
226
- return null;
227
- }
228
- /**
229
- * Is this descriptor answered by PRESENCE rather than by visibility? The
230
- * options of a collapsed `<select>` are in the document and in the tree, and
231
- * none of them has a box on the screen until a visitor opens it.
232
- */
233
- function byPresence(element) {
234
- return element.kind === "option";
235
- }
236
- /** The field's live value, or `null` when nothing (unambiguous) is there yet. */
237
- async function currentValue(locator) {
238
- try {
239
- return await locator.inputValue({ timeout: VALUE_POLL_MS });
240
- } catch (error) {
241
- if (isStrictViolation(error)) throw error;
242
- return null;
243
- }
244
- }
245
- /**
246
- * Wait until the field holds the value the descriptor carries — or, for
247
- * `gone()`, until it no longer does.
248
- *
249
- * A poll rather than a narrowed locator: a value is a PROPERTY of the node,
250
- * and the CSS a locator narrows with reads ATTRIBUTES — a controlled input
251
- * whose `value` attribute never moves would answer for a value it no longer
252
- * holds. The locator is re-read every round, so a re-rendered field is read
253
- * again rather than held as a stale handle.
254
- */
255
- async function waitForValue(locator, element, wanted) {
256
- const value = element.value ?? "";
257
- const deadline = Date.now() + VALUE_TIMEOUT_MS;
258
- for (;;) {
259
- const actual = await currentValue(locator);
260
- if (actual === null ? !wanted : actual === value === wanted) return;
261
- if (Date.now() >= deadline) throw new Error(`${formatElement(element)} ${wanted ? "never held" : "still holds"} ${JSON.stringify(value)}${actual === null ? " (it is not on the page)" : `, it holds ${JSON.stringify(actual)}`}`);
262
- await setTimeout(VALUE_POLL_MS);
263
- }
264
- }
265
- /** The visitor implementation — every action auto-waits via playwright actionability. */
266
- function createVisitor(page, baseUrl) {
267
- return {
268
- check: async (element) => {
269
- await act(page, element, async (locator) => {
270
- await locator.check();
271
- });
272
- },
273
- click: async (element) => {
274
- await act(page, element, async (locator) => {
275
- await locator.click();
276
- });
277
- },
278
- fill: async (element, value) => {
279
- await act(page, element, async (locator) => {
280
- await locator.fill(value);
281
- });
282
- },
283
- goto: async (path) => {
284
- await page.goto(`${baseUrl}${path}`, { waitUntil: "load" });
285
- },
286
- hover: async (element) => {
287
- await act(page, element, async (locator) => {
288
- await locator.hover();
289
- });
290
- },
291
- press: async (key) => {
292
- await page.keyboard.press(key);
293
- },
294
- gone: async (element) => {
295
- await act(page, element, async (locator) => {
296
- if (element.value !== void 0) {
297
- await waitForValue(locator, element, false);
298
- return;
299
- }
300
- const narrowed = stateOnly(page, locator, element);
301
- if (narrowed !== null) {
302
- await narrowed.waitFor({ state: "detached" });
303
- return;
304
- }
305
- await locator.waitFor({ state: byPresence(element) ? "detached" : "hidden" });
306
- });
307
- },
308
- see: async (element) => {
309
- await act(page, element, async (locator) => {
310
- if (element.value !== void 0) {
311
- await waitForValue(locator, element, true);
312
- return;
313
- }
314
- const narrowed = stateOnly(page, locator, element);
315
- if (narrowed !== null) {
316
- await narrowed.waitFor({ state: "attached" });
317
- return;
318
- }
319
- await locator.waitFor({ state: byPresence(element) ? "attached" : "visible" });
320
- });
321
- },
322
- select: async (element, option) => {
323
- await act(page, element, async (locator) => await locator.selectOption(option));
324
- }
325
- };
326
- }
327
- /**
328
- * Browser adapter backed by playwright chromium.
329
- *
330
- * ONE browser process per adapter (= per runner = per vitest worker),
331
- * launched lazily on the first `open()`. Each visit gets a fresh
332
- * `BrowserContext` — isolation without paying a browser launch per spec.
333
- *
334
- * Playwright is an optional peer dependency: it is only imported here, and
335
- * this module is only loaded when a spec calls `.visit()`.
336
- */
337
- var PlaywrightAdapter = class {
338
- browser = null;
339
- async close() {
340
- if (this.browser) {
341
- await this.browser.close();
342
- this.browser = null;
343
- }
344
- }
345
- async open(url, options) {
346
- const context = await (await this.launch()).newContext({ extraHTTPHeaders: options.headers ?? {} });
347
- if (options.external === "block") {
348
- const allowed = /* @__PURE__ */ new Set([new URL(options.baseUrl).origin, ...options.allowedOrigins ?? []]);
349
- await context.route("**/*", (route) => {
350
- if (allowed.has(new URL(route.request().url()).origin)) route.continue();
351
- else route.abort();
352
- });
353
- }
354
- try {
355
- const page = await context.newPage();
356
- if (options.clock !== void 0) await page.clock.setFixedTime(new Date(options.clock));
357
- const consoleMessages = [];
358
- page.on("console", (message) => {
359
- consoleMessages.push({
360
- text: message.text(),
361
- type: message.type()
362
- });
363
- });
364
- const response = await page.goto(url, { waitUntil: "load" });
365
- if (options.scenario) try {
366
- await options.scenario(createVisitor(page, options.baseUrl));
367
- } catch (error) {
368
- const evidence = await this.captureEvidence(page);
369
- const suffix = evidence ? `\nEvidence: ${evidence}` : "";
370
- if (error instanceof AmbiguousElementError) {
371
- error.message += suffix;
372
- throw error;
373
- }
374
- throw new Error(`visit scenario failed: ${error instanceof Error ? error.message : String(error)}${suffix}`, { cause: error });
375
- }
376
- const extraction = await page.evaluate(() => {
377
- const links = [...document.querySelectorAll("link")].map((link) => ({
378
- href: link.href,
379
- hreflang: link.hreflang || void 0,
380
- rel: link.rel,
381
- type: link.type || void 0
382
- }));
383
- const metas = [...document.querySelectorAll("meta")].map((meta) => ({
384
- content: meta.content,
385
- name: meta.name || void 0,
386
- property: meta.getAttribute("property") ?? void 0
387
- }));
388
- const jsonLdBlocks = [...document.querySelectorAll("script[type=\"application/ld+json\"]")].map((script) => script.textContent ?? "");
389
- return {
390
- html: document.documentElement.outerHTML,
391
- jsonLdBlocks,
392
- links,
393
- metas,
394
- text: document.body?.innerText ?? "",
395
- title: document.title
396
- };
397
- });
398
- const tree = await page.locator("body").ariaSnapshot();
399
- return {
400
- consoleMessages,
401
- status: response?.status() ?? 0,
402
- tree,
403
- url: page.url(),
404
- ...extraction
405
- };
406
- } finally {
407
- await context.close();
408
- }
409
- }
410
- /** Screenshot the failing state into a temp file; never masks the original error. */
411
- async captureEvidence(page) {
412
- try {
413
- const dir = mkdtempSync(resolve(tmpdir(), "spec-website-"));
414
- const path = resolve(dir, "failure.png");
415
- await page.screenshot({
416
- fullPage: true,
417
- path
418
- });
419
- return path;
420
- } catch {
421
- return null;
422
- }
423
- }
424
- /** Launch the shared chromium instance (once), with an actionable error when playwright is absent. */
425
- async launch() {
426
- if (this.browser) return this.browser;
427
- let chromium;
428
- try {
429
- ({chromium} = await import("playwright"));
430
- } catch {
431
- throw new Error(".visit() requires playwright (optional peer dependency): npm install -D playwright && npx playwright install chromium");
432
- }
433
- this.browser = await chromium.launch();
434
- return this.browser;
435
- }
436
- };
437
- //#endregion
438
- export { PlaywrightAdapter };
package/dist/server.js DELETED
@@ -1,160 +0,0 @@
1
- import { a as isStreamBody, n as ContractQueue, t as toReadableStream } from "./stream.js";
2
- //#region src/seams/msw/handlers.ts
3
- /**
4
- * What a chain with zero contracts gets where the facet's scope is KNOWN but
5
- * not total — the node engine, which shares a process with everything else the
6
- * test does. Nothing was declared, so nothing is guarded and nothing has to be
7
- * torn down (CONVENTIONS D7, known scope).
8
- */
9
- const NO_CONTRACTS = {
10
- cleanup: () => {},
11
- violation: () => null
12
- };
13
- /**
14
- * A contract answers the request that was made, never a copy of an earlier one.
15
- *
16
- * In a page the reply travels through a real service worker and a real HTTP
17
- * cache, so a second render of the same component can be served the FIRST
18
- * test's answer and never reach the queue at all — a contract that was
19
- * exhausted, or replaced, silently keeps answering. Stated as a default, so a
20
- * spec whose subject IS caching can still say otherwise.
21
- */
22
- const NEVER_CACHED = { "cache-control": "no-store" };
23
- /** Turn a contract response into the msw reply, body kind by body kind. */
24
- function toMswResponse(msw, response) {
25
- const { body, headers, status = 200 } = response;
26
- if (response.transport === "network-error") return msw.HttpResponse.error();
27
- if (body === null || body === void 0) return new msw.HttpResponse(null, {
28
- headers: {
29
- ...NEVER_CACHED,
30
- ...headers
31
- },
32
- status
33
- });
34
- if (isStreamBody(body)) return new msw.HttpResponse(toReadableStream(body), {
35
- headers: {
36
- ...NEVER_CACHED,
37
- "content-type": body.contentType,
38
- ...headers
39
- },
40
- status
41
- });
42
- if (typeof body === "string") return new msw.HttpResponse(body, {
43
- headers: {
44
- ...NEVER_CACHED,
45
- "content-type": "text/plain; charset=utf-8",
46
- ...headers
47
- },
48
- status
49
- });
50
- return msw.HttpResponse.json(body, {
51
- headers: {
52
- ...NEVER_CACHED,
53
- ...headers
54
- },
55
- status
56
- });
57
- }
58
- /**
59
- * The handlers one chain's contracts become, plus the trailing catch-all that
60
- * records any request no contract accepted (CONVENTIONS D7). The caller decides
61
- * what to do with the recorded violation — the api chain rethrows it, the
62
- * component chain fails the render. An EMPTY contract list is still a guard:
63
- * the catch-all alone says "this subject has no network".
64
- *
65
- * `bypass` names the traffic that is not the subject's: under node there is
66
- * none, in a page it is everything the runner fetches to BE a page.
67
- */
68
- function buildContractHandlers(msw, contracts, bypass) {
69
- const queue = new ContractQueue(contracts);
70
- let violation = null;
71
- const recordViolation = (method, url) => {
72
- violation ??= queue.unmatchedError(method, url);
73
- return msw.HttpResponse.json({ error: `@jterrazz/test strict contracts: unmatched request ${method} ${url}` }, {
74
- headers: NEVER_CACHED,
75
- status: 501
76
- });
77
- };
78
- const handlers = [];
79
- for (const route of queue.routes) for (const method of route.methods) {
80
- const handlerFn = method === "*" ? msw.http.all : msw.http[method.toLowerCase()];
81
- if (!handlerFn) continue;
82
- handlers.push(handlerFn(route.url, async ({ request }) => {
83
- const observed = await observe(request);
84
- const contract = queue.take(observed);
85
- if (!contract) return recordViolation(request.method, request.url);
86
- const response = typeof contract.response === "function" ? contract.response(observed) : contract.response;
87
- if (response.delay) await new Promise((resolve) => setTimeout(resolve, response.delay));
88
- return toMswResponse(msw, response);
89
- }));
90
- }
91
- handlers.push(msw.http.all("*", ({ request }) => bypass?.(request.url) === true ? msw.passthrough() : recordViolation(request.method, request.url)));
92
- return {
93
- handlers,
94
- violation: () => violation ?? queue.requiredError()
95
- };
96
- }
97
- /** The request as the queue matches it — body parsed when it is JSON. */
98
- async function observe(request) {
99
- let body = null;
100
- const rawText = await request.clone().text();
101
- if (rawText) try {
102
- body = JSON.parse(rawText);
103
- } catch {
104
- body = rawText;
105
- }
106
- const headers = {};
107
- request.headers.forEach((value, key) => {
108
- headers[key.toLowerCase()] = value;
109
- });
110
- return {
111
- body,
112
- headers,
113
- method: request.method.toUpperCase(),
114
- url: request.url
115
- };
116
- }
117
- //#endregion
118
- //#region src/seams/msw/server.ts
119
- let mswModule = null;
120
- let mswHttp = null;
121
- async function loadMsw() {
122
- if (mswModule === null) {
123
- mswModule = await import("msw/node");
124
- mswHttp = await import("msw");
125
- }
126
- return {
127
- msw: mswHttp,
128
- node: mswModule
129
- };
130
- }
131
- let serverInstance = null;
132
- /**
133
- * Start the MSW server (once per process).
134
- */
135
- async function ensureContractServer() {
136
- if (serverInstance !== null) return;
137
- const { node } = await loadMsw();
138
- serverInstance = node.setupServer();
139
- serverInstance.listen({ onUnhandledRequest: "bypass" });
140
- }
141
- /**
142
- * Register a chain's contracts as MSW handlers. A trailing catch-all handler
143
- * records any request no contract accepted — the builder rethrows it as the
144
- * spec failure (rejecting the action promise, never an unhandled rejection).
145
- */
146
- async function registerContracts(contracts) {
147
- if (contracts.length === 0) return NO_CONTRACTS;
148
- await ensureContractServer();
149
- const { msw } = await loadMsw();
150
- const { handlers, violation } = buildContractHandlers(msw, contracts);
151
- serverInstance.use(...handlers);
152
- return {
153
- cleanup: () => {
154
- serverInstance.resetHandlers();
155
- },
156
- violation
157
- };
158
- }
159
- //#endregion
160
- export { registerContracts };