@swmansion/argent 0.21.0 → 0.21.1-next.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/bin/argent-android-devtools-0.1.0.apk +0 -0
- package/bin/darwin/ax-service +0 -0
- package/bin/darwin/tvos-ax-service +0 -0
- package/bin/darwin/tvos-hid-daemon +0 -0
- package/bin/tcp/ax-service +0 -0
- package/dist/tool-server.cjs +338 -85
- package/dylibs/libArgentInjectionBootstrap.dylib +0 -0
- package/dylibs/libKeyboardPatch.dylib +0 -0
- package/dylibs/libNativeDevtoolsIos.dylib +0 -0
- package/dylibs/tcp/libArgentInjectionBootstrap.dylib +0 -0
- package/dylibs/tcp/libKeyboardPatch.dylib +0 -0
- package/dylibs/tcp/libNativeDevtoolsIos.dylib +0 -0
- package/dylibs/tvos/libArgentInjectionBootstrap.dylib +0 -0
- package/dylibs/tvos/libKeyboardPatch.dylib +0 -0
- package/dylibs/tvos/libNativeDevtoolsIos.dylib +0 -0
- package/package.json +1 -1
- package/skills/argent-create-flow/references/reliability-and-recovery.md +5 -5
- package/skills/argent-device-interact/SKILL.md +1 -1
|
Binary file
|
package/bin/darwin/ax-service
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/bin/tcp/ax-service
CHANGED
|
Binary file
|
package/dist/tool-server.cjs
CHANGED
|
@@ -98753,6 +98753,19 @@ var ARGENT_BOOTSTRAP_DYLIB_BASENAMES = /* @__PURE__ */ new Set([
|
|
|
98753
98753
|
"libArgentInjectionBootstrap.dylib",
|
|
98754
98754
|
"libInjectionBootstrap.dylib"
|
|
98755
98755
|
]);
|
|
98756
|
+
function processCarriesInjection(env, endpoint) {
|
|
98757
|
+
const inserted = [...ARGENT_BOOTSTRAP_DYLIB_BASENAMES].some((name) => env.includes(name));
|
|
98758
|
+
if (!inserted) return false;
|
|
98759
|
+
const expected = endpoint.transport === "tcp" ? `NATIVE_DEVTOOLS_IOS_CDP_PORT=${endpoint.port}` : `NATIVE_DEVTOOLS_IOS_CDP_SOCKET=${endpoint.socketPath}`;
|
|
98760
|
+
return env.split(/\s+/).includes(expected);
|
|
98761
|
+
}
|
|
98762
|
+
var PS_PROBE_TIMEOUT_MS = 5e3;
|
|
98763
|
+
function parsePsElapsedSeconds(etime) {
|
|
98764
|
+
const match = etime.trim().match(/^(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)$/);
|
|
98765
|
+
if (!match) return null;
|
|
98766
|
+
const [, days, hours, minutes, seconds] = match;
|
|
98767
|
+
return Number(days ?? 0) * 86400 + Number(hours ?? 0) * 3600 + Number(minutes) * 60 + Number(seconds);
|
|
98768
|
+
}
|
|
98756
98769
|
function splitDyldInsertLibraries(value) {
|
|
98757
98770
|
return value.split(":").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
98758
98771
|
}
|
|
@@ -98861,17 +98874,20 @@ async function setupNativeDevtoolsEnvRemote(udid, endpoint) {
|
|
|
98861
98874
|
}
|
|
98862
98875
|
await setSimulatorEnv(udid, "NATIVE_DEVTOOLS_IOS_CDP_PORT", String(endpoint.port));
|
|
98863
98876
|
}
|
|
98864
|
-
function
|
|
98865
|
-
const
|
|
98877
|
+
function parseUIKitApplicationJobs(stdout) {
|
|
98878
|
+
const jobs = /* @__PURE__ */ new Map();
|
|
98866
98879
|
for (const line of stdout.split("\n")) {
|
|
98867
98880
|
const match = line.match(/UIKitApplication:([^[]+)/);
|
|
98868
|
-
if (match)
|
|
98869
|
-
|
|
98870
|
-
|
|
98881
|
+
if (!match) continue;
|
|
98882
|
+
const pid = line.match(/^(\d+)\s/);
|
|
98883
|
+
jobs.set(match[1].trim(), pid ? Number(pid[1]) : null);
|
|
98871
98884
|
}
|
|
98872
|
-
return
|
|
98885
|
+
return jobs;
|
|
98873
98886
|
}
|
|
98874
|
-
|
|
98887
|
+
function parseUIKitApplicationBundleIds(stdout) {
|
|
98888
|
+
return new Set(parseUIKitApplicationJobs(stdout).keys());
|
|
98889
|
+
}
|
|
98890
|
+
async function listRunningApps(udid) {
|
|
98875
98891
|
const { stdout } = await execFileAsync8(
|
|
98876
98892
|
"xcrun",
|
|
98877
98893
|
await simctlArgsForUdid(udid, ["spawn", udid, "launchctl", "list"]),
|
|
@@ -98881,7 +98897,42 @@ async function listRunningUIKitApplicationBundleIds(udid) {
|
|
|
98881
98897
|
killSignal: SIMCTL_KILL_SIGNAL
|
|
98882
98898
|
}
|
|
98883
98899
|
);
|
|
98884
|
-
return
|
|
98900
|
+
return stdout;
|
|
98901
|
+
}
|
|
98902
|
+
async function listRunningUIKitApplicationBundleIds(udid) {
|
|
98903
|
+
return parseUIKitApplicationBundleIds(await listRunningApps(udid));
|
|
98904
|
+
}
|
|
98905
|
+
async function readProcessLaunchState(pid) {
|
|
98906
|
+
let stdout;
|
|
98907
|
+
try {
|
|
98908
|
+
({ stdout } = await execFileAsync8(PS_BIN, ["eww", "-p", String(pid), "-o", "etime=,command="], {
|
|
98909
|
+
encoding: "utf8",
|
|
98910
|
+
timeout: PS_PROBE_TIMEOUT_MS,
|
|
98911
|
+
// Matches the other `ps` probes (vega-process.ts). An environment can run
|
|
98912
|
+
// to `kern.argmax` (1 MiB), exactly Node's default cap, so the default
|
|
98913
|
+
// would ENOBUFS on a maximal one instead of reading it.
|
|
98914
|
+
maxBuffer: 16 * 1024 * 1024
|
|
98915
|
+
}));
|
|
98916
|
+
} catch (err) {
|
|
98917
|
+
process.stderr.write(`[ios-host] ps probe failed for pid ${pid}: ${String(err)}
|
|
98918
|
+
`);
|
|
98919
|
+
return null;
|
|
98920
|
+
}
|
|
98921
|
+
const trimmed = stdout.trim();
|
|
98922
|
+
const boundary = trimmed.search(/\s/);
|
|
98923
|
+
if (boundary === -1) return null;
|
|
98924
|
+
const ageSeconds = parsePsElapsedSeconds(trimmed.slice(0, boundary));
|
|
98925
|
+
if (ageSeconds === null) return null;
|
|
98926
|
+
return { pid, ageMs: ageSeconds * 1e3, env: trimmed.slice(boundary + 1) };
|
|
98927
|
+
}
|
|
98928
|
+
async function inspectRunningAppLocal(udid, bundleId) {
|
|
98929
|
+
const jobs = parseUIKitApplicationJobs(await listRunningApps(udid));
|
|
98930
|
+
if (!jobs.has(bundleId)) return { running: false, process: null };
|
|
98931
|
+
const pid = jobs.get(bundleId) ?? null;
|
|
98932
|
+
const process3 = pid === null ? null : await readProcessLaunchState(pid);
|
|
98933
|
+
if (process3 !== null) return { running: true, process: process3 };
|
|
98934
|
+
const stillListed = parseUIKitApplicationJobs(await listRunningApps(udid)).has(bundleId);
|
|
98935
|
+
return { running: stillListed, process: null };
|
|
98885
98936
|
}
|
|
98886
98937
|
function spawnAxDaemonLocal(udid, endpoint) {
|
|
98887
98938
|
const binaryPath = endpoint.transport === "tcp" ? axServiceBinaryPathTcp() : axServiceBinaryPath();
|
|
@@ -98931,6 +98982,7 @@ var localIosHost = {
|
|
|
98931
98982
|
requiresTcp: false,
|
|
98932
98983
|
setupNativeDevtoolsEnv: setupNativeDevtoolsEnvLocal,
|
|
98933
98984
|
listRunningBundleIds: listRunningUIKitApplicationBundleIds,
|
|
98985
|
+
inspectRunningApp: inspectRunningAppLocal,
|
|
98934
98986
|
async bootstrapAx(udid) {
|
|
98935
98987
|
await ensureAutomationEnabled(udid);
|
|
98936
98988
|
return { entitlementBypassActive: await isEntitlementBypassActive(udid) };
|
|
@@ -98954,6 +99006,13 @@ var remoteIosHost = {
|
|
|
98954
99006
|
const { stdout } = await simctlSpawn(udid, { args: ["launchctl", "list"] });
|
|
98955
99007
|
return parseUIKitApplicationBundleIds(stdout);
|
|
98956
99008
|
},
|
|
99009
|
+
// App processes live on the orchestrator, so the local process table says
|
|
99010
|
+
// nothing about how they were launched. Only running-ness is answerable; the
|
|
99011
|
+
// null process keeps callers on their no-evidence path.
|
|
99012
|
+
async inspectRunningApp(udid, bundleId) {
|
|
99013
|
+
const { stdout } = await simctlSpawn(udid, { args: ["launchctl", "list"] });
|
|
99014
|
+
return { running: parseUIKitApplicationJobs(stdout).has(bundleId), process: null };
|
|
99015
|
+
},
|
|
98957
99016
|
// Apply the accessibility defaults the tool-server needs (the local host does
|
|
98958
99017
|
// this via `defaults write`; here we run the same writes through the remote
|
|
98959
99018
|
// generic spawn). The entitlement-bypass plist is assumed active on cloud
|
|
@@ -112178,6 +112237,22 @@ function isInjectableBundleId(bundleId) {
|
|
|
112178
112237
|
var NON_INJECTABLE_NATIVE_WARNING = "Do not fall back to the native-devtools feature tools (native-describe-screen, native-find-views, native-full-hierarchy, native-network-logs, native-view-at-point, native-user-interactable-view-at-point) \u2014 they run the same injection precheck and fail with the same non-injectable error.";
|
|
112179
112238
|
var NON_INJECTABLE_RECOVERY = "Use the standard `describe` tool (its accessibility path reads the screen without injection) or `screenshot` (then interact by coordinate). " + NON_INJECTABLE_NATIVE_WARNING;
|
|
112180
112239
|
var MAX_NATIVE_DEVTOOLS_INIT_ATTEMPTS = 3;
|
|
112240
|
+
var NATIVE_DEVTOOLS_AGE_SLOP_MS = 3e3;
|
|
112241
|
+
var NATIVE_DEVTOOLS_CONNECT_BUDGET_MS = 15e3;
|
|
112242
|
+
function buildAppStateMessage(bundleId, state3) {
|
|
112243
|
+
switch (state3) {
|
|
112244
|
+
case "not_running":
|
|
112245
|
+
return `${bundleId} has no running process on this simulator, so there is no injected process to read. Call launch-app (or restart-app) then retry. If that launch fails rather than starting the app, the bundle id is not installed on this device \u2014 this state cannot tell the two apart; install it and no relaunch will be needed.`;
|
|
112246
|
+
case "stale_process":
|
|
112247
|
+
return `The running ${bundleId} process cannot reach this simulator's native-devtools endpoint \u2014 it was launched either before argent's instrumentation was in place or against an earlier tool-server's listener. A fresh process picks up the current one: call restart-app then retry.`;
|
|
112248
|
+
case "unregistered":
|
|
112249
|
+
return `${bundleId} is running with argent's native devtools injected and pointed at this simulator's devtools endpoint, but the service never registered its connection. Restarting the app cannot change that \u2014 it already launched under exactly the terms a restart would recreate. Restart the tool-server (\`argent server stop && argent server start --detach\`) and retry. If you have already restarted the tool-server for this app and it reads this way again, stop: the process is loading argent's dylib but never dialing, which no further restart on either side fixes. Treat native devtools as unavailable \u2014 read the screen with describe or screenshot and drive it by coordinate.`;
|
|
112250
|
+
case "connecting":
|
|
112251
|
+
return `${bundleId} is running with argent's native devtools injected and pointed at this simulator's devtools endpoint, and it launched moments ago \u2014 its connection has not finished being established. Wait a few seconds and retry the same call. Do NOT restart the app: launching it is what starts the connection, so a relaunch discards the one in progress and returns you to this same state.`;
|
|
112252
|
+
case "indeterminate":
|
|
112253
|
+
return `Native devtools are not connected to ${bundleId}, and its process could not be inspected to tell whether it is injected. Call restart-app then retry. If it is still not connected after that restart, the native-devtools service is stale rather than the app being uninjected \u2014 do not keep restarting the app; restart the tool-server (\`argent server stop && argent server start --detach\`) and retry.`;
|
|
112254
|
+
}
|
|
112255
|
+
}
|
|
112181
112256
|
function buildInitFailedResult(udid, failure) {
|
|
112182
112257
|
return {
|
|
112183
112258
|
status: "init_failed",
|
|
@@ -112185,10 +112260,33 @@ function buildInitFailedResult(udid, failure) {
|
|
|
112185
112260
|
attempts: failure.attempts
|
|
112186
112261
|
};
|
|
112187
112262
|
}
|
|
112263
|
+
var NATIVE_DEVTOOLS_PRECHECK_TOOLS = /* @__PURE__ */ new Set([
|
|
112264
|
+
"native-describe-screen",
|
|
112265
|
+
"native-find-views",
|
|
112266
|
+
"native-full-hierarchy",
|
|
112267
|
+
"native-network-logs",
|
|
112268
|
+
"native-view-at-point",
|
|
112269
|
+
"native-user-interactable-view-at-point",
|
|
112270
|
+
"native-devtools-status",
|
|
112271
|
+
"launch-app",
|
|
112272
|
+
"restart-app"
|
|
112273
|
+
]);
|
|
112274
|
+
var NATIVE_DEVTOOLS_BLOCK_STATUSES = /* @__PURE__ */ new Set([
|
|
112275
|
+
"init_failed",
|
|
112276
|
+
"restart_required",
|
|
112277
|
+
"service_stale",
|
|
112278
|
+
"connect_pending"
|
|
112279
|
+
]);
|
|
112280
|
+
function isNativeDevtoolsBlockResult(toolId, result) {
|
|
112281
|
+
if (!NATIVE_DEVTOOLS_PRECHECK_TOOLS.has(toolId)) return false;
|
|
112282
|
+
if (typeof result !== "object" || result === null) return false;
|
|
112283
|
+
const status = result.status;
|
|
112284
|
+
return typeof status === "string" && NATIVE_DEVTOOLS_BLOCK_STATUSES.has(status);
|
|
112285
|
+
}
|
|
112188
112286
|
async function precheckNativeDevtools(api, udid, bundleId) {
|
|
112189
112287
|
if (bundleId !== void 0 && !isInjectableBundleId(bundleId)) {
|
|
112190
112288
|
throw new FailureError(
|
|
112191
|
-
`${bundleId} is an Apple system app: it is a platform binary with library validation, so Argent native devtools
|
|
112289
|
+
`${bundleId} is an Apple system app: it is a platform binary with library validation, so Argent native devtools cannot be relied on to inject into it \u2014 treat it as unavailable rather than retrying. ` + NON_INJECTABLE_RECOVERY,
|
|
112192
112290
|
{
|
|
112193
112291
|
error_code: FAILURE_CODES.NATIVE_DEVTOOLS_NOT_INJECTABLE,
|
|
112194
112292
|
failure_stage: "native_devtools_precheck",
|
|
@@ -112210,13 +112308,21 @@ async function precheckNativeDevtools(api, udid, bundleId) {
|
|
|
112210
112308
|
givenUp: false
|
|
112211
112309
|
});
|
|
112212
112310
|
}
|
|
112213
|
-
if (bundleId
|
|
112214
|
-
|
|
112215
|
-
|
|
112216
|
-
|
|
112217
|
-
|
|
112218
|
-
|
|
112219
|
-
return null;
|
|
112311
|
+
if (bundleId === void 0) return null;
|
|
112312
|
+
const state3 = await api.appConnectionState(bundleId).catch(() => {
|
|
112313
|
+
const failure = api.getInitFailure();
|
|
112314
|
+
return failure ? buildInitFailedResult(udid, failure) : "indeterminate";
|
|
112315
|
+
});
|
|
112316
|
+
if (typeof state3 !== "string") return state3;
|
|
112317
|
+
if (state3 === "connected") return null;
|
|
112318
|
+
return {
|
|
112319
|
+
// Neither `unregistered` (a relaunch provably cannot fix it) nor
|
|
112320
|
+
// `connecting` (a relaunch aborts the handshake and resets the age the
|
|
112321
|
+
// verdict reads) may be reported as restart_required — obeying that would
|
|
112322
|
+
// return here forever.
|
|
112323
|
+
status: state3 === "unregistered" ? "service_stale" : state3 === "connecting" ? "connect_pending" : "restart_required",
|
|
112324
|
+
message: buildAppStateMessage(bundleId, state3)
|
|
112325
|
+
};
|
|
112220
112326
|
}
|
|
112221
112327
|
function nativeDevtoolsRef(device, { transport = "unix" } = {}) {
|
|
112222
112328
|
const transportSuffix = transport === "tcp" ? ":tcp" : "";
|
|
@@ -112478,6 +112584,7 @@ var nativeDevtoolsBlueprint = {
|
|
|
112478
112584
|
} else {
|
|
112479
112585
|
await bindNativeDevtoolsUnixSocket(server, socketPath);
|
|
112480
112586
|
}
|
|
112587
|
+
const listeningSince = Date.now();
|
|
112481
112588
|
await ensureEnvReady().catch(() => {
|
|
112482
112589
|
});
|
|
112483
112590
|
const api = {
|
|
@@ -112489,10 +112596,28 @@ var nativeDevtoolsBlueprint = {
|
|
|
112489
112596
|
isConnected: (bundleId) => connections2.has(bundleId),
|
|
112490
112597
|
isAppRunning,
|
|
112491
112598
|
listConnectedBundleIds: () => [...connections2.keys()],
|
|
112492
|
-
async
|
|
112493
|
-
if (connections2.has(bundleId)) return
|
|
112599
|
+
async appConnectionState(bundleId) {
|
|
112600
|
+
if (connections2.has(bundleId)) return "connected";
|
|
112494
112601
|
await reverifyEnv();
|
|
112495
|
-
|
|
112602
|
+
const inspection = await host.inspectRunningApp(udid, bundleId).catch((err) => {
|
|
112603
|
+
process.stderr.write(
|
|
112604
|
+
`[native-devtools] app inspection failed for ${bundleId}: ${String(err)}
|
|
112605
|
+
`
|
|
112606
|
+
);
|
|
112607
|
+
return null;
|
|
112608
|
+
});
|
|
112609
|
+
if (connections2.has(bundleId)) return "connected";
|
|
112610
|
+
if (inspection === null) return "indeterminate";
|
|
112611
|
+
if (!inspection.running) return "not_running";
|
|
112612
|
+
if (inspection.process === null) return "indeterminate";
|
|
112613
|
+
if (!processCarriesInjection(inspection.process.env, endpoint)) return "stale_process";
|
|
112614
|
+
const listenerAgeMs = Date.now() - listeningSince;
|
|
112615
|
+
const processAgeMs = inspection.process.ageMs;
|
|
112616
|
+
if (processAgeMs + NATIVE_DEVTOOLS_AGE_SLOP_MS >= listenerAgeMs) {
|
|
112617
|
+
return "stale_process";
|
|
112618
|
+
}
|
|
112619
|
+
if (processAgeMs < NATIVE_DEVTOOLS_CONNECT_BUDGET_MS) return "connecting";
|
|
112620
|
+
return "unregistered";
|
|
112496
112621
|
},
|
|
112497
112622
|
activateNetworkInspection(bundleId) {
|
|
112498
112623
|
activatedBundleIds.add(bundleId);
|
|
@@ -112846,7 +112971,7 @@ function tcpArtifactHint(err) {
|
|
|
112846
112971
|
return /TCP-transport (?:binary|dylib) not found/.test(message) ? message : void 0;
|
|
112847
112972
|
}
|
|
112848
112973
|
var TVOS_HINT = "This is an Apple TV (tvOS) simulator, which the iOS accessibility service does not support. Use the `describe` tool to read the focused and focusable elements, `tv-remote` (up/down/left/right/select/back/menu/home) to move focus, and `keyboard` to type. See the argent-tv-interact skill.";
|
|
112849
|
-
var NON_INJECTABLE_HINT = "This is an Apple system app (com.apple.*), which cannot load argent's native-devtools instrumentation \u2014 the native view hierarchy is unavailable and restarting the app will NOT help. Take a `screenshot` to see the screen and interact by coordinate. " + NON_INJECTABLE_NATIVE_WARNING;
|
|
112974
|
+
var NON_INJECTABLE_HINT = "This is an Apple system app (com.apple.*), which cannot be relied on to load argent's native-devtools instrumentation \u2014 the native view hierarchy is unavailable and restarting the app will NOT help. Take a `screenshot` to see the screen and interact by coordinate. " + NON_INJECTABLE_NATIVE_WARNING;
|
|
112850
112975
|
function emptyTree() {
|
|
112851
112976
|
return parseDescribeResult({
|
|
112852
112977
|
role: "AXGroup",
|
|
@@ -112878,27 +113003,43 @@ async function describeIos(registry2, device, params, options = {}) {
|
|
|
112878
113003
|
if (params.bundleId && !isInjectableBundleId(params.bundleId)) {
|
|
112879
113004
|
return { tree, source: "ax-service", hint: hint ?? NON_INJECTABLE_HINT };
|
|
112880
113005
|
}
|
|
113006
|
+
let nativeApi;
|
|
112881
113007
|
try {
|
|
112882
113008
|
const ndRef = nativeDevtoolsRef(device);
|
|
112883
|
-
|
|
113009
|
+
nativeApi = await registry2.resolveService(ndRef.urn, ndRef.options);
|
|
113010
|
+
} catch (err) {
|
|
113011
|
+
return { tree, source: "ax-service", hint: unexplainedHint(hint, errMsg(err), params) };
|
|
113012
|
+
}
|
|
113013
|
+
try {
|
|
112884
113014
|
const target = await resolveNativeTargetApp(nativeApi, params.bundleId);
|
|
112885
|
-
|
|
112886
|
-
|
|
113015
|
+
const state3 = await nativeApi.appConnectionState(target.bundleId).catch(() => "indeterminate");
|
|
113016
|
+
if (state3 !== "connected") {
|
|
113017
|
+
const diagnosis = buildAppStateMessage(target.bundleId, state3);
|
|
113018
|
+
const merged = hint ? `${hint} ${diagnosis}` : diagnosis;
|
|
113019
|
+
return state3 === "unregistered" || state3 === "connecting" ? { tree, source: "ax-service", hint: merged } : { tree, source: "ax-service", should_restart: true, hint: merged };
|
|
112887
113020
|
}
|
|
112888
113021
|
const rawResult = await nativeApi.queryViewHierarchy(
|
|
112889
113022
|
target.bundleId,
|
|
112890
113023
|
"ViewHierarchy.describeScreen"
|
|
112891
113024
|
);
|
|
112892
113025
|
if (rawResult.error) {
|
|
112893
|
-
return { tree, source: "ax-service", hint };
|
|
113026
|
+
return { tree, source: "ax-service", hint: unexplainedHint(hint, rawResult.error, params) };
|
|
112894
113027
|
}
|
|
112895
113028
|
const parsed = parseNativeDescribeScreenResult(rawResult);
|
|
112896
113029
|
const nativeTree = adaptNativeDescribeToDescribeResult(parsed);
|
|
112897
113030
|
return { tree: nativeTree, source: "native-devtools", hint };
|
|
112898
|
-
} catch {
|
|
112899
|
-
return { tree, source: "ax-service", hint };
|
|
113031
|
+
} catch (err) {
|
|
113032
|
+
return { tree, source: "ax-service", hint: unexplainedHint(hint, errMsg(err), params) };
|
|
112900
113033
|
}
|
|
112901
113034
|
}
|
|
113035
|
+
function errMsg(err) {
|
|
113036
|
+
return err instanceof Error ? err.message : String(err);
|
|
113037
|
+
}
|
|
113038
|
+
function unexplainedHint(hint, detail, params) {
|
|
113039
|
+
const next = params.bundleId ? "Take a `screenshot` to see what is there." : "Pass `bundleId` to have the connection state measured, or take a `screenshot` to see what is there.";
|
|
113040
|
+
const why = `The native view hierarchy could not be read (${detail}), so this empty accessibility tree is not evidence that nothing is on screen. ${next}`;
|
|
113041
|
+
return hint ? `${hint} ${why}` : why;
|
|
113042
|
+
}
|
|
112902
113043
|
|
|
112903
113044
|
// ../tool-server/src/tools/describe/platforms/android/index.ts
|
|
112904
113045
|
init_src();
|
|
@@ -117164,6 +117305,9 @@ var zodSchema2 = external_exports.object({
|
|
|
117164
117305
|
udid: external_exports.string().describe("Simulator UDID"),
|
|
117165
117306
|
bundleId: external_exports.string().describe("Bundle ID of the app to check (e.g. com.example.MyApp)")
|
|
117166
117307
|
});
|
|
117308
|
+
function envSetupReading(api, connected) {
|
|
117309
|
+
return api.isEnvSetup() && (connected || api.getInitFailure() === null);
|
|
117310
|
+
}
|
|
117167
117311
|
var nativeDevtoolsStatusTool = {
|
|
117168
117312
|
id: "native-devtools-status",
|
|
117169
117313
|
interaction: {
|
|
@@ -117179,18 +117323,23 @@ var nativeDevtoolsStatusTool = {
|
|
|
117179
117323
|
description: `Check whether native devtools are connected to a specific app and whether the next launch is prepared for injection.
|
|
117180
117324
|
Use when you need to verify native devtools readiness before calling native-full-hierarchy, native-describe-screen, or native-network-logs.
|
|
117181
117325
|
|
|
117182
|
-
Returns { envSetup, appRunning, connected, requiresRestart, nextLaunchWillBeInjected, injectable }:
|
|
117326
|
+
Returns { envSetup, appRunning, connected, requiresRestart, state, message, nextLaunchWillBeInjected, injectable }:
|
|
117183
117327
|
- envSetup: DYLD_INSERT_LIBRARIES is configured in the simulator's launchd environment
|
|
117184
117328
|
- appRunning: the target bundle currently has a running UIKit process on the simulator
|
|
117185
117329
|
- connected: the dylib is active in the current running process for this bundleId
|
|
117186
|
-
- requiresRestart: the app is already running
|
|
117330
|
+
- requiresRestart: the app is already running and a fresh process would reach this simulator's devtools endpoint where the current one does not \u2014 it carries no argent injection, was pointed at an earlier tool-server's listener, or could not be inspected to tell. Always false for a non-injectable app, and false when state is unregistered or connecting, where a relaunch cannot help.
|
|
117331
|
+
- state: why devtools are or aren't live, measured from the running process. "connected"; "not_running"; "stale_process" (the process cannot reach this simulator's devtools endpoint \u2014 launched either before argent's instrumentation was in place or against an earlier tool-server's listener \u2014 so restart-app fixes it); "unregistered" (the process IS injected and pointed at this simulator's devtools endpoint yet the service never registered it, so restarting the app cannot help); "connecting" (the process IS injected but launched moments ago and is still connecting, so waiting is what helps); "indeterminate" (the process could not be inspected). Omitted when injectable is false, which is terminal on its own.
|
|
117332
|
+
- message: the remedy for that state, in full. Omitted when connected or non-injectable. Prefer it over inferring one from the booleans \u2014 it is the only field that can tell you to stop restarting the app.
|
|
117187
117333
|
- nextLaunchWillBeInjected: if you launch this bundle now, native devtools env setup is already in place (always false for a non-injectable app)
|
|
117188
|
-
- injectable: whether native devtools can
|
|
117334
|
+
- injectable: whether native devtools can be relied on to inject into this app. Apple system apps (bundle ids under com.apple.) are platform binaries with library validation, so the dylib cannot be counted on to load into them \u2014 it has been observed both loading and not loading, depending on the simulator runtime.
|
|
117189
117335
|
|
|
117190
117336
|
Call this before using app-scoped native hierarchy tools or native-network-logs.
|
|
117191
|
-
If injectable is false: this
|
|
117337
|
+
If injectable is false: treat this as TERMINAL \u2014 injection cannot be relied on for this app, and no relaunch changes which way it goes. Do NOT restart/retry. Use the standard \`describe\` tool (its accessibility path reads the screen without injection) or \`screenshot\` (then interact by coordinate). Do not fall back to the native-devtools feature tools (native-describe-screen, native-find-views, native-full-hierarchy, native-network-logs, native-view-at-point, native-user-interactable-view-at-point) \u2014 they run the same injection precheck and fail with the same non-injectable error.
|
|
117192
117338
|
If appRunning is false and nextLaunchWillBeInjected is true: use launch-app normally.
|
|
117193
|
-
If requiresRestart is true: call restart-app, then proceed with the native feature.
|
|
117339
|
+
If requiresRestart is true: call restart-app once, then proceed with the native feature. Read state before acting on a second such reading \u2014 indeterminate reaches this rule too, and its line below bounds it at that one restart.
|
|
117340
|
+
If state is unregistered: do NOT restart the app again \u2014 it already launched under the terms a restart would recreate. Restart the tool-server (\`argent server stop && argent server start --detach\`), then retry. If it reads unregistered again after that restart, stop: the process loads argent's dylib but never dials, and no further restart on either side changes it \u2014 treat native devtools as unavailable, then use \`describe\` or \`screenshot\` and drive by coordinate.
|
|
117341
|
+
If state is connecting: do NOT restart the app \u2014 launching it is what starts the connection, so a relaunch discards the one in progress and returns this same state. Wait a few seconds and repeat this call.
|
|
117342
|
+
If state is indeterminate: the process could not be inspected, so restart-app is worth one attempt. If this call still reports it after that restart, do NOT restart the app again \u2014 the service is stale rather than the app uninjected, so restart the tool-server (\`argent server stop && argent server start --detach\`) and retry. Remote simulators can never inspect the process, so this is the only unconnected state a running app reaches there.
|
|
117194
117343
|
Returns { status: "init_failed", message, attempts } instead when the simulator's native-devtools environment failed to initialize.
|
|
117195
117344
|
Fails if the simulator server is not running for the given UDID.`,
|
|
117196
117345
|
zodSchema: zodSchema2,
|
|
@@ -117202,18 +117351,19 @@ Fails if the simulator server is not running for the given UDID.`,
|
|
|
117202
117351
|
await ensureDeps(device.platform === "ios-remote" ? ["sim-remote"] : ["xcrun"]);
|
|
117203
117352
|
const api = services.nativeDevtools;
|
|
117204
117353
|
if (!isInjectableBundleId(params.bundleId)) {
|
|
117354
|
+
const connected2 = api.isConnected(params.bundleId);
|
|
117205
117355
|
let appRunning2;
|
|
117206
117356
|
try {
|
|
117207
117357
|
appRunning2 = await api.isAppRunning(params.bundleId);
|
|
117208
117358
|
} catch (err) {
|
|
117209
|
-
const
|
|
117210
|
-
if (
|
|
117359
|
+
const failure = api.getInitFailure();
|
|
117360
|
+
if (failure) return buildInitFailedResult(params.udid, failure);
|
|
117211
117361
|
throw err;
|
|
117212
117362
|
}
|
|
117213
117363
|
return {
|
|
117214
|
-
envSetup: api
|
|
117364
|
+
envSetup: envSetupReading(api, connected2),
|
|
117215
117365
|
appRunning: appRunning2,
|
|
117216
|
-
connected:
|
|
117366
|
+
connected: connected2,
|
|
117217
117367
|
requiresRestart: false,
|
|
117218
117368
|
nextLaunchWillBeInjected: false,
|
|
117219
117369
|
injectable: false
|
|
@@ -117221,18 +117371,41 @@ Fails if the simulator server is not running for the given UDID.`,
|
|
|
117221
117371
|
}
|
|
117222
117372
|
const blocked = await precheckNativeDevtools(api, params.udid);
|
|
117223
117373
|
if (blocked) return blocked;
|
|
117224
|
-
const
|
|
117225
|
-
const connected =
|
|
117226
|
-
|
|
117227
|
-
|
|
117228
|
-
|
|
117374
|
+
const measured = await api.appConnectionState(params.bundleId).catch(() => "indeterminate");
|
|
117375
|
+
const connected = measured === "connected";
|
|
117376
|
+
let appRunning;
|
|
117377
|
+
let state3 = measured;
|
|
117378
|
+
if (state3 === "indeterminate") {
|
|
117379
|
+
try {
|
|
117380
|
+
appRunning = await api.isAppRunning(params.bundleId);
|
|
117381
|
+
} catch (err) {
|
|
117382
|
+
const failure = api.getInitFailure();
|
|
117383
|
+
if (failure) return buildInitFailedResult(params.udid, failure);
|
|
117384
|
+
throw err;
|
|
117385
|
+
}
|
|
117386
|
+
if (!appRunning) state3 = "not_running";
|
|
117387
|
+
} else {
|
|
117388
|
+
appRunning = state3 !== "not_running";
|
|
117229
117389
|
}
|
|
117230
|
-
const envSetup = api
|
|
117390
|
+
const envSetup = envSetupReading(api, connected);
|
|
117231
117391
|
return {
|
|
117232
117392
|
envSetup,
|
|
117233
117393
|
appRunning,
|
|
117234
117394
|
connected,
|
|
117235
|
-
|
|
117395
|
+
// Derived from the one state, so it can never disagree with it: a relaunch
|
|
117396
|
+
// provably changes nothing for `unregistered`, destroys the handshake for
|
|
117397
|
+
// `connecting`, and `not_running` needs a launch. That leaves the two a
|
|
117398
|
+
// fresh process fixes — `indeterminate` among them, since an uninspectable
|
|
117399
|
+
// host (ios-remote) supports no finer reading. Both already carry a live
|
|
117400
|
+
// process (the settling above rewrote an empty `indeterminate` to
|
|
117401
|
+
// `not_running`), so an `appRunning` conjunct would only restate the state.
|
|
117402
|
+
requiresRestart: state3 === "stale_process" || state3 === "indeterminate",
|
|
117403
|
+
state: state3,
|
|
117404
|
+
// The booleans cannot express "one restart, then stop" — the shape
|
|
117405
|
+
// `indeterminate` needs, and the only one ios-remote can report for a
|
|
117406
|
+
// running app. Carrying the same prose as every other consumer keeps that
|
|
117407
|
+
// escape off the agent having read this tool's description.
|
|
117408
|
+
...state3 === "connected" ? {} : { message: buildAppStateMessage(params.bundleId, state3) },
|
|
117236
117409
|
nextLaunchWillBeInjected: envSetup,
|
|
117237
117410
|
injectable: true
|
|
117238
117411
|
};
|
|
@@ -117258,8 +117431,9 @@ var nativeNetworkLogsTool = {
|
|
|
117258
117431
|
description: `Retrieve network requests captured at the native NSURLProtocol level.
|
|
117259
117432
|
Unlike the JS-level network inspector (view-network-logs), this captures ALL network traffic from the app including native modules, Swift/Objective-C networking, and background transfers that bypass JS fetch.
|
|
117260
117433
|
Use when you need to inspect native-level HTTP traffic that is invisible to JS fetch interception.
|
|
117261
|
-
Returns { status, count, events } where each event contains URL, method, status code, headers, and timing.
|
|
117262
|
-
|
|
117434
|
+
Returns { status, count, events } where each event contains URL, method, status code, headers, and timing.
|
|
117435
|
+
If status is restart_required: follow the message (usually restart-app), then retry. If status is service_stale: the app is already injected, so restarting it cannot help \u2014 restart the tool-server (\`argent server stop && argent server start --detach\`) and retry. If the same status comes back after that restart, stop restarting: follow the message, which names the terminal fallback. If status is connect_pending: the app is injected and still connecting \u2014 do not restart it, wait a few seconds and retry. If status is init_failed: the simulator's native-devtools environment could not be initialised \u2014 follow the message (re-boot the simulator) rather than retrying this tool.
|
|
117436
|
+
A not-connected or not-running app comes back as one of those statuses rather than a failure. Failures are separate: an Apple system app is rejected outright (terminal \u2014 never retry it), while a missing host dependency or a udid that is not an Apple device is not.`,
|
|
117263
117437
|
zodSchema: zodSchema3,
|
|
117264
117438
|
services: (params) => ({
|
|
117265
117439
|
nativeDevtools: nativeDevtoolsRef(resolveDevice(params.udid))
|
|
@@ -117309,7 +117483,8 @@ var nativeFindViewsTool = {
|
|
|
117309
117483
|
Use when you need to locate a specific view by its properties without dumping the entire hierarchy.
|
|
117310
117484
|
Returns { status: "ok", matches } with matching views including their frames, properties, optional ancestors, and optional children. Much more targeted than native-full-hierarchy.
|
|
117311
117485
|
At least one of className, identifier, label, tag, or nativeID must be provided.
|
|
117312
|
-
|
|
117486
|
+
If status is restart_required: follow the message (usually restart-app), then retry. If status is service_stale: the app is already injected, so restarting it cannot help \u2014 restart the tool-server (\`argent server stop && argent server start --detach\`) and retry. If the same status comes back after that restart, stop restarting: follow the message, which names the terminal fallback. If status is connect_pending: the app is injected and still connecting \u2014 do not restart it, wait a few seconds and retry. If status is init_failed: the simulator's native-devtools environment could not be initialised \u2014 follow the message (re-boot the simulator) rather than retrying this tool.
|
|
117487
|
+
A not-connected or not-running app comes back as one of those statuses rather than a failure. Failures are separate: an Apple system app is rejected outright (terminal \u2014 never retry it), and the hierarchy query itself can error or time out.`,
|
|
117313
117488
|
zodSchema: zodSchema4,
|
|
117314
117489
|
services: (params) => ({
|
|
117315
117490
|
nativeDevtools: nativeDevtoolsRef(resolveDevice(params.udid))
|
|
@@ -117376,8 +117551,9 @@ var nativeFullHierarchyTool = {
|
|
|
117376
117551
|
WARNING: Output can be extremely large (100KB\u2013500KB+) for complex apps, especially those built with SwiftUI. Prefer native-find-views for targeted queries.
|
|
117377
117552
|
Use skipClasses / skipClassPrefixes to prune SwiftUI internal subtrees and reduce output size. Use the fields param to request only the properties you need.
|
|
117378
117553
|
Use when you need deep layout debugging, finding views with no accessibility labels, or verifying view structure not exposed through the accessibility tree.
|
|
117379
|
-
Returns { status: "ok", windows } with the full view hierarchy
|
|
117380
|
-
|
|
117554
|
+
Returns { status: "ok", windows } with the full view hierarchy.
|
|
117555
|
+
If status is restart_required: follow the message (usually restart-app), then retry. If status is service_stale: the app is already injected, so restarting it cannot help \u2014 restart the tool-server (\`argent server stop && argent server start --detach\`) and retry. If the same status comes back after that restart, stop restarting: follow the message, which names the terminal fallback. If status is connect_pending: the app is injected and still connecting \u2014 do not restart it, wait a few seconds and retry. If status is init_failed: the simulator's native-devtools environment could not be initialised \u2014 follow the message (re-boot the simulator) rather than retrying this tool.
|
|
117556
|
+
A not-connected or not-running app comes back as one of those statuses rather than a failure. Failures are separate: an Apple system app is rejected outright (terminal \u2014 never retry it), and the hierarchy query itself can error or time out.`,
|
|
117381
117557
|
zodSchema: zodSchema5,
|
|
117382
117558
|
services: (params) => ({
|
|
117383
117559
|
nativeDevtools: nativeDevtoolsRef(resolveDevice(params.udid))
|
|
@@ -117435,9 +117611,12 @@ Returns a flat list of accessibility leaf elements with:
|
|
|
117435
117611
|
This is a low-level native inspection tool. The normalized fields are intended to help
|
|
117436
117612
|
with backend migration work, but the public describe contract is still separate.
|
|
117437
117613
|
|
|
117438
|
-
|
|
117614
|
+
Use when you are evaluating or debugging the lower-level native data behind the public
|
|
117615
|
+
describe tool, or when you need its raw point-space geometry rather than describe's
|
|
117616
|
+
normalized contract.
|
|
117439
117617
|
|
|
117440
|
-
If status is restart_required:
|
|
117618
|
+
If status is restart_required: follow the message (usually restart-app), then retry. If status is service_stale: the app is already injected, so restarting it cannot help \u2014 restart the tool-server (\`argent server stop && argent server start --detach\`) and retry. If the same status comes back after that restart, stop restarting: follow the message, which names the terminal fallback. If status is connect_pending: the app is injected and still connecting \u2014 do not restart it, wait a few seconds and retry. If status is init_failed: the simulator's native-devtools environment could not be initialised \u2014 follow the message (re-boot the simulator) rather than retrying this tool.
|
|
117619
|
+
A not-connected or not-running app comes back as one of those statuses rather than a failure. Failures are separate: an Apple system app is rejected outright (terminal \u2014 never retry it), and the screen query itself can error or time out.`,
|
|
117441
117620
|
zodSchema: zodSchema6,
|
|
117442
117621
|
services: (params) => ({
|
|
117443
117622
|
nativeDevtools: nativeDevtoolsRef(resolveDevice(params.udid))
|
|
@@ -117504,10 +117683,19 @@ var nativeViewAtPointTool = {
|
|
|
117504
117683
|
Unlike native-user-interactable-view-at-point, this ignores userInteractionEnabled,
|
|
117505
117684
|
so it answers "what is visually here?" rather than "what would receive the touch?".
|
|
117506
117685
|
|
|
117686
|
+
Use when a screenshot shows something the accessibility tree does not name \u2014 an
|
|
117687
|
+
unlabeled icon, a decorative overlay, a custom-drawn cell \u2014 and you need the class,
|
|
117688
|
+
identifier or nativeID of whatever draws it.
|
|
117689
|
+
|
|
117690
|
+
Returns { status: "ok", view }: the matched view with its class name, frames,
|
|
117691
|
+
identifier, label and layer name, its ancestor chain by default, and its subviews on
|
|
117692
|
+
request. view is null when nothing is drawn at that point.
|
|
117693
|
+
|
|
117507
117694
|
IMPORTANT: x and y are raw iOS window coordinates in points, NOT normalized [0,1]
|
|
117508
117695
|
simulator tap coordinates.
|
|
117509
117696
|
|
|
117510
|
-
If status is restart_required:
|
|
117697
|
+
If status is restart_required: follow the message (usually restart-app), then retry. If status is service_stale: the app is already injected, so restarting it cannot help \u2014 restart the tool-server (\`argent server stop && argent server start --detach\`) and retry. If the same status comes back after that restart, stop restarting: follow the message, which names the terminal fallback. If status is connect_pending: the app is injected and still connecting \u2014 do not restart it, wait a few seconds and retry. If status is init_failed: the simulator's native-devtools environment could not be initialised \u2014 follow the message (re-boot the simulator) rather than retrying this tool.
|
|
117698
|
+
A not-connected or not-running app comes back as one of those statuses rather than a failure. Failures are separate: an Apple system app is rejected outright (terminal \u2014 never retry it), and the point query itself can error or time out.`,
|
|
117511
117699
|
zodSchema: zodSchema7,
|
|
117512
117700
|
services: (params) => ({
|
|
117513
117701
|
nativeDevtools: nativeDevtoolsRef(resolveDevice(params.udid))
|
|
@@ -117580,10 +117768,19 @@ var nativeUserInteractableViewAtPointTool = {
|
|
|
117580
117768
|
Unlike native-view-at-point, this respects userInteractionEnabled and is closer to
|
|
117581
117769
|
UIKit hit-testing semantics.
|
|
117582
117770
|
|
|
117771
|
+
Use when a tap lands somewhere unexpected or does nothing, to see which control
|
|
117772
|
+
UIKit would hand the touch to \u2014 a transparent overlay swallowing it, a parent
|
|
117773
|
+
recognizer, a disabled button.
|
|
117774
|
+
|
|
117775
|
+
Returns { status: "ok", view }: the hit-test winner with its class name, frames,
|
|
117776
|
+
identifier, label and layer name, its ancestor chain by default, and its subviews on
|
|
117777
|
+
request. view is null when no touchable view sits under that point.
|
|
117778
|
+
|
|
117583
117779
|
IMPORTANT: x and y are raw iOS window coordinates in points, NOT normalized [0,1]
|
|
117584
117780
|
simulator tap coordinates.
|
|
117585
117781
|
|
|
117586
|
-
If status is restart_required:
|
|
117782
|
+
If status is restart_required: follow the message (usually restart-app), then retry. If status is service_stale: the app is already injected, so restarting it cannot help \u2014 restart the tool-server (\`argent server stop && argent server start --detach\`) and retry. If the same status comes back after that restart, stop restarting: follow the message, which names the terminal fallback. If status is connect_pending: the app is injected and still connecting \u2014 do not restart it, wait a few seconds and retry. If status is init_failed: the simulator's native-devtools environment could not be initialised \u2014 follow the message (re-boot the simulator) rather than retrying this tool.
|
|
117783
|
+
A not-connected or not-running app comes back as one of those statuses rather than a failure. Failures are separate: an Apple system app is rejected outright (terminal \u2014 never retry it), and the point query itself can error or time out.`,
|
|
117587
117784
|
zodSchema: zodSchema8,
|
|
117588
117785
|
services: (params) => ({
|
|
117589
117786
|
nativeDevtools: nativeDevtoolsRef(resolveDevice(params.udid))
|
|
@@ -129257,17 +129454,17 @@ function tagExpWithClosingIndex(xmlData, i, closingChar = ">") {
|
|
|
129257
129454
|
}
|
|
129258
129455
|
}
|
|
129259
129456
|
}
|
|
129260
|
-
function findClosingIndex(xmlData, str, i,
|
|
129457
|
+
function findClosingIndex(xmlData, str, i, errMsg4) {
|
|
129261
129458
|
const closingIndex = xmlData.indexOf(str, i);
|
|
129262
129459
|
if (closingIndex === -1) {
|
|
129263
|
-
throw new Error(
|
|
129460
|
+
throw new Error(errMsg4);
|
|
129264
129461
|
} else {
|
|
129265
129462
|
return closingIndex + str.length - 1;
|
|
129266
129463
|
}
|
|
129267
129464
|
}
|
|
129268
|
-
function findClosingChar(xmlData, char, i,
|
|
129465
|
+
function findClosingChar(xmlData, char, i, errMsg4) {
|
|
129269
129466
|
const closingIndex = xmlData.indexOf(char, i);
|
|
129270
|
-
if (closingIndex === -1) throw new Error(
|
|
129467
|
+
if (closingIndex === -1) throw new Error(errMsg4);
|
|
129271
129468
|
return closingIndex;
|
|
129272
129469
|
}
|
|
129273
129470
|
function readTagExp(xmlData, i, removeNSPrefix, closingChar = ">") {
|
|
@@ -145259,6 +145456,16 @@ var FULL_HIERARCHY_FIELDS = [
|
|
|
145259
145456
|
// request, which just leaves the wait's poll unconfirmed.
|
|
145260
145457
|
"firstResponder"
|
|
145261
145458
|
];
|
|
145459
|
+
async function unreadableHierarchyReason(nativeApi, bundleId) {
|
|
145460
|
+
if (!isInjectableBundleId(bundleId)) {
|
|
145461
|
+
return `${bundleId} is an Apple system app: it is a platform binary with library validation, so argent's native devtools cannot be relied on to inject into it, and without them a flow has no view hierarchy to resolve selectors against. Replace the selector steps with coordinate ones \u2014 \`tap: { x: 0.5, y: 0.35 }\` takes a point directly and reads no tree \u2014 or target an app argent installs.`;
|
|
145462
|
+
}
|
|
145463
|
+
const state3 = await nativeApi.appConnectionState(bundleId).catch(() => "indeterminate");
|
|
145464
|
+
if (state3 === "connected") {
|
|
145465
|
+
return `native devtools reported no connected app while this tree was being read, but ${bundleId} is connected now \u2014 the connection arrived mid-read. Retry: flows resolve selectors against the full view hierarchy native devtools serve.`;
|
|
145466
|
+
}
|
|
145467
|
+
return `${buildAppStateMessage(bundleId, state3)} Flows resolve selectors against the full view hierarchy native devtools serve.`;
|
|
145468
|
+
}
|
|
145262
145469
|
async function queryFullHierarchyTree(registry2, device, launchedNativeApp) {
|
|
145263
145470
|
let nativeApi;
|
|
145264
145471
|
try {
|
|
@@ -145266,10 +145473,13 @@ async function queryFullHierarchyTree(registry2, device, launchedNativeApp) {
|
|
|
145266
145473
|
nativeApi = await registry2.resolveService(ndRef.urn, ndRef.options);
|
|
145267
145474
|
} catch (err) {
|
|
145268
145475
|
throw new Error(
|
|
145269
|
-
`native devtools is unavailable (${
|
|
145476
|
+
`native devtools is unavailable (${errMsg2(err)}) \u2014 flows resolve selectors against the full view hierarchy it serves`,
|
|
145270
145477
|
{ cause: err }
|
|
145271
145478
|
);
|
|
145272
145479
|
}
|
|
145480
|
+
if (launchedNativeApp !== void 0 && nativeApi.listConnectedBundleIds().length === 0) {
|
|
145481
|
+
throw new Error(await unreadableHierarchyReason(nativeApi, launchedNativeApp));
|
|
145482
|
+
}
|
|
145273
145483
|
let target;
|
|
145274
145484
|
try {
|
|
145275
145485
|
target = await resolveNativeTargetApp(nativeApi, void 0);
|
|
@@ -145281,11 +145491,6 @@ async function queryFullHierarchyTree(registry2, device, launchedNativeApp) {
|
|
|
145281
145491
|
throw err;
|
|
145282
145492
|
}
|
|
145283
145493
|
}
|
|
145284
|
-
if (await nativeApi.requiresAppRestart(target.bundleId)) {
|
|
145285
|
-
throw new Error(
|
|
145286
|
-
`${target.bundleId} was launched before argent's instrumentation loaded \u2014 relaunch it (launch-app, or a flow \`launch\` step) so the full view hierarchy is readable`
|
|
145287
|
-
);
|
|
145288
|
-
}
|
|
145289
145494
|
const rawResult = await nativeApi.queryViewHierarchy(
|
|
145290
145495
|
target.bundleId,
|
|
145291
145496
|
"ViewHierarchy.getFullHierarchy",
|
|
@@ -145305,7 +145510,7 @@ async function queryFullHierarchyTree(registry2, device, launchedNativeApp) {
|
|
|
145305
145510
|
const { tree, screen } = adaptFullHierarchy(rawResult);
|
|
145306
145511
|
return { tree, source: "native-devtools", ...screen ? { screen } : {} };
|
|
145307
145512
|
}
|
|
145308
|
-
function
|
|
145513
|
+
function errMsg2(err) {
|
|
145309
145514
|
return err instanceof Error ? err.message : String(err);
|
|
145310
145515
|
}
|
|
145311
145516
|
|
|
@@ -145538,15 +145743,23 @@ var REPLAY_TREE_SOURCES = {
|
|
|
145538
145743
|
ios: "native-devtools",
|
|
145539
145744
|
android: "android-devtools"
|
|
145540
145745
|
};
|
|
145746
|
+
function recordedLaunchedApp(session, platform) {
|
|
145747
|
+
for (let i = session.flow.steps.length - 1; i >= 0; i--) {
|
|
145748
|
+
const step = session.flow.steps[i];
|
|
145749
|
+
if (step.kind === "launch") return appIdForPlatform(step.app, platform) ?? void 0;
|
|
145750
|
+
}
|
|
145751
|
+
return void 0;
|
|
145752
|
+
}
|
|
145541
145753
|
function fallbackSourceWarning(source, platform) {
|
|
145542
145754
|
const expected = REPLAY_TREE_SOURCES[platform];
|
|
145543
145755
|
if (!expected || source === expected) return void 0;
|
|
145544
145756
|
return `selector captured from the fallback ${source} tree (${expected} unavailable) \u2014 replay resolves against the full hierarchy, which may not match it`;
|
|
145545
145757
|
}
|
|
145546
|
-
async function captureTapSelector(registry2, udid, point) {
|
|
145758
|
+
async function captureTapSelector(registry2, session, udid, point) {
|
|
145547
145759
|
try {
|
|
145548
145760
|
const device = resolveDevice(udid);
|
|
145549
|
-
const
|
|
145761
|
+
const launched = recordedLaunchedApp(session, device.platform);
|
|
145762
|
+
const { tree, source } = await fetchFlowTree(registry2, device, launched);
|
|
145550
145763
|
const node = nodeAtPoint(tree, point);
|
|
145551
145764
|
if (!node) return { warning: "no element found under the tap; kept coordinates (brittle)" };
|
|
145552
145765
|
const selector = deriveSelector(node);
|
|
@@ -145702,7 +145915,7 @@ If a step was recorded by mistake, edit the .yaml to remove it \u2014 against a
|
|
|
145702
145915
|
const isTap = params.command === "gesture-tap" && params.delayMs === void 0 && typeof args.udid === "string" && typeof args.x === "number" && typeof args.y === "number";
|
|
145703
145916
|
let captured;
|
|
145704
145917
|
if (isTap) {
|
|
145705
|
-
captured = await captureTapSelector(registry2, args.udid, {
|
|
145918
|
+
captured = await captureTapSelector(registry2, session, args.udid, {
|
|
145706
145919
|
x: args.x,
|
|
145707
145920
|
y: args.y
|
|
145708
145921
|
});
|
|
@@ -149129,8 +149342,9 @@ var fileInputs3 = [
|
|
|
149129
149342
|
];
|
|
149130
149343
|
var MAX_RUN_DEPTH = 20;
|
|
149131
149344
|
var POST_LAUNCH_SETTLE_MS = 1500;
|
|
149132
|
-
var NATIVE_READY_TIMEOUT_MS =
|
|
149345
|
+
var NATIVE_READY_TIMEOUT_MS = NATIVE_DEVTOOLS_CONNECT_BUDGET_MS;
|
|
149133
149346
|
var NATIVE_READY_POLL_MS = 250;
|
|
149347
|
+
var LAUNCH_TO_VERDICT_MS = POST_LAUNCH_SETTLE_MS + NATIVE_READY_TIMEOUT_MS;
|
|
149134
149348
|
var FOREGROUND_CHANGING_TOOLS = /* @__PURE__ */ new Set([
|
|
149135
149349
|
"launch-app",
|
|
149136
149350
|
"restart-app",
|
|
@@ -149143,15 +149357,35 @@ async function waitForNativeDevtools(registry2, device, bundleId, signal) {
|
|
|
149143
149357
|
try {
|
|
149144
149358
|
const ref = nativeDevtoolsRef(device);
|
|
149145
149359
|
api = await registry2.resolveService(ref.urn, ref.options);
|
|
149146
|
-
} catch {
|
|
149147
|
-
return
|
|
149360
|
+
} catch (err) {
|
|
149361
|
+
if (!isInjectableBundleId(bundleId)) return null;
|
|
149362
|
+
return `the native-devtools service is unavailable for ${bundleId} (${errMsg3(err)})`;
|
|
149148
149363
|
}
|
|
149149
149364
|
const deadline = Date.now() + NATIVE_READY_TIMEOUT_MS;
|
|
149150
149365
|
for (; ; ) {
|
|
149151
|
-
if (signal?.aborted) return
|
|
149152
|
-
if (api.isConnected(bundleId)) return
|
|
149153
|
-
if (Date.now() >= deadline)
|
|
149154
|
-
if (!await sleepOrAbort(NATIVE_READY_POLL_MS, signal)) return
|
|
149366
|
+
if (signal?.aborted) return null;
|
|
149367
|
+
if (api.isConnected(bundleId)) return null;
|
|
149368
|
+
if (Date.now() >= deadline) break;
|
|
149369
|
+
if (!await sleepOrAbort(NATIVE_READY_POLL_MS, signal)) return null;
|
|
149370
|
+
}
|
|
149371
|
+
if (!isInjectableBundleId(bundleId)) return null;
|
|
149372
|
+
const state3 = await api.appConnectionState(bundleId).catch(() => "indeterminate");
|
|
149373
|
+
if (state3 === "connected") return null;
|
|
149374
|
+
return flowLaunchGateReason(bundleId, state3);
|
|
149375
|
+
}
|
|
149376
|
+
function flowLaunchGateReason(bundleId, state3) {
|
|
149377
|
+
const measured = buildAppStateMessage(bundleId, state3);
|
|
149378
|
+
switch (state3) {
|
|
149379
|
+
case "not_running":
|
|
149380
|
+
return `${bundleId} was relaunched by this step and is no longer running ${LAUNCH_TO_VERDICT_MS} ms later, so it exited after launch rather than failing to connect. Re-running the flow repeats the same launch: start it by hand (launch-app, then describe or screenshot) to see the crash or early exit first.`;
|
|
149381
|
+
case "stale_process":
|
|
149382
|
+
return `${measured} This step already relaunched it, so the process it measured predates whatever the relaunch would have given it \u2014 re-run the flow to launch again. If it lands here twice, the simulator's launchd environment is not holding argent's instrumentation: re-boot the device (boot-device with force) before re-running.`;
|
|
149383
|
+
case "unregistered":
|
|
149384
|
+
return `${measured} A cold start slower than the ${LAUNCH_TO_VERDICT_MS} ms this step waited reads the same way \u2014 if that is likely, re-run the flow to relaunch and wait again before restarting anything.`;
|
|
149385
|
+
case "connecting":
|
|
149386
|
+
return `${measured} This step launched it ${LAUNCH_TO_VERDICT_MS} ms before that reading, so the process being measured started after the step's own launch \u2014 something relaunched it in between. Re-run the flow once the app is settled.`;
|
|
149387
|
+
case "indeterminate":
|
|
149388
|
+
return `${measured} This step already performed that one restart, so re-run the flow at most once more before restarting the tool-server rather than the app.`;
|
|
149155
149389
|
}
|
|
149156
149390
|
}
|
|
149157
149391
|
async function waitForVegaAutomation(device, signal) {
|
|
@@ -149178,9 +149412,9 @@ async function androidDevtoolsReady(registry2, device) {
|
|
|
149178
149412
|
}
|
|
149179
149413
|
async function treeSourceGate(registry2, device, bundleId, signal) {
|
|
149180
149414
|
if (device.platform === "ios" && !signal?.aborted) {
|
|
149181
|
-
const
|
|
149182
|
-
if (
|
|
149183
|
-
return `could not connect to native devtools
|
|
149415
|
+
const reason = await waitForNativeDevtools(registry2, device, bundleId, signal);
|
|
149416
|
+
if (reason !== null && !signal?.aborted) {
|
|
149417
|
+
return `could not connect to native devtools. ${reason}`;
|
|
149184
149418
|
}
|
|
149185
149419
|
}
|
|
149186
149420
|
if (device.platform === "android" && !signal?.aborted) {
|
|
@@ -149209,11 +149443,15 @@ async function runLaunch(state3, app) {
|
|
|
149209
149443
|
reason: `no app id declared for platform "${device.platform}" \u2014 add a launch entry for it`
|
|
149210
149444
|
};
|
|
149211
149445
|
}
|
|
149446
|
+
let restart;
|
|
149212
149447
|
try {
|
|
149213
|
-
await invokeOnDevice(env, "restart-app", { bundleId });
|
|
149448
|
+
restart = await invokeOnDevice(env, "restart-app", { bundleId });
|
|
149214
149449
|
} catch (err) {
|
|
149215
149450
|
if (signal?.aborted) return ABORTED_OUTCOME;
|
|
149216
|
-
return { ok: false, reason: `restart-app failed: ${
|
|
149451
|
+
return { ok: false, reason: `restart-app failed: ${errMsg3(err)}` };
|
|
149452
|
+
}
|
|
149453
|
+
if (isNativeDevtoolsBlockResult("restart-app", restart)) {
|
|
149454
|
+
return { ok: false, reason: `restart-app did not start ${bundleId}: ${restart.message}` };
|
|
149217
149455
|
}
|
|
149218
149456
|
if (!await sleepOrAbort(POST_LAUNCH_SETTLE_MS, signal)) return ABORTED_OUTCOME;
|
|
149219
149457
|
const gate = await treeSourceGate(registry2, device, bundleId, signal);
|
|
@@ -149247,7 +149485,7 @@ async function runChromiumLaunch(state3, app) {
|
|
|
149247
149485
|
} catch (err) {
|
|
149248
149486
|
return {
|
|
149249
149487
|
ok: false,
|
|
149250
|
-
reason: `could not attach to chromium instance "${device.id}": ${
|
|
149488
|
+
reason: `could not attach to chromium instance "${device.id}": ${errMsg3(err)}`
|
|
149251
149489
|
};
|
|
149252
149490
|
}
|
|
149253
149491
|
state3.attachedAppPath = await resolveAppPath(spec.path, state3.flowsDir);
|
|
@@ -149314,7 +149552,7 @@ function singleInstanceLockHint(suspects) {
|
|
|
149314
149552
|
return `A clean exit before CDP comes up is the signature of a single-instance lock \u2014 an already-running copy of the app quits the new one at startup. ${clauses.join(" ")}`;
|
|
149315
149553
|
}
|
|
149316
149554
|
async function chromiumBootFailureReason(state3, err) {
|
|
149317
|
-
const base = `could not boot the chromium app: ${
|
|
149555
|
+
const base = `could not boot the chromium app: ${errMsg3(err)}`;
|
|
149318
149556
|
if (!singleInstanceLockSignal(err)) return base;
|
|
149319
149557
|
return `${base} ${singleInstanceLockHint(await liveLockSuspects(state3))}`;
|
|
149320
149558
|
}
|
|
@@ -149597,7 +149835,7 @@ function resolveOpts(params) {
|
|
|
149597
149835
|
function hoistedBootFailure(err) {
|
|
149598
149836
|
const signal = singleInstanceLockSignal(err);
|
|
149599
149837
|
if (!signal) return err;
|
|
149600
|
-
return wrapFailure(err, signal, `${
|
|
149838
|
+
return wrapFailure(err, signal, `${errMsg3(err)} ${singleInstanceLockHint(NO_LOCK_SUSPECTS)}`);
|
|
149601
149839
|
}
|
|
149602
149840
|
function pinnedToChromium(device) {
|
|
149603
149841
|
return device !== void 0 && resolveDevice(device).platform === "chromium";
|
|
@@ -150017,7 +150255,7 @@ async function execRunStep(state3, step, scope) {
|
|
|
150017
150255
|
try {
|
|
150018
150256
|
fragment = parseFlow(await fs49.readFile(canonical, "utf8"));
|
|
150019
150257
|
} catch (err) {
|
|
150020
|
-
return fail(`could not load fragment "${target}": ${
|
|
150258
|
+
return fail(`could not load fragment "${target}": ${errMsg3(err)}`);
|
|
150021
150259
|
}
|
|
150022
150260
|
pushReport(state3, {
|
|
150023
150261
|
index,
|
|
@@ -150072,7 +150310,7 @@ async function execLeafStep(state3, step, index, scope) {
|
|
|
150072
150310
|
...r.warning !== void 0 ? { warning: r.warning } : {}
|
|
150073
150311
|
};
|
|
150074
150312
|
} catch (err) {
|
|
150075
|
-
return { ...base, status: "error", reason:
|
|
150313
|
+
return { ...base, status: "error", reason: errMsg3(err) };
|
|
150076
150314
|
}
|
|
150077
150315
|
}
|
|
150078
150316
|
case "wait": {
|
|
@@ -150101,7 +150339,7 @@ async function execLeafStep(state3, step, index, scope) {
|
|
|
150101
150339
|
artifacts: r.artifacts
|
|
150102
150340
|
};
|
|
150103
150341
|
} catch (err) {
|
|
150104
|
-
return { ...base, status: "error", reason:
|
|
150342
|
+
return { ...base, status: "error", reason: errMsg3(err) };
|
|
150105
150343
|
}
|
|
150106
150344
|
}
|
|
150107
150345
|
case "tool": {
|
|
@@ -150157,16 +150395,31 @@ async function execLeafStep(state3, step, index, scope) {
|
|
|
150157
150395
|
args
|
|
150158
150396
|
};
|
|
150159
150397
|
}
|
|
150398
|
+
if (isNativeDevtoolsBlockResult(step.name, result)) {
|
|
150399
|
+
return {
|
|
150400
|
+
...base,
|
|
150401
|
+
status: "fail",
|
|
150402
|
+
tool: step.name,
|
|
150403
|
+
reason: `${step.name} did not run (${result.status}): ${result.message}`,
|
|
150404
|
+
result,
|
|
150405
|
+
outputHint,
|
|
150406
|
+
args
|
|
150407
|
+
};
|
|
150408
|
+
}
|
|
150409
|
+
if (step.name === "launch-app" || step.name === "restart-app") {
|
|
150410
|
+
const launched = args.bundleId;
|
|
150411
|
+
if (typeof launched === "string") state3.launchedNativeApp = launched;
|
|
150412
|
+
}
|
|
150160
150413
|
return { ...base, status: "pass", tool: step.name, result, outputHint, args };
|
|
150161
150414
|
} catch (err) {
|
|
150162
|
-
return { ...base, status: "error", tool: step.name, reason:
|
|
150415
|
+
return { ...base, status: "error", tool: step.name, reason: errMsg3(err) };
|
|
150163
150416
|
}
|
|
150164
150417
|
}
|
|
150165
150418
|
default:
|
|
150166
150419
|
return { ...base, status: "error", reason: `unsupported step kind` };
|
|
150167
150420
|
}
|
|
150168
150421
|
}
|
|
150169
|
-
function
|
|
150422
|
+
function errMsg3(err) {
|
|
150170
150423
|
return err instanceof Error ? err.message : String(err);
|
|
150171
150424
|
}
|
|
150172
150425
|
async function resolveFlowSource(params, fileInput, flowPathInput) {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -43,7 +43,7 @@ The full iOS flow tree exists only for an app launched by Argent with instrument
|
|
|
43
43
|
|
|
44
44
|
1. If Metro, Expo, Xcode, an icon, or a prior process launched the app, call `restart-app`. Restore the source screen and retry capture. `launch-app` can only foreground the existing process.
|
|
45
45
|
2. Tap capture does **not** wait for that connection. It makes one tree read and turns any failure straight into the kept-coordinates warning. A recording-time `restart-app` returns before the devtools connection opens, so the first tap after a restart can warn transiently. Re-record that tap once before escalating; only a warning that survives the retry is evidence of a real fault.
|
|
46
|
-
3. If the warning survives, call `native-devtools-status` with the same UDID and bundle id
|
|
46
|
+
3. If the warning survives, call `native-devtools-status` with the same UDID and bundle id and follow its `message`, which names the one action that helps and says when to stop. `requiresRestart` covers only the states a fresh process fixes: an `unregistered` or `connecting` app reports it false, because it already launched under the terms a restart would recreate.
|
|
47
47
|
4. If an injectable app remains disconnected, call `stop-all-simulator-servers` once, **scoped to `devices: [<this simulator's UDID>]`**. One tool-server serves every agent on this Argent install, so an unscoped call tears down their devices too. This does not change app or account data. Then restart and check status again.
|
|
48
48
|
5. If it still fails, report an instrumentation blocker. Do not replace selectors with coordinates in a QA flow.
|
|
49
49
|
|
|
@@ -53,20 +53,20 @@ Use the same explicit UDID throughout. Multiple booted simulators are not an inj
|
|
|
53
53
|
|
|
54
54
|
This fallback applies only to `com.apple.*` system apps. A connection failure in another app never authorizes it.
|
|
55
55
|
|
|
56
|
-
Apple system apps
|
|
56
|
+
Apple system apps are platform binaries with library validation, so the instrumentation cannot be relied on to load into them — it has been seen both loading and not loading, depending on the simulator runtime. Either way it is no basis for a selector.
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
Give the flow a `launch:` step as usual. On iOS the launch waits the full devtools budget out, then passes for one of these bundle ids: starting the app is all that step is for, and a coordinate-driven flow needs nothing more. The flow stays e2e; it just pays roughly sixteen seconds at the launch. Where the impossibility bites is selector resolution, and the first selector step reports it there — terminally, naming the coordinate remedy — rather than as a launch failure. The rest of the injection-free form:
|
|
59
59
|
|
|
60
60
|
- Raw `tool: await-ui-element` accessibility checks.
|
|
61
61
|
- Point taps or long-presses derived from `describe`, each named by an echo.
|
|
62
62
|
- A point focus tap plus a raw text-only `keyboard` with `delayMs: 500`, and a second raw `keyboard` with `key: "enter"` to submit.
|
|
63
63
|
- Raw swipes with `settle: true` because `scroll-to` needs the missing flow tree. Momentum-free scrolling keeps later coordinate taps valid.
|
|
64
64
|
|
|
65
|
-
Every point tap or long-press in such a flow passes **carrying a warning
|
|
65
|
+
Every point tap or long-press in such a flow passes **carrying a warning** for as long as the app serves no tree: each [selector-less gesture](flow-yaml.md#directives) dispatches unsettled. Nothing here repairs it. Accept the warnings, read each green as "the gesture was sent, not that it landed", and put an explicit `wait:` or a raw `tool: await-ui-element` before a gesture that follows a transition. Raw `tool:` steps never take that settle, so they never warn.
|
|
66
66
|
|
|
67
67
|
Report that the flow is injection-free and its coordinates are not portable. It cannot satisfy the QA contract. Report the artifact and platform blocker instead.
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
A normally injectable app that is broken in the environment gets the same coordinate-only treatment, but not the same launch: there the `launch:` step fails, since the gate withholds its verdict only for a bundle id injection may never reach. Start such a flow with a raw `tool: restart-app`, which terminates and relaunches without the readiness gate, and accept that the result is a **fragment** — its first non-echo step is not `launch:`, so the runner never classifies it as e2e, and it cannot complete `argent-qa-flows`, which requires a leading `launch:`. Report the blocker rather than labeling that fallback a completed QA test.
|
|
70
70
|
|
|
71
71
|
## Tree source recovery on Android, Chromium, and Vega
|
|
72
72
|
|
|
@@ -106,7 +106,7 @@ Read the exact error and choose the action that matches it:
|
|
|
106
106
|
- `describe` succeeds but is not detailed enough for a React Native app:
|
|
107
107
|
use `debugger-component-tree` next.
|
|
108
108
|
- You need app-scoped inspection with full UIKit properties (`accessibilityIdentifier`, `viewClassName`):
|
|
109
|
-
use `native-describe-screen` with an explicit `bundleId`. This requires native devtools (dylib) injection
|
|
109
|
+
use `native-describe-screen` with an explicit `bundleId`. This requires native devtools (dylib) injection.
|
|
110
110
|
- You already have a candidate point and want to confirm what would actually receive touch:
|
|
111
111
|
use `native-user-interactable-view-at-point`. Use `native-view-at-point` when you want the visually deepest view instead of the hit-test target.
|
|
112
112
|
|