@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
@@ -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 };