agent-scenario-loop 0.1.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/LICENSE +21 -0
- package/README.md +119 -0
- package/app/profile-session.ts +812 -0
- package/core/config-template.json +41 -0
- package/dist/core/agent-summary.d.ts +15 -0
- package/dist/core/agent-summary.js +177 -0
- package/dist/core/artifact-contract.d.ts +151 -0
- package/dist/core/artifact-contract.js +897 -0
- package/dist/core/artifact-layout.d.ts +56 -0
- package/dist/core/artifact-layout.js +61 -0
- package/dist/core/artifact-writer.d.ts +44 -0
- package/dist/core/artifact-writer.js +55 -0
- package/dist/core/comparison.d.ts +133 -0
- package/dist/core/comparison.js +294 -0
- package/dist/core/evidence-interpreter.d.ts +28 -0
- package/dist/core/evidence-interpreter.js +69 -0
- package/dist/core/execution-plan.d.ts +44 -0
- package/dist/core/execution-plan.js +95 -0
- package/dist/core/planner.d.ts +132 -0
- package/dist/core/planner.js +812 -0
- package/dist/core/ports.d.ts +198 -0
- package/dist/core/ports.js +146 -0
- package/dist/core/run-index.d.ts +62 -0
- package/dist/core/run-index.js +143 -0
- package/dist/core/schema-validator.d.ts +86 -0
- package/dist/core/schema-validator.js +407 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +27 -0
- package/dist/runner/agent-device-driver.d.ts +126 -0
- package/dist/runner/agent-device-driver.js +168 -0
- package/dist/runner/agent-device.d.ts +295 -0
- package/dist/runner/agent-device.js +1271 -0
- package/dist/runner/android-adb-driver.d.ts +175 -0
- package/dist/runner/android-adb-driver.js +399 -0
- package/dist/runner/android-adb.d.ts +254 -0
- package/dist/runner/android-adb.js +1618 -0
- package/dist/runner/argent-driver.d.ts +183 -0
- package/dist/runner/argent-driver.js +297 -0
- package/dist/runner/argent.d.ts +349 -0
- package/dist/runner/argent.js +1211 -0
- package/dist/runner/check-plan.d.ts +45 -0
- package/dist/runner/check-plan.js +210 -0
- package/dist/runner/cli.d.ts +20 -0
- package/dist/runner/cli.js +23 -0
- package/dist/runner/compare-latest.d.ts +99 -0
- package/dist/runner/compare-latest.js +233 -0
- package/dist/runner/compare.d.ts +58 -0
- package/dist/runner/compare.js +157 -0
- package/dist/runner/demo-loop.d.ts +45 -0
- package/dist/runner/demo-loop.js +170 -0
- package/dist/runner/example-android-live.d.ts +137 -0
- package/dist/runner/example-android-live.js +454 -0
- package/dist/runner/example-ios-live.d.ts +137 -0
- package/dist/runner/example-ios-live.js +471 -0
- package/dist/runner/host-doctor.d.ts +131 -0
- package/dist/runner/host-doctor.js +628 -0
- package/dist/runner/init-project.d.ts +88 -0
- package/dist/runner/init-project.js +263 -0
- package/dist/runner/ios-simctl-driver.d.ts +69 -0
- package/dist/runner/ios-simctl-driver.js +97 -0
- package/dist/runner/ios-simctl.d.ts +254 -0
- package/dist/runner/ios-simctl.js +1415 -0
- package/dist/runner/live-android.d.ts +137 -0
- package/dist/runner/live-android.js +539 -0
- package/dist/runner/live-comparison.d.ts +67 -0
- package/dist/runner/live-comparison.js +147 -0
- package/dist/runner/live-ios.d.ts +137 -0
- package/dist/runner/live-ios.js +460 -0
- package/dist/runner/live-proof-summary.d.ts +263 -0
- package/dist/runner/live-proof-summary.js +465 -0
- package/dist/runner/live-proof.d.ts +467 -0
- package/dist/runner/live-proof.js +920 -0
- package/dist/runner/local-env.d.ts +64 -0
- package/dist/runner/local-env.js +155 -0
- package/dist/runner/profile-android.d.ts +82 -0
- package/dist/runner/profile-android.js +671 -0
- package/dist/runner/profile-ios.d.ts +108 -0
- package/dist/runner/profile-ios.js +532 -0
- package/dist/runner/profile-mobile.d.ts +254 -0
- package/dist/runner/profile-mobile.js +1307 -0
- package/dist/runner/validate-project.d.ts +273 -0
- package/dist/runner/validate-project.js +1501 -0
- package/docs/adapters.md +145 -0
- package/docs/api.md +94 -0
- package/docs/authoring.md +196 -0
- package/docs/concepts.md +136 -0
- package/docs/consumer-rehearsal.md +115 -0
- package/docs/contracts.md +267 -0
- package/docs/live-proofs.md +270 -0
- package/docs/principles.md +46 -0
- package/examples/event-logs/app-startup-baseline.log +4 -0
- package/examples/event-logs/app-startup-current.log +4 -0
- package/examples/minimal-app/README.md +70 -0
- package/examples/mobile-app/README.md +302 -0
- package/examples/mobile-app/app.json +22 -0
- package/examples/mobile-app/asl/package-scripts.json +32 -0
- package/examples/mobile-app/asl.config.json +37 -0
- package/examples/mobile-app/event-logs/android-app-startup.log +4 -0
- package/examples/mobile-app/event-logs/android-open-close-cycle.log +12 -0
- package/examples/mobile-app/event-logs/android-scroll-settle.log +12 -0
- package/examples/mobile-app/event-logs/app-startup.log +4 -0
- package/examples/mobile-app/event-logs/open-close-cycle.log +12 -0
- package/examples/mobile-app/event-logs/scroll-settle.log +12 -0
- package/examples/mobile-app/index.ts +20 -0
- package/examples/mobile-app/metro.config.js +20 -0
- package/examples/mobile-app/package.json +62 -0
- package/examples/mobile-app/patches/expo-modules-jsi@56.0.10.patch +19 -0
- package/examples/mobile-app/plugins/with-ios-build-compat.js +271 -0
- package/examples/mobile-app/pnpm-lock.yaml +4440 -0
- package/examples/mobile-app/runner-manifests/evidence-provider.json +79 -0
- package/examples/mobile-app/runner-manifests/primary-runner.json +19 -0
- package/examples/mobile-app/scenarios/android/app-startup-video.json +73 -0
- package/examples/mobile-app/scenarios/android/app-startup.json +44 -0
- package/examples/mobile-app/scenarios/android/open-close-cycle.json +54 -0
- package/examples/mobile-app/scenarios/android/scroll-settle.json +49 -0
- package/examples/mobile-app/scenarios/ios/app-startup.json +44 -0
- package/examples/mobile-app/scenarios/ios/open-close-cycle.json +54 -0
- package/examples/mobile-app/scenarios/ios/scroll-settle.json +49 -0
- package/examples/mobile-app/scenarios/mobile/app-startup.json +91 -0
- package/examples/mobile-app/scenarios/mobile/open-close-cycle.json +160 -0
- package/examples/mobile-app/scenarios/mobile/scroll-settle.json +148 -0
- package/examples/mobile-app/scripts/asl-capture-accessibility-provider.mjs +112 -0
- package/examples/mobile-app/scripts/asl-capture-profiler-provider.mjs +127 -0
- package/examples/mobile-app/src/devtools/profile-session.ts +7 -0
- package/examples/mobile-app/src/example-screen.tsx +322 -0
- package/examples/mobile-app/tsconfig.json +16 -0
- package/examples/mobile-app/tsconfig.typecheck.json +13 -0
- package/examples/runners/README.md +44 -0
- package/examples/runners/adb-android.json +25 -0
- package/examples/runners/agent-device-android.json +27 -0
- package/examples/runners/agent-device-ios.json +27 -0
- package/examples/runners/argent-android.json +32 -0
- package/examples/runners/argent-ios.json +32 -0
- package/examples/runners/argent-react-profiler-provider.json +15 -0
- package/examples/runners/axe-accessibility-provider.json +24 -0
- package/examples/runners/manual-log-ingest.json +9 -0
- package/examples/runners/rozenite-profiler-provider.json +9 -0
- package/examples/runners/script-accessibility-provider.json +24 -0
- package/examples/runners/script-memory-provider.json +24 -0
- package/examples/runners/script-network-provider.json +24 -0
- package/examples/runners/script-profiler-provider.json +30 -0
- package/examples/runners/xcodebuildmcp-ios.json +29 -0
- package/examples/scenarios/ios/app-startup.json +28 -0
- package/examples/scenarios/ios/open-close-cycle.json +35 -0
- package/examples/scenarios/mobile/app-startup.json +72 -0
- package/examples/scenarios/mobile/media-open-close.json +141 -0
- package/examples/scenarios/mobile/open-close-cycle.json +135 -0
- package/examples/scenarios/mobile/scroll-settle.json +106 -0
- package/package.json +240 -0
- package/schemas/budget-verdict.schema.json +115 -0
- package/schemas/causal-run.schema.json +279 -0
- package/schemas/comparison.schema.json +196 -0
- package/schemas/health.schema.json +108 -0
- package/schemas/live-proof-set.schema.json +195 -0
- package/schemas/live-proof.schema.json +413 -0
- package/schemas/manifest.schema.json +204 -0
- package/schemas/metrics.schema.json +137 -0
- package/schemas/project-validation.schema.json +343 -0
- package/schemas/runner-capabilities.schema.json +217 -0
- package/schemas/scenario.schema.json +400 -0
- package/schemas/verdict.schema.json +88 -0
- package/templates/evidence-provider.json +83 -0
- package/templates/gitignore-snippet +9 -0
- package/templates/integration-readme.md +125 -0
- package/templates/mobile-scenario.json +133 -0
- package/templates/package-scripts.json +32 -0
- package/templates/primary-runner.json +19 -0
- package/templates/project.config.json +37 -0
- package/templates/scripts/asl-capture-accessibility-provider.mjs +112 -0
- package/templates/scripts/asl-capture-profiler-provider.mjs +127 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
type ArgentCommandResult = {
|
|
2
|
+
action: string;
|
|
3
|
+
args: string[];
|
|
4
|
+
capturePath?: string;
|
|
5
|
+
command: string;
|
|
6
|
+
exitCode: number;
|
|
7
|
+
rawFileName: string;
|
|
8
|
+
stderr: string;
|
|
9
|
+
stdout: string;
|
|
10
|
+
};
|
|
11
|
+
type ArgentCommandExecutor = (command: string, args: string[]) => Promise<{
|
|
12
|
+
args: string[];
|
|
13
|
+
command: string;
|
|
14
|
+
exitCode: number;
|
|
15
|
+
stderr: string;
|
|
16
|
+
stdout: string;
|
|
17
|
+
}>;
|
|
18
|
+
type ArgentDriverOptions = {
|
|
19
|
+
appFlag?: string;
|
|
20
|
+
appId?: string;
|
|
21
|
+
argentCommand: string;
|
|
22
|
+
baseArgs?: string[];
|
|
23
|
+
deviceFlag?: string;
|
|
24
|
+
deviceId: string;
|
|
25
|
+
executor: ArgentCommandExecutor;
|
|
26
|
+
extraArgs?: string[];
|
|
27
|
+
screenSize?: ArgentScreenSize;
|
|
28
|
+
};
|
|
29
|
+
type ArgentDriver = {
|
|
30
|
+
assertVisible: (options: ArgentAssertVisibleOptions) => Promise<ArgentCommandResult>;
|
|
31
|
+
inspectTree: (options?: ArgentInspectTreeOptions) => Promise<ArgentCommandResult>;
|
|
32
|
+
launchApp: (options?: ArgentLaunchAppOptions) => Promise<ArgentCommandResult>;
|
|
33
|
+
openUrl: (options: ArgentOpenUrlOptions) => Promise<ArgentCommandResult>;
|
|
34
|
+
screenshot: (options?: ArgentScreenshotOptions) => Promise<ArgentCommandResult>;
|
|
35
|
+
scroll: (options: ArgentScrollOptions) => Promise<ArgentCommandResult>;
|
|
36
|
+
tap: (options: ArgentTapOptions) => Promise<ArgentCommandResult>;
|
|
37
|
+
};
|
|
38
|
+
type ArgentScreenSize = {
|
|
39
|
+
height: number;
|
|
40
|
+
width: number;
|
|
41
|
+
};
|
|
42
|
+
type ArgentPointInput = {
|
|
43
|
+
screenSize?: ArgentScreenSize;
|
|
44
|
+
x: number;
|
|
45
|
+
y: number;
|
|
46
|
+
};
|
|
47
|
+
type ArgentNormalizedPoint = {
|
|
48
|
+
x: number;
|
|
49
|
+
y: number;
|
|
50
|
+
};
|
|
51
|
+
type ArgentSelector = {
|
|
52
|
+
kind: string;
|
|
53
|
+
match?: string;
|
|
54
|
+
value: string;
|
|
55
|
+
};
|
|
56
|
+
type ArgentAssertVisibleOptions = {
|
|
57
|
+
appId?: string;
|
|
58
|
+
rawFileName?: string;
|
|
59
|
+
selector: ArgentSelector;
|
|
60
|
+
};
|
|
61
|
+
type ArgentInspectTreeOptions = {
|
|
62
|
+
appId?: string;
|
|
63
|
+
rawFileName?: string;
|
|
64
|
+
};
|
|
65
|
+
type ArgentLaunchAppOptions = {
|
|
66
|
+
appId?: string;
|
|
67
|
+
rawFileName?: string;
|
|
68
|
+
};
|
|
69
|
+
type ArgentOpenUrlOptions = {
|
|
70
|
+
rawFileName?: string;
|
|
71
|
+
url: string;
|
|
72
|
+
};
|
|
73
|
+
type ArgentScreenshotOptions = {
|
|
74
|
+
rawFileName?: string;
|
|
75
|
+
};
|
|
76
|
+
type ArgentScrollOptions = {
|
|
77
|
+
durationMs?: number;
|
|
78
|
+
endX: number;
|
|
79
|
+
endY: number;
|
|
80
|
+
rawFileName?: string;
|
|
81
|
+
screenSize?: ArgentScreenSize;
|
|
82
|
+
startX: number;
|
|
83
|
+
startY: number;
|
|
84
|
+
};
|
|
85
|
+
type ArgentTapOptions = {
|
|
86
|
+
rawFileName?: string;
|
|
87
|
+
screenSize?: ArgentScreenSize;
|
|
88
|
+
x: number;
|
|
89
|
+
y: number;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Returns true when Argent emitted a complete action result before a wrapper timeout.
|
|
93
|
+
*
|
|
94
|
+
* @param {string} action
|
|
95
|
+
* @param {string} stdout
|
|
96
|
+
* @param {string | undefined} capturePath
|
|
97
|
+
* @returns {boolean}
|
|
98
|
+
*/
|
|
99
|
+
declare function hasCompletedArgentOutput(action: string, stdout: string, capturePath?: string): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Keeps successful Argent output from being failed only because a wrapper helper lingered.
|
|
102
|
+
*
|
|
103
|
+
* @param {ArgentCommandResult} result
|
|
104
|
+
* @returns {ArgentCommandResult}
|
|
105
|
+
*/
|
|
106
|
+
declare function normalizeArgentResultExitCode(result: ArgentCommandResult): ArgentCommandResult;
|
|
107
|
+
/**
|
|
108
|
+
* Combines stdout and stderr into raw evidence text.
|
|
109
|
+
*
|
|
110
|
+
* @param {{stdout: string, stderr: string}} result
|
|
111
|
+
* @returns {string}
|
|
112
|
+
*/
|
|
113
|
+
declare function formatArgentRawOutput(result: {
|
|
114
|
+
stdout: string;
|
|
115
|
+
stderr: string;
|
|
116
|
+
}): string;
|
|
117
|
+
/**
|
|
118
|
+
* Builds the command argv for one `argent run <tool>` style invocation.
|
|
119
|
+
*
|
|
120
|
+
* @param {ArgentDriverOptions} options
|
|
121
|
+
* @param {string} tool
|
|
122
|
+
* @param {string[]} [args]
|
|
123
|
+
* @returns {string[]}
|
|
124
|
+
*/
|
|
125
|
+
declare function buildArgentRunArgs(options: ArgentDriverOptions, tool: string, args?: string[]): string[];
|
|
126
|
+
/**
|
|
127
|
+
* Extracts a local screenshot path from common Argent CLI output.
|
|
128
|
+
*
|
|
129
|
+
* @param {string} text
|
|
130
|
+
* @returns {string | null}
|
|
131
|
+
*/
|
|
132
|
+
declare function extractArgentScreenshotPath(text: string): string | null;
|
|
133
|
+
/**
|
|
134
|
+
* Parses a JSON object from Argent output when the tool returns structured data.
|
|
135
|
+
*
|
|
136
|
+
* @param {string} text
|
|
137
|
+
* @returns {Record<string, unknown> | null}
|
|
138
|
+
*/
|
|
139
|
+
declare function parseArgentRunJson(text: string): Record<string, unknown> | null;
|
|
140
|
+
/**
|
|
141
|
+
* Returns the Argent description text from either plain or JSON command output.
|
|
142
|
+
*
|
|
143
|
+
* @param {string} text
|
|
144
|
+
* @returns {string}
|
|
145
|
+
*/
|
|
146
|
+
declare function readArgentDescription(text: string): string;
|
|
147
|
+
/**
|
|
148
|
+
* Detects collapsed UI descriptions that should fail scenario health upstream.
|
|
149
|
+
*
|
|
150
|
+
* @param {string} text
|
|
151
|
+
* @returns {boolean}
|
|
152
|
+
*/
|
|
153
|
+
declare function isArgentRootOnlyDescription(text: string): boolean;
|
|
154
|
+
/**
|
|
155
|
+
* Converts pixel coordinates into Argent's normalized 0-1 coordinate space.
|
|
156
|
+
*
|
|
157
|
+
* @param {ArgentPointInput} point
|
|
158
|
+
* @returns {ArgentNormalizedPoint}
|
|
159
|
+
*/
|
|
160
|
+
declare function normalizeArgentPoint({ screenSize, x, y }: ArgentPointInput): ArgentNormalizedPoint;
|
|
161
|
+
/**
|
|
162
|
+
* Checks whether an Argent UI description contains a portable selector target.
|
|
163
|
+
*
|
|
164
|
+
* @param {{description: string, selector: ArgentSelector}} options
|
|
165
|
+
* @returns {boolean}
|
|
166
|
+
*/
|
|
167
|
+
declare function matchesArgentSelector({ description, selector, }: {
|
|
168
|
+
description: string;
|
|
169
|
+
selector: ArgentSelector;
|
|
170
|
+
}): boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Creates an Argent-backed driver for portable interaction actions.
|
|
173
|
+
*
|
|
174
|
+
* The adapter shells out through an injected executor so consumers can use a
|
|
175
|
+
* global `argent` binary, `npx --yes @swmansion/argent run`, or a test double
|
|
176
|
+
* without making Argent a package dependency.
|
|
177
|
+
*
|
|
178
|
+
* @param {ArgentDriverOptions} options
|
|
179
|
+
* @returns {ArgentDriver}
|
|
180
|
+
*/
|
|
181
|
+
declare function createArgentDriver(options: ArgentDriverOptions): ArgentDriver;
|
|
182
|
+
export { buildArgentRunArgs, createArgentDriver, extractArgentScreenshotPath, formatArgentRawOutput, hasCompletedArgentOutput, isArgentRootOnlyDescription, matchesArgentSelector, normalizeArgentPoint, normalizeArgentResultExitCode, parseArgentRunJson, readArgentDescription, };
|
|
183
|
+
export type { ArgentAssertVisibleOptions, ArgentCommandExecutor, ArgentCommandResult, ArgentDriver, ArgentDriverOptions, ArgentInspectTreeOptions, ArgentLaunchAppOptions, ArgentNormalizedPoint, ArgentOpenUrlOptions, ArgentPointInput, ArgentScreenshotOptions, ArgentScreenSize, ArgentScrollOptions, ArgentSelector, ArgentTapOptions, };
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildArgentRunArgs = buildArgentRunArgs;
|
|
4
|
+
exports.createArgentDriver = createArgentDriver;
|
|
5
|
+
exports.extractArgentScreenshotPath = extractArgentScreenshotPath;
|
|
6
|
+
exports.formatArgentRawOutput = formatArgentRawOutput;
|
|
7
|
+
exports.hasCompletedArgentOutput = hasCompletedArgentOutput;
|
|
8
|
+
exports.isArgentRootOnlyDescription = isArgentRootOnlyDescription;
|
|
9
|
+
exports.matchesArgentSelector = matchesArgentSelector;
|
|
10
|
+
exports.normalizeArgentPoint = normalizeArgentPoint;
|
|
11
|
+
exports.normalizeArgentResultExitCode = normalizeArgentResultExitCode;
|
|
12
|
+
exports.parseArgentRunJson = parseArgentRunJson;
|
|
13
|
+
exports.readArgentDescription = readArgentDescription;
|
|
14
|
+
/**
|
|
15
|
+
* Adds stable driver metadata to one Argent command result.
|
|
16
|
+
*
|
|
17
|
+
* @param {{action: string, capturePath?: string, rawFileName: string, result: Awaited<ReturnType<ArgentCommandExecutor>>}} options
|
|
18
|
+
* @returns {ArgentCommandResult}
|
|
19
|
+
*/
|
|
20
|
+
function buildArgentResult({ action, capturePath, rawFileName, result, }) {
|
|
21
|
+
return {
|
|
22
|
+
action,
|
|
23
|
+
args: result.args,
|
|
24
|
+
command: result.command,
|
|
25
|
+
exitCode: result.exitCode,
|
|
26
|
+
rawFileName,
|
|
27
|
+
stderr: result.stderr,
|
|
28
|
+
stdout: result.stdout,
|
|
29
|
+
...(capturePath ? { capturePath } : {}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns true when Argent emitted a complete action result before a wrapper timeout.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} action
|
|
36
|
+
* @param {string} stdout
|
|
37
|
+
* @param {string | undefined} capturePath
|
|
38
|
+
* @returns {boolean}
|
|
39
|
+
*/
|
|
40
|
+
function hasCompletedArgentOutput(action, stdout, capturePath) {
|
|
41
|
+
const parsed = parseArgentRunJson(stdout);
|
|
42
|
+
if (action === 'screenshot') {
|
|
43
|
+
return typeof capturePath === 'string' && capturePath.length > 0;
|
|
44
|
+
}
|
|
45
|
+
if (['assertVisible', 'inspectTree'].includes(action)) {
|
|
46
|
+
return readArgentDescription(stdout).trim().length > 0;
|
|
47
|
+
}
|
|
48
|
+
if (parsed?.success === true || parsed?.launched === true) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
if (action === 'launch' && typeof parsed?.bundleId === 'string') {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Keeps successful Argent output from being failed only because a wrapper helper lingered.
|
|
58
|
+
*
|
|
59
|
+
* @param {ArgentCommandResult} result
|
|
60
|
+
* @returns {ArgentCommandResult}
|
|
61
|
+
*/
|
|
62
|
+
function normalizeArgentResultExitCode(result) {
|
|
63
|
+
if (result.exitCode !== 0 &&
|
|
64
|
+
/timed out after \d+ms/iu.test(result.stderr) &&
|
|
65
|
+
hasCompletedArgentOutput(result.action, result.stdout, result.capturePath)) {
|
|
66
|
+
return {
|
|
67
|
+
...result,
|
|
68
|
+
exitCode: 0,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Combines stdout and stderr into raw evidence text.
|
|
75
|
+
*
|
|
76
|
+
* @param {{stdout: string, stderr: string}} result
|
|
77
|
+
* @returns {string}
|
|
78
|
+
*/
|
|
79
|
+
function formatArgentRawOutput(result) {
|
|
80
|
+
return [result.stdout, result.stderr].filter(Boolean).join('\n');
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Builds the command argv for one `argent run <tool>` style invocation.
|
|
84
|
+
*
|
|
85
|
+
* @param {ArgentDriverOptions} options
|
|
86
|
+
* @param {string} tool
|
|
87
|
+
* @param {string[]} [args]
|
|
88
|
+
* @returns {string[]}
|
|
89
|
+
*/
|
|
90
|
+
function buildArgentRunArgs(options, tool, args = []) {
|
|
91
|
+
return [
|
|
92
|
+
...(options.baseArgs ?? ['run']),
|
|
93
|
+
tool,
|
|
94
|
+
options.deviceFlag ?? '--udid',
|
|
95
|
+
options.deviceId,
|
|
96
|
+
...args,
|
|
97
|
+
...(options.extraArgs ?? []),
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Extracts a local screenshot path from common Argent CLI output.
|
|
102
|
+
*
|
|
103
|
+
* @param {string} text
|
|
104
|
+
* @returns {string | null}
|
|
105
|
+
*/
|
|
106
|
+
function extractArgentScreenshotPath(text) {
|
|
107
|
+
const parsed = parseArgentRunJson(text);
|
|
108
|
+
for (const key of ['path', 'image']) {
|
|
109
|
+
const jsonPath = parsed?.[key];
|
|
110
|
+
if (typeof jsonPath === 'string' && jsonPath.length > 0) {
|
|
111
|
+
return jsonPath;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const savedMatch = /(?:Saved screenshot|Screenshot saved(?: to)?):\s*(?<path>\S.+)$/imu.exec(text);
|
|
115
|
+
return savedMatch?.groups?.path?.trim() ?? null;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Parses a JSON object from Argent output when the tool returns structured data.
|
|
119
|
+
*
|
|
120
|
+
* @param {string} text
|
|
121
|
+
* @returns {Record<string, unknown> | null}
|
|
122
|
+
*/
|
|
123
|
+
function parseArgentRunJson(text) {
|
|
124
|
+
const trimmed = text.trim();
|
|
125
|
+
if (!trimmed) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const parsed = JSON.parse(trimmed);
|
|
130
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
131
|
+
? parsed
|
|
132
|
+
: null;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Returns the Argent description text from either plain or JSON command output.
|
|
140
|
+
*
|
|
141
|
+
* @param {string} text
|
|
142
|
+
* @returns {string}
|
|
143
|
+
*/
|
|
144
|
+
function readArgentDescription(text) {
|
|
145
|
+
const parsed = parseArgentRunJson(text);
|
|
146
|
+
return typeof parsed?.description === 'string' ? parsed.description : text;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Detects collapsed UI descriptions that should fail scenario health upstream.
|
|
150
|
+
*
|
|
151
|
+
* @param {string} text
|
|
152
|
+
* @returns {boolean}
|
|
153
|
+
*/
|
|
154
|
+
function isArgentRootOnlyDescription(text) {
|
|
155
|
+
const description = readArgentDescription(text).trim();
|
|
156
|
+
return /^root$/iu.test(description) || /^<root\s*\/?>$/iu.test(description);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Converts pixel coordinates into Argent's normalized 0-1 coordinate space.
|
|
160
|
+
*
|
|
161
|
+
* @param {ArgentPointInput} point
|
|
162
|
+
* @returns {ArgentNormalizedPoint}
|
|
163
|
+
*/
|
|
164
|
+
function normalizeArgentPoint({ screenSize, x, y }) {
|
|
165
|
+
if (!screenSize) {
|
|
166
|
+
return { x, y };
|
|
167
|
+
}
|
|
168
|
+
if (!Number.isFinite(screenSize.width) ||
|
|
169
|
+
!Number.isFinite(screenSize.height) ||
|
|
170
|
+
screenSize.width <= 0 ||
|
|
171
|
+
screenSize.height <= 0) {
|
|
172
|
+
throw new Error('Argent screen size must have positive width and height.');
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
x: Math.min(Math.max(x / screenSize.width, 0), 1),
|
|
176
|
+
y: Math.min(Math.max(y / screenSize.height, 0), 1),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Normalizes a point while preserving exact optional property semantics.
|
|
181
|
+
*
|
|
182
|
+
* @param {{screenSize?: ArgentScreenSize, x: number, y: number}} point
|
|
183
|
+
* @returns {ArgentNormalizedPoint}
|
|
184
|
+
*/
|
|
185
|
+
function normalizeArgentPointWithOptionalScreenSize({ screenSize, x, y, }) {
|
|
186
|
+
return screenSize
|
|
187
|
+
? normalizeArgentPoint({ screenSize, x, y })
|
|
188
|
+
: normalizeArgentPoint({ x, y });
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Checks whether an Argent UI description contains a portable selector target.
|
|
192
|
+
*
|
|
193
|
+
* @param {{description: string, selector: ArgentSelector}} options
|
|
194
|
+
* @returns {boolean}
|
|
195
|
+
*/
|
|
196
|
+
function matchesArgentSelector({ description, selector, }) {
|
|
197
|
+
const descriptionText = readArgentDescription(description);
|
|
198
|
+
const matchMode = selector.match ?? (selector.kind === 'regex' ? 'regex' : undefined);
|
|
199
|
+
if (matchMode === 'regex') {
|
|
200
|
+
try {
|
|
201
|
+
return new RegExp(selector.value, 'u').test(descriptionText);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return descriptionText.includes(selector.value);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Creates an Argent-backed driver for portable interaction actions.
|
|
211
|
+
*
|
|
212
|
+
* The adapter shells out through an injected executor so consumers can use a
|
|
213
|
+
* global `argent` binary, `npx --yes @swmansion/argent run`, or a test double
|
|
214
|
+
* without making Argent a package dependency.
|
|
215
|
+
*
|
|
216
|
+
* @param {ArgentDriverOptions} options
|
|
217
|
+
* @returns {ArgentDriver}
|
|
218
|
+
*/
|
|
219
|
+
function createArgentDriver(options) {
|
|
220
|
+
const run = async (action, tool, rawFileName, args = [], capturePath) => {
|
|
221
|
+
const result = await options.executor(options.argentCommand, buildArgentRunArgs(options, tool, args));
|
|
222
|
+
return normalizeArgentResultExitCode(buildArgentResult({
|
|
223
|
+
action,
|
|
224
|
+
rawFileName,
|
|
225
|
+
result,
|
|
226
|
+
...(capturePath ? { capturePath } : {}),
|
|
227
|
+
}));
|
|
228
|
+
};
|
|
229
|
+
const appArgs = (appId) => {
|
|
230
|
+
const resolvedAppId = appId ?? options.appId;
|
|
231
|
+
return resolvedAppId ? [options.appFlag ?? '--bundleId', resolvedAppId] : [];
|
|
232
|
+
};
|
|
233
|
+
return {
|
|
234
|
+
async assertVisible({ appId, rawFileName = 'argent-assert-visible.txt', selector, }) {
|
|
235
|
+
const result = await run('assertVisible', 'describe', rawFileName, appArgs(appId));
|
|
236
|
+
if (!matchesArgentSelector({ description: formatArgentRawOutput(result), selector })) {
|
|
237
|
+
return {
|
|
238
|
+
...result,
|
|
239
|
+
exitCode: result.exitCode === 0 ? 1 : result.exitCode,
|
|
240
|
+
stderr: [result.stderr, `Argent description did not include ${selector.kind} \`${selector.value}\`.`]
|
|
241
|
+
.filter(Boolean)
|
|
242
|
+
.join('\n'),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
...result,
|
|
247
|
+
exitCode: 0,
|
|
248
|
+
};
|
|
249
|
+
},
|
|
250
|
+
async inspectTree({ appId, rawFileName = 'argent-describe.txt', } = {}) {
|
|
251
|
+
return run('inspectTree', 'describe', rawFileName, appArgs(appId));
|
|
252
|
+
},
|
|
253
|
+
async launchApp({ appId, rawFileName = 'argent-launch-app.txt', } = {}) {
|
|
254
|
+
return run('launch', 'launch-app', rawFileName, appArgs(appId));
|
|
255
|
+
},
|
|
256
|
+
async openUrl({ rawFileName = 'argent-open-url.txt', url, }) {
|
|
257
|
+
return run('openUrl', 'open-url', rawFileName, ['--url', url]);
|
|
258
|
+
},
|
|
259
|
+
async screenshot({ rawFileName = 'argent-screenshot.txt', } = {}) {
|
|
260
|
+
const result = await run('screenshot', 'screenshot', rawFileName, ['--includeImageInContext', 'false']);
|
|
261
|
+
const capturePath = extractArgentScreenshotPath(result.stdout);
|
|
262
|
+
return normalizeArgentResultExitCode(capturePath ? { ...result, capturePath } : result);
|
|
263
|
+
},
|
|
264
|
+
async scroll({ durationMs = 300, endX, endY, rawFileName = 'argent-swipe.txt', screenSize, startX, startY, }) {
|
|
265
|
+
const from = normalizeArgentPointWithOptionalScreenSize({
|
|
266
|
+
...(screenSize ?? options.screenSize ? { screenSize: screenSize ?? options.screenSize } : {}),
|
|
267
|
+
x: startX,
|
|
268
|
+
y: startY,
|
|
269
|
+
});
|
|
270
|
+
const to = normalizeArgentPointWithOptionalScreenSize({
|
|
271
|
+
...(screenSize ?? options.screenSize ? { screenSize: screenSize ?? options.screenSize } : {}),
|
|
272
|
+
x: endX,
|
|
273
|
+
y: endY,
|
|
274
|
+
});
|
|
275
|
+
return run('scroll', 'gesture-swipe', rawFileName, [
|
|
276
|
+
'--fromX',
|
|
277
|
+
String(from.x),
|
|
278
|
+
'--fromY',
|
|
279
|
+
String(from.y),
|
|
280
|
+
'--toX',
|
|
281
|
+
String(to.x),
|
|
282
|
+
'--toY',
|
|
283
|
+
String(to.y),
|
|
284
|
+
'--durationMs',
|
|
285
|
+
String(durationMs),
|
|
286
|
+
]);
|
|
287
|
+
},
|
|
288
|
+
async tap({ rawFileName = 'argent-tap.txt', screenSize, x, y, }) {
|
|
289
|
+
const point = normalizeArgentPointWithOptionalScreenSize({
|
|
290
|
+
...(screenSize ?? options.screenSize ? { screenSize: screenSize ?? options.screenSize } : {}),
|
|
291
|
+
x,
|
|
292
|
+
y,
|
|
293
|
+
});
|
|
294
|
+
return run('tap', 'gesture-tap', rawFileName, ['--x', String(point.x), '--y', String(point.y)]);
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
}
|