swipium 2.0.1 → 2.2.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/CHANGELOG.md +64 -0
- package/README.md +32 -20
- package/THREAT_MODEL.md +109 -21
- package/dist/automationGen/run.js.map +1 -1
- package/dist/cli/init.js +101 -11
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/verify.js +2 -2
- package/dist/cli/verify.js.map +1 -1
- package/dist/consent/consent.js +365 -17
- package/dist/consent/consent.js.map +1 -1
- package/dist/context/projectRoot.js +9 -4
- package/dist/context/projectRoot.js.map +1 -1
- package/dist/context/protocolEra.js +15 -0
- package/dist/context/protocolEra.js.map +1 -0
- package/dist/drivers/DirectDriver.js +5 -2
- package/dist/drivers/DirectDriver.js.map +1 -1
- package/dist/featureTesting/executionBootstrap.js +85 -77
- package/dist/featureTesting/executionBootstrap.js.map +1 -1
- package/dist/flows/run.js +9 -5
- package/dist/flows/run.js.map +1 -1
- package/dist/lib/abortScope.js +36 -3
- package/dist/lib/abortScope.js.map +1 -1
- package/dist/lib/android.js +15 -6
- package/dist/lib/android.js.map +1 -1
- package/dist/lib/codexEnv.js +112 -0
- package/dist/lib/codexEnv.js.map +1 -0
- package/dist/lib/logger.js +18 -0
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/result.js +188 -10
- package/dist/lib/result.js.map +1 -1
- package/dist/lib/schemaHash.js +20 -31
- package/dist/lib/schemaHash.js.map +1 -1
- package/dist/lib/simctl.js +102 -3
- package/dist/lib/simctl.js.map +1 -1
- package/dist/lib/toolSchema.js +143 -0
- package/dist/lib/toolSchema.js.map +1 -0
- package/dist/lib/wda.js +5 -2
- package/dist/lib/wda.js.map +1 -1
- package/dist/mobileAudit/runner.js +12 -4
- package/dist/mobileAudit/runner.js.map +1 -1
- package/dist/oracle/failures.js +2 -2
- package/dist/oracle/failures.js.map +1 -1
- package/dist/orchestration/testThis/execute.js +21 -4
- package/dist/orchestration/testThis/execute.js.map +1 -1
- package/dist/orchestration/testThis/pipeline.js +2 -0
- package/dist/orchestration/testThis/pipeline.js.map +1 -1
- package/dist/orchestration/testThis/plan.js.map +1 -1
- package/dist/server.js +564 -139
- package/dist/server.js.map +1 -1
- package/dist/services/prepareAndroid.js +3 -2
- package/dist/services/prepareAndroid.js.map +1 -1
- package/dist/services/prepareIos.js +31 -4
- package/dist/services/prepareIos.js.map +1 -1
- package/dist/services/smoke.js +38 -4
- package/dist/services/smoke.js.map +1 -1
- package/dist/session/processRegistry.js +12 -1
- package/dist/session/processRegistry.js.map +1 -1
- package/dist/snapshot/parse.js +64 -9
- package/dist/snapshot/parse.js.map +1 -1
- package/dist/snapshot/present.js +14 -4
- package/dist/snapshot/present.js.map +1 -1
- package/dist/snapshot/settle.js +14 -3
- package/dist/snapshot/settle.js.map +1 -1
- package/dist/tools/act.js +694 -670
- package/dist/tools/act.js.map +1 -1
- package/dist/tools/agent.js +15 -16
- package/dist/tools/agent.js.map +1 -1
- package/dist/tools/appControl.js +4 -4
- package/dist/tools/appControl.js.map +1 -1
- package/dist/tools/appMap.js +11 -13
- package/dist/tools/appMap.js.map +1 -1
- package/dist/tools/build.js +4 -6
- package/dist/tools/build.js.map +1 -1
- package/dist/tools/bundletool.js +3 -3
- package/dist/tools/bundletool.js.map +1 -1
- package/dist/tools/clearOverlay.js +2 -3
- package/dist/tools/clearOverlay.js.map +1 -1
- package/dist/tools/device.js +4 -3
- package/dist/tools/device.js.map +1 -1
- package/dist/tools/doctor.js +23 -11
- package/dist/tools/doctor.js.map +1 -1
- package/dist/tools/explore.js +15 -21
- package/dist/tools/explore.js.map +1 -1
- package/dist/tools/featureTesting.js +22 -6
- package/dist/tools/featureTesting.js.map +1 -1
- package/dist/tools/firstRun.js +4 -5
- package/dist/tools/firstRun.js.map +1 -1
- package/dist/tools/flow.js +13 -17
- package/dist/tools/flow.js.map +1 -1
- package/dist/tools/flowRepair.js +3 -5
- package/dist/tools/flowRepair.js.map +1 -1
- package/dist/tools/generate.js +10 -11
- package/dist/tools/generate.js.map +1 -1
- package/dist/tools/getArtifact.js +114 -10
- package/dist/tools/getArtifact.js.map +1 -1
- package/dist/tools/health.js +2 -1
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/ios.js +35 -13
- package/dist/tools/ios.js.map +1 -1
- package/dist/tools/issues.js +8 -8
- package/dist/tools/issues.js.map +1 -1
- package/dist/tools/jobs.js +26 -10
- package/dist/tools/jobs.js.map +1 -1
- package/dist/tools/metro.js +4 -5
- package/dist/tools/metro.js.map +1 -1
- package/dist/tools/mobileAudit.js +11 -10
- package/dist/tools/mobileAudit.js.map +1 -1
- package/dist/tools/note.js +2 -3
- package/dist/tools/note.js.map +1 -1
- package/dist/tools/prepareIosTarget.js +102 -31
- package/dist/tools/prepareIosTarget.js.map +1 -1
- package/dist/tools/prepareTarget.js +3 -5
- package/dist/tools/prepareTarget.js.map +1 -1
- package/dist/tools/report.js +4 -5
- package/dist/tools/report.js.map +1 -1
- package/dist/tools/resolveArtifact.js +2 -3
- package/dist/tools/resolveArtifact.js.map +1 -1
- package/dist/tools/resolveTarget.js +3 -6
- package/dist/tools/resolveTarget.js.map +1 -1
- package/dist/tools/screenRecord.js +2 -3
- package/dist/tools/screenRecord.js.map +1 -1
- package/dist/tools/screenshot.js +2 -2
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/smoke.js +4 -2
- package/dist/tools/smoke.js.map +1 -1
- package/dist/tools/snapshot.js +6 -7
- package/dist/tools/snapshot.js.map +1 -1
- package/dist/tools/startSession.js +12 -16
- package/dist/tools/startSession.js.map +1 -1
- package/dist/tools/suite.js +2 -4
- package/dist/tools/suite.js.map +1 -1
- package/dist/tools/testSuite.js +13 -19
- package/dist/tools/testSuite.js.map +1 -1
- package/dist/tools/testThis.js +28 -13
- package/dist/tools/testThis.js.map +1 -1
- package/dist/tools/visual.js +7 -9
- package/dist/tools/visual.js.map +1 -1
- package/dist/tools/wait.js +130 -21
- package/dist/tools/wait.js.map +1 -1
- package/dist/tools/wda.js +199 -60
- package/dist/tools/wda.js.map +1 -1
- package/dist/version.js +1 -1
- package/docs/README.md +4 -4
- package/docs/ci-reports.md +39 -7
- package/docs/concepts.md +37 -25
- package/docs/flows.md +1 -1
- package/docs/mcp-server.md +281 -76
- package/docs/physical-devices.md +4 -4
- package/docs/tools.md +59 -30
- package/package.json +4 -3
package/dist/tools/wait.js
CHANGED
|
@@ -1,44 +1,153 @@
|
|
|
1
1
|
// qa_wait: non-shell synchronization for setup conditions, so agents don't shell out to
|
|
2
|
-
// `sleep`/poll. Waits for: device_online
|
|
3
|
-
//
|
|
2
|
+
// `sleep`/poll. Waits for: device_online (adb), metro_ready (serving), wda_ready (WDA /status
|
|
3
|
+
// ready) or simulator_booted (the session's iOS Simulator finished booting, e.g. after qa_ios boot
|
|
4
|
+
// returned status:"booting").
|
|
5
|
+
// Jobs are waited on with qa_job_status waitMs. Returns timeout + current state + next steps.
|
|
4
6
|
import { z } from 'zod';
|
|
5
|
-
import { qaOk, unknownSessionError } from '../lib/result.js';
|
|
7
|
+
import { cancelledResult, qaAnnotate, qaError, qaOk, unknownSessionError } from '../lib/result.js';
|
|
8
|
+
import { isAbortError, sleepOrCancel, throwIfCancelled } from '../lib/abortScope.js';
|
|
6
9
|
import { resolveDevice } from '../session/attach.js';
|
|
7
10
|
import { metroReadiness } from '../lib/metroState.js';
|
|
8
|
-
|
|
11
|
+
import { checkWda, isLoopbackWdaUrl, remoteWdaAllowedByUser, REMOTE_WDA_ENV } from '../lib/wda.js';
|
|
12
|
+
import { wdaUrlForSession } from './wda.js';
|
|
13
|
+
import { boundSimulatorUdid } from './ios.js';
|
|
14
|
+
import { simulatorBootState } from '../lib/simctl.js';
|
|
15
|
+
/** Default wait. Kept under common client tool timeouts (Codex: 60 s); the model can call again. */
|
|
16
|
+
export const DEFAULT_WAIT_TIMEOUT_MS = 45_000;
|
|
17
|
+
/** Effective upper bound for timeoutMs, same reasoning: one call must not outlive the client's
|
|
18
|
+
* timeout. Larger values (older docs said 60000/180000) are accepted and CLAMPED, with a note. */
|
|
19
|
+
export const MAX_WAIT_TIMEOUT_MS = 50_000;
|
|
20
|
+
const POLL_INTERVAL_MS = 1500;
|
|
9
21
|
export function registerWait(server, sessions) {
|
|
10
22
|
server.registerTool('qa_wait', {
|
|
11
23
|
title: 'Wait for a setup condition',
|
|
12
|
-
description: '
|
|
13
|
-
'"
|
|
24
|
+
description: 'Bounded wait for a setup condition instead of shell sleep: device_online (adb), metro_ready, wda_ready (after qa_wda ' +
|
|
25
|
+
'start returned status:"starting"), or simulator_booted (the bound iOS Simulator, after qa_ios boot returned ' +
|
|
26
|
+
'status:"booting"). Returns satisfied, or timedOut:true (call again). For jobs use qa_job_status waitMs.',
|
|
14
27
|
inputSchema: {
|
|
15
28
|
sessionId: z.string(),
|
|
16
|
-
for: z.enum(['device_online', 'metro_ready']),
|
|
17
|
-
timeoutMs: z
|
|
29
|
+
for: z.enum(['device_online', 'metro_ready', 'wda_ready', 'simulator_booted']),
|
|
30
|
+
timeoutMs: z
|
|
31
|
+
.number()
|
|
32
|
+
.int()
|
|
33
|
+
.min(0)
|
|
34
|
+
.optional()
|
|
35
|
+
.describe(`default ${DEFAULT_WAIT_TIMEOUT_MS}; values above ${MAX_WAIT_TIMEOUT_MS} are clamped. On timedOut, call again to keep waiting.`),
|
|
18
36
|
},
|
|
19
37
|
}, async ({ sessionId, for: cond, timeoutMs }) => {
|
|
20
38
|
const session = sessions.get(sessionId);
|
|
21
39
|
if (!session)
|
|
22
40
|
return unknownSessionError(sessionId);
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
const requested = timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
|
|
42
|
+
const effectiveMs = Math.min(requested, MAX_WAIT_TIMEOUT_MS);
|
|
43
|
+
const notes = requested > MAX_WAIT_TIMEOUT_MS
|
|
44
|
+
? [
|
|
45
|
+
`timeoutMs ${requested} clamped to ${MAX_WAIT_TIMEOUT_MS}: one call stays under client tool timeouts (Codex: 60 s). Call qa_wait again to keep waiting.`,
|
|
46
|
+
]
|
|
47
|
+
: [];
|
|
48
|
+
const done = (r) => qaAnnotate(r, notes);
|
|
49
|
+
const deadline = Date.now() + effectiveMs;
|
|
50
|
+
// wda_ready: the URL already used by this session (attached driver / last qa_wda start), else
|
|
51
|
+
// the configured one, which must pass the same non-loopback gate as qa_wda.
|
|
52
|
+
let wdaUrl;
|
|
53
|
+
if (cond === 'wda_ready') {
|
|
54
|
+
const resolved = wdaUrlForSession(session);
|
|
55
|
+
if (resolved.source === 'config' && !isLoopbackWdaUrl(resolved.url) && !remoteWdaAllowedByUser(resolved.url))
|
|
56
|
+
return qaError({
|
|
57
|
+
what: `Refused to poll non-loopback WDA URL ${resolved.url} from the repository config`,
|
|
58
|
+
changedState: false,
|
|
59
|
+
retrySafe: false,
|
|
60
|
+
failureCode: 'DESTRUCTIVE_REFUSED',
|
|
61
|
+
nextSteps: [
|
|
62
|
+
`Attach it first with qa_wda { action:"attach", webDriverAgentUrl, allowNonLoopback:true } (consent-gated), or set ${REMOTE_WDA_ENV}=<exact url> in the MCP server environment.`,
|
|
63
|
+
],
|
|
64
|
+
});
|
|
65
|
+
wdaUrl = resolved.url;
|
|
66
|
+
}
|
|
67
|
+
let lastWda;
|
|
68
|
+
// simulator_booted: the iOS Simulator bound to this session (qa_ios boot binds it even when
|
|
69
|
+
// it returns status:"booting").
|
|
70
|
+
let simUdid;
|
|
71
|
+
if (cond === 'simulator_booted') {
|
|
72
|
+
simUdid = boundSimulatorUdid(session);
|
|
73
|
+
if (!simUdid)
|
|
74
|
+
return qaError({
|
|
75
|
+
what: 'No iOS Simulator bound to this session',
|
|
76
|
+
changedState: false,
|
|
77
|
+
retrySafe: true,
|
|
78
|
+
failureCode: 'NO_DEVICE',
|
|
79
|
+
nextSteps: ['Call qa_ios { action:"boot" } first; it binds the simulator even while it is still booting.'],
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
let lastSim;
|
|
83
|
+
// Cancellation (notifications/cancelled): the call's signal is scoped by the tool wrapper
|
|
84
|
+
// (abortScope), so every poll checks it and the pause between polls wakes on abort.
|
|
85
|
+
try {
|
|
86
|
+
while (Date.now() < deadline) {
|
|
87
|
+
throwIfCancelled();
|
|
88
|
+
if (cond === 'wda_ready') {
|
|
89
|
+
lastWda = await checkWda(wdaUrl, Math.max(250, Math.min(1500, deadline - Date.now())));
|
|
90
|
+
throwIfCancelled();
|
|
91
|
+
if (lastWda.ready)
|
|
92
|
+
return done(qaOk({ satisfied: true, condition: cond, webDriverAgentUrl: wdaUrl, wda: lastWda }, `WDA ready at ${wdaUrl}\nNext: qa_wda attach (if the session is not attached yet).`));
|
|
93
|
+
await sleepOrCancel(Math.min(POLL_INTERVAL_MS, Math.max(0, deadline - Date.now())));
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (cond === 'simulator_booted') {
|
|
97
|
+
lastSim = await simulatorBootState(simUdid, Math.max(250, Math.min(3000, deadline - Date.now())));
|
|
98
|
+
throwIfCancelled();
|
|
99
|
+
if (lastSim.state === 'booted') {
|
|
100
|
+
sessions.milestone(session, 'simulator_boot_end');
|
|
101
|
+
return done(qaOk({ satisfied: true, condition: cond, udid: simUdid, simulator: lastSim }, `Simulator ${lastSim.name ?? simUdid} booted\nNext: qa_ios install/launch (or qa_wda start).`));
|
|
102
|
+
}
|
|
103
|
+
if (lastSim.state === 'failed' || lastSim.state === 'shutdown' || lastSim.state === 'unknown')
|
|
104
|
+
return done(qaError({
|
|
105
|
+
what: lastSim.state === 'failed'
|
|
106
|
+
? `Simulator ${simUdid} failed to boot: ${lastSim.error}`
|
|
107
|
+
: lastSim.state === 'shutdown'
|
|
108
|
+
? `Simulator ${simUdid} is not booting (simctl state: ${lastSim.simctlState})`
|
|
109
|
+
: `Simulator ${simUdid} is not listed by simctl`,
|
|
110
|
+
changedState: false,
|
|
111
|
+
retrySafe: true,
|
|
112
|
+
failureCode: lastSim.state === 'failed' && /timed out/i.test(lastSim.error) ? 'SIMULATOR_BOOT_TIMEOUT' : 'SIMULATOR_BOOT_FAILED',
|
|
113
|
+
nextSteps: ['Boot it again with qa_ios { action:"boot" }, or pick another simulator from qa_ios { action:"list" }.'],
|
|
114
|
+
}));
|
|
115
|
+
await sleepOrCancel(Math.min(POLL_INTERVAL_MS, Math.max(0, deadline - Date.now())));
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
32
118
|
const dev = await resolveDevice(session);
|
|
33
|
-
|
|
119
|
+
throwIfCancelled();
|
|
120
|
+
if (cond === 'device_online') {
|
|
121
|
+
if (dev.available.length > 0)
|
|
122
|
+
return done(qaOk({ satisfied: true, condition: cond, availableDevices: dev.available }, `device online: ${dev.available.join(', ')}`));
|
|
123
|
+
}
|
|
124
|
+
else if (dev.effective) {
|
|
34
125
|
const rd = await metroReadiness(dev.effective);
|
|
35
126
|
if (rd.serving)
|
|
36
|
-
return qaOk({ satisfied: true, condition: cond, metro: rd }, `Metro serving=${rd.serving} reverse=${rd.reverseSet} ready=${rd.ready}`);
|
|
127
|
+
return done(qaOk({ satisfied: true, condition: cond, metro: rd }, `Metro serving=${rd.serving} reverse=${rd.reverseSet} ready=${rd.ready}`));
|
|
37
128
|
}
|
|
129
|
+
await sleepOrCancel(Math.min(POLL_INTERVAL_MS, Math.max(0, deadline - Date.now())));
|
|
38
130
|
}
|
|
39
|
-
await sleep(intervalMs);
|
|
40
131
|
}
|
|
41
|
-
|
|
132
|
+
catch (e) {
|
|
133
|
+
if (isAbortError(e))
|
|
134
|
+
return cancelledResult(`qa_wait ${cond} cancelled: the call was aborted before the condition held`);
|
|
135
|
+
throw e;
|
|
136
|
+
}
|
|
137
|
+
const hint = cond === 'device_online'
|
|
138
|
+
? 'boot one with qa_prepare_target { bindOnly:true }.'
|
|
139
|
+
: cond === 'metro_ready'
|
|
140
|
+
? 'start Metro with qa_metro action="start", or qa_metro diagnose.'
|
|
141
|
+
: cond === 'simulator_booted'
|
|
142
|
+
? 'check qa_ios { action:"list" } (a cold boot of a fresh runtime can take a few minutes).'
|
|
143
|
+
: 'check qa_wda { action:"logs" } / { action:"diagnose" } (a managed WDA may have failed to launch).';
|
|
144
|
+
return done(qaOk({
|
|
145
|
+
satisfied: false,
|
|
146
|
+
timedOut: true,
|
|
147
|
+
condition: cond,
|
|
148
|
+
...(cond === 'wda_ready' ? { webDriverAgentUrl: wdaUrl, wda: lastWda ?? null } : {}),
|
|
149
|
+
...(cond === 'simulator_booted' ? { udid: simUdid, simulator: lastSim ?? null } : {}),
|
|
150
|
+
}, `Timed out waiting for ${cond}. Call qa_wait again to keep waiting, or ${hint}`));
|
|
42
151
|
});
|
|
43
152
|
}
|
|
44
153
|
//# sourceMappingURL=wait.js.map
|
package/dist/tools/wait.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wait.js","sourceRoot":"","sources":["../../src/tools/wait.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,
|
|
1
|
+
{"version":3,"file":"wait.js","sourceRoot":"","sources":["../../src/tools/wait.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/wda.js
CHANGED
|
@@ -2,7 +2,8 @@ import { z } from 'zod';
|
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import { closeSync, existsSync, openSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
4
4
|
import { isAbsolute, join } from 'node:path';
|
|
5
|
-
import { qaError, qaOk, unknownSessionError } from '../lib/result.js';
|
|
5
|
+
import { cancelledResult, qaError, qaOk, unknownSessionError } from '../lib/result.js';
|
|
6
|
+
import { isAbortError, runWithSignal } from '../lib/abortScope.js';
|
|
6
7
|
import { consumeConsent, requireConsent } from '../consent/consent.js';
|
|
7
8
|
import { sensitiveRefusal } from '../lib/sensitive.js';
|
|
8
9
|
import { run } from '../lib/spawn.js';
|
|
@@ -12,6 +13,17 @@ import { recordWdaTiming, wdaRecommendations, wdaTimingSummary } from '../lib/wd
|
|
|
12
13
|
import { WdaDriver } from '../drivers/WdaDriver.js';
|
|
13
14
|
import * as sim from '../lib/simctl.js';
|
|
14
15
|
import { findManagedWdaProcesses, killManagedWda, reclaimPid, registerManagedProcess, registeredWdaForSession, unregisterManagedProcess, } from '../session/processRegistry.js';
|
|
16
|
+
/** qa_wda actions that run xcodebuild and are consent-gated as `wda_<name>` (CONSENT_ACTIONS in consent.ts). */
|
|
17
|
+
export const WDA_XCODEBUILD_ACTIONS = ['build', 'start'];
|
|
18
|
+
function isWdaXcodebuildAction(a) {
|
|
19
|
+
return WDA_XCODEBUILD_ACTIONS.includes(a);
|
|
20
|
+
}
|
|
21
|
+
/** How long `qa_wda start` blocks inside ONE tool call waiting for /status. One call must stay
|
|
22
|
+
* under common client tool timeouts (Codex: 60 s), so a slower start returns status:"starting"
|
|
23
|
+
* and the agent polls with qa_wait { for:"wda_ready" } (bounded by ios.wda.startupTimeoutMs). */
|
|
24
|
+
export const WDA_START_CALL_WAIT_MS = 45_000;
|
|
25
|
+
/** Kill timer for the background `qa_wda build` job (a cold WDA build-for-testing is slow). */
|
|
26
|
+
export const WDA_BUILD_TIMEOUT_MS = 600_000;
|
|
15
27
|
const managedProcesses = new Map();
|
|
16
28
|
function pidIsAlive(pid) {
|
|
17
29
|
if (!(pid > 0))
|
|
@@ -64,6 +76,19 @@ export function lastManagedWdaStart(session) {
|
|
|
64
76
|
...(port ? { port } : {}),
|
|
65
77
|
};
|
|
66
78
|
}
|
|
79
|
+
/** The WDA URL to probe for this session: the attached WdaDriver's, else the URL of the latest
|
|
80
|
+
* executed `qa_wda start` (both already passed the loopback/consent gate), else the configured
|
|
81
|
+
* one (source 'config': the caller must apply the non-loopback gate). Exported for qa_wait. */
|
|
82
|
+
export function wdaUrlForSession(session) {
|
|
83
|
+
if (session.driver instanceof WdaDriver)
|
|
84
|
+
return { url: session.driver.baseUrl, source: 'driver' };
|
|
85
|
+
const m = [...(session.mutations ?? [])]
|
|
86
|
+
.reverse()
|
|
87
|
+
.find((r) => r.tool === 'qa_wda' && r.action === 'wda_start' && r.status === 'executed' && typeof r.target?.webDriverAgentUrl === 'string');
|
|
88
|
+
if (m)
|
|
89
|
+
return { url: String(m.target.webDriverAgentUrl), source: 'wda_start' };
|
|
90
|
+
return { url: loadWdaConfig(session.root).url, source: 'config' };
|
|
91
|
+
}
|
|
67
92
|
const isLoopback = isLoopbackWdaUrl;
|
|
68
93
|
function latestWdaArtifacts(session) {
|
|
69
94
|
const logs = [...session.artifacts].reverse().filter((a) => a.kind === 'wda');
|
|
@@ -120,19 +145,20 @@ function managedWdaBuildProductStatus(derivedDataPath) {
|
|
|
120
145
|
export function registerWda(server, sessions) {
|
|
121
146
|
server.registerTool('qa_wda', {
|
|
122
147
|
title: 'WebDriverAgent diagnostics and attach',
|
|
123
|
-
description: '
|
|
124
|
-
'
|
|
125
|
-
'
|
|
148
|
+
description: 'Manage the iOS WebDriverAgent backend behind structured iOS tap/type/snapshot: attach an external WDA ' +
|
|
149
|
+
'(webDriverAgentUrl), build/start a managed one (consent-gated), stop it; status/doctor/diagnose/logs/tune inspect ' +
|
|
150
|
+
'the setup. build runs as a background job (poll qa_job_status); start waits up to 45 s, then returns status:"starting" ' +
|
|
151
|
+
'(poll qa_wait { for:"wda_ready" }).',
|
|
126
152
|
inputSchema: {
|
|
127
153
|
sessionId: z.string(),
|
|
128
154
|
action: z.enum(['status', 'doctor', 'build', 'start', 'stop', 'attach', 'diagnose', 'logs', 'tune']),
|
|
129
155
|
webDriverAgentUrl: z.string().optional().describe('Default http://127.0.0.1:8100; non-loopback needs allowNonLoopback + consent.'),
|
|
130
|
-
device: z.string().optional().describe('Simulator UDID
|
|
156
|
+
device: z.string().optional().describe('Simulator UDID (default: session device).'),
|
|
131
157
|
udid: z.string().optional().describe('Deprecated alias of device.'),
|
|
132
158
|
bundleId: z.string().optional().describe('Default: the session appId.'),
|
|
133
159
|
wdaProjectPath: z.string().optional().describe('WebDriverAgent.xcodeproj (default: Appium WDA if found).'),
|
|
134
160
|
derivedDataPath: z.string().optional().describe('xcodebuild -derivedDataPath for reuse.'),
|
|
135
|
-
scheme: z.string().optional().describe('
|
|
161
|
+
scheme: z.string().optional().describe('Default WebDriverAgentRunner.'),
|
|
136
162
|
allowNonLoopback: z.boolean().optional().describe('Required to use a non-loopback external WDA URL.'),
|
|
137
163
|
consentId: z.string().optional(),
|
|
138
164
|
approve: z.boolean().optional(),
|
|
@@ -187,7 +213,7 @@ export function registerWda(server, sessions) {
|
|
|
187
213
|
const explicitProjectPath = resolvePath(wdaProjectPath);
|
|
188
214
|
// Managed build/start without wdaProjectPath: use a user-installed Appium WebDriverAgent
|
|
189
215
|
// (~/.appium/…/appium-webdriveragent, global npm), reported as wdaProjectSource.
|
|
190
|
-
const discoveredAppiumProject = !explicitProjectPath && (action
|
|
216
|
+
const discoveredAppiumProject = !explicitProjectPath && isWdaXcodebuildAction(action) ? discoverAppiumWdaProjects()[0] : undefined;
|
|
191
217
|
const projectPath = explicitProjectPath ?? discoveredAppiumProject;
|
|
192
218
|
const wdaProjectSource = explicitProjectPath ? 'argument' : discoveredAppiumProject ? 'appium-discovered' : null;
|
|
193
219
|
const ddPath = resolvePath(derivedDataPath) ?? configured.derivedDataPath;
|
|
@@ -286,7 +312,7 @@ export function registerWda(server, sessions) {
|
|
|
286
312
|
});
|
|
287
313
|
return qaOk({ stopped: true, pid: proc.pid, logUri: proc.logUri }, `stopped managed WDA pid ${proc.pid}`);
|
|
288
314
|
}
|
|
289
|
-
if (action
|
|
315
|
+
if (isWdaXcodebuildAction(action)) {
|
|
290
316
|
if (!targetUdid) {
|
|
291
317
|
return qaError({
|
|
292
318
|
what: `qa_wda ${action} requires a simulator/device UDID`,
|
|
@@ -389,60 +415,30 @@ export function registerWda(server, sessions) {
|
|
|
389
415
|
status: 'approved',
|
|
390
416
|
});
|
|
391
417
|
if (action === 'build') {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
sessions.
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
action: 'wda_build',
|
|
403
|
-
risk: 'medium',
|
|
404
|
-
target: { udid: targetUdid, projectPath, derivedDataPath: ddPath, scheme: scheme ?? null },
|
|
405
|
-
consent: { required: true, consentId, approved: true },
|
|
406
|
-
status: 'blocked',
|
|
407
|
-
ledgerUri: logUri,
|
|
408
|
-
detail: `${failureCode}: exit ${r.code}`,
|
|
409
|
-
});
|
|
410
|
-
return qaError({
|
|
411
|
-
what: failureCode === 'WDA_SIGNING_FAILED'
|
|
412
|
-
? `WDA signing/provisioning failed with exit ${r.code}`
|
|
413
|
-
: `WDA build failed with exit ${r.code}`,
|
|
414
|
-
changedState: true,
|
|
415
|
-
retrySafe: failureCode !== 'WDA_SIGNING_FAILED',
|
|
416
|
-
failureCode,
|
|
417
|
-
artifactUri: logUri,
|
|
418
|
-
nextSteps: failureCode === 'WDA_SIGNING_FAILED'
|
|
419
|
-
? [
|
|
420
|
-
'Open the WDA build log artifact, configure a valid development team/certificate/provisioning profile for the device, then retry.',
|
|
421
|
-
]
|
|
422
|
-
: ['Open the WDA build log artifact, fix the Xcode build error, then retry.'],
|
|
423
|
-
}, { xcode, logUri, timedOut: r.timedOut });
|
|
424
|
-
}
|
|
425
|
-
sessions.recordMutation(session, {
|
|
426
|
-
tool: 'qa_wda',
|
|
427
|
-
action: 'wda_build',
|
|
428
|
-
risk: 'medium',
|
|
429
|
-
target: { udid: targetUdid, projectPath, derivedDataPath: ddPath, scheme: scheme ?? null },
|
|
430
|
-
consent: { required: true, consentId, approved: true },
|
|
431
|
-
status: 'executed',
|
|
432
|
-
ledgerUri: logUri,
|
|
433
|
-
});
|
|
434
|
-
const wdaBuildProduct = managedWdaBuildProductStatus(ddPath);
|
|
435
|
-
return qaOk({
|
|
436
|
-
built: true,
|
|
437
|
-
logUri,
|
|
418
|
+
// xcodebuild build-for-testing can take minutes: run it as a background job so this call
|
|
419
|
+
// returns at once (one tool call stays under client timeouts). Consent was consumed above.
|
|
420
|
+
const job = sessions.createJob(session, 'wda_build');
|
|
421
|
+
const buildCtx = {
|
|
422
|
+
projectPath,
|
|
423
|
+
udid: targetUdid,
|
|
424
|
+
derivedDataPath: ddPath,
|
|
425
|
+
scheme,
|
|
426
|
+
consentId,
|
|
427
|
+
args,
|
|
438
428
|
xcode,
|
|
439
|
-
|
|
429
|
+
wdaProjectSource,
|
|
440
430
|
wdaConfig: effectiveConfig,
|
|
431
|
+
};
|
|
432
|
+
void runWithSignal(sessions.abortSignal(session, job.jobId), () => runWdaBuildJob(sessions, session, job, buildCtx));
|
|
433
|
+
return qaOk({
|
|
434
|
+
jobId: job.jobId,
|
|
435
|
+
status: 'running',
|
|
436
|
+
kind: job.kind,
|
|
437
|
+
command: ['xcodebuild', ...args],
|
|
441
438
|
wdaProjectPath: projectPath,
|
|
442
439
|
wdaProjectSource,
|
|
443
440
|
derivedDataPath: ddPath,
|
|
444
|
-
|
|
445
|
-
}, `WDA build completed (${wdaProjectSource === 'appium-discovered' ? 'auto-discovered Appium WDA ' : ''}${projectPath}) > ${logUri}`);
|
|
441
|
+
}, `Started WDA build (${wdaProjectSource === 'appium-discovered' ? 'auto-discovered Appium WDA ' : ''}${projectPath}) as job ${job.jobId}.\nNext: qa_job_status { sessionId:"${session.id}", jobId:"${job.jobId}", waitMs:45000 }, then qa_wda start.`);
|
|
446
442
|
}
|
|
447
443
|
const logUri = sessions.saveArtifact(session, 'wda', `wda-start-${Date.now()}.log`, '', 'text/plain', 'WDA start log');
|
|
448
444
|
const rec = sessions.findArtifact(logUri);
|
|
@@ -479,9 +475,43 @@ export function registerWda(server, sessions) {
|
|
|
479
475
|
ledgerUri: logUri,
|
|
480
476
|
detail: 'managed WDA process started',
|
|
481
477
|
});
|
|
482
|
-
|
|
478
|
+
// Cancelled while waiting: WDA stays started (managed + registered, so the next attach can
|
|
479
|
+
// adopt it); report CANCELLED, not a WDA startup failure.
|
|
480
|
+
// Bounded in-call wait: at most WDA_START_CALL_WAIT_MS here; the rest of the configured
|
|
481
|
+
// startupTimeoutMs is polled by the agent via qa_wait { for:"wda_ready" }.
|
|
482
|
+
const callWaitMs = Math.min(configured.startupTimeoutMs, WDA_START_CALL_WAIT_MS);
|
|
483
|
+
const waited = await waitForWdaReady(url, callWaitMs).catch((e) => {
|
|
484
|
+
if (isAbortError(e))
|
|
485
|
+
return null;
|
|
486
|
+
throw e;
|
|
487
|
+
});
|
|
488
|
+
if (!waited)
|
|
489
|
+
return cancelledResult('qa_wda start cancelled while waiting for WDA; the managed WDA process was left running', true);
|
|
483
490
|
sessions.addMilestoneDuration(session, 'wda_startup_wait_ms', waited.durationMs);
|
|
491
|
+
const remainingMs = configured.startupTimeoutMs - waited.durationMs;
|
|
492
|
+
if (!waited.ready && remainingMs > 0 && pidIsAlive(child.pid ?? -1)) {
|
|
493
|
+
return qaOk({
|
|
494
|
+
started: true,
|
|
495
|
+
ready: false,
|
|
496
|
+
status: 'starting',
|
|
497
|
+
pid: child.pid ?? null,
|
|
498
|
+
logUri,
|
|
499
|
+
webDriverAgentUrl: url,
|
|
500
|
+
wdaProjectPath: projectPath,
|
|
501
|
+
wdaProjectSource,
|
|
502
|
+
command: ['xcodebuild', ...args],
|
|
503
|
+
wdaConfig: effectiveConfig,
|
|
504
|
+
wda: waited.status,
|
|
505
|
+
startupWaitMs: waited.durationMs,
|
|
506
|
+
startupTimeoutMs: configured.startupTimeoutMs,
|
|
507
|
+
remainingStartupMs: remainingMs,
|
|
508
|
+
}, `started managed WDA pid ${child.pid ?? 'unknown'}; /status not ready yet after ${Math.round(waited.durationMs / 1000)} s (still starting) > ${logUri}
|
|
509
|
+
` +
|
|
510
|
+
`Next: qa_wait { sessionId:"${session.id}", for:"wda_ready" } (repeat while timedOut, up to ~${Math.ceil(remainingMs / 1000)} s more), then qa_wda attach.`);
|
|
511
|
+
}
|
|
484
512
|
if (!waited.ready) {
|
|
513
|
+
const exited = remainingMs > 0; // only reached early when the xcodebuild process is gone
|
|
514
|
+
const why = `WDA not ready after ${waited.durationMs}ms${exited ? ' (the managed xcodebuild process exited)' : ''}`;
|
|
485
515
|
sessions.recordMutation(session, {
|
|
486
516
|
tool: 'qa_wda',
|
|
487
517
|
action: 'wda_start',
|
|
@@ -497,10 +527,12 @@ export function registerWda(server, sessions) {
|
|
|
497
527
|
consent: { required: true, consentId, approved: true },
|
|
498
528
|
status: 'blocked',
|
|
499
529
|
ledgerUri: logUri,
|
|
500
|
-
detail:
|
|
530
|
+
detail: why,
|
|
501
531
|
});
|
|
502
532
|
return qaError({
|
|
503
|
-
what:
|
|
533
|
+
what: exited
|
|
534
|
+
? `Managed WDA exited before becoming ready at ${url} (waited ${waited.durationMs}ms)`
|
|
535
|
+
: `Managed WDA did not become ready at ${url} within ${configured.startupTimeoutMs}ms`,
|
|
504
536
|
changedState: true,
|
|
505
537
|
retrySafe: true,
|
|
506
538
|
failureCode: 'WDA_START_FAILED',
|
|
@@ -732,4 +764,111 @@ export function registerWda(server, sessions) {
|
|
|
732
764
|
}
|
|
733
765
|
});
|
|
734
766
|
}
|
|
767
|
+
/** Background body of `qa_wda build` (runs inside the job's cancellation scope). The job ends
|
|
768
|
+
* done (built) or failed (classified build failure); a cancelled job is left cancelled. Exported
|
|
769
|
+
* for tests. */
|
|
770
|
+
export async function runWdaBuildJob(sessions, session, job, ctx) {
|
|
771
|
+
const signal = sessions.abortSignal(session, job.jobId);
|
|
772
|
+
const upd = (patch) => sessions.updateJobIfRunning(session, job, patch);
|
|
773
|
+
const target = { udid: ctx.udid, projectPath: ctx.projectPath, derivedDataPath: ctx.derivedDataPath, scheme: ctx.scheme ?? null };
|
|
774
|
+
const consent = { required: true, consentId: ctx.consentId, approved: true };
|
|
775
|
+
upd({ progress: 'xcodebuild build-for-testing' });
|
|
776
|
+
sessions.milestone(session, 'wda_build_start');
|
|
777
|
+
let r;
|
|
778
|
+
try {
|
|
779
|
+
r = await run('xcodebuild', ctx.args, { timeoutMs: WDA_BUILD_TIMEOUT_MS, signal });
|
|
780
|
+
}
|
|
781
|
+
catch (e) {
|
|
782
|
+
if (isAbortError(e, signal))
|
|
783
|
+
return; // cancelled (qa_job_cancel / shutdown): the job already says so
|
|
784
|
+
const what = `xcodebuild could not run: ${String(e.message ?? e)}`;
|
|
785
|
+
sessions.recordMutation(session, {
|
|
786
|
+
tool: 'qa_wda',
|
|
787
|
+
action: 'wda_build',
|
|
788
|
+
risk: 'medium',
|
|
789
|
+
target,
|
|
790
|
+
consent,
|
|
791
|
+
status: 'blocked',
|
|
792
|
+
detail: what,
|
|
793
|
+
});
|
|
794
|
+
upd({
|
|
795
|
+
status: 'failed',
|
|
796
|
+
error: `WDA_BUILD_FAILED: ${what}`,
|
|
797
|
+
result: { failureCode: 'WDA_BUILD_FAILED', nextSteps: ['Check the Xcode installation (qa_wda doctor), then retry qa_wda build.'] },
|
|
798
|
+
resultText: `WDA build could not start: ${what}`,
|
|
799
|
+
endedAt: Date.now(),
|
|
800
|
+
});
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
sessions.milestone(session, 'wda_build_end');
|
|
804
|
+
if (signal?.aborted)
|
|
805
|
+
return;
|
|
806
|
+
const log = `${r.stdout}\n${r.stderr}`;
|
|
807
|
+
const logUri = sessions.saveArtifact(session, 'wda', `wda-build-${Date.now()}.log`, log, 'text/plain', `WDA build ${r.code === 0 ? 'success' : 'failed'}`);
|
|
808
|
+
sessions.addEnvChange(session, `wda build ${ctx.projectPath} ${ctx.udid}`);
|
|
809
|
+
if (r.code !== 0) {
|
|
810
|
+
const failureCode = classifyWdaBuildFailure(log);
|
|
811
|
+
sessions.recordMutation(session, {
|
|
812
|
+
tool: 'qa_wda',
|
|
813
|
+
action: 'wda_build',
|
|
814
|
+
risk: 'medium',
|
|
815
|
+
target,
|
|
816
|
+
consent,
|
|
817
|
+
status: 'blocked',
|
|
818
|
+
ledgerUri: logUri,
|
|
819
|
+
detail: `${failureCode}: exit ${r.code}`,
|
|
820
|
+
});
|
|
821
|
+
const signing = failureCode === 'WDA_SIGNING_FAILED';
|
|
822
|
+
const what = signing
|
|
823
|
+
? `WDA signing/provisioning failed with exit ${r.code}`
|
|
824
|
+
: `WDA build failed with exit ${r.code}${r.timedOut ? ` (timed out after ${WDA_BUILD_TIMEOUT_MS} ms)` : ''}`;
|
|
825
|
+
upd({
|
|
826
|
+
status: 'failed',
|
|
827
|
+
error: `${failureCode}: ${what}`,
|
|
828
|
+
artifactUris: [logUri],
|
|
829
|
+
result: {
|
|
830
|
+
failureCode,
|
|
831
|
+
logUri,
|
|
832
|
+
timedOut: r.timedOut,
|
|
833
|
+
retrySafe: !signing,
|
|
834
|
+
nextSteps: signing
|
|
835
|
+
? [
|
|
836
|
+
'Open the WDA build log artifact, configure a valid development team/certificate/provisioning profile for the device, then retry.',
|
|
837
|
+
]
|
|
838
|
+
: ['Open the WDA build log artifact, fix the Xcode build error, then retry.'],
|
|
839
|
+
},
|
|
840
|
+
resultText: `${what}. Log: ${logUri}`,
|
|
841
|
+
endedAt: Date.now(),
|
|
842
|
+
});
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
sessions.recordMutation(session, {
|
|
846
|
+
tool: 'qa_wda',
|
|
847
|
+
action: 'wda_build',
|
|
848
|
+
risk: 'medium',
|
|
849
|
+
target,
|
|
850
|
+
consent,
|
|
851
|
+
status: 'executed',
|
|
852
|
+
ledgerUri: logUri,
|
|
853
|
+
});
|
|
854
|
+
const wdaBuildProduct = managedWdaBuildProductStatus(ctx.derivedDataPath);
|
|
855
|
+
upd({
|
|
856
|
+
status: 'done',
|
|
857
|
+
progress: 'done',
|
|
858
|
+
artifactUris: [logUri],
|
|
859
|
+
result: {
|
|
860
|
+
built: true,
|
|
861
|
+
logUri,
|
|
862
|
+
xcode: ctx.xcode,
|
|
863
|
+
command: ['xcodebuild', ...ctx.args],
|
|
864
|
+
wdaConfig: ctx.wdaConfig,
|
|
865
|
+
wdaProjectPath: ctx.projectPath,
|
|
866
|
+
wdaProjectSource: ctx.wdaProjectSource,
|
|
867
|
+
derivedDataPath: ctx.derivedDataPath,
|
|
868
|
+
wdaBuildProduct,
|
|
869
|
+
},
|
|
870
|
+
resultText: `WDA build completed (${ctx.wdaProjectSource === 'appium-discovered' ? 'auto-discovered Appium WDA ' : ''}${ctx.projectPath}) > ${logUri}\nNext: qa_wda start.`,
|
|
871
|
+
endedAt: Date.now(),
|
|
872
|
+
});
|
|
873
|
+
}
|
|
735
874
|
//# sourceMappingURL=wda.js.map
|