@tapflowio/ios-agent 0.13.0 → 0.14.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/dist/IOSAgent.d.ts +6 -1
- package/dist/IOSAgent.d.ts.map +1 -1
- package/dist/IOSAgent.js +114 -3
- package/dist/IOSAgent.js.map +1 -1
- package/dist/SimctlWrapper.d.ts +2 -0
- package/dist/SimctlWrapper.d.ts.map +1 -1
- package/dist/SimctlWrapper.js +34 -1
- package/dist/SimctlWrapper.js.map +1 -1
- package/dist/XCUITreeReader.d.ts +26 -0
- package/dist/XCUITreeReader.d.ts.map +1 -0
- package/dist/XCUITreeReader.js +194 -0
- package/dist/XCUITreeReader.js.map +1 -0
- package/dist/xcuiTree.d.ts +3 -0
- package/dist/xcuiTree.d.ts.map +1 -0
- package/dist/xcuiTree.js +129 -0
- package/dist/xcuiTree.js.map +1 -0
- package/package.json +8 -4
- package/xctest-runner/TapflowTreeRunner.xcodeproj/project.pbxproj +388 -0
- package/xctest-runner/TapflowTreeRunner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/xctest-runner/TapflowTreeRunner.xcodeproj/xcshareddata/xcschemes/TreeRunner.xcscheme +116 -0
- package/xctest-runner/TreeHost/TreeHostApp.swift +13 -0
- package/xctest-runner/TreeRunner/TreeServer.swift +107 -0
- package/xctest-runner/TreeRunner/TreeServerTest.swift +19 -0
- package/xctest-runner/project.yml +37 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { spawn, execFile } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import { readdirSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { createLogger, PlatformError } from '@tapflowio/agent-core';
|
|
6
|
+
import { parseTreeText } from './xcuiTree.js';
|
|
7
|
+
const logger = createLogger('ios-agent:xcui-tree');
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
const RUNNER_DIR = join(import.meta.dirname, '..', 'xctest-runner');
|
|
10
|
+
const PROJECT = join(RUNNER_DIR, 'TapflowTreeRunner.xcodeproj');
|
|
11
|
+
const BUILD_DIR = join(RUNNER_DIR, 'build');
|
|
12
|
+
const PRODUCTS_DIR = join(BUILD_DIR, 'Build', 'Products');
|
|
13
|
+
const SCHEME = 'TreeRunner';
|
|
14
|
+
// The in-simulator host app the UI-test runner attaches to. Terminating it ends
|
|
15
|
+
// the test session so the HTTP port is released (killing the xcodebuild wrapper
|
|
16
|
+
// alone does not tear the in-simulator process down).
|
|
17
|
+
const RUNNER_HOST_BUNDLE = 'dev.tapflow.treerunner.host';
|
|
18
|
+
// Fixed port: xcodebuild does not propagate host env to the in-simulator test
|
|
19
|
+
// runner, so a per-udid port can't be passed this way. A single resident runner
|
|
20
|
+
// (guarded below) means no collision; concurrent multi-device is deferred (Q8).
|
|
21
|
+
const PORT = 22087;
|
|
22
|
+
const defaultNative = {
|
|
23
|
+
xctestrunReady() {
|
|
24
|
+
// test-without-building needs the .xctestrun, not just the .app — gate on it.
|
|
25
|
+
try {
|
|
26
|
+
return readdirSync(PRODUCTS_DIR).some((f) => f.endsWith('.xctestrun'));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
async build(destination) {
|
|
33
|
+
await execFileAsync('xcodebuild', ['build-for-testing', '-project', PROJECT, '-scheme', SCHEME, '-destination', destination, '-derivedDataPath', BUILD_DIR, '-quiet'], { timeout: 300_000, maxBuffer: 64 * 1024 * 1024 });
|
|
34
|
+
},
|
|
35
|
+
spawn(destination, port) {
|
|
36
|
+
return spawn('xcodebuild', ['test-without-building', '-project', PROJECT, '-scheme', SCHEME, '-destination', destination, '-derivedDataPath', BUILD_DIR, '-quiet'], { env: { ...process.env, TAPFLOW_TREE_PORT: String(port) }, stdio: 'ignore', detached: true });
|
|
37
|
+
},
|
|
38
|
+
terminateHost(udid) {
|
|
39
|
+
execFile('xcrun', ['simctl', 'terminate', udid, RUNNER_HOST_BUNDLE], () => { });
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
// Drives the resident XCUITest tree runner: builds it once, launches it (staying
|
|
43
|
+
// resident), and queries GET /tree over the simulator-shared loopback. Replaces
|
|
44
|
+
// the AXUIElement path (UITreeReader), which required a Simulator.app window.
|
|
45
|
+
export class XCUITreeReader {
|
|
46
|
+
native;
|
|
47
|
+
runner;
|
|
48
|
+
building;
|
|
49
|
+
starting;
|
|
50
|
+
constructor(native = defaultNative) {
|
|
51
|
+
this.native = native;
|
|
52
|
+
}
|
|
53
|
+
destination(udid) {
|
|
54
|
+
// No arch — xcodebuild resolves it (arm64 on Apple Silicon, x86_64 on Intel).
|
|
55
|
+
return `platform=iOS Simulator,id=${udid}`;
|
|
56
|
+
}
|
|
57
|
+
async ensureBuilt(udid) {
|
|
58
|
+
if (this.native.xctestrunReady())
|
|
59
|
+
return;
|
|
60
|
+
if (this.building)
|
|
61
|
+
return this.building;
|
|
62
|
+
this.building = (async () => {
|
|
63
|
+
logger.info('building tree runner (first run — cached afterwards)...');
|
|
64
|
+
await this.native.build(this.destination(udid));
|
|
65
|
+
logger.info('tree runner built');
|
|
66
|
+
})();
|
|
67
|
+
try {
|
|
68
|
+
await this.building;
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
throw new PlatformError(`failed to build the UI-tree runner — ensure Xcode and an iOS simulator runtime are installed (xcode-select -p): ${e.message.slice(0, 300)}`);
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
this.building = undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
runnerAlive(h) {
|
|
78
|
+
return !!h && h.proc.exitCode === null && !h.proc.killed;
|
|
79
|
+
}
|
|
80
|
+
async ensureRunner(udid) {
|
|
81
|
+
if (this.runnerAlive(this.runner) && this.runner.udid === udid)
|
|
82
|
+
return this.runner;
|
|
83
|
+
// Serialize concurrent callers so we never double-spawn or hand back an
|
|
84
|
+
// un-ready runner (a second caller must await waitReady, not just the object).
|
|
85
|
+
if (this.starting) {
|
|
86
|
+
const h = await this.starting.catch(() => undefined);
|
|
87
|
+
if (h && this.runnerAlive(h) && h.udid === udid)
|
|
88
|
+
return h;
|
|
89
|
+
}
|
|
90
|
+
this.starting = this.launchRunner(udid);
|
|
91
|
+
try {
|
|
92
|
+
return await this.starting;
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
this.starting = undefined;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async launchRunner(udid) {
|
|
99
|
+
this.stop(); // tear down any stale/other-udid runner first
|
|
100
|
+
await this.ensureBuilt(udid);
|
|
101
|
+
const proc = this.native.spawn(this.destination(udid), PORT);
|
|
102
|
+
proc.on('error', (e) => logger.error('tree runner process error:', e.message));
|
|
103
|
+
const handle = { proc, udid, port: PORT };
|
|
104
|
+
this.runner = handle;
|
|
105
|
+
await this.waitReady(handle);
|
|
106
|
+
return handle;
|
|
107
|
+
}
|
|
108
|
+
async waitReady(handle) {
|
|
109
|
+
for (let i = 0; i < 90; i++) {
|
|
110
|
+
if (!this.runnerAlive(handle)) {
|
|
111
|
+
if (this.runner === handle)
|
|
112
|
+
this.runner = undefined;
|
|
113
|
+
throw new PlatformError('tree runner exited before becoming ready — check Xcode/simulator state');
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const r = await fetch(`http://localhost:${handle.port}/health`, { signal: AbortSignal.timeout(1000) });
|
|
117
|
+
if (r.ok)
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
/* not listening yet */
|
|
122
|
+
}
|
|
123
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
124
|
+
}
|
|
125
|
+
this.stop();
|
|
126
|
+
throw new PlatformError('tree runner did not become ready in time');
|
|
127
|
+
}
|
|
128
|
+
async read(udid, bundleId) {
|
|
129
|
+
if (!bundleId)
|
|
130
|
+
throw new PlatformError('UI tree query needs a foreground app — launch an app first');
|
|
131
|
+
const { port } = await this.ensureRunner(udid);
|
|
132
|
+
let text;
|
|
133
|
+
try {
|
|
134
|
+
const r = await fetch(`http://localhost:${port}/tree?bundleId=${encodeURIComponent(bundleId)}`, { signal: AbortSignal.timeout(10_000) });
|
|
135
|
+
if (!r.ok)
|
|
136
|
+
throw new PlatformError(`tree runner returned HTTP ${r.status} for ${bundleId}`);
|
|
137
|
+
text = await r.text();
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
if (e instanceof PlatformError)
|
|
141
|
+
throw e;
|
|
142
|
+
throw new PlatformError(`UI tree query failed: ${e.message}`);
|
|
143
|
+
}
|
|
144
|
+
// Guard against a garbage / wrong-bundleId body silently parsing to an empty
|
|
145
|
+
// tree — consumers must see an error, never a false "screen has no elements".
|
|
146
|
+
if (!text.includes('Element subtree:') && !text.includes('Application,')) {
|
|
147
|
+
throw new PlatformError(`tree runner returned an unexpected response for ${bundleId} — is the app running in the foreground?`);
|
|
148
|
+
}
|
|
149
|
+
return parseTreeText(text);
|
|
150
|
+
}
|
|
151
|
+
// Stop the resident runner regardless of which device it serves.
|
|
152
|
+
stop() {
|
|
153
|
+
const handle = this.runner;
|
|
154
|
+
if (!handle)
|
|
155
|
+
return;
|
|
156
|
+
this.runner = undefined;
|
|
157
|
+
this.killHandle(handle);
|
|
158
|
+
}
|
|
159
|
+
// Stop only if the runner currently serves this device — so shutting down one
|
|
160
|
+
// booted device does not kill another device's runner.
|
|
161
|
+
stopIfDevice(udid) {
|
|
162
|
+
if (this.runner?.udid === udid)
|
|
163
|
+
this.stop();
|
|
164
|
+
}
|
|
165
|
+
killHandle(handle) {
|
|
166
|
+
const { proc, udid } = handle;
|
|
167
|
+
const pid = proc.pid;
|
|
168
|
+
// detached spawn → the child leads its own group; kill the whole group.
|
|
169
|
+
try {
|
|
170
|
+
if (pid)
|
|
171
|
+
process.kill(-pid, 'SIGTERM');
|
|
172
|
+
else
|
|
173
|
+
proc.kill('SIGTERM');
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
/* already gone */
|
|
177
|
+
}
|
|
178
|
+
// The in-simulator test host is not in that group — terminate it so the HTTP
|
|
179
|
+
// port is released instead of lingering.
|
|
180
|
+
this.native.terminateHost(udid);
|
|
181
|
+
// SIGKILL fallback if SIGTERM did not take.
|
|
182
|
+
const timer = setTimeout(() => {
|
|
183
|
+
try {
|
|
184
|
+
if (pid)
|
|
185
|
+
process.kill(-pid, 'SIGKILL');
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
/* gone */
|
|
189
|
+
}
|
|
190
|
+
}, 3000);
|
|
191
|
+
timer.unref?.();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=XCUITreeReader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"XCUITreeReader.js","sourceRoot":"","sources":["../src/XCUITreeReader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xcuiTree.d.ts","sourceRoot":"","sources":["../src/xcuiTree.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAiB,MAAM,uBAAuB,CAAA;AAuFrE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,CAgDvD"}
|
package/dist/xcuiTree.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Parses the text emitted by XCUIApplication.debugDescription (served by the
|
|
2
|
+
// resident xctest-runner) into the unified UIElement schema. Kept as a pure
|
|
3
|
+
// function so it stays unit-testable against a fixed fixture (Open Q9: a format
|
|
4
|
+
// change in a future Xcode is caught by the snapshot regression test).
|
|
5
|
+
//
|
|
6
|
+
// A debugDescription element line looks like:
|
|
7
|
+
// NavigationBar, 0x105808b10, {{0.0, 47.4}, {402.0, 47.4}}, identifier: 'The App'
|
|
8
|
+
// Other, 0x105810800, {{0.0, 131.3}, {402.0, 66.4}}, identifier: 'Echo Box', label: 'Echo Box'
|
|
9
|
+
// Other, 0x105811820, {{366.6, 94.7}, {32.2, 779.3}}, label: 'Vertical scroll bar', value: 0%
|
|
10
|
+
// Fields after the address appear in order: [pid], [frame], [identifier], [label], [value].
|
|
11
|
+
// XCUITest element type → unified vocabulary. XCUITest labels many custom/composed
|
|
12
|
+
// views as "Other" (they still carry identifier/label), so role is best-effort and
|
|
13
|
+
// identifier/label carry the selector weight.
|
|
14
|
+
function toRole(type) {
|
|
15
|
+
switch (type) {
|
|
16
|
+
case 'Button':
|
|
17
|
+
case 'Key':
|
|
18
|
+
return 'button';
|
|
19
|
+
case 'TextField':
|
|
20
|
+
case 'SecureTextField':
|
|
21
|
+
case 'SearchField':
|
|
22
|
+
case 'TextView':
|
|
23
|
+
return 'input';
|
|
24
|
+
case 'StaticText':
|
|
25
|
+
return 'text';
|
|
26
|
+
case 'Image':
|
|
27
|
+
case 'Icon':
|
|
28
|
+
return 'image';
|
|
29
|
+
case 'Switch':
|
|
30
|
+
case 'Toggle':
|
|
31
|
+
return 'switch';
|
|
32
|
+
case 'CheckBox':
|
|
33
|
+
return 'checkbox';
|
|
34
|
+
case 'Slider':
|
|
35
|
+
return 'slider';
|
|
36
|
+
case 'Table':
|
|
37
|
+
case 'CollectionView':
|
|
38
|
+
case 'ScrollView':
|
|
39
|
+
return 'list';
|
|
40
|
+
case 'Cell':
|
|
41
|
+
return 'cell';
|
|
42
|
+
case 'TabBar':
|
|
43
|
+
case 'TabGroup':
|
|
44
|
+
return 'tab';
|
|
45
|
+
default:
|
|
46
|
+
return 'other';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const ACTIONABLE = new Set(['button', 'input', 'checkbox', 'switch', 'slider', 'tab', 'cell']);
|
|
50
|
+
const round4 = (n) => Math.round(n * 10000) / 10000;
|
|
51
|
+
const FRAME_RE = /\{\{(-?[\d.]+), (-?[\d.]+)\}, \{(-?[\d.]+), (-?[\d.]+)\}\}/;
|
|
52
|
+
// Terminate quoted fields on the next field boundary, so a quote inside the
|
|
53
|
+
// value (e.g. label: 'Don't') does not truncate the match. The `, value: `
|
|
54
|
+
// lookahead stays even though value isn't captured — a trailing value field is
|
|
55
|
+
// still a valid label boundary.
|
|
56
|
+
const IDENTIFIER_RE = /, identifier: '(.*?)'(?=, label: |, value: |$)/;
|
|
57
|
+
const LABEL_RE = /, label: '(.*?)'(?=, value: |$)/;
|
|
58
|
+
function parseLine(line) {
|
|
59
|
+
// Strip indentation + the root arrow, then take the type up to ", 0x<addr>".
|
|
60
|
+
const m = /^\s*→?\s*(.+?), 0x[0-9a-fA-F]+/.exec(line);
|
|
61
|
+
if (!m)
|
|
62
|
+
return null;
|
|
63
|
+
const node = { type: m[1] };
|
|
64
|
+
const fm = FRAME_RE.exec(line);
|
|
65
|
+
if (fm) {
|
|
66
|
+
node.frame = { x: parseFloat(fm[1]), y: parseFloat(fm[2]), width: parseFloat(fm[3]), height: parseFloat(fm[4]) };
|
|
67
|
+
}
|
|
68
|
+
const im = IDENTIFIER_RE.exec(line);
|
|
69
|
+
if (im)
|
|
70
|
+
node.identifier = im[1];
|
|
71
|
+
const lm = LABEL_RE.exec(line);
|
|
72
|
+
if (lm)
|
|
73
|
+
node.label = lm[1];
|
|
74
|
+
return node;
|
|
75
|
+
}
|
|
76
|
+
export function parseTreeText(text) {
|
|
77
|
+
const lines = text.split('\n');
|
|
78
|
+
const startIdx = lines.findIndex((l) => l.trim() === 'Element subtree:');
|
|
79
|
+
const bodyLines = startIdx >= 0 ? lines.slice(startIdx + 1) : lines;
|
|
80
|
+
const raw = [];
|
|
81
|
+
for (const line of bodyLines) {
|
|
82
|
+
if (!line.trim())
|
|
83
|
+
continue;
|
|
84
|
+
// debugDescription ends with sections like "Path to element:" / "Query chain:".
|
|
85
|
+
if (!/, 0x[0-9a-fA-F]+/.test(line))
|
|
86
|
+
break;
|
|
87
|
+
const node = parseLine(line);
|
|
88
|
+
if (node)
|
|
89
|
+
raw.push(node);
|
|
90
|
+
}
|
|
91
|
+
// Screen size = the first Window frame (normalization basis, same coordinate
|
|
92
|
+
// space as the tap path). Fall back to the largest frame if no Window is found.
|
|
93
|
+
const windowFrame = raw.find((n) => n.type.startsWith('Window') && n.frame)?.frame;
|
|
94
|
+
const basis = windowFrame ?? raw.reduce((max, n) => {
|
|
95
|
+
if (!n.frame)
|
|
96
|
+
return max;
|
|
97
|
+
const area = n.frame.width * n.frame.height;
|
|
98
|
+
return !max || area > max.width * max.height ? n.frame : max;
|
|
99
|
+
}, undefined);
|
|
100
|
+
if (!basis || basis.width <= 0 || basis.height <= 0)
|
|
101
|
+
return [];
|
|
102
|
+
const elements = [];
|
|
103
|
+
for (const node of raw) {
|
|
104
|
+
if (!node.frame || node.frame.width <= 0 || node.frame.height <= 0)
|
|
105
|
+
continue;
|
|
106
|
+
const role = toRole(node.type);
|
|
107
|
+
const label = node.label ?? '';
|
|
108
|
+
// Skip pure structural containers with no label and no actionable role.
|
|
109
|
+
if (!ACTIONABLE.has(role) && label === '' && !node.identifier)
|
|
110
|
+
continue;
|
|
111
|
+
const element = {
|
|
112
|
+
role,
|
|
113
|
+
label,
|
|
114
|
+
frame: {
|
|
115
|
+
x: round4((node.frame.x - basis.x) / basis.width),
|
|
116
|
+
y: round4((node.frame.y - basis.y) / basis.height),
|
|
117
|
+
width: round4(node.frame.width / basis.width),
|
|
118
|
+
height: round4(node.frame.height / basis.height),
|
|
119
|
+
},
|
|
120
|
+
enabled: true, // debugDescription does not expose enabled; default true (Open Q9)
|
|
121
|
+
};
|
|
122
|
+
if (node.identifier)
|
|
123
|
+
element.identifier = node.identifier;
|
|
124
|
+
element.rawRole = node.type;
|
|
125
|
+
elements.push(element);
|
|
126
|
+
}
|
|
127
|
+
return elements;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=xcuiTree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xcuiTree.js","sourceRoot":"","sources":["../src/xcuiTree.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tapflowio/ios-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "iOS simulator agent for tapflow — xcrun simctl control and screen streaming",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tapflow",
|
|
@@ -38,6 +38,10 @@
|
|
|
38
38
|
"files": [
|
|
39
39
|
"dist",
|
|
40
40
|
"bin",
|
|
41
|
+
"xctest-runner/project.yml",
|
|
42
|
+
"xctest-runner/TreeHost",
|
|
43
|
+
"xctest-runner/TreeRunner",
|
|
44
|
+
"xctest-runner/TapflowTreeRunner.xcodeproj",
|
|
41
45
|
"README.md",
|
|
42
46
|
"LICENSE"
|
|
43
47
|
],
|
|
@@ -49,15 +53,15 @@
|
|
|
49
53
|
},
|
|
50
54
|
"dependencies": {
|
|
51
55
|
"ws": "^8.21.0",
|
|
52
|
-
"@tapflowio/
|
|
53
|
-
"@tapflowio/
|
|
56
|
+
"@tapflowio/agent-core": "0.14.0",
|
|
57
|
+
"@tapflowio/audiotap-helper": "0.2.4"
|
|
54
58
|
},
|
|
55
59
|
"devDependencies": {
|
|
56
60
|
"@types/node": "^20.19.43",
|
|
57
61
|
"@types/ws": "^8.0.0",
|
|
58
62
|
"typescript": "^5.0.0",
|
|
59
63
|
"vitest": "^4.1.9",
|
|
60
|
-
"@tapflowio/relay": "0.
|
|
64
|
+
"@tapflowio/relay": "0.14.0"
|
|
61
65
|
},
|
|
62
66
|
"scripts": {
|
|
63
67
|
"postinstall": "node -e \"if (process.platform === 'darwin') { require('fs').readdirSync('bin').forEach(f => require('fs').chmodSync('bin/' + f, 0o755)) }\"",
|