@jterrazz/test 16.1.0 → 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 -2867
  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 +4551 -2612
  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 -3002
  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 +6073 -5616
  16. package/dist/oxlint.d.cts +43 -7212
  17. package/dist/oxlint.d.ts +43 -7212
  18. package/dist/oxlint.js +6065 -5609
  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 +374 -507
  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 -349
  104. package/dist/server.js +0 -160
  105. package/dist/stream.js +0 -321
  106. package/dist/substring-warning.js +0 -244
  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 -242
  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 -305
  125. package/docs/19-linting.md +0 -510
  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,349 +0,0 @@
1
- import { a as formatElement, i as describeAmbiguity, n as widenedForWindow, r as AmbiguousElementError } 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/seams/playwright/playwright.adapter.ts
7
- /** How many candidates the ambiguity error enumerates before truncating. */
8
- const MAX_REPORTED_MATCHES = 10;
9
- const VALUE_TIMEOUT_MS = 3e4;
10
- const VALUE_POLL_MS = 100;
11
- /**
12
- * The roles a FIELD can carry — what `field('Name')` designates.
13
- *
14
- * A field is named by its ACCESSIBLE name, not by the text of the label
15
- * element that happens to wrap it: `<label><span>Channel</span><select>…` has
16
- * a label whose text carries every option of the select, while the control's
17
- * accessible name is `Channel` — which is what the ARIA tree shows and what a
18
- * screen reader reads. Resolving by label text alone made the ordinary
19
- * wrapping-label form unreachable under the 16.0 whole-name default.
20
- *
21
- * The label-text locator stays in the union for the controls ARIA gives no
22
- * role at all (a date or colour input), which nothing else would find.
23
- */
24
- const FIELD_ROLES = [
25
- "checkbox",
26
- "combobox",
27
- "radio",
28
- "searchbox",
29
- "slider",
30
- "spinbutton",
31
- "switch",
32
- "textbox"
33
- ];
34
- /**
35
- * Translate a user-facing descriptor into a playwright locator, resolving
36
- * `scope` outside-in so `within(navigation(), link('X'))` searches the nav
37
- * subtree. No `.first()` anywhere: playwright's strict mode is the mechanism
38
- * behind CONVENTIONS W3, and swallowing it would reintroduce the silent
39
- * wrong-element bug the rule exists to prevent.
40
- */
41
- function locate(root, element) {
42
- const scope = element.scope ? locate(root, element.scope) : root;
43
- const exact = element.exact ?? true;
44
- const name = element.name ?? "";
45
- if (element.kind === "field") {
46
- let field = scope.getByLabel(name, { exact });
47
- for (const role of FIELD_ROLES) field = field.or(scope.getByRole(role, {
48
- exact,
49
- name
50
- }));
51
- return field;
52
- }
53
- if (element.kind === "testId") return scope.getByTestId(name);
54
- if (element.kind === "text") return scope.getByText(name, { exact });
55
- return scope.getByRole(element.kind, element.name === void 0 ? { exact } : {
56
- exact,
57
- name: element.name
58
- });
59
- }
60
- /** Playwright signals "more than one match" through this error text. */
61
- function isStrictViolation(error) {
62
- return error instanceof Error && error.message.includes("strict mode violation");
63
- }
64
- /** Capture the candidates in-page — the evidence the refusal enumerates. */
65
- async function captureMatches(locator) {
66
- return await locator.evaluateAll((nodes, limit) => {
67
- const LANDMARKS = "nav, header, footer, main, aside, section, form, [role]";
68
- return nodes.slice(0, limit).map((node) => {
69
- const element = node;
70
- const landmark = element.parentElement?.closest(LANDMARKS);
71
- const context = landmark ? landmark.getAttribute("role") ?? landmark.tagName.toLowerCase() : null;
72
- const detail = element.getAttribute("href") ?? element.getAttribute("name");
73
- const text = (element.textContent ?? "").replaceAll(/\s+/gu, " ").trim().slice(0, 80);
74
- const label = element.getAttribute("aria-label")?.trim();
75
- return {
76
- accessibleName: label && label !== text ? label : void 0,
77
- context: context ?? void 0,
78
- detail: detail ?? void 0,
79
- tag: element.tagName.toLowerCase(),
80
- text
81
- };
82
- });
83
- }, MAX_REPORTED_MATCHES);
84
- }
85
- /**
86
- * Run one visitor action, converting a strict-mode violation into the W3
87
- * refusal. The ambiguous level is identified before reporting: a scope that
88
- * matches several landmarks is the real fault, and naming the target instead
89
- * would send the author to fix the wrong descriptor.
90
- */
91
- async function act(page, element, action) {
92
- const target = await widenedForWindow(element, page.url(), probeOf(page)) ?? element;
93
- try {
94
- return await action(locate(page, target));
95
- } catch (error) {
96
- if (!isStrictViolation(error)) throw error;
97
- const culprit = await findAmbiguousLevel(page, target);
98
- const matches = await captureMatches(locate(page, culprit));
99
- throw new AmbiguousElementError(describeAmbiguity({
100
- element: culprit,
101
- matches,
102
- url: page.url()
103
- }));
104
- }
105
- }
106
- /** What the transitional window may ask of the page. */
107
- function probeOf(page) {
108
- return {
109
- count: async (element) => await locate(page, element).count(),
110
- nameOf: async (element) => {
111
- const [match] = await captureMatches(locate(page, element));
112
- return match?.accessibleName ?? match?.text;
113
- }
114
- };
115
- }
116
- /** Walk the scope chain outside-in; the outermost ambiguous level is the fault. */
117
- async function findAmbiguousLevel(page, element) {
118
- const chain = [];
119
- for (let current = element; current; current = current.scope) chain.unshift(current);
120
- for (const level of chain.slice(0, -1)) if (await locate(page, level).count() > 1) return level;
121
- return element;
122
- }
123
- /**
124
- * The same element, narrowed by whatever STATE the descriptor carries — focus,
125
- * or accepting input — or `null` when it carries none.
126
- *
127
- * A second condition on the locator rather than a one-shot probe: the locator
128
- * keeps playwright's own retry, so `see(focused(x))` waits for the keyboard to
129
- * arrive instead of asking once and failing on a frame of animation. It also
130
- * needs nothing but `playwright` — `expect(...).toBeFocused()` lives in
131
- * `@playwright/test`, which this package deliberately does not carry.
132
- */
133
- function stateOnly(page, locator, element) {
134
- if (element.focused === true) return locator.and(page.locator(":focus"));
135
- if (element.disabled !== void 0) return locator.and(page.locator(element.disabled ? ":disabled" : ":enabled"));
136
- if (element.selected !== void 0) return locator.and(page.locator(element.selected ? ":checked" : ":not(:checked)"));
137
- return null;
138
- }
139
- /**
140
- * Is this descriptor answered by PRESENCE rather than by visibility? The
141
- * options of a collapsed `<select>` are in the document and in the tree, and
142
- * none of them has a box on the screen until a visitor opens it.
143
- */
144
- function byPresence(element) {
145
- return element.kind === "option";
146
- }
147
- /** The field's live value, or `null` when nothing (unambiguous) is there yet. */
148
- async function currentValue(locator) {
149
- try {
150
- return await locator.inputValue({ timeout: VALUE_POLL_MS });
151
- } catch (error) {
152
- if (isStrictViolation(error)) throw error;
153
- return null;
154
- }
155
- }
156
- /**
157
- * Wait until the field holds the value the descriptor carries — or, for
158
- * `gone()`, until it no longer does.
159
- *
160
- * A poll rather than a narrowed locator: a value is a PROPERTY of the node,
161
- * and the CSS a locator narrows with reads ATTRIBUTES — a controlled input
162
- * whose `value` attribute never moves would answer for a value it no longer
163
- * holds. The locator is re-read every round, so a re-rendered field is read
164
- * again rather than held as a stale handle.
165
- */
166
- async function waitForValue(locator, element, wanted) {
167
- const value = element.value ?? "";
168
- const deadline = Date.now() + VALUE_TIMEOUT_MS;
169
- for (;;) {
170
- const actual = await currentValue(locator);
171
- if (actual === null ? !wanted : actual === value === wanted) return;
172
- 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)}`}`);
173
- await setTimeout(VALUE_POLL_MS);
174
- }
175
- }
176
- /** The visitor implementation — every action auto-waits via playwright actionability. */
177
- function createVisitor(page, baseUrl) {
178
- return {
179
- check: async (element) => {
180
- await act(page, element, async (locator) => {
181
- await locator.check();
182
- });
183
- },
184
- click: async (element) => {
185
- await act(page, element, async (locator) => {
186
- await locator.click();
187
- });
188
- },
189
- fill: async (element, value) => {
190
- await act(page, element, async (locator) => {
191
- await locator.fill(value);
192
- });
193
- },
194
- goto: async (path) => {
195
- await page.goto(`${baseUrl}${path}`, { waitUntil: "load" });
196
- },
197
- hover: async (element) => {
198
- await act(page, element, async (locator) => {
199
- await locator.hover();
200
- });
201
- },
202
- press: async (key) => {
203
- await page.keyboard.press(key);
204
- },
205
- gone: async (element) => {
206
- await act(page, element, async (locator) => {
207
- if (element.value !== void 0) {
208
- await waitForValue(locator, element, false);
209
- return;
210
- }
211
- const narrowed = stateOnly(page, locator, element);
212
- if (narrowed !== null) {
213
- await narrowed.waitFor({ state: "detached" });
214
- return;
215
- }
216
- await locator.waitFor({ state: byPresence(element) ? "detached" : "hidden" });
217
- });
218
- },
219
- see: async (element) => {
220
- await act(page, element, async (locator) => {
221
- if (element.value !== void 0) {
222
- await waitForValue(locator, element, true);
223
- return;
224
- }
225
- const narrowed = stateOnly(page, locator, element);
226
- if (narrowed !== null) {
227
- await narrowed.waitFor({ state: "attached" });
228
- return;
229
- }
230
- await locator.waitFor({ state: byPresence(element) ? "attached" : "visible" });
231
- });
232
- },
233
- select: async (element, option) => {
234
- await act(page, element, async (locator) => await locator.selectOption(option));
235
- }
236
- };
237
- }
238
- /**
239
- * Browser adapter backed by playwright chromium.
240
- *
241
- * ONE browser process per adapter (= per runner = per vitest worker),
242
- * launched lazily on the first `open()`. Each visit gets a fresh
243
- * `BrowserContext` — isolation without paying a browser launch per spec.
244
- *
245
- * Playwright is an optional peer dependency: it is only imported here, and
246
- * this module is only loaded when a spec calls `.visit()`.
247
- */
248
- var PlaywrightAdapter = class {
249
- browser = null;
250
- async close() {
251
- if (this.browser) {
252
- await this.browser.close();
253
- this.browser = null;
254
- }
255
- }
256
- async open(url, options) {
257
- const context = await (await this.launch()).newContext({ extraHTTPHeaders: options.headers ?? {} });
258
- if (options.external === "block") {
259
- const allowed = /* @__PURE__ */ new Set([new URL(options.baseUrl).origin, ...options.allowedOrigins ?? []]);
260
- await context.route("**/*", (route) => {
261
- if (allowed.has(new URL(route.request().url()).origin)) route.continue();
262
- else route.abort();
263
- });
264
- }
265
- try {
266
- const page = await context.newPage();
267
- if (options.clock !== void 0) await page.clock.setFixedTime(new Date(options.clock));
268
- const consoleMessages = [];
269
- page.on("console", (message) => {
270
- consoleMessages.push({
271
- text: message.text(),
272
- type: message.type()
273
- });
274
- });
275
- const response = await page.goto(url, { waitUntil: "load" });
276
- if (options.scenario) try {
277
- await options.scenario(createVisitor(page, options.baseUrl));
278
- } catch (error) {
279
- const evidence = await this.captureEvidence(page);
280
- const suffix = evidence ? `\nEvidence: ${evidence}` : "";
281
- if (error instanceof AmbiguousElementError) {
282
- error.message += suffix;
283
- throw error;
284
- }
285
- throw new Error(`visit scenario failed: ${error instanceof Error ? error.message : String(error)}${suffix}`, { cause: error });
286
- }
287
- const extraction = await page.evaluate(() => {
288
- const links = [...document.querySelectorAll("link")].map((link) => ({
289
- href: link.href,
290
- hreflang: link.hreflang || void 0,
291
- rel: link.rel,
292
- type: link.type || void 0
293
- }));
294
- const metas = [...document.querySelectorAll("meta")].map((meta) => ({
295
- content: meta.content,
296
- name: meta.name || void 0,
297
- property: meta.getAttribute("property") ?? void 0
298
- }));
299
- const jsonLdBlocks = [...document.querySelectorAll("script[type=\"application/ld+json\"]")].map((script) => script.textContent ?? "");
300
- return {
301
- html: document.documentElement.outerHTML,
302
- jsonLdBlocks,
303
- links,
304
- metas,
305
- text: document.body?.innerText ?? "",
306
- title: document.title
307
- };
308
- });
309
- const tree = await page.locator("body").ariaSnapshot();
310
- return {
311
- consoleMessages,
312
- status: response?.status() ?? 0,
313
- tree,
314
- url: page.url(),
315
- ...extraction
316
- };
317
- } finally {
318
- await context.close();
319
- }
320
- }
321
- /** Screenshot the failing state into a temp file; never masks the original error. */
322
- async captureEvidence(page) {
323
- try {
324
- const dir = mkdtempSync(resolve(tmpdir(), "spec-website-"));
325
- const path = resolve(dir, "failure.png");
326
- await page.screenshot({
327
- fullPage: true,
328
- path
329
- });
330
- return path;
331
- } catch {
332
- return null;
333
- }
334
- }
335
- /** Launch the shared chromium instance (once), with an actionable error when playwright is absent. */
336
- async launch() {
337
- if (this.browser) return this.browser;
338
- let chromium;
339
- try {
340
- ({chromium} = await import("playwright"));
341
- } catch {
342
- throw new Error(".visit() requires playwright (optional peer dependency): npm install -D playwright && npx playwright install chromium");
343
- }
344
- this.browser = await chromium.launch();
345
- return this.browser;
346
- }
347
- };
348
- //#endregion
349
- 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 };