swipium 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -27
- package/dist/appMap/codeIndex.js +37 -5
- package/dist/appMap/codeIndex.js.map +1 -1
- package/dist/appMap/query.js +22 -4
- package/dist/appMap/query.js.map +1 -1
- package/dist/build/plan.js +2 -1
- package/dist/build/plan.js.map +1 -1
- package/dist/drivers/WdaDriver.js +42 -6
- package/dist/drivers/WdaDriver.js.map +1 -1
- package/dist/explore/candidates.js +17 -0
- package/dist/explore/candidates.js.map +1 -1
- package/dist/explore/runner.js +29 -2
- package/dist/explore/runner.js.map +1 -1
- package/dist/lib/wda.js +70 -2
- package/dist/lib/wda.js.map +1 -1
- package/dist/oracle/failures.js +1 -0
- package/dist/oracle/failures.js.map +1 -1
- package/dist/orchestration/envelope.js +1 -0
- package/dist/orchestration/envelope.js.map +1 -1
- package/dist/prompts/index.js +10 -0
- package/dist/prompts/index.js.map +1 -1
- package/dist/report/export.js +9 -0
- package/dist/report/export.js.map +1 -1
- package/dist/server.js +29 -2
- package/dist/server.js.map +1 -1
- package/dist/services/build.js +15 -2
- package/dist/services/build.js.map +1 -1
- package/dist/services/prepareIos.js +34 -4
- package/dist/services/prepareIos.js.map +1 -1
- package/dist/services/report.js +47 -5
- package/dist/services/report.js.map +1 -1
- package/dist/session/store.js.map +1 -1
- package/dist/suite/pom.js +24 -9
- package/dist/suite/pom.js.map +1 -1
- package/dist/tools/act.js +23 -2
- package/dist/tools/act.js.map +1 -1
- package/dist/tools/appControl.js +176 -0
- package/dist/tools/appControl.js.map +1 -0
- package/dist/tools/capabilities.js +29 -0
- package/dist/tools/capabilities.js.map +1 -1
- package/dist/tools/clearOverlay.js +56 -21
- package/dist/tools/clearOverlay.js.map +1 -1
- package/dist/tools/device.js +151 -0
- package/dist/tools/device.js.map +1 -0
- package/dist/tools/doctor.js +174 -52
- package/dist/tools/doctor.js.map +1 -1
- package/dist/tools/history.js +56 -0
- package/dist/tools/history.js.map +1 -0
- package/dist/tools/metro.js +260 -0
- package/dist/tools/metro.js.map +1 -0
- package/dist/tools/network.js +97 -5
- package/dist/tools/network.js.map +1 -1
- package/dist/tools/permissions.js +149 -0
- package/dist/tools/permissions.js.map +1 -0
- package/dist/tools/screenInfo.js +70 -0
- package/dist/tools/screenInfo.js.map +1 -0
- package/dist/tools/screenRecord.js +215 -6
- package/dist/tools/screenRecord.js.map +1 -1
- package/dist/tools/seed.js +101 -0
- package/dist/tools/seed.js.map +1 -0
- package/dist/tools/state.js +83 -0
- package/dist/tools/state.js.map +1 -1
- package/dist/tools/testThis.js +55 -6
- package/dist/tools/testThis.js.map +1 -1
- package/dist/tools/visual.js +139 -0
- package/dist/tools/visual.js.map +1 -0
- package/dist/tools/visualText.js +61 -0
- package/dist/tools/visualText.js.map +1 -0
- package/dist/version.js +25 -3
- package/dist/version.js.map +1 -1
- package/docs/README.md +4 -4
- package/docs/mcp-server.md +5 -3
- package/docs/tools.md +68 -4
- package/package.json +2 -2
|
@@ -8,6 +8,14 @@ import { parseSnapshot, boundsContain } from '../snapshot/parse.js';
|
|
|
8
8
|
import { detectTreeOverlays, classifyForeground, obstructionAt } from '../snapshot/overlays.js';
|
|
9
9
|
const STRATEGIES = ['auto', 'hide_keyboard', 'press_back', 'tap_outside', 'minimize_logbox', 'dismiss_logbox', 'allow_permission', 'deny_permission', 'dismiss_toast_if_possible'];
|
|
10
10
|
const center = (b) => ({ x: Math.round((b[0] + b[2]) / 2), y: Math.round((b[1] + b[3]) / 2) });
|
|
11
|
+
async function safe(fn, fallback) {
|
|
12
|
+
try {
|
|
13
|
+
return await fn();
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return fallback;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
11
19
|
async function tapByText(d, nodes, re) {
|
|
12
20
|
const n = nodes.find((x) => re.test(x.text) || re.test(x.desc));
|
|
13
21
|
if (!n)
|
|
@@ -16,6 +24,18 @@ async function tapByText(d, nodes, re) {
|
|
|
16
24
|
await d.pressXY(c.x, c.y, 100);
|
|
17
25
|
return true;
|
|
18
26
|
}
|
|
27
|
+
async function handleNativeAlert(d, action) {
|
|
28
|
+
const fn = action === 'accept' ? d.acceptAlert : d.dismissAlert;
|
|
29
|
+
if (!fn)
|
|
30
|
+
return false;
|
|
31
|
+
try {
|
|
32
|
+
await fn.call(d);
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
19
39
|
export function registerClearOverlay(server, sessions) {
|
|
20
40
|
server.registerTool('qa_clear_overlay', {
|
|
21
41
|
title: 'Clear an overlay',
|
|
@@ -53,25 +73,42 @@ export function registerClearOverlay(server, sessions) {
|
|
|
53
73
|
const before = (await obstructionFor()).obstructed;
|
|
54
74
|
const cleared = [];
|
|
55
75
|
// gather context
|
|
56
|
-
const ime = await d.imeShown();
|
|
57
|
-
const fg = await d.foregroundOwner()
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
xml = await d.dumpXml();
|
|
61
|
-
}
|
|
62
|
-
catch { /* animated/idle-fail — proceed with what we know */ }
|
|
76
|
+
const ime = await safe(() => d.imeShown(), false);
|
|
77
|
+
const fg = await safe(() => d.foregroundOwner(), 'unknown');
|
|
78
|
+
const xml = await safe(() => d.dumpXml(), '');
|
|
63
79
|
const parsedBefore = xml ? parseSnapshot(xml) : undefined;
|
|
64
80
|
const nodes = parsedBefore?.allNodes ?? [];
|
|
65
81
|
const treeOverlays = detectTreeOverlays(nodes, parsedBefore?.screen);
|
|
66
82
|
const overlaysBefore = treeOverlays.map((o) => o.type);
|
|
67
83
|
const fgOverlay = classifyForeground(session.appId, fg);
|
|
68
|
-
const doHideKeyboard = async () => {
|
|
69
|
-
await d.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
84
|
+
const doHideKeyboard = async () => {
|
|
85
|
+
if (await safe(() => d.imeShown(), false)) {
|
|
86
|
+
await d.pressKey('back');
|
|
87
|
+
cleared.push({ type: 'keyboard', action: 'hidden' });
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
cleared.push({ type: 'keyboard', action: 'not_visible_or_unsupported' });
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
const doTapOutside = async () => { const sz = await safe(() => d.screenSize(), null); const x = Math.round((sz?.width ?? 1080) * 0.5); const y = Math.round((sz?.height ?? 2000) * 0.08); await d.pressXY(x, y, 80); cleared.push({ type: 'unknown', action: 'tap_outside' }); };
|
|
73
94
|
const doMinimizeLogbox = async () => { const ok = await tapByText(d, nodes, /minimize/i); cleared.push({ type: 'rn_logbox', action: ok ? 'minimized' : 'minimize_not_found' }); };
|
|
74
95
|
const doDismissLogbox = async () => { const ok = await tapByText(d, nodes, /dismiss/i); cleared.push({ type: 'rn_logbox', action: ok ? 'dismissed' : 'dismiss_not_found' }); };
|
|
96
|
+
const doAllowPermission = async () => {
|
|
97
|
+
if (await handleNativeAlert(d, 'accept')) {
|
|
98
|
+
cleared.push({ type: 'permission_dialog', action: 'allowed_native_alert' });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const ok = await tapByText(d, nodes, /allow|while using/i);
|
|
102
|
+
cleared.push({ type: 'permission_dialog', action: ok ? 'allowed' : 'allow_not_found' });
|
|
103
|
+
};
|
|
104
|
+
const doDenyPermission = async () => {
|
|
105
|
+
if (await handleNativeAlert(d, 'dismiss')) {
|
|
106
|
+
cleared.push({ type: 'permission_dialog', action: 'denied_native_alert' });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const ok = await tapByText(d, nodes, /deny|don.?t allow/i);
|
|
110
|
+
cleared.push({ type: 'permission_dialog', action: ok ? 'denied' : 'deny_not_found' });
|
|
111
|
+
};
|
|
75
112
|
try {
|
|
76
113
|
switch (strategy) {
|
|
77
114
|
case 'hide_keyboard':
|
|
@@ -90,16 +127,12 @@ export function registerClearOverlay(server, sessions) {
|
|
|
90
127
|
case 'dismiss_logbox':
|
|
91
128
|
await doDismissLogbox();
|
|
92
129
|
break;
|
|
93
|
-
case 'allow_permission':
|
|
94
|
-
|
|
95
|
-
cleared.push({ type: 'permission_dialog', action: ok ? 'allowed' : 'allow_not_found' });
|
|
130
|
+
case 'allow_permission':
|
|
131
|
+
await doAllowPermission();
|
|
96
132
|
break;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const ok = await tapByText(d, nodes, /deny|don.?t allow/i);
|
|
100
|
-
cleared.push({ type: 'permission_dialog', action: ok ? 'denied' : 'deny_not_found' });
|
|
133
|
+
case 'deny_permission':
|
|
134
|
+
await doDenyPermission();
|
|
101
135
|
break;
|
|
102
|
-
}
|
|
103
136
|
case 'dismiss_toast_if_possible':
|
|
104
137
|
cleared.push({ type: 'toast', action: 'cannot_target (toasts are separate windows; wait it out)' });
|
|
105
138
|
break;
|
|
@@ -125,7 +158,9 @@ export function registerClearOverlay(server, sessions) {
|
|
|
125
158
|
const after = (await obstructionFor()).obstructed;
|
|
126
159
|
let parsedAfter;
|
|
127
160
|
try {
|
|
128
|
-
|
|
161
|
+
const afterXml = await safe(() => d.dumpXml(), '');
|
|
162
|
+
if (afterXml)
|
|
163
|
+
parsedAfter = parseSnapshot(afterXml);
|
|
129
164
|
}
|
|
130
165
|
catch { /* keep undefined */ }
|
|
131
166
|
const afterOverlays = parsedAfter ? detectTreeOverlays(parsedAfter.allNodes, parsedAfter.screen) : [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clearOverlay.js","sourceRoot":"","sources":["../../src/tools/clearOverlay.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"clearOverlay.js","sourceRoot":"","sources":["../../src/tools/clearOverlay.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// qa_device_info + qa_orientation (PHASE3-PLAN §4.2) — device-parity introspection and rotation,
|
|
2
|
+
// without raw adb. Read-only info needs no consent; orientation is a logged, non-destructive
|
|
3
|
+
// environment change.
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { qaOk, qaError } from '../lib/result.js';
|
|
6
|
+
import { requireConsent, consumeConsent } from '../consent/consent.js';
|
|
7
|
+
import { getDriver } from '../session/attach.js';
|
|
8
|
+
import { getDeviceProps, getOrientation, setOrientation, listPackages, setGeo } from '../lib/device.js';
|
|
9
|
+
function rotationLabel(rotation, auto) {
|
|
10
|
+
return auto ? 'auto' : rotation === 1 || rotation === 3 ? 'landscape' : 'portrait';
|
|
11
|
+
}
|
|
12
|
+
export function registerDevice(server, sessions) {
|
|
13
|
+
server.registerTool('qa_device_info', {
|
|
14
|
+
title: 'Device info',
|
|
15
|
+
description: 'Read-only device introspection (no consent needed): model/manufacturer/Android SDK+release, supported ABIs, locale, timezone, screen size/density, current orientation, and installed third-party app count. Pass listPackages:true (optionally packageFilter) to include package names.',
|
|
16
|
+
inputSchema: {
|
|
17
|
+
sessionId: z.string(),
|
|
18
|
+
listPackages: z.boolean().optional().describe('Include installed third-party package names.'),
|
|
19
|
+
packageFilter: z.string().optional().describe('Substring filter for the package list.'),
|
|
20
|
+
},
|
|
21
|
+
}, async ({ sessionId, listPackages: withPkgs, packageFilter }) => {
|
|
22
|
+
const session = sessions.get(sessionId);
|
|
23
|
+
const { driver } = session ? await getDriver(session) : { driver: undefined };
|
|
24
|
+
const serial = driver?.currentDevice();
|
|
25
|
+
if (!session || !driver || !serial) {
|
|
26
|
+
return qaError({ what: 'No device attached to this session', changedState: false, retrySafe: true, nextSteps: ['Call qa_prepare_target first.'] });
|
|
27
|
+
}
|
|
28
|
+
const [props, screen, density, orientation, pkgs] = await Promise.all([
|
|
29
|
+
getDeviceProps(serial),
|
|
30
|
+
driver.screenSize().catch(() => null),
|
|
31
|
+
driver.screenDensity().catch(() => null),
|
|
32
|
+
getOrientation(serial),
|
|
33
|
+
listPackages(serial, { thirdPartyOnly: true, filter: packageFilter }),
|
|
34
|
+
]);
|
|
35
|
+
const orient = orientation ? rotationLabel(orientation.rotation, orientation.auto) : 'unknown';
|
|
36
|
+
const payload = {
|
|
37
|
+
device: serial,
|
|
38
|
+
props,
|
|
39
|
+
screen: screen ? { ...screen, density } : null,
|
|
40
|
+
orientation: orient,
|
|
41
|
+
rotation: orientation?.rotation ?? null,
|
|
42
|
+
autoRotate: orientation?.auto ?? null,
|
|
43
|
+
installedThirdPartyCount: pkgs.length,
|
|
44
|
+
...(withPkgs ? { packages: pkgs } : {}),
|
|
45
|
+
};
|
|
46
|
+
const summary = `${props.manufacturer ?? '?'} ${props.model ?? '?'} · Android ${props.release ?? '?'} (SDK ${props.sdk ?? '?'}) · ${props.abis[0] ?? '?'}\n` +
|
|
47
|
+
`screen ${screen ? `${screen.width}x${screen.height}@${density ?? '?'}dpi` : '?'} · orientation ${orient} · locale ${props.locale ?? '?'} · tz ${props.timezone ?? '?'}\n` +
|
|
48
|
+
`${pkgs.length} third-party apps installed${withPkgs ? ':\n ' + pkgs.join('\n ') : ' (listPackages:true to list)'}`;
|
|
49
|
+
return qaOk(payload, summary);
|
|
50
|
+
});
|
|
51
|
+
server.registerTool('qa_orientation', {
|
|
52
|
+
title: 'Set orientation',
|
|
53
|
+
description: 'Set screen orientation: portrait | landscape | auto (re-enables auto-rotate). Non-destructive; logged as an environment change and surfaced in qa_report. Useful for testing rotation handling.',
|
|
54
|
+
inputSchema: {
|
|
55
|
+
sessionId: z.string(),
|
|
56
|
+
orientation: z.enum(['portrait', 'landscape', 'auto']),
|
|
57
|
+
},
|
|
58
|
+
}, async ({ sessionId, orientation }) => {
|
|
59
|
+
const session = sessions.get(sessionId);
|
|
60
|
+
const { driver } = session ? await getDriver(session) : { driver: undefined };
|
|
61
|
+
const serial = driver?.currentDevice();
|
|
62
|
+
if (!session || !driver || !serial) {
|
|
63
|
+
return qaError({ what: 'No device attached to this session', changedState: false, retrySafe: true, nextSteps: ['Call qa_prepare_target first.'] });
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
await setOrientation(serial, orientation);
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
return qaError({ what: `Could not set orientation: ${String(e)}`, changedState: false, retrySafe: true, nextSteps: ['Confirm the device is online.'] });
|
|
70
|
+
}
|
|
71
|
+
sessions.addEnvChange(session, `orientation → ${orientation}`);
|
|
72
|
+
const now = await getOrientation(serial);
|
|
73
|
+
sessions.recordMutation(session, {
|
|
74
|
+
tool: 'qa_orientation',
|
|
75
|
+
action: 'orientation_set',
|
|
76
|
+
risk: 'low',
|
|
77
|
+
target: { device: serial, orientation, rotation: now?.rotation ?? null, autoRotate: now?.auto ?? null },
|
|
78
|
+
consent: { required: false, approved: true },
|
|
79
|
+
status: 'executed',
|
|
80
|
+
});
|
|
81
|
+
return qaOk({ orientation, rotation: now?.rotation ?? null, autoRotate: now?.auto ?? null }, `orientation set to ${orientation}${now ? ` (rotation=${now.rotation}, auto=${now.auto})` : ''}`);
|
|
82
|
+
});
|
|
83
|
+
server.registerTool('qa_geolocation', {
|
|
84
|
+
title: 'Set location',
|
|
85
|
+
description: 'Spoof the device GPS location (emulator only). Consent-gated and logged as an environment change. Useful for testing location-dependent apps (maps, nearby, geofencing). Pass lat + lng (decimal degrees).',
|
|
86
|
+
inputSchema: {
|
|
87
|
+
sessionId: z.string(),
|
|
88
|
+
lat: z.number().describe('Latitude in decimal degrees.'),
|
|
89
|
+
lng: z.number().describe('Longitude in decimal degrees.'),
|
|
90
|
+
consentId: z.string().optional(),
|
|
91
|
+
approve: z.boolean().optional(),
|
|
92
|
+
},
|
|
93
|
+
}, async ({ sessionId, lat, lng, consentId, approve }) => {
|
|
94
|
+
const session = sessions.get(sessionId);
|
|
95
|
+
const { driver } = session ? await getDriver(session) : { driver: undefined };
|
|
96
|
+
const serial = driver?.currentDevice();
|
|
97
|
+
if (!session || !driver || !serial) {
|
|
98
|
+
return qaError({ what: 'No device attached to this session', changedState: false, retrySafe: true, nextSteps: ['Call qa_prepare_target first.'] });
|
|
99
|
+
}
|
|
100
|
+
if (driver.kind !== 'direct') {
|
|
101
|
+
return qaError({ what: 'Geolocation spoofing is only supported on the Android emulator backend', changedState: false, retrySafe: false, failureCode: 'BACKEND_UNSUPPORTED', nextSteps: ['On iOS, set a simulated location from the simulator UI; on real devices use a mock-location app.'] });
|
|
102
|
+
}
|
|
103
|
+
const gate = consumeConsent(consentId, approve, { action: 'geo_set', affects: { lat, lng } });
|
|
104
|
+
if (!gate.approved) {
|
|
105
|
+
sessions.recordMutation(session, {
|
|
106
|
+
tool: 'qa_geolocation',
|
|
107
|
+
action: 'geo_set',
|
|
108
|
+
risk: 'medium',
|
|
109
|
+
target: { device: serial, lat, lng },
|
|
110
|
+
consent: { required: true, approved: false },
|
|
111
|
+
status: 'requested',
|
|
112
|
+
});
|
|
113
|
+
return requireConsent({ action: 'geo_set', risk: 'medium', exactCommand: `adb -s ${serial} emu geo fix ${lng} ${lat}`, affects: { lat, lng }, explain: `Spoof the device location to (${lat}, ${lng})? Affects any location-aware app.` });
|
|
114
|
+
}
|
|
115
|
+
sessions.recordMutation(session, {
|
|
116
|
+
tool: 'qa_geolocation',
|
|
117
|
+
action: 'geo_set',
|
|
118
|
+
risk: 'medium',
|
|
119
|
+
target: { device: serial, lat, lng },
|
|
120
|
+
consent: { required: true, consentId, approved: true },
|
|
121
|
+
status: 'approved',
|
|
122
|
+
});
|
|
123
|
+
try {
|
|
124
|
+
await setGeo(serial, lat, lng);
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
const emulatorHint = !serial.startsWith('emulator-') ? ' (this looks like a real device — `adb emu` only works on emulators)' : '';
|
|
128
|
+
sessions.recordMutation(session, {
|
|
129
|
+
tool: 'qa_geolocation',
|
|
130
|
+
action: 'geo_set',
|
|
131
|
+
risk: 'medium',
|
|
132
|
+
target: { device: serial, lat, lng },
|
|
133
|
+
consent: { required: true, consentId, approved: true },
|
|
134
|
+
status: 'blocked',
|
|
135
|
+
detail: String(e),
|
|
136
|
+
});
|
|
137
|
+
return qaError({ what: `Could not set location: ${String(e)}${emulatorHint}`, changedState: false, retrySafe: true, nextSteps: ['Use an emulator for location spoofing.'] });
|
|
138
|
+
}
|
|
139
|
+
sessions.addEnvChange(session, `geolocation → (${lat}, ${lng})`);
|
|
140
|
+
sessions.recordMutation(session, {
|
|
141
|
+
tool: 'qa_geolocation',
|
|
142
|
+
action: 'geo_set',
|
|
143
|
+
risk: 'medium',
|
|
144
|
+
target: { device: serial, lat, lng },
|
|
145
|
+
consent: { required: true, consentId, approved: true },
|
|
146
|
+
status: 'executed',
|
|
147
|
+
});
|
|
148
|
+
return qaOk({ lat, lng, set: true }, `location set to (${lat}, ${lng})`);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=device.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device.js","sourceRoot":"","sources":["../../src/tools/device.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/doctor.js
CHANGED
|
@@ -1,19 +1,65 @@
|
|
|
1
1
|
// qa_doctor — proactive environment self-diagnosis (DESIGN §3, §7). Run first.
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
3
5
|
import { qaOk } from '../lib/result.js';
|
|
4
6
|
import { getSchemaHash } from '../lib/schemaHash.js';
|
|
5
7
|
import { which, firstLine, adbDevices, listAvds, deviceFreeDataBytes, fmtBytes } from '../lib/android.js';
|
|
8
|
+
import { simctlAvailable, listSimulators } from '../lib/simctl.js';
|
|
9
|
+
import { checkWda, discoverWdaProjects, xcodeAvailable } from '../lib/wda.js';
|
|
10
|
+
import { loadWdaConfig } from '../lib/wdaConfig.js';
|
|
6
11
|
import { SWIPIUM_VERSION, TOOL_NAMES, TOOL_COUNT, STALE_CLIENT_HINT } from '../version.js';
|
|
7
12
|
const CLIENT_HINTS = {
|
|
8
13
|
claude: 'Register with: claude mcp add swipium --scope project -- node <abs>/dist/index.js',
|
|
9
14
|
gemini: 'Add to settings.json mcpServers; set an absolute "command" and a "cwd". stdio cwd is otherwise undefined.',
|
|
10
15
|
codex: 'Codex has an open tool-injection regression (#19425) on builds after ~0.120.0. Set cwd explicitly, use an absolute node path, and verify tools actually appear after `init codex`.',
|
|
11
16
|
};
|
|
17
|
+
function checkLine(c) {
|
|
18
|
+
const status = c.ok ? '[ok]' : c.optional ? '[warn]' : '[fail]';
|
|
19
|
+
return `${status} ${c.name}: ${c.detail}${c.fix && !c.ok ? ` -> ${c.fix}` : ''}`;
|
|
20
|
+
}
|
|
21
|
+
function wdaBuildProductStatus(derivedDataPath) {
|
|
22
|
+
if (!existsSync(derivedDataPath))
|
|
23
|
+
return { built: false, checkedPath: derivedDataPath };
|
|
24
|
+
const stack = [{ path: derivedDataPath, depth: 0 }];
|
|
25
|
+
while (stack.length) {
|
|
26
|
+
const cur = stack.pop();
|
|
27
|
+
if (cur.depth > 6)
|
|
28
|
+
continue;
|
|
29
|
+
let entries;
|
|
30
|
+
try {
|
|
31
|
+
entries = readdirSync(cur.path);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
for (const name of entries) {
|
|
37
|
+
const p = join(cur.path, name);
|
|
38
|
+
let st;
|
|
39
|
+
try {
|
|
40
|
+
st = statSync(p);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (!st.isDirectory())
|
|
46
|
+
continue;
|
|
47
|
+
if (/WebDriverAgentRunner.*\.app$/i.test(name))
|
|
48
|
+
return { built: true, productPath: p, checkedPath: derivedDataPath };
|
|
49
|
+
stack.push({ path: p, depth: cur.depth + 1 });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { built: false, checkedPath: derivedDataPath };
|
|
53
|
+
}
|
|
12
54
|
export function registerDoctor(server) {
|
|
13
55
|
server.registerTool('qa_doctor', {
|
|
14
56
|
title: 'QA environment doctor',
|
|
15
|
-
description: 'Probe the local environment for
|
|
57
|
+
description: 'Probe the local environment for simulator QA prerequisites. Use platform:"android" for Android Emulator, platform:"ios" for iOS Simulator + WDA status, or platform:"both". Pass client to tailor MCP registration hints.',
|
|
16
58
|
inputSchema: {
|
|
59
|
+
platform: z
|
|
60
|
+
.enum(['android', 'ios', 'both'])
|
|
61
|
+
.optional()
|
|
62
|
+
.describe('Which simulator environment to evaluate. Defaults to android for backward compatibility.'),
|
|
17
63
|
client: z
|
|
18
64
|
.enum(['claude', 'gemini', 'codex'])
|
|
19
65
|
.optional()
|
|
@@ -25,8 +71,13 @@ export function registerDoctor(server) {
|
|
|
25
71
|
expectedVersion: z.string().optional().describe('Swipium version your docs expect; mismatch ⇒ stale client.'),
|
|
26
72
|
expectedSchemaHash: z.string().optional().describe('Tool-surface hash your docs expect; mismatch ⇒ the client is on a build with a different surface (same tool count can still be stale).'),
|
|
27
73
|
},
|
|
28
|
-
}, async ({ client, expectedToolCount, expectedVersion, expectedSchemaHash }) => {
|
|
74
|
+
}, async ({ platform = 'android', client, expectedToolCount, expectedVersion, expectedSchemaHash }) => {
|
|
75
|
+
const requested = platform;
|
|
76
|
+
const wantsAndroid = requested === 'android' || requested === 'both';
|
|
77
|
+
const wantsIos = requested === 'ios' || requested === 'both';
|
|
29
78
|
const checks = [];
|
|
79
|
+
const androidChecks = [];
|
|
80
|
+
const iosChecks = [];
|
|
30
81
|
const schemaHash = getSchemaHash();
|
|
31
82
|
// Stale-client detection (P1.8 + 3.3 A): the agent (via docs) tells us what it expects; we are
|
|
32
83
|
// the source of truth. Version is for humans; the schema hash catches "same count, different
|
|
@@ -46,69 +97,140 @@ export function registerDoctor(server) {
|
|
|
46
97
|
});
|
|
47
98
|
}
|
|
48
99
|
checks.push({ name: 'node', ok: true, detail: process.version });
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
100
|
+
let devices = [];
|
|
101
|
+
let avds = [];
|
|
102
|
+
let androidReady = true;
|
|
103
|
+
if (wantsAndroid) {
|
|
104
|
+
const hasAdb = await which('adb');
|
|
105
|
+
androidChecks.push({
|
|
106
|
+
name: 'adb',
|
|
107
|
+
ok: hasAdb,
|
|
108
|
+
detail: hasAdb ? ((await firstLine('adb', ['version'])) ?? 'present') : 'not on PATH',
|
|
109
|
+
fix: hasAdb ? undefined : 'Install Android platform-tools and add to PATH (ANDROID_HOME/platform-tools).',
|
|
110
|
+
});
|
|
111
|
+
devices = hasAdb ? await adbDevices() : [];
|
|
112
|
+
const deviceDetails = await Promise.all(devices.map(async (d) => {
|
|
113
|
+
const free = await deviceFreeDataBytes(d);
|
|
114
|
+
const low = free != null && free < 600 * 1024 * 1024;
|
|
115
|
+
return `${d} (/data free: ${fmtBytes(free)}${low ? ' low' : ''})`;
|
|
116
|
+
}));
|
|
117
|
+
const anyLowSpace = deviceDetails.some((d) => d.includes(' low'));
|
|
118
|
+
androidChecks.push({
|
|
119
|
+
name: 'device-online',
|
|
120
|
+
ok: devices.length > 0,
|
|
121
|
+
optional: true,
|
|
122
|
+
detail: devices.length ? deviceDetails.join(', ') : 'no device/emulator online',
|
|
123
|
+
fix: devices.length
|
|
124
|
+
? anyLowSpace
|
|
125
|
+
? 'Low /data space: reboot the emulator with `-wipe-data -partition-size 8192` before installing large RN APKs.'
|
|
126
|
+
: undefined
|
|
127
|
+
: 'Boot an emulator, or let qa_prepare_target boot an available AVD.',
|
|
128
|
+
});
|
|
129
|
+
const hasEmulator = await which('emulator');
|
|
130
|
+
avds = hasEmulator ? await listAvds() : [];
|
|
131
|
+
androidChecks.push({
|
|
132
|
+
name: 'emulator+avd',
|
|
133
|
+
ok: hasEmulator && avds.length > 0,
|
|
134
|
+
optional: devices.length > 0,
|
|
135
|
+
detail: hasEmulator ? (avds.length ? `AVDs: ${avds.join(', ')}` : 'emulator present, no AVDs') : 'emulator not on PATH',
|
|
136
|
+
fix: hasEmulator && avds.length === 0
|
|
137
|
+
? 'Create an AVD with Android Studio or avdmanager.'
|
|
138
|
+
: hasEmulator
|
|
139
|
+
? undefined
|
|
140
|
+
: 'Install the Android Emulator package via Android Studio or sdkmanager.',
|
|
141
|
+
});
|
|
142
|
+
androidChecks.push({
|
|
143
|
+
name: 'android-target',
|
|
144
|
+
ok: devices.length > 0 || (hasEmulator && avds.length > 0),
|
|
145
|
+
detail: devices.length > 0 ? 'online device/emulator available' : hasEmulator && avds.length > 0 ? 'bootable AVD available' : 'no online target or bootable AVD',
|
|
146
|
+
fix: devices.length > 0 || (hasEmulator && avds.length > 0) ? undefined : 'Create or boot an Android Emulator, then rerun qa_doctor.',
|
|
147
|
+
});
|
|
148
|
+
const java = await firstLine('java', ['-version']);
|
|
149
|
+
androidChecks.push({
|
|
150
|
+
name: 'java',
|
|
151
|
+
ok: java !== null,
|
|
152
|
+
optional: true,
|
|
153
|
+
detail: java ?? 'not found (only needed for build-from-source / native Android builds)',
|
|
154
|
+
});
|
|
155
|
+
checks.push(...androidChecks);
|
|
156
|
+
androidReady = androidChecks.filter((c) => !c.optional).every((c) => c.ok);
|
|
157
|
+
}
|
|
158
|
+
let simulators = [];
|
|
159
|
+
let iosReady = true;
|
|
160
|
+
let wdaSummary;
|
|
161
|
+
if (wantsIos) {
|
|
162
|
+
const xcode = await xcodeAvailable();
|
|
163
|
+
iosChecks.push({
|
|
164
|
+
name: 'xcodebuild',
|
|
165
|
+
ok: xcode.available,
|
|
166
|
+
detail: xcode.available ? (xcode.version ?? 'present') : (xcode.error ?? 'not available'),
|
|
167
|
+
fix: xcode.available ? undefined : 'Install Xcode and select it with `xcode-select`.',
|
|
168
|
+
});
|
|
169
|
+
const simctlOk = await simctlAvailable();
|
|
170
|
+
iosChecks.push({
|
|
171
|
+
name: 'simctl',
|
|
172
|
+
ok: simctlOk,
|
|
173
|
+
detail: simctlOk ? 'available' : 'xcrun simctl unavailable',
|
|
174
|
+
fix: simctlOk ? undefined : 'Install Xcode command line tools and an iOS simulator runtime.',
|
|
175
|
+
});
|
|
176
|
+
simulators = simctlOk ? await listSimulators() : [];
|
|
177
|
+
const booted = simulators.filter((s) => s.state === 'Booted');
|
|
178
|
+
iosChecks.push({
|
|
179
|
+
name: 'ios-simulator',
|
|
180
|
+
ok: simulators.length > 0,
|
|
181
|
+
detail: simulators.length ? `${booted.length} booted, ${simulators.length} available` : 'no iOS simulators available',
|
|
182
|
+
fix: simulators.length ? undefined : 'Install an iOS Simulator runtime in Xcode and create a simulator.',
|
|
183
|
+
});
|
|
184
|
+
const root = process.cwd();
|
|
185
|
+
const wdaConfig = loadWdaConfig(root);
|
|
186
|
+
const wda = await checkWda(wdaConfig.url, 1200);
|
|
187
|
+
const discovery = discoverWdaProjects(root);
|
|
188
|
+
const wdaProduct = wdaBuildProductStatus(wdaConfig.derivedDataPath);
|
|
189
|
+
iosChecks.push({
|
|
190
|
+
name: 'wda-server',
|
|
191
|
+
ok: wda.reachable && wda.ready,
|
|
192
|
+
optional: true,
|
|
193
|
+
detail: wda.reachable ? `${wda.ready ? 'ready' : 'reachable but not ready'} at ${wdaConfig.url}` : `not reachable at ${wdaConfig.url}`,
|
|
194
|
+
fix: wda.reachable && wda.ready ? undefined : 'For structured iOS flows, run qa_wda doctor/build/start or attach an external WDA URL.',
|
|
195
|
+
});
|
|
196
|
+
iosChecks.push({
|
|
197
|
+
name: 'wda-project-cache',
|
|
198
|
+
ok: discovery.candidates.length > 0 || wdaProduct.built,
|
|
199
|
+
optional: true,
|
|
200
|
+
detail: wdaProduct.built
|
|
201
|
+
? `built product: ${wdaProduct.productPath}`
|
|
202
|
+
: discovery.candidates.length
|
|
203
|
+
? `project candidates: ${discovery.candidates.slice(0, 3).join(', ')}`
|
|
204
|
+
: `no WDA project found; checked cache ${wdaProduct.checkedPath}`,
|
|
205
|
+
fix: discovery.candidates.length > 0 || wdaProduct.built ? undefined : 'Install appium-webdriveragent or configure ios.wda.derivedDataPath / wdaProjectPath.',
|
|
206
|
+
});
|
|
207
|
+
checks.push(...iosChecks);
|
|
208
|
+
iosReady = iosChecks.filter((c) => !c.optional).every((c) => c.ok);
|
|
209
|
+
wdaSummary = { config: { url: wdaConfig.url, mode: wdaConfig.mode, derivedDataPath: wdaConfig.derivedDataPath }, status: wda, projectDiscovery: discovery, buildProduct: wdaProduct };
|
|
210
|
+
}
|
|
211
|
+
const requiredOk = requested === 'android' ? androidReady :
|
|
212
|
+
requested === 'ios' ? iosReady :
|
|
213
|
+
androidReady && iosReady;
|
|
95
214
|
const clientHint = client ? CLIENT_HINTS[client] : undefined;
|
|
96
|
-
const table = checks
|
|
97
|
-
.map((c) => `${c.ok ? '✅' : c.optional ? '⚠️ ' : '❌'} ${c.name}: ${c.detail}${c.fix && !c.ok ? ` → ${c.fix}` : ''}`)
|
|
98
|
-
.join('\n');
|
|
215
|
+
const table = checks.map(checkLine).join('\n');
|
|
99
216
|
const summary = `Swipium v${SWIPIUM_VERSION} · ${TOOL_COUNT} tools · schema ${schemaHash}\n${STALE_CLIENT_HINT}\n\n` +
|
|
100
|
-
`${requiredOk ?
|
|
217
|
+
`${requiredOk ? `Environment ready for ${requested} simulator QA.` : `Environment NOT ready for ${requested} simulator QA. See [fail] rows.`}\n${table}`;
|
|
101
218
|
return qaOk({
|
|
102
219
|
swipiumVersion: SWIPIUM_VERSION,
|
|
103
220
|
schemaHash,
|
|
104
221
|
toolSurface: { count: TOOL_COUNT, tools: TOOL_NAMES },
|
|
222
|
+
platform: requested,
|
|
105
223
|
ready: requiredOk,
|
|
224
|
+
platformReady: { android: wantsAndroid ? androidReady : null, ios: wantsIos ? iosReady : null },
|
|
106
225
|
clientFreshness: expectedToolCount != null || expectedVersion != null || expectedSchemaHash != null
|
|
107
226
|
? { stale: versionMismatch || toolCountMismatch || schemaHashMismatch, runningVersion: SWIPIUM_VERSION, runningToolCount: TOOL_COUNT, runningSchemaHash: schemaHash, expectedVersion: expectedVersion ?? null, expectedToolCount: expectedToolCount ?? null, expectedSchemaHash: expectedSchemaHash ?? null }
|
|
108
227
|
: undefined,
|
|
109
228
|
checks,
|
|
229
|
+
checksByPlatform: { android: androidChecks, ios: iosChecks },
|
|
110
230
|
devicesOnline: devices,
|
|
111
231
|
avds,
|
|
232
|
+
simulators: wantsIos ? simulators : undefined,
|
|
233
|
+
wda: wdaSummary,
|
|
112
234
|
...(clientHint ? { clientHint } : {}),
|
|
113
235
|
}, summary);
|
|
114
236
|
});
|