@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.
- package/README.md +45 -171
- package/dist/browser/index.js +1 -2980
- package/dist/browser/services.js +1 -0
- package/dist/browser/stubs.js +1 -0
- package/dist/browser-shared-index.browser.js +3233 -0
- package/dist/checker.d.ts +6 -1
- package/dist/checker.js +3721 -2624
- package/dist/config.d.ts +40 -0
- package/dist/config.js +309 -0
- package/dist/in-place.global-setup.d.ts +12 -0
- package/dist/in-place.global-setup.js +21 -0
- package/dist/index.d.ts +1641 -3004
- package/dist/index.js +5095 -3835
- package/dist/mobile-session.global-setup.d.ts +15 -0
- package/dist/mobile-session.global-setup.js +31 -0
- package/dist/oxlint.cjs +6069 -5634
- package/dist/oxlint.d.cts +43 -7212
- package/dist/oxlint.d.ts +43 -7212
- package/dist/oxlint.js +3979 -5799
- package/dist/route-coverage.global-setup.d.ts +43 -0
- package/dist/route-coverage.global-setup.js +1 -0
- package/dist/services.d.ts +3 -0
- package/dist/services.global-setup.d.ts +43 -0
- package/dist/services.global-setup.js +1 -0
- package/dist/services.js +597 -0
- package/dist/shared-appium.adapter.js +428 -0
- package/dist/shared-artifacts.js +34 -0
- package/dist/shared-chapter.js +7 -0
- package/dist/shared-container.port.d.ts +31 -0
- package/dist/shared-derived-projects.js +73 -0
- package/dist/shared-device.port.d.ts +189 -0
- package/dist/shared-exchange.js +447 -0
- package/dist/shared-in-place.js +172 -0
- package/dist/shared-index.services.d.ts +346 -0
- package/dist/shared-index.stubs.d.ts +287 -0
- package/dist/shared-json-shape.js +17 -0
- package/dist/shared-landmarks.js +71 -0
- package/dist/shared-ledger.js +68 -0
- package/dist/shared-listened-port.js +19 -0
- package/dist/shared-literate-plugin.js +184 -0
- package/dist/shared-manifest.js +1321 -0
- package/dist/shared-pause.js +22 -0
- package/dist/shared-peer.js +88 -0
- package/dist/shared-playwright.adapter.js +328 -0
- package/dist/shared-process-alive.js +13 -0
- package/dist/shared-process.adapter.js +74 -0
- package/dist/shared-record-key.js +13 -0
- package/dist/shared-refusal.js +35 -0
- package/dist/shared-resolve.js +24 -0
- package/dist/shared-route-coverage.global-setup.js +187 -0
- package/dist/shared-scenario-failure.js +198 -0
- package/dist/shared-serve.js +407 -0
- package/dist/shared-service-images.js +10 -0
- package/dist/shared-services.global-setup.js +174 -0
- package/dist/shared-session-ledger.js +305 -0
- package/dist/shared-setup-entry.js +12 -0
- package/dist/shared-source-scan.js +133 -0
- package/dist/shared-spec-document.js +303 -0
- package/dist/shared-spec-grammar.js +66 -0
- package/dist/shared-specifications.js +26 -0
- package/dist/shared-specs-modules.js +1033 -0
- package/dist/shared-stub-reply.js +167 -0
- package/dist/shared-stub-server.js +142 -0
- package/dist/shared-temp-root.global-setup.js +73 -0
- package/dist/shared-test-baseline.js +26 -0
- package/dist/shared-test-section.js +58 -0
- package/dist/shared-testcontainers.adapter.js +306 -0
- package/dist/shared-tokens.js +55 -0
- package/dist/shared-yaml-document.js +119 -0
- package/dist/stubs.d.ts +2 -0
- package/dist/stubs.js +657 -0
- package/dist/temp-root.global-setup.d.ts +7 -0
- package/dist/temp-root.global-setup.js +1 -0
- package/dist/vitest.d.ts +93 -164
- package/dist/vitest.js +382 -550
- package/docs/README.md +82 -36
- package/docs/concepts/architecture.md +118 -0
- package/docs/concepts/conventions.md +333 -0
- package/docs/guides/api-specs.md +374 -0
- package/docs/guides/cli-specs.md +566 -0
- package/docs/guides/component-specs.md +252 -0
- package/docs/guides/develop.md +97 -0
- package/docs/guides/integration-specs.md +181 -0
- package/docs/guides/job-specs.md +180 -0
- package/docs/guides/lint-specs.md +174 -0
- package/docs/guides/mobile-specs.md +319 -0
- package/docs/guides/module-tests.md +149 -0
- package/docs/guides/release.md +44 -0
- package/docs/guides/spec-documents.md +189 -0
- package/docs/guides/website-specs.md +413 -0
- package/docs/reference/assertions.md +345 -0
- package/docs/reference/glossary.md +20 -0
- package/docs/reference/linting.md +483 -0
- package/docs/reference/services.md +236 -0
- package/docs/reference/stubs.md +500 -0
- package/docs/reference/time-and-doubles.md +137 -0
- package/docs/reference/tokens.md +258 -0
- package/docs/reference/visitor.md +221 -0
- package/docs/start/getting-started.md +187 -0
- package/docs/start/set-up-a-project.md +228 -0
- package/package.json +56 -36
- package/schema/spec.schema.json +43 -17
- package/dist/appium.adapter.js +0 -489
- package/dist/artifacts.js +0 -32
- package/dist/fixtures.js +0 -110
- package/dist/literate.js +0 -1747
- package/dist/match.js +0 -804
- package/dist/playwright.adapter.js +0 -438
- package/dist/server.js +0 -160
- package/dist/stream.js +0 -321
- package/dist/substring-warning.js +0 -282
- package/docs/01-architecture.md +0 -95
- package/docs/02-developing.md +0 -351
- package/docs/03-testing.md +0 -495
- package/docs/04-operating.md +0 -101
- package/docs/05-module-tests.md +0 -152
- package/docs/06-integration.md +0 -180
- package/docs/07-component.md +0 -246
- package/docs/08-website.md +0 -404
- package/docs/09-mobile.md +0 -270
- package/docs/10-api.md +0 -264
- package/docs/11-jobs.md +0 -157
- package/docs/12-cli.md +0 -677
- package/docs/13-elements.md +0 -248
- package/docs/14-assertions.md +0 -314
- package/docs/15-tokens.md +0 -253
- package/docs/16-contracts.md +0 -438
- package/docs/17-services.md +0 -188
- package/docs/18-conventions.md +0 -332
- package/docs/19-linting.md +0 -512
- package/docs/decisions/001-v9-conventions-on-three-channels.md +0 -50
- package/docs/decisions/002-the-statique-channel-holds-only-what-upstream-does-not.md +0 -49
- package/docs/decisions/003-a-rendered-component-is-a-facet-on-vitest-browser-mode.md +0 -84
- package/docs/decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-facet.md +0 -81
- package/docs/decisions/005-the-doubles-ladder-is-closed.md +0 -86
- package/docs/decisions/006-the-catalogue-reaches-a-file-by-its-role.md +0 -245
- package/docs/decisions/007-compose-mode-leaves-the-package.md +0 -53
- package/docs/decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md +0 -75
- package/docs/decisions/_template.md +0 -22
- package/docs/reference/classes/Matcher.md +0 -120
- package/docs/reference/classes/ProcessHandle.md +0 -368
- package/docs/reference/functions/defineContract.md +0 -39
- package/docs/reference/functions/defineContracts.md +0 -36
- package/docs/reference/functions/disabled.md +0 -28
- package/docs/reference/functions/enabled.md +0 -20
- package/docs/reference/functions/focused.md +0 -26
- package/docs/reference/functions/mockOf.md +0 -79
- package/docs/reference/functions/postgres.md +0 -31
- package/docs/reference/functions/process.md +0 -33
- package/docs/reference/functions/redis.md +0 -28
- package/docs/reference/functions/required.md +0 -41
- package/docs/reference/functions/selected.md +0 -26
- package/docs/reference/functions/sqlite.md +0 -33
- package/docs/reference/functions/testId.md +0 -20
- package/docs/reference/functions/text.md +0 -40
- package/docs/reference/functions/valued.md +0 -28
- package/docs/reference/functions/waitUntil.md +0 -46
- package/docs/reference/functions/within.md +0 -29
- package/docs/reference/index.md +0 -196
- package/docs/reference/interfaces/BaseResult.md +0 -87
- package/docs/reference/interfaces/CallResult.md +0 -160
- package/docs/reference/interfaces/CaptureScope.md +0 -82
- package/docs/reference/interfaces/CliResult.md +0 -253
- package/docs/reference/interfaces/ContainerAccessor.md +0 -166
- package/docs/reference/interfaces/DirectoryAccessor.md +0 -33
- package/docs/reference/interfaces/FetchResult.md +0 -180
- package/docs/reference/interfaces/FilesystemAccessor.md +0 -46
- package/docs/reference/interfaces/HttpResult.md +0 -126
- package/docs/reference/interfaces/JsonAccessor.md +0 -46
- package/docs/reference/interfaces/PageResult.md +0 -356
- package/docs/reference/interfaces/RenderResult.md +0 -72
- package/docs/reference/interfaces/RenderedText.md +0 -110
- package/docs/reference/interfaces/ResponseAccessor.md +0 -47
- package/docs/reference/interfaces/ScreenResult.md +0 -128
- package/docs/reference/interfaces/TableAccessor.md +0 -26
- package/docs/reference/interfaces/TextAccessor.md +0 -96
- package/docs/reference/type-aliases/ApiHandle.md +0 -68
- package/docs/reference/type-aliases/ApiSpecification.md +0 -213
- package/docs/reference/type-aliases/ApiSpecificationOptions.md +0 -68
- package/docs/reference/type-aliases/BrowserConsoleMessage.md +0 -30
- package/docs/reference/type-aliases/BrowserLinkElement.md +0 -50
- package/docs/reference/type-aliases/BrowserMetaElement.md +0 -40
- package/docs/reference/type-aliases/BrowserOpenOptions.md +0 -87
- package/docs/reference/type-aliases/BrowserPage.md +0 -132
- package/docs/reference/type-aliases/BrowserPort.md +0 -51
- package/docs/reference/type-aliases/CliEnv.md +0 -11
- package/docs/reference/type-aliases/CliHandle.md +0 -80
- package/docs/reference/type-aliases/CliInput.md +0 -36
- package/docs/reference/type-aliases/CliOutput.md +0 -46
- package/docs/reference/type-aliases/CliPort.md +0 -58
- package/docs/reference/type-aliases/CliSpecification.md +0 -144
- package/docs/reference/type-aliases/CliSpecificationOptions.md +0 -134
- package/docs/reference/type-aliases/ComponentChain.md +0 -156
- package/docs/reference/type-aliases/ComponentScenario.md +0 -20
- package/docs/reference/type-aliases/ComponentUi.md +0 -47
- package/docs/reference/type-aliases/ComponentVisitor.md +0 -10
- package/docs/reference/type-aliases/ContainerPort.md +0 -113
- package/docs/reference/type-aliases/Contract.md +0 -68
- package/docs/reference/type-aliases/ContractInput.md +0 -13
- package/docs/reference/type-aliases/ContractRequest.md +0 -95
- package/docs/reference/type-aliases/ContractResponder.md +0 -23
- package/docs/reference/type-aliases/ContractResponse.md +0 -78
- package/docs/reference/type-aliases/ContractResponseValue.md +0 -13
- package/docs/reference/type-aliases/Contracts.md +0 -48
- package/docs/reference/type-aliases/DatabaseKeys.md +0 -16
- package/docs/reference/type-aliases/DatabasePort.md +0 -72
- package/docs/reference/type-aliases/DeviceOpenOptions.md +0 -46
- package/docs/reference/type-aliases/DevicePort.md +0 -51
- package/docs/reference/type-aliases/DeviceScreen.md +0 -32
- package/docs/reference/type-aliases/DeviceTimeouts.md +0 -39
- package/docs/reference/type-aliases/DockerSpecConfig.md +0 -43
- package/docs/reference/type-aliases/DomMount.md +0 -21
- package/docs/reference/type-aliases/ElementKind.md +0 -27
- package/docs/reference/type-aliases/ElementMatch.md +0 -74
- package/docs/reference/type-aliases/ElementOptions.md +0 -29
- package/docs/reference/type-aliases/ElementRef.md +0 -128
- package/docs/reference/type-aliases/ExecOptions.md +0 -37
- package/docs/reference/type-aliases/FileAccessor.md +0 -54
- package/docs/reference/type-aliases/HonoApp.md +0 -31
- package/docs/reference/type-aliases/HttpContractFilter.md +0 -49
- package/docs/reference/type-aliases/HttpResponseInit.md +0 -46
- package/docs/reference/type-aliases/HttpStreamInit.md +0 -58
- package/docs/reference/type-aliases/IntegrationHandle.md +0 -46
- package/docs/reference/type-aliases/IntegrationSpecification.md +0 -111
- package/docs/reference/type-aliases/IntegrationSpecificationOptions.md +0 -42
- package/docs/reference/type-aliases/Intercept.md +0 -19
- package/docs/reference/type-aliases/InterceptOptions.md +0 -28
- package/docs/reference/type-aliases/InterceptScope.md +0 -11
- package/docs/reference/type-aliases/IsolationStrategy.md +0 -75
- package/docs/reference/type-aliases/JobHandle.md +0 -34
- package/docs/reference/type-aliases/JobsHandle.md +0 -57
- package/docs/reference/type-aliases/JobsResult.md +0 -16
- package/docs/reference/type-aliases/JobsSpecification.md +0 -94
- package/docs/reference/type-aliases/JobsSpecificationOptions.md +0 -56
- package/docs/reference/type-aliases/LandmarkKind.md +0 -20
- package/docs/reference/type-aliases/LiterateRunFlags.md +0 -27
- package/docs/reference/type-aliases/LiterateServeRegistration.md +0 -70
- package/docs/reference/type-aliases/MatchFixtureOptions.md +0 -27
- package/docs/reference/type-aliases/MatchableRequest.md +0 -61
- package/docs/reference/type-aliases/MatcherKind.md +0 -34
- package/docs/reference/type-aliases/MobileBackendOptions.md +0 -28
- package/docs/reference/type-aliases/MobileElementKind.md +0 -14
- package/docs/reference/type-aliases/MobileElementMatch.md +0 -60
- package/docs/reference/type-aliases/MobileElementRef.md +0 -20
- package/docs/reference/type-aliases/MobileHandle.md +0 -65
- package/docs/reference/type-aliases/MobileScenario.md +0 -20
- package/docs/reference/type-aliases/MobileSpecification.md +0 -51
- package/docs/reference/type-aliases/MobileSpecificationOptions.md +0 -126
- package/docs/reference/type-aliases/MobileVisitor.md +0 -81
- package/docs/reference/type-aliases/MockOfOptions.md +0 -26
- package/docs/reference/type-aliases/MockPort.md +0 -10
- package/docs/reference/type-aliases/PinnedClock.md +0 -10
- package/docs/reference/type-aliases/PostgresOptions.md +0 -32
- package/docs/reference/type-aliases/ProcessOptions.md +0 -112
- package/docs/reference/type-aliases/RedisOptions.md +0 -20
- package/docs/reference/type-aliases/RenderSubject.md +0 -12
- package/docs/reference/type-aliases/ScreenNode.md +0 -67
- package/docs/reference/type-aliases/ServerPort.md +0 -36
- package/docs/reference/type-aliases/ServerResponse.md +0 -46
- package/docs/reference/type-aliases/ServerSpec.md +0 -21
- package/docs/reference/type-aliases/ServiceHandle.md +0 -216
- package/docs/reference/type-aliases/ServiceRecord.md +0 -12
- package/docs/reference/type-aliases/SpecDocument.md +0 -84
- package/docs/reference/type-aliases/SpecEnvToken.md +0 -21
- package/docs/reference/type-aliases/SpecFileAssertion.md +0 -27
- package/docs/reference/type-aliases/SpecFixture.md +0 -30
- package/docs/reference/type-aliases/SpecKind.md +0 -8
- package/docs/reference/type-aliases/SpecRun.md +0 -108
- package/docs/reference/type-aliases/SpecServeEntry.md +0 -40
- package/docs/reference/type-aliases/SpecStream.md +0 -32
- package/docs/reference/type-aliases/SpecificationConfig.md +0 -290
- package/docs/reference/type-aliases/SqliteOptions.md +0 -36
- package/docs/reference/type-aliases/SseEvent.md +0 -58
- package/docs/reference/type-aliases/StreamBody.md +0 -61
- package/docs/reference/type-aliases/TextFilter.md +0 -22
- package/docs/reference/type-aliases/VisitScenario.md +0 -20
- package/docs/reference/type-aliases/Visitor.md +0 -215
- package/docs/reference/type-aliases/WaitUntilOptions.md +0 -46
- package/docs/reference/type-aliases/WebsiteBackendOptions.md +0 -37
- package/docs/reference/type-aliases/WebsiteHandle.md +0 -51
- package/docs/reference/type-aliases/WebsiteSpecification.md +0 -118
- package/docs/reference/type-aliases/WebsiteSpecificationOptions.md +0 -40
- package/docs/reference/variables/anthropic.md +0 -19
- package/docs/reference/variables/banner.md +0 -10
- package/docs/reference/variables/button.md +0 -10
- package/docs/reference/variables/clock.md +0 -16
- package/docs/reference/variables/complementary.md +0 -10
- package/docs/reference/variables/component.md +0 -10
- package/docs/reference/variables/content.md +0 -10
- package/docs/reference/variables/contentinfo.md +0 -10
- package/docs/reference/variables/dialog.md +0 -10
- package/docs/reference/variables/field.md +0 -10
- package/docs/reference/variables/form.md +0 -10
- package/docs/reference/variables/heading.md +0 -10
- package/docs/reference/variables/http.md +0 -40
- package/docs/reference/variables/intercept.md +0 -11
- package/docs/reference/variables/link.md +0 -10
- package/docs/reference/variables/listitem.md +0 -10
- package/docs/reference/variables/main.md +0 -10
- package/docs/reference/variables/match.md +0 -8
- package/docs/reference/variables/navigation.md +0 -10
- package/docs/reference/variables/openai.md +0 -21
- package/docs/reference/variables/option.md +0 -17
- package/docs/reference/variables/region.md +0 -10
- package/docs/reference/variables/row.md +0 -10
- package/docs/reference/variables/search.md +0 -10
- package/docs/reference/variables/specification.md +0 -8
- package/docs/reference/variables/status.md +0 -10
- package/docs/reference/variables/table.md +0 -10
package/dist/appium.adapter.js
DELETED
|
@@ -1,489 +0,0 @@
|
|
|
1
|
-
import { i as AmbiguousElementError, n as warnSubstringOnly, o as formatElement } from "./substring-warning.js";
|
|
2
|
-
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { resolve } from "node:path";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
//#region src/facets/mobile/ambiguity.ts
|
|
6
|
-
/**
|
|
7
|
-
* The ambiguity refusal, mobile edition — CONVENTIONS W3.
|
|
8
|
-
*
|
|
9
|
-
* A mobile descriptor must designate exactly one element. Acting on "the
|
|
10
|
-
* first match" is the failure mode this module exists to prevent: the spec
|
|
11
|
-
* keeps passing while the visitor taps something else, and nothing ever
|
|
12
|
-
* reports it. So when a descriptor matches several elements the framework
|
|
13
|
-
* refuses, and the refusal carries everything needed to fix it without
|
|
14
|
-
* opening the simulator — the descriptor in the caller's own vocabulary,
|
|
15
|
-
* every candidate, and the concrete rewrites that would resolve it.
|
|
16
|
-
*
|
|
17
|
-
* The element vocabulary is shared with the website facet, so the source
|
|
18
|
-
* rendering (`formatElement`) is too; only the evidence differs — XCUITest
|
|
19
|
-
* types, labels and accessibility identifiers instead of tags and landmarks.
|
|
20
|
-
* Pure string building: the appium integration stays a thin adapter.
|
|
21
|
-
*/
|
|
22
|
-
/** `1. Button "Bookmark" [testId: event-42]` — one evidence line per candidate. */
|
|
23
|
-
function formatMatch(match, index) {
|
|
24
|
-
const label = match.label === void 0 ? "" : ` ${JSON.stringify(match.label)}`;
|
|
25
|
-
const value = match.value === void 0 ? "" : ` value=${JSON.stringify(match.value)}`;
|
|
26
|
-
const identifier = match.identifier === void 0 ? "" : ` [testId: ${match.identifier}]`;
|
|
27
|
-
return ` ${index + 1}. ${match.type}${label}${value}${identifier}`;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* The rewrites worth offering, in the order they should be tried. Scoping is
|
|
31
|
-
* always available; `exact` only to a descriptor that opted OUT of it (exact
|
|
32
|
-
* is the default), and the count it would leave is stated so a
|
|
33
|
-
* still-ambiguous suggestion never reads as a fix; distinct identifiers make
|
|
34
|
-
* `testId()` a concrete rewrite rather than a guess.
|
|
35
|
-
*/
|
|
36
|
-
function formatFixes(element, matches) {
|
|
37
|
-
const fixes = [];
|
|
38
|
-
if (!element.scope) fixes.push(`scope it within(testId('…'), ${formatElement(element)}) — a screen has no landmarks; any descriptor works as the scope`);
|
|
39
|
-
if (element.exact === false && element.name !== void 0) {
|
|
40
|
-
const remaining = matches.filter((match) => match.label === element.name).length;
|
|
41
|
-
if (remaining > 0 && remaining < matches.length) fixes.push(`exact name ${formatElement({
|
|
42
|
-
exact: true,
|
|
43
|
-
kind: element.kind,
|
|
44
|
-
name: element.name
|
|
45
|
-
})} [leaves ${remaining} of ${matches.length}]`);
|
|
46
|
-
}
|
|
47
|
-
const identifiers = [...new Set(matches.map((match) => match.identifier).filter((identifier) => identifier !== void 0))];
|
|
48
|
-
if (identifiers.length > 1 && element.kind !== "testId") fixes.push(`test id testId(${JSON.stringify(identifiers[0])}) [also here: ${identifiers.slice(1).join(", ")}]`);
|
|
49
|
-
fixes.push("other element a button() or field() may name one thing where this does not");
|
|
50
|
-
return fixes;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Build the refusal thrown when a descriptor matches more than one element
|
|
54
|
-
* on the screen.
|
|
55
|
-
*
|
|
56
|
-
* @param options.element The descriptor as the caller wrote it.
|
|
57
|
-
* @param options.matches Every candidate, in screen order (already truncated).
|
|
58
|
-
*/
|
|
59
|
-
function describeMobileAmbiguity(options) {
|
|
60
|
-
const { element, matches } = options;
|
|
61
|
-
const fixes = formatFixes(element, matches).map((fix) => ` • ${fix}`);
|
|
62
|
-
return [
|
|
63
|
-
`Ambiguous element: ${formatElement(element)} matched ${matches.length} elements on the screen.`,
|
|
64
|
-
"",
|
|
65
|
-
"A spec must designate exactly one element. Acting on the first match would let",
|
|
66
|
-
"this test keep passing while the visitor taps something else.",
|
|
67
|
-
"",
|
|
68
|
-
"Matched:",
|
|
69
|
-
...matches.map((match, index) => formatMatch(match, index)),
|
|
70
|
-
"",
|
|
71
|
-
"Disambiguate with one of:",
|
|
72
|
-
...fixes,
|
|
73
|
-
"",
|
|
74
|
-
"Docs: docs/13-elements.md#designating-exactly-one-element (CONVENTIONS W3)"
|
|
75
|
-
].join("\n");
|
|
76
|
-
}
|
|
77
|
-
//#endregion
|
|
78
|
-
//#region src/facets/mobile/projection.ts
|
|
79
|
-
const TAG_PATTERN = /<(?<openTag>[A-Za-z][\w.]*)(?<rawAttributes>(?:\s+[\w:.-]+="[^"]*")*)\s*(?<selfClosing>\/?)>|<\/(?<closeTag>[\w.]*)\s*>/gu;
|
|
80
|
-
const ATTRIBUTE_PATTERN = /(?<key>[\w:.-]+)="(?<value>[^"]*)"/gu;
|
|
81
|
-
const NAMED_ENTITIES = {
|
|
82
|
-
"&": "&",
|
|
83
|
-
"'": "'",
|
|
84
|
-
">": ">",
|
|
85
|
-
"<": "<",
|
|
86
|
-
""": "\""
|
|
87
|
-
};
|
|
88
|
-
/** Decode the XML entities an attribute value can carry. */
|
|
89
|
-
function decodeEntities(value) {
|
|
90
|
-
return value.replaceAll(/&(?:#x(?<hex>[\dA-Fa-f]+)|#(?<decimal>\d+)|(?<named>amp|apos|gt|lt|quot));/gu, (entity, hex, decimal) => {
|
|
91
|
-
if (hex) return String.fromCodePoint(Number.parseInt(hex, 16));
|
|
92
|
-
if (decimal) return String.fromCodePoint(Number(decimal));
|
|
93
|
-
return NAMED_ENTITIES[entity] ?? entity;
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
/** Parse the page source into a raw element tree (strict, attribute-only XML). */
|
|
97
|
-
function parsePageSource(xml) {
|
|
98
|
-
const stack = [];
|
|
99
|
-
let root = null;
|
|
100
|
-
for (const match of xml.matchAll(TAG_PATTERN)) {
|
|
101
|
-
const { closeTag, openTag, rawAttributes, selfClosing } = match.groups ?? {};
|
|
102
|
-
if (closeTag !== void 0 || openTag === void 0) {
|
|
103
|
-
stack.pop();
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
const attributes = {};
|
|
107
|
-
for (const attribute of (rawAttributes ?? "").matchAll(ATTRIBUTE_PATTERN)) {
|
|
108
|
-
const { key, value } = attribute.groups ?? {};
|
|
109
|
-
if (key !== void 0 && value !== void 0) attributes[key] = decodeEntities(value);
|
|
110
|
-
}
|
|
111
|
-
const node = {
|
|
112
|
-
attributes,
|
|
113
|
-
children: [],
|
|
114
|
-
tag: openTag
|
|
115
|
-
};
|
|
116
|
-
const parent = stack.at(-1);
|
|
117
|
-
if (parent === void 0) root = node;
|
|
118
|
-
else parent.children.push(node);
|
|
119
|
-
if (!selfClosing) stack.push(node);
|
|
120
|
-
}
|
|
121
|
-
if (!root) throw new Error("projectScreen(): the page source contains no XML element");
|
|
122
|
-
return root;
|
|
123
|
-
}
|
|
124
|
-
/** The substance of a node — what survives projection, or nothing. */
|
|
125
|
-
function substance(node) {
|
|
126
|
-
const label = node.attributes.label || void 0;
|
|
127
|
-
const value = node.attributes.value || void 0;
|
|
128
|
-
const identifier = node.attributes.name || void 0;
|
|
129
|
-
return {
|
|
130
|
-
type: node.tag.replace(/^XCUIElementType/u, ""),
|
|
131
|
-
...label === void 0 ? {} : { label },
|
|
132
|
-
...value === void 0 || value === label ? {} : { value },
|
|
133
|
-
...identifier === void 0 || identifier === label ? {} : { identifier }
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
function hasSubstance(node) {
|
|
137
|
-
return node.label !== void 0 || node.value !== void 0 || node.identifier !== void 0;
|
|
138
|
-
}
|
|
139
|
-
/** A childless child that repeats its parent is the same accessibility element twice. */
|
|
140
|
-
function repeatsParent(child, parent) {
|
|
141
|
-
return child.children === void 0 && child.type === parent.type && child.label === parent.label && child.value === parent.value && child.identifier === parent.identifier;
|
|
142
|
-
}
|
|
143
|
-
/** Project one raw node: a kept node, or (hoisting) the projections of its children. */
|
|
144
|
-
function projectNode(node) {
|
|
145
|
-
const own = substance(node);
|
|
146
|
-
const children = node.children.flatMap((child) => projectNode(child)).filter((child) => !repeatsParent(child, own));
|
|
147
|
-
if (!hasSubstance(own)) return children;
|
|
148
|
-
return [{
|
|
149
|
-
...own,
|
|
150
|
-
...children.length > 0 ? { children } : {}
|
|
151
|
-
}];
|
|
152
|
-
}
|
|
153
|
-
/** Collect the visible texts in document order, collapsing consecutive repeats. */
|
|
154
|
-
function collectTexts(node, texts) {
|
|
155
|
-
if (node.attributes.visible === "true") {
|
|
156
|
-
const text = node.attributes.label || node.attributes.value || void 0;
|
|
157
|
-
if (text !== void 0 && texts.at(-1) !== text) texts.push(text);
|
|
158
|
-
}
|
|
159
|
-
for (const child of node.children) collectTexts(child, texts);
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Project a raw XCUITest page source into the captured screen: the collapsed
|
|
163
|
-
* tree (rooted at the application node, which is always kept) and the
|
|
164
|
-
* visible texts in document order.
|
|
165
|
-
*/
|
|
166
|
-
function projectScreen(xml) {
|
|
167
|
-
let root = parsePageSource(xml);
|
|
168
|
-
const [onlyChild] = root.children;
|
|
169
|
-
if (root.tag === "AppiumAUT" && root.children.length === 1 && onlyChild !== void 0) root = onlyChild;
|
|
170
|
-
const texts = [];
|
|
171
|
-
for (const child of root.children) collectTexts(child, texts);
|
|
172
|
-
const own = substance(root);
|
|
173
|
-
const children = root.children.flatMap((child) => projectNode(child)).filter((child) => !repeatsParent(child, own));
|
|
174
|
-
return {
|
|
175
|
-
texts,
|
|
176
|
-
tree: {
|
|
177
|
-
...own,
|
|
178
|
-
...children.length > 0 ? { children } : {}
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
//#endregion
|
|
183
|
-
//#region src/seams/appium/appium.adapter.ts
|
|
184
|
-
const DEFAULT_ACTION_TIMEOUT_MS = 3e4;
|
|
185
|
-
const POLL_INTERVAL_MS = 500;
|
|
186
|
-
/** How many candidates the ambiguity error enumerates before truncating. */
|
|
187
|
-
const MAX_REPORTED_MATCHES = 10;
|
|
188
|
-
/** WebDriverAgent builds once per simulator — the first session is the slow one. */
|
|
189
|
-
const DEFAULT_WDA_LAUNCH_TIMEOUT_MS = 24e4;
|
|
190
|
-
const delay = async (ms) => {
|
|
191
|
-
await new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
|
|
192
|
-
};
|
|
193
|
-
/** Escape a name for an NSPredicate single-quoted string literal. */
|
|
194
|
-
function escapePredicate(value) {
|
|
195
|
-
return value.replaceAll("\\", String.raw`\\`).replaceAll("'", String.raw`\'`);
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* Translate a user-facing descriptor into an iOS predicate string. Landmarks
|
|
199
|
-
* have no XCUITest analog — the shared vocabulary is wider than a screen,
|
|
200
|
-
* and the boundary is named rather than silently approximated.
|
|
201
|
-
*/
|
|
202
|
-
function compilePredicate(element, options) {
|
|
203
|
-
const name = escapePredicate(element.name ?? "");
|
|
204
|
-
const contains = (attribute) => element.exact === false ? `${attribute} CONTAINS '${name}'` : `${attribute} == '${name}'`;
|
|
205
|
-
const visible = options?.anyVisibility ? "1 == 1" : "visible == 1";
|
|
206
|
-
switch (element.kind) {
|
|
207
|
-
case "button": return `type == 'XCUIElementTypeButton' AND ${contains("label")} AND ${visible}`;
|
|
208
|
-
case "field": return `type IN {'XCUIElementTypeTextField','XCUIElementTypeSecureTextField'} AND (${contains("label")} OR ${contains("value")}) AND ${visible}`;
|
|
209
|
-
case "testId": return `name == '${name}' AND ${visible}`;
|
|
210
|
-
case "text": return `(${contains("label")} OR ${contains("value")}) AND ${visible}`;
|
|
211
|
-
default: throw new Error(`${formatElement(element)}: landmarks are a website concept — an iOS screen has no '${element.kind}' region. Scope with within(testId('…'), …) instead.`);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Every element a predicate designates under `scope`.
|
|
216
|
-
*
|
|
217
|
-
* ONE place awaits webdriverio's query: `$$` answers a chainable typed as the
|
|
218
|
-
* array it resolves to, so the await that makes the array real reads to the
|
|
219
|
-
* typechecker as an await of a non-promise.
|
|
220
|
-
*/
|
|
221
|
-
async function matching(scope, element, options) {
|
|
222
|
-
return await scope.$$(`-ios predicate string:${compilePredicate(element, options)}`);
|
|
223
|
-
}
|
|
224
|
-
/** The outside-in scope chain of a descriptor, ending on the target itself. */
|
|
225
|
-
function scopeChain(element) {
|
|
226
|
-
const chain = [];
|
|
227
|
-
for (let current = element; current; current = current.scope) chain.unshift(current);
|
|
228
|
-
return chain;
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Load webdriverio's session factory, naming the install when the optional
|
|
232
|
-
* peer dependency is absent. Lazy: the dependency is only required by a spec
|
|
233
|
-
* that actually opens the app.
|
|
234
|
-
*/
|
|
235
|
-
async function loadRemote() {
|
|
236
|
-
try {
|
|
237
|
-
const { remote: remoteFn } = await import("webdriverio");
|
|
238
|
-
return remoteFn;
|
|
239
|
-
} catch {
|
|
240
|
-
throw new Error(".open() requires webdriverio (optional peer dependency): npm install -D appium webdriverio && npx appium driver install xcuitest");
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
/** Capture one candidate's evidence for the ambiguity refusal. */
|
|
244
|
-
async function captureMatch(element) {
|
|
245
|
-
const [type, label, value, identifier] = await Promise.all([
|
|
246
|
-
element.getAttribute("type"),
|
|
247
|
-
element.getAttribute("label"),
|
|
248
|
-
element.getAttribute("value"),
|
|
249
|
-
element.getAttribute("name")
|
|
250
|
-
]);
|
|
251
|
-
const shortLabel = label ? label.replaceAll(/\s+/gu, " ").trim().slice(0, 80) : void 0;
|
|
252
|
-
return {
|
|
253
|
-
type: (type ?? "Unknown").replace(/^XCUIElementType/u, ""),
|
|
254
|
-
...shortLabel === void 0 ? {} : { label: shortLabel },
|
|
255
|
-
...value && value !== label ? { value } : {},
|
|
256
|
-
...identifier && identifier !== label ? { identifier } : {}
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Device adapter backed by appium's XCUITest driver over webdriverio.
|
|
261
|
-
*
|
|
262
|
-
* ONE driver session per adapter (= per runner = per vitest worker), created
|
|
263
|
-
* lazily on the first `open()` and reused — WebDriverAgent startup is the
|
|
264
|
-
* expensive part, not the app relaunch. Every `open()` terminates and
|
|
265
|
-
* relaunches the app, so each spec starts from a deterministic fresh state.
|
|
266
|
-
*
|
|
267
|
-
* Webdriverio is an optional peer dependency: it is only imported here, and
|
|
268
|
-
* this module is only loaded when a spec calls `.open()`.
|
|
269
|
-
*/
|
|
270
|
-
var AppiumAdapter = class {
|
|
271
|
-
/** The verb poll budget — the runner's `timeouts.action`, or the default. */
|
|
272
|
-
actionTimeoutMs;
|
|
273
|
-
driver = null;
|
|
274
|
-
/** The WebDriverAgent launch budget — the runner's `timeouts.launch`, or the default. */
|
|
275
|
-
launchTimeoutMs;
|
|
276
|
-
options;
|
|
277
|
-
/**
|
|
278
|
-
* The session factory. Absent in production — webdriverio is then loaded
|
|
279
|
-
* lazily on the first `open()`. Injected by this module's own unit test,
|
|
280
|
-
* which drives the waits over a stub driver with no simulator in reach
|
|
281
|
-
* (mocks are CODE here, CONVENTIONS I4).
|
|
282
|
-
*/
|
|
283
|
-
remote;
|
|
284
|
-
constructor(options) {
|
|
285
|
-
this.options = {
|
|
286
|
-
serverUrl: options.serverUrl,
|
|
287
|
-
udid: options.udid
|
|
288
|
-
};
|
|
289
|
-
this.remote = options.remote;
|
|
290
|
-
this.actionTimeoutMs = options.timeouts?.action ?? DEFAULT_ACTION_TIMEOUT_MS;
|
|
291
|
-
this.launchTimeoutMs = options.timeouts?.launch ?? DEFAULT_WDA_LAUNCH_TIMEOUT_MS;
|
|
292
|
-
}
|
|
293
|
-
async close() {
|
|
294
|
-
if (this.driver) {
|
|
295
|
-
await this.driver.deleteSession();
|
|
296
|
-
this.driver = null;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
async open(options) {
|
|
300
|
-
const driver = await this.session(options.bundleId);
|
|
301
|
-
try {
|
|
302
|
-
await driver.executeScript("mobile: terminateApp", [{ bundleId: options.bundleId }]);
|
|
303
|
-
} catch {}
|
|
304
|
-
await (options.deepLink === void 0 ? driver.executeScript("mobile: activateApp", [{ bundleId: options.bundleId }]) : driver.executeScript("mobile: deepLink", [{
|
|
305
|
-
bundleId: options.bundleId,
|
|
306
|
-
url: options.deepLink
|
|
307
|
-
}]));
|
|
308
|
-
if (options.scenario) try {
|
|
309
|
-
await options.scenario(this.createVisitor(driver));
|
|
310
|
-
} catch (error) {
|
|
311
|
-
const evidence = await this.captureEvidence(driver);
|
|
312
|
-
const suffix = evidence ? `\nEvidence: ${evidence}` : "";
|
|
313
|
-
if (error instanceof AmbiguousElementError) {
|
|
314
|
-
error.message += suffix;
|
|
315
|
-
throw error;
|
|
316
|
-
}
|
|
317
|
-
throw new Error(`open scenario failed: ${error instanceof Error ? error.message : String(error)}${suffix}`, { cause: error });
|
|
318
|
-
}
|
|
319
|
-
return projectScreen(await driver.getPageSource());
|
|
320
|
-
}
|
|
321
|
-
/**
|
|
322
|
-
* The visitor implementation. Acting verbs (tap/fill) poll until exactly
|
|
323
|
-
* one visible match exists (CONVENTIONS W3); `see()` acts on nothing, so
|
|
324
|
-
* ANY visible match satisfies it — XCUITest trees legitimately expose the
|
|
325
|
-
* same text more than once (container/child label duplication), and a
|
|
326
|
-
* synchronization primitive refusing on that would punish honest screens.
|
|
327
|
-
*/
|
|
328
|
-
createVisitor(driver) {
|
|
329
|
-
return {
|
|
330
|
-
fill: async (element, value) => {
|
|
331
|
-
await (await this.resolveOne(driver, element, "fill", "one")).setValue(value);
|
|
332
|
-
},
|
|
333
|
-
see: async (element) => {
|
|
334
|
-
await this.resolveOne(driver, element, "see", "any");
|
|
335
|
-
},
|
|
336
|
-
tap: async (element) => {
|
|
337
|
-
await (await this.resolveOne(driver, element, "tap", "one")).click();
|
|
338
|
-
}
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
/**
|
|
342
|
-
* Resolve a descriptor: poll until at least one visible match exists,
|
|
343
|
-
* then enforce the verb's cardinality — `'one'` refuses ambiguity
|
|
344
|
-
* (CONVENTIONS W3), `'any'` settles for the first match. Scopes stay
|
|
345
|
-
* strict in BOTH modes: "within one of several containers" designates
|
|
346
|
-
* nothing, whatever the verb. The chain resolves outside-in, so an
|
|
347
|
-
* ambiguous scope names ITSELF as the fault rather than sending the
|
|
348
|
-
* author to fix the target.
|
|
349
|
-
*/
|
|
350
|
-
async resolveOne(driver, element, verb, cardinality) {
|
|
351
|
-
const chain = scopeChain(element);
|
|
352
|
-
const deadline = Date.now() + this.actionTimeoutMs;
|
|
353
|
-
let scrollAttempted = false;
|
|
354
|
-
let windowAsked = false;
|
|
355
|
-
let looking = chain;
|
|
356
|
-
for (;;) {
|
|
357
|
-
const resolved = await this.resolveChain(driver, looking, cardinality, { tryScroll: !scrollAttempted });
|
|
358
|
-
scrollAttempted = true;
|
|
359
|
-
if (resolved) return resolved;
|
|
360
|
-
if (!windowAsked) {
|
|
361
|
-
windowAsked = true;
|
|
362
|
-
looking = await this.widenedChain(driver, chain) ?? chain;
|
|
363
|
-
}
|
|
364
|
-
if (Date.now() > deadline) throw await this.timeoutError(driver, element, verb);
|
|
365
|
-
await delay(POLL_INTERVAL_MS);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
/** One resolution pass over the chain — `null` means "nothing yet, keep polling". */
|
|
369
|
-
async resolveChain(driver, chain, cardinality, options) {
|
|
370
|
-
let scope = driver;
|
|
371
|
-
for (const [index, level] of chain.entries()) {
|
|
372
|
-
const matches = await matching(scope, level);
|
|
373
|
-
const count = await matches.length;
|
|
374
|
-
if (count === 0) {
|
|
375
|
-
if (options?.tryScroll) {
|
|
376
|
-
const first = (await matching(scope, level, { anyVisibility: true }))[0];
|
|
377
|
-
if (first !== void 0) try {
|
|
378
|
-
await driver.executeScript("mobile: scroll", [{
|
|
379
|
-
elementId: first.elementId,
|
|
380
|
-
toVisible: true
|
|
381
|
-
}]);
|
|
382
|
-
} catch {}
|
|
383
|
-
}
|
|
384
|
-
return null;
|
|
385
|
-
}
|
|
386
|
-
const isTarget = index === chain.length - 1;
|
|
387
|
-
if (count > 1 && (cardinality === "one" || !isTarget)) throw new AmbiguousElementError(describeMobileAmbiguity({
|
|
388
|
-
element: level,
|
|
389
|
-
matches: await this.captureMatches(matches)
|
|
390
|
-
}));
|
|
391
|
-
const next = matches[0];
|
|
392
|
-
if (next === void 0) return null;
|
|
393
|
-
scope = next;
|
|
394
|
-
}
|
|
395
|
-
return scope;
|
|
396
|
-
}
|
|
397
|
-
/**
|
|
398
|
-
* The chain to keep polling with, level by level: a descriptor that
|
|
399
|
-
* designated nothing as a whole label but designates exactly one thing as a
|
|
400
|
-
* SUBSTRING is widened, with the warning that names the label to write. The
|
|
401
|
-
* window is the vocabulary's, not one surface's — chapter 13 — so the
|
|
402
|
-
* screen answers it exactly as a page does.
|
|
403
|
-
*
|
|
404
|
-
* `null` when nothing would change, which is the ordinary wait.
|
|
405
|
-
*/
|
|
406
|
-
async widenedChain(driver, chain) {
|
|
407
|
-
let scope = driver;
|
|
408
|
-
const widened = [];
|
|
409
|
-
let changed = false;
|
|
410
|
-
for (const level of chain) {
|
|
411
|
-
const found = await this.widenedLevel(scope, level);
|
|
412
|
-
changed ||= found !== null;
|
|
413
|
-
const candidate = found ?? level;
|
|
414
|
-
const next = (await matching(scope, candidate))[0];
|
|
415
|
-
if (next === void 0) return null;
|
|
416
|
-
scope = next;
|
|
417
|
-
widened.push(candidate);
|
|
418
|
-
}
|
|
419
|
-
return changed ? widened : null;
|
|
420
|
-
}
|
|
421
|
-
/** One level of the chain, widened and warned about, or `null`. */
|
|
422
|
-
async widenedLevel(scope, level) {
|
|
423
|
-
if (level.exact !== void 0 || level.name === void 0) return null;
|
|
424
|
-
if (await (await matching(scope, level)).length > 0) return null;
|
|
425
|
-
const loose = {
|
|
426
|
-
...level,
|
|
427
|
-
exact: false
|
|
428
|
-
};
|
|
429
|
-
const matches = await matching(scope, loose);
|
|
430
|
-
const first = matches[0];
|
|
431
|
-
if (first === void 0 || await matches.length !== 1) return null;
|
|
432
|
-
const evidence = await captureMatch(first);
|
|
433
|
-
await warnSubstringOnly(level, this.options.udid, evidence.label);
|
|
434
|
-
return loose;
|
|
435
|
-
}
|
|
436
|
-
/** Enumerate the candidates' evidence, truncated. */
|
|
437
|
-
async captureMatches(matches) {
|
|
438
|
-
const count = Math.min(await matches.length, MAX_REPORTED_MATCHES);
|
|
439
|
-
const evidence = [];
|
|
440
|
-
for (let index = 0; index < count; index++) {
|
|
441
|
-
const match = matches[index];
|
|
442
|
-
if (match !== void 0) evidence.push(await captureMatch(match));
|
|
443
|
-
}
|
|
444
|
-
return evidence;
|
|
445
|
-
}
|
|
446
|
-
/** Screenshot the failing state into a temp file; never masks the original error. */
|
|
447
|
-
async captureEvidence(driver) {
|
|
448
|
-
try {
|
|
449
|
-
const dir = mkdtempSync(resolve(tmpdir(), "spec-mobile-"));
|
|
450
|
-
const path = resolve(dir, "failure.png");
|
|
451
|
-
writeFileSync(path, Buffer.from(await driver.takeScreenshot(), "base64"));
|
|
452
|
-
return path;
|
|
453
|
-
} catch {
|
|
454
|
-
return null;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
/** The timeout refusal, with a compact excerpt of what IS on screen to aid diagnosis. */
|
|
458
|
-
async timeoutError(driver, element, verb) {
|
|
459
|
-
let excerpt = "";
|
|
460
|
-
try {
|
|
461
|
-
const source = await driver.getPageSource();
|
|
462
|
-
const labels = [...new Set([...source.matchAll(/label="(?<label>[^"]{2,60})"/gu)].map((match) => match.groups?.label ?? ""))].slice(0, 15);
|
|
463
|
-
if (labels.length > 0) excerpt = `\nCurrently on screen: ${labels.join(" | ")}`;
|
|
464
|
-
} catch {}
|
|
465
|
-
return /* @__PURE__ */ new Error(`${verb}(${formatElement(element)}) timed out after ${this.actionTimeoutMs}ms — no visible match.${excerpt}`);
|
|
466
|
-
}
|
|
467
|
-
/** Create the shared driver session (once), with an actionable error when webdriverio is absent. */
|
|
468
|
-
async session(bundleId) {
|
|
469
|
-
if (this.driver) return this.driver;
|
|
470
|
-
const remoteFn = this.remote ?? await loadRemote();
|
|
471
|
-
const url = new URL(this.options.serverUrl);
|
|
472
|
-
this.driver = await remoteFn({
|
|
473
|
-
capabilities: {
|
|
474
|
-
"appium:automationName": "XCUITest",
|
|
475
|
-
"appium:bundleId": bundleId,
|
|
476
|
-
"appium:noReset": true,
|
|
477
|
-
"appium:udid": this.options.udid,
|
|
478
|
-
"appium:wdaLaunchTimeout": this.launchTimeoutMs,
|
|
479
|
-
platformName: "iOS"
|
|
480
|
-
},
|
|
481
|
-
hostname: url.hostname,
|
|
482
|
-
logLevel: "error",
|
|
483
|
-
port: Number(url.port)
|
|
484
|
-
});
|
|
485
|
-
return this.driver;
|
|
486
|
-
}
|
|
487
|
-
};
|
|
488
|
-
//#endregion
|
|
489
|
-
export { AppiumAdapter };
|
package/dist/artifacts.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/** Everything a vitest run generates. */
|
|
2
|
-
const VITEST_ARTIFACTS_DIR = `.artifacts/vitest`;
|
|
3
|
-
/** Where the coverage provider writes its report. */
|
|
4
|
-
const COVERAGE_DIR = `${VITEST_ARTIFACTS_DIR}/coverage`;
|
|
5
|
-
/** Where `sqlite()` caches the schema template it copies per worker. */
|
|
6
|
-
const SQLITE_TEMPLATE_DIR = `${VITEST_ARTIFACTS_DIR}/sqlite`;
|
|
7
|
-
/**
|
|
8
|
-
* Where `context.annotate()` puts what a test attaches, and where Browser Mode
|
|
9
|
-
* drops a failure screenshot. Vitest 5 defaults both to `.vitest/` at the
|
|
10
|
-
* repository ROOT — a second artefact folder beside the one the estate declares,
|
|
11
|
-
* and one no `.gitignore` rule of ours covers.
|
|
12
|
-
*/
|
|
13
|
-
const ATTACHMENTS_DIR = `${VITEST_ARTIFACTS_DIR}/attachments`;
|
|
14
|
-
/** Where the browser provider drops a screenshot of a failing test. */
|
|
15
|
-
const SCREENSHOTS_DIR = `${VITEST_ARTIFACTS_DIR}/screenshots`;
|
|
16
|
-
/**
|
|
17
|
-
* What each file-writing reporter writes, by reporter name.
|
|
18
|
-
*
|
|
19
|
-
* Vitest 5 turned the json and junit reporters into FILE writers (they printed
|
|
20
|
-
* to stdout before) and gave the html and blob ones a `.vitest/` home. Each one
|
|
21
|
-
* is a path the tool pins and a consumer never states, so the preset moves all
|
|
22
|
-
* four at once rather than leaving a repository to discover them one CI run at
|
|
23
|
-
* a time.
|
|
24
|
-
*/
|
|
25
|
-
const REPORTER_OUTPUT_FILES = {
|
|
26
|
-
blob: `${VITEST_ARTIFACTS_DIR}/blob`,
|
|
27
|
-
html: `${VITEST_ARTIFACTS_DIR}/html/index.html`,
|
|
28
|
-
json: `${VITEST_ARTIFACTS_DIR}/json/output.json`,
|
|
29
|
-
junit: `${VITEST_ARTIFACTS_DIR}/junit/output.xml`
|
|
30
|
-
};
|
|
31
|
-
//#endregion
|
|
32
|
-
export { SQLITE_TEMPLATE_DIR as a, SCREENSHOTS_DIR as i, COVERAGE_DIR as n, VITEST_ARTIFACTS_DIR as o, REPORTER_OUTPUT_FILES as r, ATTACHMENTS_DIR as t };
|
package/dist/fixtures.js
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { basename, dirname, resolve } from "node:path";
|
|
2
|
-
//#region src/model/chain/ground.ts
|
|
3
|
-
/**
|
|
4
|
-
* The GROUND a row of specs stands on — the naming law of a spec tree.
|
|
5
|
-
*
|
|
6
|
-
* In a row of specs, a MEMBER is a spec: `<aspect>.test.ts`, `<case>.spec.yaml`,
|
|
7
|
-
* `<name>.specification.ts`, and the facet/domain folders that hold them.
|
|
8
|
-
* Everything else in that row is what the members STAND ON — inert material the
|
|
9
|
-
* framework resolves BY PATH, never imported, never executed — and it carries a
|
|
10
|
-
* LEADING UNDERSCORE. A spec's own folder never does.
|
|
11
|
-
*
|
|
12
|
-
* Four names, and only four: `_fixtures/` (file state), `_expected/` (goldens),
|
|
13
|
-
* `_requests/` (inputs), `_seeds/` (database state). `contracts/` is NOT ground —
|
|
14
|
-
* a contract is TypeScript a spec imports, behaviour composed with the spec's own,
|
|
15
|
-
* so it stays a member. `docker/` is not ground either: it sits at the PROJECT
|
|
16
|
-
* root beside `package.json`, outside any row of specs.
|
|
17
|
-
*
|
|
18
|
-
* This module is the single home of those names. Every resolver, matcher, lint
|
|
19
|
-
* rule and checker pass reads them from here, so the runtime and the static
|
|
20
|
-
* channels cannot disagree about where a spec's ground lives.
|
|
21
|
-
*/
|
|
22
|
-
/** Goldens: every expected fixture, flat, extension included. */
|
|
23
|
-
const GROUND_EXPECTED = "_expected";
|
|
24
|
-
/** File state copied into a working directory by `.fixture()`. */
|
|
25
|
-
const GROUND_FIXTURES = "_fixtures";
|
|
26
|
-
/** Inputs: complete `.http` request documents. */
|
|
27
|
-
const GROUND_REQUESTS = "_requests";
|
|
28
|
-
/** Database state: `.sql` fragments loaded by `.seed()`. */
|
|
29
|
-
const GROUND_SEEDS = "_seeds";
|
|
30
|
-
/** The four ground directory names, in the order they read. */
|
|
31
|
-
const GROUND_DIRS = [
|
|
32
|
-
GROUND_EXPECTED,
|
|
33
|
-
GROUND_FIXTURES,
|
|
34
|
-
GROUND_REQUESTS,
|
|
35
|
-
GROUND_SEEDS
|
|
36
|
-
];
|
|
37
|
-
//#endregion
|
|
38
|
-
//#region src/model/chain/fixtures.ts
|
|
39
|
-
/**
|
|
40
|
-
* Fixture path resolution + copy semantics for the `cli` facet's `.fixture()`.
|
|
41
|
-
*
|
|
42
|
-
* A fixture path is one of two shapes:
|
|
43
|
-
*
|
|
44
|
-
* - `$FIXTURES/<rest>` — the shared pool at `<specs-root>/_fixtures/<rest>`,
|
|
45
|
-
* where `<specs-root>` is the nearest ancestor directory named `specs`.
|
|
46
|
-
* - `<path>` (no marker) — feature-local, at `<test-dir>/_fixtures/<path>`.
|
|
47
|
-
*
|
|
48
|
-
* The leading underscore is the ground rule of a spec tree ({@link GROUND_FIXTURES}).
|
|
49
|
-
* Any other `$`-prefixed marker is a usage error (listed against the known
|
|
50
|
-
* markers). Copy semantics mirror rsync's trailing-slash rule (see
|
|
51
|
-
* {@link copyPlan}).
|
|
52
|
-
*/
|
|
53
|
-
/**
|
|
54
|
-
* Markers understood in a `.fixture()` path. Extend here + in
|
|
55
|
-
* {@link resolveFixtureSource}. Exported so the lint layer (rule
|
|
56
|
-
* `jterrazz/b2-known-fixture-marker`) validates literals against the same list.
|
|
57
|
-
*/
|
|
58
|
-
const KNOWN_FIXTURE_MARKERS = ["$FIXTURES"];
|
|
59
|
-
/**
|
|
60
|
-
* Walk up from `startDir` to the nearest ancestor directory named `specs`.
|
|
61
|
-
* Throws with guidance if none is found up to the filesystem root — the
|
|
62
|
-
* `$FIXTURES` marker is meaningless without a specs root.
|
|
63
|
-
*/
|
|
64
|
-
function discoverSpecsRoot(startDir) {
|
|
65
|
-
let dir = startDir;
|
|
66
|
-
for (;;) {
|
|
67
|
-
if (basename(dir) === "specs") return dir;
|
|
68
|
-
const parent = dirname(dir);
|
|
69
|
-
if (parent === dir) break;
|
|
70
|
-
dir = parent;
|
|
71
|
-
}
|
|
72
|
-
throw new Error(`.fixture(): the $FIXTURES marker resolves to <specs-root>/${GROUND_FIXTURES}, but no directory named "specs" was found walking up from ${startDir}. Move the specification under a specs/ directory, or use a feature-local ${GROUND_FIXTURES}/ path (no $FIXTURES marker).`);
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Resolve the absolute source path of a fixture, applying marker resolution.
|
|
76
|
-
* The trailing slash (spread semantics) is preserved by callers via
|
|
77
|
-
* {@link copyPlan} — it is irrelevant to source resolution.
|
|
78
|
-
*/
|
|
79
|
-
function resolveFixtureSource(path, testDir) {
|
|
80
|
-
const clean = path.replace(/\/+$/u, "");
|
|
81
|
-
if (clean === "$FIXTURES" || path.startsWith("$FIXTURES/")) {
|
|
82
|
-
const rest = clean.slice(9).replace(/^\/+/u, "");
|
|
83
|
-
return resolve(discoverSpecsRoot(testDir), GROUND_FIXTURES, rest);
|
|
84
|
-
}
|
|
85
|
-
if (path.startsWith("$")) {
|
|
86
|
-
const marker = clean.split("/")[0];
|
|
87
|
-
throw new Error(`.fixture("${path}"): unknown marker "${marker}". Known markers: ${KNOWN_FIXTURE_MARKERS.join(", ")}. A path without a marker is feature-local (resolved under <test-dir>/${GROUND_FIXTURES}/).`);
|
|
88
|
-
}
|
|
89
|
-
return resolve(testDir, GROUND_FIXTURES, clean);
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Compute the source + destination for copying a fixture into the working
|
|
93
|
-
* directory. Copy semantics follow rsync's trailing-slash rule:
|
|
94
|
-
*
|
|
95
|
-
* - `dir/` (trailing slash) — the CONTENTS are spread into the cwd.
|
|
96
|
-
* - `dir` (no slash, a directory) — the directory is copied as `<cwd>/<basename>`.
|
|
97
|
-
* - `file` — copied as `<cwd>/<basename>`.
|
|
98
|
-
*
|
|
99
|
-
* Chained `.fixture()` calls layer in order: a later fixture overwrites files
|
|
100
|
-
* written by an earlier one.
|
|
101
|
-
*/
|
|
102
|
-
function copyPlan(path, testDir, workDir) {
|
|
103
|
-
const src = resolveFixtureSource(path, testDir);
|
|
104
|
-
return {
|
|
105
|
-
dest: /\/+$/u.test(path) ? workDir : resolve(workDir, basename(src)),
|
|
106
|
-
src
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
//#endregion
|
|
110
|
-
export { GROUND_FIXTURES as a, GROUND_EXPECTED as i, resolveFixtureSource as n, GROUND_REQUESTS as o, GROUND_DIRS as r, GROUND_SEEDS as s, copyPlan as t };
|