@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.
- package/README.md +45 -171
- package/dist/browser/index.js +2 -2980
- package/dist/browser/services.js +2 -0
- package/dist/browser/stubs.js +2 -0
- package/dist/checker.d.ts +6 -1
- package/dist/checker.js +4534 -2628
- package/dist/config.d.ts +40 -0
- package/dist/config.js +309 -0
- package/dist/in-place.global-setup.d.ts +11 -0
- package/dist/in-place.global-setup.js +21 -0
- package/dist/index.d.ts +1653 -3004
- package/dist/index.js +5226 -3845
- package/dist/mobile-session.global-setup.d.ts +14 -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 +6127 -5693
- package/dist/route-coverage.global-setup.d.ts +42 -0
- package/dist/route-coverage.global-setup.js +2 -0
- package/dist/services.d.ts +3 -0
- package/dist/services.global-setup.d.ts +43 -0
- package/dist/services.global-setup.js +2 -0
- package/dist/services.js +597 -0
- package/dist/shared-appium.adapter.js +428 -0
- package/dist/shared-artifacts.js +32 -0
- package/dist/shared-chapter.js +7 -0
- package/dist/shared-container.port.d.ts +31 -0
- package/dist/shared-derived-projects.js +58 -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.browser.js +3191 -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-literate-plugin.js +183 -0
- package/dist/shared-manifest.js +1321 -0
- package/dist/shared-pause.js +11 -0
- package/dist/shared-peer.js +88 -0
- package/dist/shared-playwright.adapter.js +330 -0
- package/dist/shared-process.adapter.js +74 -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 +365 -0
- package/dist/shared-service-images.js +10 -0
- package/dist/shared-services.global-setup.js +179 -0
- package/dist/shared-session-ledger.js +301 -0
- package/dist/shared-setup-entry.js +12 -0
- package/dist/shared-source-scan.js +133 -0
- package/dist/shared-spec-document.js +365 -0
- package/dist/shared-specifications.js +26 -0
- package/dist/shared-stream-body.js +130 -0
- package/dist/shared-stub-server.js +190 -0
- package/dist/shared-temp-root.global-setup.js +80 -0
- package/dist/shared-test-baseline.js +26 -0
- package/dist/shared-test-section.js +58 -0
- package/dist/shared-testcontainers.adapter.js +203 -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 +2 -0
- package/dist/vitest.d.ts +97 -163
- package/dist/vitest.js +373 -525
- 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 +564 -0
- package/docs/guides/component-specs.md +252 -0
- package/docs/guides/develop.md +94 -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 +43 -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 +498 -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 +214 -0
- package/package.json +50 -30
- 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
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { t as ServedProcess } from "./shared-serve.js";
|
|
2
|
+
import { t as isRecord } from "./shared-json-shape.js";
|
|
3
|
+
import { n as refusal } from "./shared-refusal.js";
|
|
4
|
+
import { l as VITEST_ARTIFACTS_DIR } from "./shared-artifacts.js";
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { join, resolve } from "node:path";
|
|
7
|
+
import { execFile } from "node:child_process";
|
|
8
|
+
//#region src/specifications/mobile/server/simulator.ts
|
|
9
|
+
async function run(command, args, options = {}) {
|
|
10
|
+
return await new Promise((resolve, reject) => {
|
|
11
|
+
execFile(command, args, {
|
|
12
|
+
encoding: "utf8",
|
|
13
|
+
...options
|
|
14
|
+
}, (error, stdout) => {
|
|
15
|
+
if (error === null) resolve({ stdout });
|
|
16
|
+
else reject(error);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
let firstLaunch;
|
|
21
|
+
async function simctl(args, options = {}) {
|
|
22
|
+
firstLaunch ??= run("xcodebuild", ["-checkFirstLaunchStatus"]);
|
|
23
|
+
await firstLaunch;
|
|
24
|
+
return await run("xcrun", ["simctl", ...args], options);
|
|
25
|
+
}
|
|
26
|
+
const BOOT_TIMEOUT_MS = 12e4;
|
|
27
|
+
const isSimctlDevices = (value) => isRecord(value) && Object.values(value).every((devices) => Array.isArray(devices));
|
|
28
|
+
const RUNTIME_KEY = /^.*SimRuntime\.(?<platform>[A-Za-z]+)-(?<version>[\d-]+)$/u;
|
|
29
|
+
const WORDS = /\s+/u;
|
|
30
|
+
function runtimeLabel(runtime) {
|
|
31
|
+
return runtime.replace(RUNTIME_KEY, (_key, platform, version) => `${platform} ${version.replaceAll("-", ".")}`);
|
|
32
|
+
}
|
|
33
|
+
function listSimulators(listJson) {
|
|
34
|
+
const parsed = JSON.parse(listJson);
|
|
35
|
+
const devices = isRecord(parsed) && isSimctlDevices(parsed.devices) ? parsed.devices : {};
|
|
36
|
+
return Object.entries(devices).flatMap(([runtime, listed]) => listed.filter((device) => device.isAvailable !== false).map((device) => ({
|
|
37
|
+
name: device.name,
|
|
38
|
+
os: runtimeLabel(runtime),
|
|
39
|
+
state: device.state,
|
|
40
|
+
udid: device.udid
|
|
41
|
+
})));
|
|
42
|
+
}
|
|
43
|
+
function runtimeOf(os) {
|
|
44
|
+
const words = os.trim().split(WORDS);
|
|
45
|
+
const platform = String(words[0]).toLowerCase();
|
|
46
|
+
return {
|
|
47
|
+
platform: words.length > 1 ? platform : void 0,
|
|
48
|
+
version: String(words.at(-1))
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** `'iOS 26.5'` and `'26.5'` name one runtime; two naming platforms must name the same one. */
|
|
52
|
+
function sameRuntime(one, other) {
|
|
53
|
+
const [a, b] = [runtimeOf(one), runtimeOf(other)];
|
|
54
|
+
return a.version === b.version && (a.platform === void 0 || b.platform === void 0 || a.platform === b.platform);
|
|
55
|
+
}
|
|
56
|
+
function formatListing(devices) {
|
|
57
|
+
return devices.map((device) => ` ${device.name}: ${device.os} (${device.state}) ${device.udid}`).join("\n");
|
|
58
|
+
}
|
|
59
|
+
function selectSimulator(listJson, criteria) {
|
|
60
|
+
const available = listSimulators(listJson);
|
|
61
|
+
const matches = available.filter((device) => device.name === criteria.name && (criteria.os === void 0 || sameRuntime(device.os, criteria.os)));
|
|
62
|
+
const wanted = criteria.os === void 0 ? `"${criteria.name}"` : `"${criteria.name}" on ${criteria.os}`;
|
|
63
|
+
const [only] = matches;
|
|
64
|
+
if (only === void 0) throw new Error(refusal({
|
|
65
|
+
fix: "name one of the available devices, or create the simulator in Xcode",
|
|
66
|
+
found: `specification.mobile(): no simulator matches ${wanted}.\navailable devices:\n${formatListing(available)}`,
|
|
67
|
+
read: "docs/guides/mobile-specs.md#options",
|
|
68
|
+
why: "Resolution refuses rather than guesses: a device names exactly one simulator."
|
|
69
|
+
}));
|
|
70
|
+
if (matches.length > 1) throw new Error(refusal({
|
|
71
|
+
fix: "add `os:` or `udid:` to pick one",
|
|
72
|
+
found: `specification.mobile(): ${matches.length} simulators match ${wanted}.\nmatched:\n${formatListing(matches)}`,
|
|
73
|
+
read: "docs/guides/mobile-specs.md#options",
|
|
74
|
+
why: "Resolution refuses rather than guesses: a device names exactly one simulator."
|
|
75
|
+
}));
|
|
76
|
+
return only;
|
|
77
|
+
}
|
|
78
|
+
async function resolveSimulatorUdid(criteria) {
|
|
79
|
+
const { stdout } = await simctl([
|
|
80
|
+
"list",
|
|
81
|
+
"devices",
|
|
82
|
+
"--json"
|
|
83
|
+
], { maxBuffer: 16777216 });
|
|
84
|
+
return selectSimulator(stdout, criteria).udid;
|
|
85
|
+
}
|
|
86
|
+
/** Idempotent: an already-booted device is tolerated. */
|
|
87
|
+
async function ensureBooted(udid) {
|
|
88
|
+
try {
|
|
89
|
+
await simctl(["boot", udid]);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
92
|
+
if (!message.includes("current state: Booted")) throw new Error(refusal({
|
|
93
|
+
fix: "finish Xcode's first launch if it asks (`sudo xcodebuild -runFirstLaunch`), boot it by hand (`xcrun simctl boot <udid>`) and correct what simctl names",
|
|
94
|
+
found: `specification.mobile(): could not boot simulator ${udid}.\n${message}`,
|
|
95
|
+
read: "docs/guides/mobile-specs.md#requirements-what-the-machine-must-have",
|
|
96
|
+
why: "A mobile spec runs on a simulator the machine can boot."
|
|
97
|
+
}), { cause: error });
|
|
98
|
+
}
|
|
99
|
+
await simctl([
|
|
100
|
+
"bootstatus",
|
|
101
|
+
udid,
|
|
102
|
+
"-b"
|
|
103
|
+
], { timeout: BOOT_TIMEOUT_MS });
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/specifications/mobile/server/appium-server.ts
|
|
107
|
+
const APPIUM_READY_TIMEOUT_MS = 6e4;
|
|
108
|
+
const APPIUM_STATUS = "/status";
|
|
109
|
+
/** outside: the XCUITest driver runs WebDriverAgent's xcodebuild in a group of its own and keeps it past the session and past its own exit, beside the runner app it launches inside the simulator. */
|
|
110
|
+
function webDriverAgentOf(udid) {
|
|
111
|
+
return [{
|
|
112
|
+
holds: ["WebDriverAgent", `id=${udid}`],
|
|
113
|
+
program: "xcodebuild"
|
|
114
|
+
}, {
|
|
115
|
+
holds: [`/Devices/${udid}/`],
|
|
116
|
+
program: "WebDriverAgentRunner-Runner"
|
|
117
|
+
}];
|
|
118
|
+
}
|
|
119
|
+
/** Appium is an optional peer resolved from `node_modules/.bin`; the served process owns port, readiness, the group kill, and the WebDriverAgent the server spawned for the simulator. */
|
|
120
|
+
async function startAppiumServer(root, udid) {
|
|
121
|
+
const bin = resolve(root, "node_modules", ".bin", "appium");
|
|
122
|
+
if (!existsSync(bin)) throw new Error(refusal("seam-is-an-optional-peer", {
|
|
123
|
+
fix: "npm install -D appium webdriverio && npx appium driver install xcuitest",
|
|
124
|
+
found: `specification.mobile() requires \`appium\`, and ${bin} does not exist`
|
|
125
|
+
}));
|
|
126
|
+
const serve = new ServedProcess({
|
|
127
|
+
command: `"${bin}" --port "$PORT"`,
|
|
128
|
+
ready: APPIUM_STATUS,
|
|
129
|
+
strays: webDriverAgentOf(udid),
|
|
130
|
+
timeout: APPIUM_READY_TIMEOUT_MS
|
|
131
|
+
}, root, "mobile");
|
|
132
|
+
return {
|
|
133
|
+
stop: async () => {
|
|
134
|
+
await serve.stop();
|
|
135
|
+
},
|
|
136
|
+
url: await serve.start()
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/** The booted simulator is the machine's and outlives the server; its WebDriverAgent is the server's, and does not. */
|
|
140
|
+
async function serveAppium(device, root) {
|
|
141
|
+
const udid = device.udid ?? await resolveSimulatorUdid(device);
|
|
142
|
+
await ensureBooted(udid);
|
|
143
|
+
return {
|
|
144
|
+
server: await startAppiumServer(root, udid),
|
|
145
|
+
udid
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/** The run's Appium server, provided by a `mobile({ device })` project's setup. */
|
|
149
|
+
async function mobileServer() {
|
|
150
|
+
try {
|
|
151
|
+
const { inject } = await import("vitest");
|
|
152
|
+
return inject("mobileServer");
|
|
153
|
+
} catch {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function notOurs(url) {
|
|
158
|
+
return {
|
|
159
|
+
stop: async () => {},
|
|
160
|
+
url
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function named({ name, os, udid }) {
|
|
164
|
+
return [
|
|
165
|
+
`'${name}'`,
|
|
166
|
+
os === void 0 ? "" : ` (${os})`,
|
|
167
|
+
udid === void 0 ? "" : ` [${udid}]`
|
|
168
|
+
].join("");
|
|
169
|
+
}
|
|
170
|
+
/** Never another device: no other simulator was booted for the run. */
|
|
171
|
+
function runDevice(device, run) {
|
|
172
|
+
if (device === void 0) return {
|
|
173
|
+
...run.device,
|
|
174
|
+
udid: run.udid
|
|
175
|
+
};
|
|
176
|
+
if (!(device.name === run.device.name && (device.os === void 0 || run.device.os !== void 0 && sameRuntime(device.os, run.device.os)) && (device.udid === void 0 || device.udid === run.udid))) throw new Error(refusal({
|
|
177
|
+
fix: "name that device, or declare no device on mobile() so each file serves its own",
|
|
178
|
+
found: `specification.mobile() names the device ${named(device)}, and the run's Appium server drives ${named({
|
|
179
|
+
...run.device,
|
|
180
|
+
udid: run.udid
|
|
181
|
+
})}, the device its mobile({ device }) project declares`,
|
|
182
|
+
read: "docs/guides/mobile-specs.md#one-session-per-run",
|
|
183
|
+
why: "The run's setup booted the project's device alone."
|
|
184
|
+
}));
|
|
185
|
+
return {
|
|
186
|
+
...run.device,
|
|
187
|
+
udid: run.udid
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/** A declared server owns its devices; the run's server drives the one its setup booted; otherwise resolved, booted and spawned here for this file. */
|
|
191
|
+
async function reachAppium({ appium: declared, device }, root, run) {
|
|
192
|
+
if (declared !== void 0) {
|
|
193
|
+
if (device === void 0) throw new Error(refusal({
|
|
194
|
+
fix: "name the simulator with `device: { name }`",
|
|
195
|
+
found: `specification.mobile() names no device to ask the Appium server at ${declared.url} for`,
|
|
196
|
+
read: "docs/guides/mobile-specs.md#a-declared-appium-server",
|
|
197
|
+
why: "A declared server is handed its device by capabilities."
|
|
198
|
+
}));
|
|
199
|
+
return {
|
|
200
|
+
appium: notOurs(declared.url),
|
|
201
|
+
device,
|
|
202
|
+
outlivesFile: true
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (run !== void 0) return {
|
|
206
|
+
appium: notOurs(run.url),
|
|
207
|
+
device: runDevice(device, run),
|
|
208
|
+
outlivesFile: true
|
|
209
|
+
};
|
|
210
|
+
if (device === void 0) throw new Error(refusal({
|
|
211
|
+
fix: "name the simulator with `device: { name }`, or declare it once on the project, mobile({ device, root }), so every file drives the run's",
|
|
212
|
+
found: "specification.mobile() names no device, and no mobile({ device }) project serves one",
|
|
213
|
+
read: "docs/guides/mobile-specs.md#one-session-per-run",
|
|
214
|
+
why: "A mobile spec drives one simulator, named by the runner or by its project."
|
|
215
|
+
}));
|
|
216
|
+
const { server, udid } = await serveAppium(device, root);
|
|
217
|
+
return {
|
|
218
|
+
appium: server,
|
|
219
|
+
device: {
|
|
220
|
+
...device,
|
|
221
|
+
udid
|
|
222
|
+
},
|
|
223
|
+
outlivesFile: false
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
//#endregion
|
|
227
|
+
//#region src/specifications/mobile/session/session-ledger.ts
|
|
228
|
+
const SERVER_CALL_TIMEOUT_MS = 1e4;
|
|
229
|
+
const MOBILE_SESSIONS_DIR = `${VITEST_ARTIFACTS_DIR}/mobile-sessions`;
|
|
230
|
+
function keyOf(serverUrl, requested) {
|
|
231
|
+
const sorted = Object.entries(requested).toSorted(([a], [b]) => a.localeCompare(b));
|
|
232
|
+
return JSON.stringify([serverUrl, sorted]);
|
|
233
|
+
}
|
|
234
|
+
function isEntry(value) {
|
|
235
|
+
return isRecord(value) && [
|
|
236
|
+
value.key,
|
|
237
|
+
value.serverUrl,
|
|
238
|
+
value.sessionId
|
|
239
|
+
].every((field) => typeof field === "string") && isRecord(value.capabilities);
|
|
240
|
+
}
|
|
241
|
+
function entryFiles(directory) {
|
|
242
|
+
try {
|
|
243
|
+
return readdirSync(directory).filter((name) => name.endsWith(".json"));
|
|
244
|
+
} catch {
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function entries(directory) {
|
|
249
|
+
const parsed = (name) => JSON.parse(readFileSync(join(directory, name), "utf8"));
|
|
250
|
+
return entryFiles(directory).map((name) => parsed(name)).filter((entry) => isEntry(entry));
|
|
251
|
+
}
|
|
252
|
+
function sessionLedger(directory) {
|
|
253
|
+
return {
|
|
254
|
+
forget: (sessionId) => {
|
|
255
|
+
rmSync(join(directory, `${sessionId}.json`), { force: true });
|
|
256
|
+
},
|
|
257
|
+
lookup: (serverUrl, requested) => {
|
|
258
|
+
const key = keyOf(serverUrl, requested);
|
|
259
|
+
return entries(directory).filter((entry) => entry.key === key).map(({ capabilities, serverUrl: url, sessionId }) => ({
|
|
260
|
+
capabilities,
|
|
261
|
+
serverUrl: url,
|
|
262
|
+
sessionId
|
|
263
|
+
}));
|
|
264
|
+
},
|
|
265
|
+
record: (requested, session) => {
|
|
266
|
+
const entry = {
|
|
267
|
+
...session,
|
|
268
|
+
key: keyOf(session.serverUrl, requested)
|
|
269
|
+
};
|
|
270
|
+
mkdirSync(directory, { recursive: true });
|
|
271
|
+
writeFileSync(join(directory, `${session.sessionId}.json`), `${JSON.stringify(entry)}\n`);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
/** A server that no longer answers has taken its sessions with it. */
|
|
276
|
+
async function endSessions(directory) {
|
|
277
|
+
await Promise.all(entries(directory).map(async (entry) => {
|
|
278
|
+
try {
|
|
279
|
+
await (await fetch(`${entry.serverUrl}/session/${entry.sessionId}`, {
|
|
280
|
+
method: "DELETE",
|
|
281
|
+
signal: AbortSignal.timeout(SERVER_CALL_TIMEOUT_MS)
|
|
282
|
+
})).body?.cancel();
|
|
283
|
+
} catch {}
|
|
284
|
+
}));
|
|
285
|
+
rmSync(directory, {
|
|
286
|
+
force: true,
|
|
287
|
+
recursive: true
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
async function mobileSessions() {
|
|
291
|
+
try {
|
|
292
|
+
const { inject } = await import("vitest");
|
|
293
|
+
return inject("mobileSessions");
|
|
294
|
+
} catch {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
//#endregion
|
|
299
|
+
export { sessionLedger as a, serveAppium as c, mobileSessions as i, SERVER_CALL_TIMEOUT_MS as n, mobileServer as o, endSessions as r, reachAppium as s, MOBILE_SESSIONS_DIR as t };
|
|
300
|
+
|
|
301
|
+
//# sourceMappingURL=shared-session-ledger.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
//#region src/chain/paths/setup-entry.ts
|
|
4
|
+
/** A global setup's source in a checkout, else the entry the build names after it: `<name>.global-setup.js`, beside its owner's chunk. */
|
|
5
|
+
function setupEntry(directory, name) {
|
|
6
|
+
const source = join(directory, `${name}.global-setup.ts`);
|
|
7
|
+
return existsSync(source) ? source : join(directory, `${name}.global-setup.js`);
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { setupEntry as t };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=shared-setup-entry.js.map
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
function skipString(text, start) {
|
|
2
|
+
const quote = text[start];
|
|
3
|
+
let index = start + 1;
|
|
4
|
+
while (index < text.length) {
|
|
5
|
+
const character = text[index];
|
|
6
|
+
if (character === "\\") {
|
|
7
|
+
index += 2;
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
if (character === quote) return index + 1;
|
|
11
|
+
if (quote === "`" && character === "$" && text[index + 1] === "{") {
|
|
12
|
+
index = closingOf(text, index + 1) + 1;
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
index += 1;
|
|
16
|
+
}
|
|
17
|
+
return index;
|
|
18
|
+
}
|
|
19
|
+
const BEFORE_REGEX = /(?:^|[(,=:[!&|?{};+\-*%<>~^]|\breturn|\btypeof|\bcase)\s*$/u;
|
|
20
|
+
function opensRegex(text, start) {
|
|
21
|
+
return text[start] === "/" && text[start + 1] !== "/" && text[start + 1] !== "*" && BEFORE_REGEX.test(text.slice(Math.max(0, start - 16), start));
|
|
22
|
+
}
|
|
23
|
+
function pastFlags(text, close) {
|
|
24
|
+
let end = close + 1;
|
|
25
|
+
while (/[a-z]/u.test(text[end] ?? "")) end += 1;
|
|
26
|
+
return end;
|
|
27
|
+
}
|
|
28
|
+
function regexEnd(text, start) {
|
|
29
|
+
if (!opensRegex(text, start)) return start;
|
|
30
|
+
let inClass = false;
|
|
31
|
+
for (let index = start + 1; index < text.length; index += 1) {
|
|
32
|
+
const character = text[index];
|
|
33
|
+
if (character === "\\") index += 1;
|
|
34
|
+
else if (character === "\n") return start;
|
|
35
|
+
else if (character === "[" || character === "]") inClass = character === "[";
|
|
36
|
+
else if (character === "/" && !inClass) return pastFlags(text, index);
|
|
37
|
+
}
|
|
38
|
+
return start;
|
|
39
|
+
}
|
|
40
|
+
function literalEnd(text, start) {
|
|
41
|
+
const character = text[start];
|
|
42
|
+
if (character === "'" || character === "\"" || character === "`") return skipString(text, start);
|
|
43
|
+
return regexEnd(text, start);
|
|
44
|
+
}
|
|
45
|
+
function closingOf(text, open) {
|
|
46
|
+
const pairs = {
|
|
47
|
+
"(": ")",
|
|
48
|
+
"[": "]",
|
|
49
|
+
"{": "}"
|
|
50
|
+
};
|
|
51
|
+
const stack = [];
|
|
52
|
+
let index = open;
|
|
53
|
+
while (index < text.length) {
|
|
54
|
+
const character = text[index] ?? "";
|
|
55
|
+
const past = index === open ? index : literalEnd(text, index);
|
|
56
|
+
if (past !== index) {
|
|
57
|
+
index = past;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (character in pairs) stack.push(pairs[character] ?? "");
|
|
61
|
+
else if (character === stack.at(-1)) {
|
|
62
|
+
stack.pop();
|
|
63
|
+
if (stack.length === 0) return index;
|
|
64
|
+
}
|
|
65
|
+
index += 1;
|
|
66
|
+
}
|
|
67
|
+
return text.length;
|
|
68
|
+
}
|
|
69
|
+
/** The offsets of the brackets still open at `index`, outermost first; a literal's brackets are none. */
|
|
70
|
+
function openersAt(text, index) {
|
|
71
|
+
const closers = {
|
|
72
|
+
")": "(",
|
|
73
|
+
"]": "[",
|
|
74
|
+
"}": "{"
|
|
75
|
+
};
|
|
76
|
+
const stack = [];
|
|
77
|
+
let at = 0;
|
|
78
|
+
while (at < index) {
|
|
79
|
+
const past = literalEnd(text, at);
|
|
80
|
+
if (past !== at) {
|
|
81
|
+
at = past;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const character = text[at] ?? "";
|
|
85
|
+
if (character === "(" || character === "[" || character === "{") stack.push(at);
|
|
86
|
+
else if (character in closers && text[stack.at(-1) ?? -1] === closers[character]) stack.pop();
|
|
87
|
+
at += 1;
|
|
88
|
+
}
|
|
89
|
+
return stack;
|
|
90
|
+
}
|
|
91
|
+
/** Each comma-separated argument between `start` and `end`, trimmed, an empty one dropped. */
|
|
92
|
+
function argumentSpans(text, start = 0, end = text.length) {
|
|
93
|
+
const spans = [];
|
|
94
|
+
let from = start;
|
|
95
|
+
const close = (to) => {
|
|
96
|
+
const raw = text.slice(from, to);
|
|
97
|
+
if (raw.trim() !== "") {
|
|
98
|
+
const lead = raw.length - raw.trimStart().length;
|
|
99
|
+
spans.push({
|
|
100
|
+
end: from + raw.trimEnd().length,
|
|
101
|
+
start: from + lead
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
from = to + 1;
|
|
105
|
+
};
|
|
106
|
+
let index = start;
|
|
107
|
+
while (index < end) {
|
|
108
|
+
const past = literalEnd(text, index);
|
|
109
|
+
const character = text[index];
|
|
110
|
+
if (past !== index) index = past;
|
|
111
|
+
else if (character === "(" || character === "[" || character === "{") index = closingOf(text, index) + 1;
|
|
112
|
+
else {
|
|
113
|
+
if (character === ",") close(index);
|
|
114
|
+
index += 1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
close(end);
|
|
118
|
+
return spans;
|
|
119
|
+
}
|
|
120
|
+
function argumentsOf(text) {
|
|
121
|
+
return argumentSpans(text).map(({ end, start }) => text.slice(start, end));
|
|
122
|
+
}
|
|
123
|
+
function literalOf(argument) {
|
|
124
|
+
const text = argument?.trim() ?? "";
|
|
125
|
+
const quote = text[0];
|
|
126
|
+
if (quote !== "'" && quote !== "\"" && quote !== "`" || skipString(text, 0) !== text.length) return;
|
|
127
|
+
const body = text.slice(1, -1);
|
|
128
|
+
return quote === "`" ? body.replaceAll(/\$\{[^}]*\}/gu, "\0") : body;
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
export { literalOf as a, literalEnd as i, argumentsOf as n, openersAt as o, closingOf as r, skipString as s, argumentSpans as t };
|
|
132
|
+
|
|
133
|
+
//# sourceMappingURL=shared-source-scan.js.map
|