@houwert/conductor 0.2.0 → 0.4.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/.claude-plugin/plugin.json +4 -1
- package/README.md +121 -21
- package/dist/commands/assert-not-visible.js +5 -0
- package/dist/commands/assert-visible.js +15 -0
- package/dist/commands/back.js +6 -1
- package/dist/commands/cheat-sheet.js +4 -0
- package/dist/commands/clear-state.js +27 -0
- package/dist/commands/copy-app.js +55 -0
- package/dist/commands/daemon.js +4 -0
- package/dist/commands/device-pool.js +4 -0
- package/dist/commands/erase-text.js +2 -0
- package/dist/commands/focused.js +233 -0
- package/dist/commands/foreground-app.js +2 -0
- package/dist/commands/hide-keyboard.js +2 -0
- package/dist/commands/inspect.js +23 -1
- package/dist/commands/install.js +69 -4
- package/dist/commands/launch-app.js +6 -0
- package/dist/commands/list-apps.js +3 -1
- package/dist/commands/list-devices.js +70 -7
- package/dist/commands/open-link.js +2 -0
- package/dist/commands/press-key.js +73 -7
- package/dist/commands/run-flow-inline.js +3 -0
- package/dist/commands/run-flow.js +4 -0
- package/dist/commands/run-parallel.js +2 -0
- package/dist/commands/screenshot.js +2 -0
- package/dist/commands/scroll-until-visible.js +6 -0
- package/dist/commands/scroll.js +6 -0
- package/dist/commands/session.js +2 -0
- package/dist/commands/set-location.js +2 -0
- package/dist/commands/set-orientation.js +2 -0
- package/dist/commands/start-device.js +311 -9
- package/dist/commands/stop-app.js +2 -0
- package/dist/commands/swipe.js +10 -0
- package/dist/commands/tap.js +20 -0
- package/dist/commands/type.js +2 -0
- package/dist/commands/uninstall-app.js +31 -0
- package/dist/daemon/server.js +64 -22
- package/dist/device-picker.js +65 -0
- package/dist/drivers/android.js +18 -13
- package/dist/drivers/bootstrap.js +179 -6
- package/dist/drivers/element-resolver.js +10 -0
- package/dist/drivers/flow-runner.js +21 -5
- package/dist/drivers/ios.js +7 -1
- package/dist/index.js +134 -97
- package/dist/postinstall.js +2 -2
- package/dist/runner.js +40 -6
- package/drivers/ios/conductor-driver-ios.zip +0 -0
- package/drivers/ios/conductor-driver-iosUITests-Runner.zip +0 -0
- package/drivers/tvos/conductor-driver-tvos-config.xctestrun +121 -0
- package/drivers/tvos/conductor-driver-tvos.zip +0 -0
- package/drivers/tvos/conductor-driver-tvosUITests-Runner.zip +0 -0
- package/package.json +1 -1
- package/skills/conductor/SKILL.md +66 -6
- package/skills/conductor/references/flow-syntax.md +3 -0
- package/skills/skills.yaml +8 -0
package/dist/index.js
CHANGED
|
@@ -9,6 +9,8 @@ const verbose_js_1 = require("./verbose.js");
|
|
|
9
9
|
const list_devices_js_1 = require("./commands/list-devices.js");
|
|
10
10
|
const launch_app_js_1 = require("./commands/launch-app.js");
|
|
11
11
|
const stop_app_js_1 = require("./commands/stop-app.js");
|
|
12
|
+
const clear_state_js_1 = require("./commands/clear-state.js");
|
|
13
|
+
const uninstall_app_js_1 = require("./commands/uninstall-app.js");
|
|
12
14
|
const tap_js_1 = require("./commands/tap.js");
|
|
13
15
|
const type_js_1 = require("./commands/type.js");
|
|
14
16
|
const back_js_1 = require("./commands/back.js");
|
|
@@ -17,6 +19,7 @@ const swipe_js_1 = require("./commands/swipe.js");
|
|
|
17
19
|
const assert_visible_js_1 = require("./commands/assert-visible.js");
|
|
18
20
|
const screenshot_js_1 = require("./commands/screenshot.js");
|
|
19
21
|
const inspect_js_1 = require("./commands/inspect.js");
|
|
22
|
+
const focused_js_1 = require("./commands/focused.js");
|
|
20
23
|
const run_flow_js_1 = require("./commands/run-flow.js");
|
|
21
24
|
const run_flow_inline_js_1 = require("./commands/run-flow-inline.js");
|
|
22
25
|
const press_key_js_1 = require("./commands/press-key.js");
|
|
@@ -28,6 +31,7 @@ const device_pool_js_1 = require("./commands/device-pool.js");
|
|
|
28
31
|
const run_parallel_js_1 = require("./commands/run-parallel.js");
|
|
29
32
|
const foreground_app_js_1 = require("./commands/foreground-app.js");
|
|
30
33
|
const list_apps_js_1 = require("./commands/list-apps.js");
|
|
34
|
+
const copy_app_js_1 = require("./commands/copy-app.js");
|
|
31
35
|
const erase_text_js_1 = require("./commands/erase-text.js");
|
|
32
36
|
const assert_not_visible_js_1 = require("./commands/assert-not-visible.js");
|
|
33
37
|
const open_link_js_1 = require("./commands/open-link.js");
|
|
@@ -36,100 +40,58 @@ const scroll_until_visible_js_1 = require("./commands/scroll-until-visible.js");
|
|
|
36
40
|
const set_location_js_1 = require("./commands/set-location.js");
|
|
37
41
|
const set_orientation_js_1 = require("./commands/set-orientation.js");
|
|
38
42
|
const start_device_js_1 = require("./commands/start-device.js");
|
|
39
|
-
const
|
|
43
|
+
const device_picker_js_1 = require("./device-picker.js");
|
|
40
44
|
const update_check_js_1 = require("./update-check.js");
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
--start <x,y> Start coordinate (0–1 normalised or absolute px)
|
|
81
|
-
--end <x,y> End coordinate (0–1 normalised or absolute px)
|
|
82
|
-
--duration <ms> Swipe duration in milliseconds (default: 500)
|
|
83
|
-
scroll-until-visible <element> Scroll until element is visible
|
|
84
|
-
--id <id> Match by accessibility id instead of text
|
|
85
|
-
--text <text> Match by text only (not id)
|
|
86
|
-
--direction <down|up|left|right> Scroll direction (default: down)
|
|
87
|
-
--timeout <ms> Max time in milliseconds (default: 30000)
|
|
88
|
-
assert-visible <element> Assert element is visible
|
|
89
|
-
--id <id> Match by accessibility id instead of text
|
|
90
|
-
--text <text> Match by text only (not id)
|
|
91
|
-
--index <n> Pick the nth match (0-based)
|
|
92
|
-
--timeout <ms> Max wait time in milliseconds
|
|
93
|
-
--optional Do not fail if element is not found
|
|
94
|
-
--focused Match only focused elements
|
|
95
|
-
--enabled / --no-enabled Match by enabled state
|
|
96
|
-
--checked / --no-checked Match by checked state
|
|
97
|
-
--selected / --no-selected Match by selected state
|
|
98
|
-
--below <text> Match element below the given reference
|
|
99
|
-
--above <text> Match element above the given reference
|
|
100
|
-
--left-of <text> Match element left of the given reference
|
|
101
|
-
--right-of <text> Match element right of the given reference
|
|
102
|
-
assert-not-visible <element> Assert element is absent from screen
|
|
103
|
-
--id <id> Match by accessibility id instead of text
|
|
104
|
-
--text <text> Match by text only (not id)
|
|
105
|
-
--timeout <ms> Max check time in milliseconds (default: 1000)
|
|
106
|
-
open-link <url> Open a URL / deep link
|
|
107
|
-
set-location --lat <n> --lng <n> Set GPS coordinates
|
|
108
|
-
set-orientation <portrait|landscape> Set device orientation
|
|
109
|
-
screenshot [--output <path>] Take screenshot
|
|
110
|
-
inspect Print UI hierarchy
|
|
111
|
-
run-flow <file> [--device <id>] Run a Maestro YAML flow file
|
|
112
|
-
--env KEY=VALUE Inject env var (repeatable; overrides flow env block)
|
|
113
|
-
--benchmark Print elapsed time for each command and total flow time
|
|
114
|
-
run-flow-inline <yaml> Run inline YAML commands
|
|
115
|
-
--benchmark Print elapsed time for each command and total flow time
|
|
116
|
-
session [--clear] [--list] Show, clear, or list device sessions
|
|
117
|
-
cheat-sheet Print command reference
|
|
118
|
-
install Install/reinstall Claude Code plugin
|
|
119
|
-
daemon-start Start background daemon (manages driver process)
|
|
120
|
-
daemon-stop [--all] Stop background daemon (--all stops every session's daemon)
|
|
121
|
-
daemon-status Show daemon status
|
|
122
|
-
device-pool --list List all devices and pool status
|
|
123
|
-
device-pool --acquire Claim a free device (prints device ID)
|
|
124
|
-
device-pool --release <id> Release a device back to the pool
|
|
125
|
-
run-parallel --flows-dir <path> Run flows in parallel across all devices
|
|
126
|
-
|
|
127
|
-
Options:
|
|
45
|
+
const COMMAND_HELP = {
|
|
46
|
+
'start-device': start_device_js_1.HELP,
|
|
47
|
+
'list-devices': list_devices_js_1.HELP,
|
|
48
|
+
'foreground-app': foreground_app_js_1.HELP,
|
|
49
|
+
'list-apps': list_apps_js_1.HELP,
|
|
50
|
+
'copy-app': copy_app_js_1.HELP,
|
|
51
|
+
'launch-app': launch_app_js_1.HELP,
|
|
52
|
+
'stop-app': stop_app_js_1.HELP,
|
|
53
|
+
'clear-state': clear_state_js_1.HELP,
|
|
54
|
+
'uninstall-app': uninstall_app_js_1.HELP,
|
|
55
|
+
tap: tap_js_1.HELP,
|
|
56
|
+
type: type_js_1.HELP,
|
|
57
|
+
'erase-text': erase_text_js_1.HELP,
|
|
58
|
+
back: back_js_1.HELP,
|
|
59
|
+
'press-key': press_key_js_1.HELP,
|
|
60
|
+
'hide-keyboard': hide_keyboard_js_1.HELP,
|
|
61
|
+
scroll: scroll_js_1.HELP,
|
|
62
|
+
swipe: swipe_js_1.HELP,
|
|
63
|
+
'scroll-until-visible': scroll_until_visible_js_1.HELP,
|
|
64
|
+
'assert-visible': assert_visible_js_1.HELP,
|
|
65
|
+
'assert-not-visible': assert_not_visible_js_1.HELP,
|
|
66
|
+
'open-link': open_link_js_1.HELP,
|
|
67
|
+
'set-location': set_location_js_1.HELP,
|
|
68
|
+
'set-orientation': set_orientation_js_1.HELP,
|
|
69
|
+
screenshot: screenshot_js_1.HELP,
|
|
70
|
+
inspect: inspect_js_1.HELP,
|
|
71
|
+
focused: focused_js_1.HELP,
|
|
72
|
+
'run-flow': run_flow_js_1.HELP,
|
|
73
|
+
'run-flow-inline': run_flow_inline_js_1.HELP,
|
|
74
|
+
session: session_js_1.HELP,
|
|
75
|
+
'cheat-sheet': cheat_sheet_js_1.HELP,
|
|
76
|
+
install: install_js_1.HELP,
|
|
77
|
+
'daemon-start': daemon_js_1.HELP_DAEMON_START,
|
|
78
|
+
'daemon-stop': daemon_js_1.HELP_DAEMON_STOP,
|
|
79
|
+
'daemon-status': daemon_js_1.HELP_DAEMON_STATUS,
|
|
80
|
+
'device-pool': device_pool_js_1.HELP,
|
|
81
|
+
'run-parallel': run_parallel_js_1.HELP,
|
|
82
|
+
};
|
|
83
|
+
const OPTIONS_HELP = `Options:
|
|
128
84
|
--device <id> Target device ID (also keys the session and daemon)
|
|
85
|
+
--device-name <n> Target a booted device by name (resolved to ID from booted devices)
|
|
129
86
|
--json Output as machine-readable JSON
|
|
130
87
|
--verbose, -v Log daemon calls, fallbacks, and raw output
|
|
131
|
-
--help, -h Show this help
|
|
132
|
-
|
|
88
|
+
--help, -h Show this help`;
|
|
89
|
+
const HELP = `Usage: conductor <command> [args] [options]
|
|
90
|
+
|
|
91
|
+
Commands:
|
|
92
|
+
${Object.values(COMMAND_HELP).join('\n')}
|
|
93
|
+
|
|
94
|
+
${OPTIONS_HELP}`;
|
|
133
95
|
async function main() {
|
|
134
96
|
(0, update_check_js_1.checkForUpdates)();
|
|
135
97
|
const argv = (0, minimist_1.default)(process.argv.slice(2), {
|
|
@@ -140,6 +102,7 @@ async function main() {
|
|
|
140
102
|
'list',
|
|
141
103
|
'verbose',
|
|
142
104
|
'skills',
|
|
105
|
+
'check',
|
|
143
106
|
'all',
|
|
144
107
|
'acquire',
|
|
145
108
|
'release',
|
|
@@ -150,6 +113,7 @@ async function main() {
|
|
|
150
113
|
'double-tap',
|
|
151
114
|
'optional',
|
|
152
115
|
'benchmark',
|
|
116
|
+
'dump',
|
|
153
117
|
],
|
|
154
118
|
string: [
|
|
155
119
|
'device',
|
|
@@ -171,6 +135,11 @@ async function main() {
|
|
|
171
135
|
'platform',
|
|
172
136
|
'os-version',
|
|
173
137
|
'avd',
|
|
138
|
+
'name',
|
|
139
|
+
'device-name',
|
|
140
|
+
'device-type',
|
|
141
|
+
'from',
|
|
142
|
+
'to',
|
|
174
143
|
],
|
|
175
144
|
alias: { h: 'help', v: 'verbose' },
|
|
176
145
|
});
|
|
@@ -178,20 +147,64 @@ async function main() {
|
|
|
178
147
|
(0, verbose_js_1.setVerbose)(true);
|
|
179
148
|
const [command, ...rest] = argv._;
|
|
180
149
|
const opts = { json: argv['json'] };
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
const explicitDevice = argv['device'];
|
|
184
|
-
const sessionName = explicitDevice ?? (await (0, runner_js_1.detectFirstDevice)()) ?? 'default';
|
|
150
|
+
// Handle help and unknown commands before device resolution —
|
|
151
|
+
// no point prompting for a device if we're just printing help or erroring out.
|
|
185
152
|
if (!command || argv['help']) {
|
|
186
|
-
|
|
153
|
+
if (command && argv['help'] && COMMAND_HELP[command]) {
|
|
154
|
+
console.log(`Usage: conductor ${command} [options]\n\n${COMMAND_HELP[command]}\n\n${OPTIONS_HELP}`);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
console.log(HELP);
|
|
158
|
+
}
|
|
187
159
|
process.exit(0);
|
|
188
160
|
}
|
|
161
|
+
// Commands that don't need a device session
|
|
162
|
+
const NO_DEVICE_COMMANDS = new Set([
|
|
163
|
+
'list-devices',
|
|
164
|
+
'start-device',
|
|
165
|
+
'cheat-sheet',
|
|
166
|
+
'install',
|
|
167
|
+
'copy-app',
|
|
168
|
+
'device-pool',
|
|
169
|
+
'run-parallel',
|
|
170
|
+
]);
|
|
171
|
+
if (!NO_DEVICE_COMMANDS.has(command) && !COMMAND_HELP[command]) {
|
|
172
|
+
console.error(`Unknown command: ${command}`);
|
|
173
|
+
console.error('Run `conductor --help` for usage.');
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
// The device ID is the natural key for both the session file and the daemon.
|
|
177
|
+
// Use --device if given, otherwise detect the first booted device, otherwise 'default'.
|
|
178
|
+
// Only resolve for commands that actually need a device.
|
|
179
|
+
let sessionName = 'default';
|
|
180
|
+
if (!NO_DEVICE_COMMANDS.has(command)) {
|
|
181
|
+
const explicitDevice = argv['device'];
|
|
182
|
+
const deviceName = argv['device-name'];
|
|
183
|
+
if (explicitDevice && deviceName) {
|
|
184
|
+
console.error('Error: --device and --device-name are mutually exclusive.');
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
if (deviceName) {
|
|
188
|
+
const devices = await (0, list_devices_js_1.discoverBootedDevices)();
|
|
189
|
+
const match = devices.find((d) => d.name === deviceName);
|
|
190
|
+
if (!match) {
|
|
191
|
+
console.error(`Error: No booted device found with name "${deviceName}". Run \`conductor list-devices\` to see booted devices.`);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
sessionName = match.id;
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
sessionName = explicitDevice ?? (await (0, device_picker_js_1.pickDevice)()) ?? 'default';
|
|
198
|
+
}
|
|
199
|
+
}
|
|
189
200
|
let exitCode = 0;
|
|
190
201
|
switch (command) {
|
|
191
202
|
case 'start-device':
|
|
192
203
|
exitCode = await (0, start_device_js_1.startDevice)(argv['platform'], opts, {
|
|
193
204
|
osVersion: argv['os-version'],
|
|
194
205
|
avd: argv['avd'],
|
|
206
|
+
name: argv['name'],
|
|
207
|
+
deviceType: argv['device-type'],
|
|
195
208
|
});
|
|
196
209
|
break;
|
|
197
210
|
case 'list-devices':
|
|
@@ -203,6 +216,13 @@ async function main() {
|
|
|
203
216
|
case 'list-apps':
|
|
204
217
|
exitCode = await (0, list_apps_js_1.listApps)(opts, sessionName);
|
|
205
218
|
break;
|
|
219
|
+
case 'copy-app': {
|
|
220
|
+
const bundleId = rest[0] ?? '';
|
|
221
|
+
const from = argv['from'];
|
|
222
|
+
const to = argv['to'];
|
|
223
|
+
exitCode = await (0, copy_app_js_1.copyApp)(bundleId, from ?? '', to ?? '', opts);
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
206
226
|
case 'launch-app': {
|
|
207
227
|
const appId = rest[0] ?? '';
|
|
208
228
|
// --argument key=value (repeatable) → Record<string, string>
|
|
@@ -224,6 +244,16 @@ async function main() {
|
|
|
224
244
|
exitCode = await (0, stop_app_js_1.stopApp)(appId, opts, sessionName);
|
|
225
245
|
break;
|
|
226
246
|
}
|
|
247
|
+
case 'clear-state': {
|
|
248
|
+
const appId = rest[0];
|
|
249
|
+
exitCode = await (0, clear_state_js_1.clearState)(appId, opts, sessionName);
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
case 'uninstall-app': {
|
|
253
|
+
const appId = rest[0] ?? '';
|
|
254
|
+
exitCode = await (0, uninstall_app_js_1.uninstallApp)(appId, opts, sessionName);
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
227
257
|
case 'tap': {
|
|
228
258
|
const element = rest.join(' ');
|
|
229
259
|
exitCode = await (0, tap_js_1.tap)(element, opts, sessionName, {
|
|
@@ -366,7 +396,13 @@ async function main() {
|
|
|
366
396
|
break;
|
|
367
397
|
}
|
|
368
398
|
case 'inspect':
|
|
369
|
-
exitCode = await (0, inspect_js_1.inspect)(opts, sessionName);
|
|
399
|
+
exitCode = await (0, inspect_js_1.inspect)(opts, sessionName, { dump: argv['dump'] });
|
|
400
|
+
break;
|
|
401
|
+
case 'focused':
|
|
402
|
+
exitCode = await (0, focused_js_1.focused)(opts, sessionName, {
|
|
403
|
+
poll: argv['poll'],
|
|
404
|
+
interval: argv['interval'] !== undefined ? Number(argv['interval']) : undefined,
|
|
405
|
+
});
|
|
370
406
|
break;
|
|
371
407
|
case 'run-flow': {
|
|
372
408
|
const file = rest[0] ?? '';
|
|
@@ -388,7 +424,7 @@ async function main() {
|
|
|
388
424
|
exitCode = await (0, cheat_sheet_js_1.cheatSheet)();
|
|
389
425
|
break;
|
|
390
426
|
case 'install':
|
|
391
|
-
exitCode = await (0, install_js_1.installSkills)(opts);
|
|
427
|
+
exitCode = await (0, install_js_1.installSkills)(opts, argv['skills'], argv['check']);
|
|
392
428
|
break;
|
|
393
429
|
case 'daemon-start':
|
|
394
430
|
exitCode = await (0, daemon_js_1.daemonStart)(opts, sessionName);
|
|
@@ -413,6 +449,7 @@ async function main() {
|
|
|
413
449
|
break;
|
|
414
450
|
}
|
|
415
451
|
default:
|
|
452
|
+
// Should be unreachable — unknown commands are caught before device resolution.
|
|
416
453
|
console.error(`Unknown command: ${command}`);
|
|
417
454
|
console.error('Run `conductor --help` for usage.');
|
|
418
455
|
exitCode = 1;
|
package/dist/postinstall.js
CHANGED
|
@@ -4,8 +4,8 @@ const install_js_1 = require("./commands/install.js");
|
|
|
4
4
|
// Runs as `postinstall` after `npm install -g @houwert/conductor`.
|
|
5
5
|
// Silent on failure so it never breaks the npm install.
|
|
6
6
|
try {
|
|
7
|
-
(0, install_js_1.installPlugin)();
|
|
8
|
-
process.stdout.write(
|
|
7
|
+
const version = (0, install_js_1.installPlugin)();
|
|
8
|
+
process.stdout.write(`✓ Conductor Claude Code plugin installed (v${version})\n`);
|
|
9
9
|
}
|
|
10
10
|
catch {
|
|
11
11
|
// Silent on failure — never break npm install
|
package/dist/runner.js
CHANGED
|
@@ -95,20 +95,54 @@ async function getDriver(sessionName = 'default') {
|
|
|
95
95
|
const port = await (0, bootstrap_js_1.getDriverPort)(platform, deviceId);
|
|
96
96
|
(0, verbose_js_1.log)(`getDriver: platform=${platform} deviceId=${deviceId} port=${port}`);
|
|
97
97
|
let driver;
|
|
98
|
-
if (!(await (0, bootstrap_js_1.isPortOpen)(port))) {
|
|
99
|
-
(0, verbose_js_1.log)(`Driver not running — starting daemon for ${deviceId}...`);
|
|
100
|
-
await (0, client_js_1.startDaemon)(deviceId);
|
|
101
|
-
await waitForPort(port);
|
|
102
|
-
}
|
|
103
98
|
if (platform === 'ios') {
|
|
104
|
-
|
|
99
|
+
if (!(await (0, bootstrap_js_1.isPortOpen)(port))) {
|
|
100
|
+
(0, verbose_js_1.log)(`Driver not running — starting daemon for ${deviceId}...`);
|
|
101
|
+
await (0, client_js_1.startDaemon)(deviceId);
|
|
102
|
+
await waitForPort(port);
|
|
103
|
+
}
|
|
104
|
+
const iosDriver = new ios_js_1.IOSDriver(port, '127.0.0.1', deviceId, 'ios');
|
|
105
105
|
if (!(await iosDriver.isAlive())) {
|
|
106
106
|
throw new Error(`iOS XCTest driver on port ${port} is not responding.\n` +
|
|
107
107
|
`Run: conductor daemon-start --device ${deviceId}`);
|
|
108
108
|
}
|
|
109
109
|
driver = iosDriver;
|
|
110
110
|
}
|
|
111
|
+
else if (platform === 'tvos') {
|
|
112
|
+
if (!(await (0, bootstrap_js_1.isPortOpen)(port))) {
|
|
113
|
+
(0, verbose_js_1.log)(`tvOS driver not running — starting daemon for ${deviceId}...`);
|
|
114
|
+
await (0, client_js_1.startDaemon)(deviceId);
|
|
115
|
+
await waitForPort(port);
|
|
116
|
+
}
|
|
117
|
+
const tvosDriver = new ios_js_1.IOSDriver(port, '127.0.0.1', deviceId, 'tvos');
|
|
118
|
+
if (!(await tvosDriver.isAlive())) {
|
|
119
|
+
throw new Error(`tvOS XCTest driver on port ${port} is not responding.\n` +
|
|
120
|
+
`Run: conductor daemon-start --device ${deviceId}`);
|
|
121
|
+
}
|
|
122
|
+
driver = tvosDriver;
|
|
123
|
+
}
|
|
111
124
|
else {
|
|
125
|
+
// Ensure the daemon is running — it handles APK install and driver startup.
|
|
126
|
+
await (0, client_js_1.startDaemon)(deviceId);
|
|
127
|
+
// Poll isAlive() directly instead of isPortOpen(): with ADB port forwarding the
|
|
128
|
+
// local port appears open as soon as `adb forward` runs, before the gRPC server
|
|
129
|
+
// on the device is ready, making isPortOpen() unreliable as a readiness signal.
|
|
130
|
+
const ANDROID_READY_TIMEOUT_MS = 180000;
|
|
131
|
+
const deadline = Date.now() + ANDROID_READY_TIMEOUT_MS;
|
|
132
|
+
let alive = false;
|
|
133
|
+
while (Date.now() < deadline) {
|
|
134
|
+
const probe = new android_js_1.AndroidDriver(deviceId, port);
|
|
135
|
+
await probe.connect();
|
|
136
|
+
alive = await probe.isAlive().catch(() => false);
|
|
137
|
+
probe.close();
|
|
138
|
+
if (alive)
|
|
139
|
+
break;
|
|
140
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
141
|
+
}
|
|
142
|
+
if (!alive) {
|
|
143
|
+
throw new Error(`Android gRPC driver on port ${port} is not responding.\n` +
|
|
144
|
+
`Make sure the Conductor driver APK is installed: conductor install --device ${deviceId}`);
|
|
145
|
+
}
|
|
112
146
|
const androidDriver = new android_js_1.AndroidDriver(deviceId, port);
|
|
113
147
|
await androidDriver.connect();
|
|
114
148
|
driver = androidDriver;
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>__xctestrun_metadata__</key>
|
|
6
|
+
<dict>
|
|
7
|
+
<key>ContainerInfo</key>
|
|
8
|
+
<dict>
|
|
9
|
+
<key>ContainerName</key>
|
|
10
|
+
<string>conductor-driver-ios</string>
|
|
11
|
+
<key>SchemeName</key>
|
|
12
|
+
<string>conductor-driver-tvos</string>
|
|
13
|
+
</dict>
|
|
14
|
+
<key>FormatVersion</key>
|
|
15
|
+
<integer>1</integer>
|
|
16
|
+
</dict>
|
|
17
|
+
<key>conductor-driver-tvosUITests</key>
|
|
18
|
+
<dict>
|
|
19
|
+
<key>BlueprintName</key>
|
|
20
|
+
<string>conductor-driver-tvosUITests</string>
|
|
21
|
+
<key>BlueprintProviderName</key>
|
|
22
|
+
<string>conductor-driver-ios</string>
|
|
23
|
+
<key>BlueprintProviderRelativePath</key>
|
|
24
|
+
<string>conductor-driver-ios.xcodeproj</string>
|
|
25
|
+
<key>BundleIdentifiersForCrashReportEmphasis</key>
|
|
26
|
+
<array>
|
|
27
|
+
<string>dev.houwert.ConductorDriverLib</string>
|
|
28
|
+
<string>dev.houwert.conductor-driver-tvos</string>
|
|
29
|
+
<string>dev.houwert.conductor-driver-tvosUITests</string>
|
|
30
|
+
</array>
|
|
31
|
+
<key>CommandLineArguments</key>
|
|
32
|
+
<array/>
|
|
33
|
+
<key>DefaultTestExecutionTimeAllowance</key>
|
|
34
|
+
<integer>600</integer>
|
|
35
|
+
<key>DependentProductPaths</key>
|
|
36
|
+
<array>
|
|
37
|
+
<string>__TESTROOT__/Debug-appletvsimulator/ConductorDriverLib.framework</string>
|
|
38
|
+
<string>__TESTROOT__/Debug-appletvsimulator/conductor-driver-tvos.app</string>
|
|
39
|
+
<string>__TESTROOT__/Debug-appletvsimulator/conductor-driver-tvosUITests-Runner.app</string>
|
|
40
|
+
<string>__TESTROOT__/Debug-appletvsimulator/conductor-driver-tvosUITests-Runner.app/PlugIns/conductor-driver-tvosUITests.xctest</string>
|
|
41
|
+
</array>
|
|
42
|
+
<key>DiagnosticCollectionPolicy</key>
|
|
43
|
+
<integer>1</integer>
|
|
44
|
+
<key>EnvironmentVariables</key>
|
|
45
|
+
<dict>
|
|
46
|
+
<key>APP_DISTRIBUTOR_ID_OVERRIDE</key>
|
|
47
|
+
<string>com.apple.AppStore</string>
|
|
48
|
+
<key>OS_ACTIVITY_DT_MODE</key>
|
|
49
|
+
<string>YES</string>
|
|
50
|
+
<key>SQLITE_ENABLE_THREAD_ASSERTIONS</key>
|
|
51
|
+
<string>1</string>
|
|
52
|
+
<key>TERM</key>
|
|
53
|
+
<string>dumb</string>
|
|
54
|
+
</dict>
|
|
55
|
+
<key>IsUITestBundle</key>
|
|
56
|
+
<true/>
|
|
57
|
+
<key>IsXCTRunnerHostedTestBundle</key>
|
|
58
|
+
<true/>
|
|
59
|
+
<key>PreferredScreenCaptureFormat</key>
|
|
60
|
+
<string>screenRecording</string>
|
|
61
|
+
<key>ProductModuleName</key>
|
|
62
|
+
<string>conductor_driver_tvosUITests</string>
|
|
63
|
+
<key>RunOrder</key>
|
|
64
|
+
<integer>0</integer>
|
|
65
|
+
<key>SystemAttachmentLifetime</key>
|
|
66
|
+
<string>deleteOnSuccess</string>
|
|
67
|
+
<key>TestBundlePath</key>
|
|
68
|
+
<string>__TESTHOST__/PlugIns/conductor-driver-tvosUITests.xctest</string>
|
|
69
|
+
<key>TestHostBundleIdentifier</key>
|
|
70
|
+
<string>dev.houwert.conductor-driver-tvosUITests.xctrunner</string>
|
|
71
|
+
<key>TestHostPath</key>
|
|
72
|
+
<string>__TESTROOT__/Debug-appletvsimulator/conductor-driver-tvosUITests-Runner.app</string>
|
|
73
|
+
<key>TestLanguage</key>
|
|
74
|
+
<string></string>
|
|
75
|
+
<key>TestRegion</key>
|
|
76
|
+
<string></string>
|
|
77
|
+
<key>TestTimeoutsEnabled</key>
|
|
78
|
+
<false/>
|
|
79
|
+
<key>TestingEnvironmentVariables</key>
|
|
80
|
+
<dict>
|
|
81
|
+
<key>DYLD_FRAMEWORK_PATH</key>
|
|
82
|
+
<string>__TESTROOT__/Debug-appletvsimulator:__TESTROOT__/Debug-appletvsimulator/PackageFrameworks:__PLATFORMS__/AppleTVSimulator.platform/Developer/Library/Frameworks</string>
|
|
83
|
+
<key>DYLD_LIBRARY_PATH</key>
|
|
84
|
+
<string>__TESTROOT__/Debug-appletvsimulator:__PLATFORMS__/AppleTVSimulator.platform/Developer/usr/lib</string>
|
|
85
|
+
<key>XCODE_SCHEME_NAME</key>
|
|
86
|
+
<string>conductor-driver-tvos</string>
|
|
87
|
+
<key>__XCODE_BUILT_PRODUCTS_DIR_PATHS</key>
|
|
88
|
+
<string>__TESTROOT__/Debug-appletvsimulator</string>
|
|
89
|
+
<key>__XPC_DYLD_FRAMEWORK_PATH</key>
|
|
90
|
+
<string>__TESTROOT__/Debug-appletvsimulator</string>
|
|
91
|
+
<key>__XPC_DYLD_LIBRARY_PATH</key>
|
|
92
|
+
<string>__TESTROOT__/Debug-appletvsimulator</string>
|
|
93
|
+
</dict>
|
|
94
|
+
<key>ToolchainsSettingValue</key>
|
|
95
|
+
<array/>
|
|
96
|
+
<key>UITargetAppCommandLineArguments</key>
|
|
97
|
+
<array/>
|
|
98
|
+
<key>UITargetAppEnvironmentVariables</key>
|
|
99
|
+
<dict>
|
|
100
|
+
<key>APP_DISTRIBUTOR_ID_OVERRIDE</key>
|
|
101
|
+
<string>com.apple.AppStore</string>
|
|
102
|
+
<key>DYLD_FRAMEWORK_PATH</key>
|
|
103
|
+
<string>__TESTROOT__/Debug-appletvsimulator:__TESTROOT__/Debug-appletvsimulator/PackageFrameworks</string>
|
|
104
|
+
<key>DYLD_LIBRARY_PATH</key>
|
|
105
|
+
<string>__TESTROOT__/Debug-appletvsimulator</string>
|
|
106
|
+
<key>XCODE_SCHEME_NAME</key>
|
|
107
|
+
<string>conductor-driver-tvos</string>
|
|
108
|
+
<key>__XCODE_BUILT_PRODUCTS_DIR_PATHS</key>
|
|
109
|
+
<string>__TESTROOT__/Debug-appletvsimulator</string>
|
|
110
|
+
<key>__XPC_DYLD_FRAMEWORK_PATH</key>
|
|
111
|
+
<string>__TESTROOT__/Debug-appletvsimulator</string>
|
|
112
|
+
<key>__XPC_DYLD_LIBRARY_PATH</key>
|
|
113
|
+
<string>__TESTROOT__/Debug-appletvsimulator</string>
|
|
114
|
+
</dict>
|
|
115
|
+
<key>UITargetAppPath</key>
|
|
116
|
+
<string>__TESTROOT__/Debug-appletvsimulator/conductor-driver-tvos.app</string>
|
|
117
|
+
<key>UserAttachmentLifetime</key>
|
|
118
|
+
<string>deleteOnSuccess</string>
|
|
119
|
+
</dict>
|
|
120
|
+
</dict>
|
|
121
|
+
</plist>
|
|
Binary file
|
|
Binary file
|