@houwert/conductor 0.29.3 → 0.31.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 +1 -1
- package/dist/commands/assert-not-visible.js +4 -1
- package/dist/commands/assert-visible.js +5 -2
- package/dist/commands/back.js +2 -1
- package/dist/commands/capture-ui.js +7 -3
- package/dist/commands/clipboard.js +9 -0
- package/dist/commands/copy-text-from.js +2 -1
- package/dist/commands/crashes.js +5 -0
- package/dist/commands/delete-device.js +5 -0
- package/dist/commands/download-app.js +4 -0
- package/dist/commands/erase-text.js +4 -1
- package/dist/commands/focused.js +5 -2
- package/dist/commands/foreground-app.js +4 -1
- package/dist/commands/gestures.js +7 -0
- package/dist/commands/hide-keyboard.js +5 -0
- package/dist/commands/inspect.js +10 -3
- package/dist/commands/install-app.js +21 -4
- package/dist/commands/launch-app.js +3 -2
- package/dist/commands/list-apps.js +16 -0
- package/dist/commands/list-devices.js +37 -0
- package/dist/commands/memory.js +5 -0
- package/dist/commands/press-key.js +36 -3
- package/dist/commands/profile.js +4 -0
- package/dist/commands/screenshot.js +5 -2
- package/dist/commands/scroll-until-visible.js +5 -2
- package/dist/commands/scroll.js +4 -1
- package/dist/commands/start-device.js +27 -3
- package/dist/commands/stop-app.js +2 -1
- package/dist/commands/stop-device.js +25 -3
- package/dist/commands/swipe.js +8 -3
- package/dist/commands/tap.js +3 -2
- package/dist/commands/uninstall-app.js +4 -0
- package/dist/daemon/input-backends.js +26 -1
- package/dist/daemon/log-collector.js +6 -0
- package/dist/daemon/server.js +54 -11
- package/dist/drivers/bootstrap.js +263 -4
- package/dist/drivers/devicectl.js +243 -0
- package/dist/drivers/flow-runner.js +26 -4
- package/dist/drivers/ios.js +96 -4
- package/dist/drivers/roku/app-ui-parser.js +122 -0
- package/dist/drivers/roku/discovery.js +136 -0
- package/dist/drivers/roku/ecp-client.js +396 -0
- package/dist/drivers/roku/key-mapping.js +67 -0
- package/dist/drivers/roku.js +237 -0
- package/dist/drivers/vega/page-source-parser.js +5 -118
- package/dist/drivers/xml.js +128 -0
- package/dist/enum-options.js +3 -1
- package/dist/index.js +1 -1
- package/dist/runner.js +48 -8
- package/package.json +1 -1
- package/skills/conductor-device-interact/SKILL.md +14 -3
- package/skills/conductor-device-setup/SKILL.md +62 -2
- package/skills/conductor-profiler/SKILL.md +1 -1
|
@@ -28,6 +28,7 @@ const ios_js_1 = require("../drivers/ios.js");
|
|
|
28
28
|
const android_js_1 = require("../drivers/android.js");
|
|
29
29
|
const web_js_1 = require("../drivers/web.js");
|
|
30
30
|
const vega_js_1 = require("../drivers/vega.js");
|
|
31
|
+
const roku_js_1 = require("../drivers/roku.js");
|
|
31
32
|
const wait_js_1 = require("../drivers/wait.js");
|
|
32
33
|
const direct_ios_selector_js_1 = require("../drivers/direct-ios-selector.js");
|
|
33
34
|
const png_crop_js_1 = require("../png-crop.js");
|
|
@@ -85,8 +86,10 @@ async function screenshot(outputPath, opts = {}, sessionName = 'default', fullPa
|
|
|
85
86
|
hierarchyH = info.heightPixels;
|
|
86
87
|
el = await (0, wait_js_1.waitForWebElement)(() => driver.viewHierarchy(), sel);
|
|
87
88
|
}
|
|
88
|
-
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
89
|
-
|
|
89
|
+
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
90
|
+
driver instanceof vega_js_1.VegaDriver ||
|
|
91
|
+
driver instanceof roku_js_1.RokuDriver) {
|
|
92
|
+
// Vega and Roku emit uiautomator-style XML, so they reuse the Android resolver.
|
|
90
93
|
const xml = await driver.viewHierarchy();
|
|
91
94
|
// Android XML root bounds: derive from the first parseable <node bounds="[0,0][W,H]">
|
|
92
95
|
const m = xml.match(/<node[^>]*bounds="\[0,0\]\[(\d+),(\d+)\]"/);
|
|
@@ -13,6 +13,7 @@ const ios_js_1 = require("../drivers/ios.js");
|
|
|
13
13
|
const android_js_1 = require("../drivers/android.js");
|
|
14
14
|
const web_js_1 = require("../drivers/web.js");
|
|
15
15
|
const vega_js_1 = require("../drivers/vega.js");
|
|
16
|
+
const roku_js_1 = require("../drivers/roku.js");
|
|
16
17
|
const element_resolver_js_1 = require("../drivers/element-resolver.js");
|
|
17
18
|
const direct_ios_selector_js_1 = require("../drivers/direct-ios-selector.js");
|
|
18
19
|
const utils_js_1 = require("../utils.js");
|
|
@@ -78,8 +79,10 @@ async function scrollUntilVisible(element, opts = {}, sessionName = 'default', f
|
|
|
78
79
|
const { widthPixels: w, heightPixels: h } = info;
|
|
79
80
|
await driver.swipe(coords.startX * w, coords.startY * h, coords.endX * w, coords.endY * h, 500);
|
|
80
81
|
}
|
|
81
|
-
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
82
|
-
|
|
82
|
+
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
83
|
+
driver instanceof vega_js_1.VegaDriver ||
|
|
84
|
+
driver instanceof roku_js_1.RokuDriver) {
|
|
85
|
+
// Vega and Roku emit uiautomator-style XML, so they reuse the Android resolver.
|
|
83
86
|
const xml = await driver.viewHierarchy();
|
|
84
87
|
if ((0, element_resolver_js_1.findAndroidElement)(xml, sel)) {
|
|
85
88
|
(0, output_js_1.printSuccess)(`scroll-until-visible ${label} — found`, opts);
|
package/dist/commands/scroll.js
CHANGED
|
@@ -9,6 +9,7 @@ const ios_js_1 = require("../drivers/ios.js");
|
|
|
9
9
|
const android_js_1 = require("../drivers/android.js");
|
|
10
10
|
const web_js_1 = require("../drivers/web.js");
|
|
11
11
|
const vega_js_1 = require("../drivers/vega.js");
|
|
12
|
+
const roku_js_1 = require("../drivers/roku.js");
|
|
12
13
|
const utils_js_1 = require("../utils.js");
|
|
13
14
|
async function scroll(direction = 'down', opts = {}, sessionName = 'default') {
|
|
14
15
|
const valid = ['down', 'up', 'left', 'right'];
|
|
@@ -32,7 +33,9 @@ async function scroll(direction = 'down', opts = {}, sessionName = 'default') {
|
|
|
32
33
|
const { widthPixels: w, heightPixels: h } = info;
|
|
33
34
|
await driver.swipe(coords.startX * w, coords.startY * h, coords.endX * w, coords.endY * h, 500);
|
|
34
35
|
}
|
|
35
|
-
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
36
|
+
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
37
|
+
driver instanceof vega_js_1.VegaDriver ||
|
|
38
|
+
driver instanceof roku_js_1.RokuDriver) {
|
|
36
39
|
const info = await driver.deviceInfo();
|
|
37
40
|
const { widthPixels: w, heightPixels: h } = info;
|
|
38
41
|
await driver.swipe(coords.startX * w, coords.startY * h, coords.endX * w, coords.endY * h, 500);
|
|
@@ -11,7 +11,7 @@ exports.buildAvdmanagerCreateArgs = buildAvdmanagerCreateArgs;
|
|
|
11
11
|
exports.raiseAvdConfigRam = raiseAvdConfigRam;
|
|
12
12
|
exports.startDevice = startDevice;
|
|
13
13
|
exports.HELP = ` start-device
|
|
14
|
-
--platform <ios|android|tvos|web|vega> Boot a simulator/emulator, start the web driver (Playwright),
|
|
14
|
+
--platform <ios|android|tvos|web|vega|roku> Boot a simulator/emulator, start the web driver (Playwright), attach a Vega VVD, or check a Roku device
|
|
15
15
|
--os-version <n> iOS/tvOS version (e.g. 18) or Android API level (e.g. 33)
|
|
16
16
|
--avd <name> Android AVD name (default: first available; created if missing + --device-type)
|
|
17
17
|
--name <name> Set a custom name on the device after creation (iOS/tvOS/web)
|
|
@@ -34,6 +34,7 @@ const bootstrap_js_1 = require("../drivers/bootstrap.js");
|
|
|
34
34
|
const output_js_1 = require("../output.js");
|
|
35
35
|
const utils_js_1 = require("../utils.js");
|
|
36
36
|
const cli_js_1 = require("../drivers/vega/cli.js");
|
|
37
|
+
const discovery_js_1 = require("../drivers/roku/discovery.js");
|
|
37
38
|
const IOS_BOOT_TIMEOUT_MS = 120000;
|
|
38
39
|
const ANDROID_BOOT_TIMEOUT_MS = 120000;
|
|
39
40
|
const POLL_MS = 1000;
|
|
@@ -807,10 +808,31 @@ async function startVega(opts, deviceName) {
|
|
|
807
808
|
(0, output_js_1.printSuccess)(`Vega device ready: ${device.serial}`, opts);
|
|
808
809
|
return 0;
|
|
809
810
|
}
|
|
811
|
+
// ── Roku ────────────────────────────────────────────────────────────────────
|
|
812
|
+
/**
|
|
813
|
+
* Roku is physical hardware with no emulator, so there is nothing to boot: this
|
|
814
|
+
* resolves the target device (explicit `--name <host>`, else `CONDUCTOR_ROKU_HOST`
|
|
815
|
+
* or an SSDP hit) and confirms it answers on ECP, so a misconfigured device is
|
|
816
|
+
* reported here rather than on the first `tap-on`.
|
|
817
|
+
*/
|
|
818
|
+
async function startRoku(opts, deviceName) {
|
|
819
|
+
const host = deviceName?.replace(/^roku:/, '') ?? process.env.CONDUCTOR_ROKU_HOST?.trim();
|
|
820
|
+
const device = host ? await (0, discovery_js_1.describe)(host) : (await (0, discovery_js_1.discoverRokuDevices)())[0];
|
|
821
|
+
if (!device) {
|
|
822
|
+
(0, output_js_1.printError)(host
|
|
823
|
+
? `Cannot reach the Roku device at ${host} on ECP port 8060. Check that it is on ` +
|
|
824
|
+
`this network and in developer mode.`
|
|
825
|
+
: 'No Roku device found. Set CONDUCTOR_ROKU_HOST=<device-ip>, pass --name <device-ip>, ' +
|
|
826
|
+
'or set CONDUCTOR_ROKU_DISCOVERY=true to scan the LAN.', opts);
|
|
827
|
+
return 1;
|
|
828
|
+
}
|
|
829
|
+
(0, output_js_1.printSuccess)(`Roku device ready: ${device.friendlyName || device.modelName} (roku:${device.host})`, opts);
|
|
830
|
+
return 0;
|
|
831
|
+
}
|
|
810
832
|
// ── Entry point ───────────────────────────────────────────────────────────────
|
|
811
833
|
async function startDevice(platform, opts, flags) {
|
|
812
834
|
if (!platform) {
|
|
813
|
-
(0, output_js_1.printError)('start-device requires --platform ios|android|tvos|web|vega', opts);
|
|
835
|
+
(0, output_js_1.printError)('start-device requires --platform ios|android|tvos|web|vega|roku', opts);
|
|
814
836
|
return 1;
|
|
815
837
|
}
|
|
816
838
|
switch (platform.toLowerCase()) {
|
|
@@ -824,8 +846,10 @@ async function startDevice(platform, opts, flags) {
|
|
|
824
846
|
return startWebDriver(opts, flags.browser, flags.name);
|
|
825
847
|
case 'vega':
|
|
826
848
|
return startVega(opts, flags.name);
|
|
849
|
+
case 'roku':
|
|
850
|
+
return startRoku(opts, flags.name);
|
|
827
851
|
default:
|
|
828
|
-
(0, output_js_1.printError)(`Unknown platform "${platform}". Use ios, android, tvos, web, or
|
|
852
|
+
(0, output_js_1.printError)(`Unknown platform "${platform}". Use ios, android, tvos, web, vega, or roku.`, opts);
|
|
829
853
|
return 1;
|
|
830
854
|
}
|
|
831
855
|
}
|
|
@@ -10,6 +10,7 @@ const ios_js_1 = require("../drivers/ios.js");
|
|
|
10
10
|
const android_js_1 = require("../drivers/android.js");
|
|
11
11
|
const web_js_1 = require("../drivers/web.js");
|
|
12
12
|
const vega_js_1 = require("../drivers/vega.js");
|
|
13
|
+
const roku_js_1 = require("../drivers/roku.js");
|
|
13
14
|
async function stopApp(appId, opts = {}, sessionName = 'default') {
|
|
14
15
|
const session = await (0, session_js_1.getSession)(sessionName);
|
|
15
16
|
const resolvedAppId = appId ?? session.appId;
|
|
@@ -24,7 +25,7 @@ async function stopApp(appId, opts = {}, sessionName = 'default') {
|
|
|
24
25
|
else if (driver instanceof web_js_1.WebDriver) {
|
|
25
26
|
await driver.terminateApp();
|
|
26
27
|
}
|
|
27
|
-
else if (driver instanceof vega_js_1.VegaDriver) {
|
|
28
|
+
else if (driver instanceof vega_js_1.VegaDriver || driver instanceof roku_js_1.RokuDriver) {
|
|
28
29
|
await driver.stopApp(resolvedAppId);
|
|
29
30
|
}
|
|
30
31
|
else if (driver instanceof android_js_1.AndroidDriver) {
|
|
@@ -3,13 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.HELP = void 0;
|
|
4
4
|
exports.stopDevice = stopDevice;
|
|
5
5
|
exports.HELP = ` stop-device [<name-or-id>]
|
|
6
|
-
--platform <ios|tvos|android|web|vega> Scope to a single platform
|
|
6
|
+
--platform <ios|tvos|android|web|vega|roku> Scope to a single platform
|
|
7
7
|
--all Stop all booted simulators / running emulators / web sessions`;
|
|
8
8
|
const runner_js_1 = require("../runner.js");
|
|
9
9
|
const sdk_js_1 = require("../android/sdk.js");
|
|
10
10
|
const output_js_1 = require("../output.js");
|
|
11
11
|
const client_js_1 = require("../daemon/client.js");
|
|
12
12
|
const list_devices_js_1 = require("./list-devices.js");
|
|
13
|
+
const bootstrap_js_1 = require("../drivers/bootstrap.js");
|
|
13
14
|
// ── iOS / tvOS ───────────────────────────────────────────────────────────────
|
|
14
15
|
async function shutdownSimulator(udid) {
|
|
15
16
|
const result = await (0, runner_js_1.spawnCommand)('xcrun', ['simctl', 'shutdown', udid]);
|
|
@@ -36,6 +37,7 @@ async function stopDevice(nameOrId, opts, flags) {
|
|
|
36
37
|
const includeAndroid = !platform || platform === 'android';
|
|
37
38
|
const includeWeb = !platform || platform === 'web';
|
|
38
39
|
const includeVega = !platform || platform === 'vega';
|
|
40
|
+
const includeRoku = !platform || platform === 'roku';
|
|
39
41
|
const stopped = [];
|
|
40
42
|
// ── --all mode ───────────────────────────────────────────────────────────
|
|
41
43
|
if (flags.all) {
|
|
@@ -51,8 +53,15 @@ async function stopDevice(nameOrId, opts, flags) {
|
|
|
51
53
|
continue;
|
|
52
54
|
if (d.platform === 'vega' && !includeVega)
|
|
53
55
|
continue;
|
|
56
|
+
if (d.platform === 'roku' && !includeRoku)
|
|
57
|
+
continue;
|
|
54
58
|
try {
|
|
55
|
-
if (d.platform === 'ios' || d.platform === 'tvos')
|
|
59
|
+
if ((d.platform === 'ios' || d.platform === 'tvos') &&
|
|
60
|
+
(await (0, bootstrap_js_1.detectDeviceKind)(d.id)) === 'physical') {
|
|
61
|
+
// Real hardware can't be shut down from here — just release our driver.
|
|
62
|
+
await (0, client_js_1.stopDaemon)(d.id);
|
|
63
|
+
}
|
|
64
|
+
else if (d.platform === 'ios' || d.platform === 'tvos') {
|
|
56
65
|
await shutdownSimulator(d.id);
|
|
57
66
|
}
|
|
58
67
|
else if (d.platform === 'android') {
|
|
@@ -62,6 +71,10 @@ async function stopDevice(nameOrId, opts, flags) {
|
|
|
62
71
|
// Vega VVD lifecycle is owned by Amazon's tooling — we only stop our log daemon.
|
|
63
72
|
await (0, client_js_1.stopDaemon)(d.id);
|
|
64
73
|
}
|
|
74
|
+
else if (d.platform === 'roku') {
|
|
75
|
+
// A Roku is physical hardware we never booted; there is nothing to stop.
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
65
78
|
stopped.push({ id: d.id, name: d.name, platform: d.platform });
|
|
66
79
|
}
|
|
67
80
|
catch (e) {
|
|
@@ -94,7 +107,12 @@ async function stopDevice(nameOrId, opts, flags) {
|
|
|
94
107
|
return 1;
|
|
95
108
|
}
|
|
96
109
|
try {
|
|
97
|
-
if (match.platform === 'ios' || match.platform === 'tvos')
|
|
110
|
+
if ((match.platform === 'ios' || match.platform === 'tvos') &&
|
|
111
|
+
(await (0, bootstrap_js_1.detectDeviceKind)(match.id)) === 'physical') {
|
|
112
|
+
// Real hardware can't be shut down from here — just release our driver.
|
|
113
|
+
await (0, client_js_1.stopDaemon)(match.id);
|
|
114
|
+
}
|
|
115
|
+
else if (match.platform === 'ios' || match.platform === 'tvos') {
|
|
98
116
|
await shutdownSimulator(match.id);
|
|
99
117
|
}
|
|
100
118
|
else if (match.platform === 'android') {
|
|
@@ -104,6 +122,10 @@ async function stopDevice(nameOrId, opts, flags) {
|
|
|
104
122
|
// Vega VVD lifecycle is owned by Amazon's tooling — we only stop our log daemon.
|
|
105
123
|
await (0, client_js_1.stopDaemon)(match.id);
|
|
106
124
|
}
|
|
125
|
+
else if (match.platform === 'roku') {
|
|
126
|
+
(0, output_js_1.printError)(`${match.name} is a physical Roku device — conductor never booted it, so there is nothing to stop.`, opts);
|
|
127
|
+
return 1;
|
|
128
|
+
}
|
|
107
129
|
}
|
|
108
130
|
catch (e) {
|
|
109
131
|
(0, output_js_1.printError)(e instanceof Error ? e.message : String(e), opts);
|
package/dist/commands/swipe.js
CHANGED
|
@@ -13,6 +13,7 @@ const ios_js_1 = require("../drivers/ios.js");
|
|
|
13
13
|
const android_js_1 = require("../drivers/android.js");
|
|
14
14
|
const web_js_1 = require("../drivers/web.js");
|
|
15
15
|
const vega_js_1 = require("../drivers/vega.js");
|
|
16
|
+
const roku_js_1 = require("../drivers/roku.js");
|
|
16
17
|
const utils_js_1 = require("../utils.js");
|
|
17
18
|
function parseCoordPair(s) {
|
|
18
19
|
const [xs, ys] = s.split(',').map((p) => p.trim());
|
|
@@ -25,8 +26,10 @@ async function swipe(direction, opts = {}, sessionName = 'default', flags = {})
|
|
|
25
26
|
}
|
|
26
27
|
const result = await (0, runner_js_1.runDirect)(async (driver) => {
|
|
27
28
|
if (driver instanceof ios_js_1.IOSDriver && driver.platform === 'tvos') {
|
|
28
|
-
throw new Error('swipe is not supported on tvOS —
|
|
29
|
-
'
|
|
29
|
+
throw new Error('swipe is not supported on tvOS — XCTest has no Siri Remote touch-surface\n' +
|
|
30
|
+
'gesture ("Swipe events are only implemented for iOS, visionOS, and watchOS").\n' +
|
|
31
|
+
'Navigate with press-key "Remote Dpad Left"/"Right"/"Up"/"Down", or cover\n' +
|
|
32
|
+
'long lists faster with press-key "Remote Page Up"/"Remote Page Down".');
|
|
30
33
|
}
|
|
31
34
|
let startX, startY, endX, endY;
|
|
32
35
|
if (driver instanceof ios_js_1.IOSDriver) {
|
|
@@ -71,7 +74,9 @@ async function swipe(direction, opts = {}, sessionName = 'default', flags = {})
|
|
|
71
74
|
}
|
|
72
75
|
await driver.swipe(startX, startY, endX, endY, durationMs);
|
|
73
76
|
}
|
|
74
|
-
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
77
|
+
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
78
|
+
driver instanceof vega_js_1.VegaDriver ||
|
|
79
|
+
driver instanceof roku_js_1.RokuDriver) {
|
|
75
80
|
const { widthPixels: w, heightPixels: h } = await driver.deviceInfo();
|
|
76
81
|
const durationMs = flags.duration ?? 500;
|
|
77
82
|
if (flags.start && flags.end) {
|
package/dist/commands/tap.js
CHANGED
|
@@ -28,6 +28,7 @@ const ios_js_1 = require("../drivers/ios.js");
|
|
|
28
28
|
const android_js_1 = require("../drivers/android.js");
|
|
29
29
|
const web_js_1 = require("../drivers/web.js");
|
|
30
30
|
const vega_js_1 = require("../drivers/vega.js");
|
|
31
|
+
const roku_js_1 = require("../drivers/roku.js");
|
|
31
32
|
const wait_js_1 = require("../drivers/wait.js");
|
|
32
33
|
const direct_ios_selector_js_1 = require("../drivers/direct-ios-selector.js");
|
|
33
34
|
const snapshot_store_js_1 = require("../snapshot-store.js");
|
|
@@ -88,8 +89,8 @@ async function tap(query, opts = {}, sessionName = 'default', flags = {}) {
|
|
|
88
89
|
else if (driver instanceof web_js_1.WebDriver) {
|
|
89
90
|
el = await (0, wait_js_1.waitForWebElement)(() => driver.viewHierarchy(), sel);
|
|
90
91
|
}
|
|
91
|
-
else if (driver instanceof vega_js_1.VegaDriver) {
|
|
92
|
-
// Vega
|
|
92
|
+
else if (driver instanceof vega_js_1.VegaDriver || driver instanceof roku_js_1.RokuDriver) {
|
|
93
|
+
// Vega and Roku emit uiautomator-style XML, so they reuse the Android resolver.
|
|
93
94
|
el = await (0, wait_js_1.waitForAndroidElement)(() => driver.viewHierarchy(), sel);
|
|
94
95
|
}
|
|
95
96
|
else if (driver instanceof android_js_1.AndroidDriver) {
|
|
@@ -9,6 +9,7 @@ const ios_js_1 = require("../drivers/ios.js");
|
|
|
9
9
|
const android_js_1 = require("../drivers/android.js");
|
|
10
10
|
const web_js_1 = require("../drivers/web.js");
|
|
11
11
|
const vega_js_1 = require("../drivers/vega.js");
|
|
12
|
+
const roku_js_1 = require("../drivers/roku.js");
|
|
12
13
|
async function uninstallApp(appId, opts = {}, sessionName = 'default') {
|
|
13
14
|
if (!appId) {
|
|
14
15
|
(0, output_js_1.printError)('uninstall-app requires <appId>', opts);
|
|
@@ -24,6 +25,9 @@ async function uninstallApp(appId, opts = {}, sessionName = 'default') {
|
|
|
24
25
|
else if (driver instanceof vega_js_1.VegaDriver) {
|
|
25
26
|
throw new Error('uninstall-app is not supported on vega (Amazon Fire TV)');
|
|
26
27
|
}
|
|
28
|
+
else if (driver instanceof roku_js_1.RokuDriver) {
|
|
29
|
+
throw new Error('uninstall-app is not supported on roku');
|
|
30
|
+
}
|
|
27
31
|
else if (driver instanceof android_js_1.AndroidDriver) {
|
|
28
32
|
await driver.uninstallApp(appId);
|
|
29
33
|
}
|
|
@@ -34,6 +34,15 @@ const IOS_BUTTON = {
|
|
|
34
34
|
ArrowDown: 'down',
|
|
35
35
|
ArrowLeft: 'left',
|
|
36
36
|
ArrowRight: 'right',
|
|
37
|
+
// Newer remote buttons; the driver rejects these on older tvOS.
|
|
38
|
+
pageUp: 'pageUp',
|
|
39
|
+
PageUp: 'pageUp',
|
|
40
|
+
pageDown: 'pageDown',
|
|
41
|
+
PageDown: 'pageDown',
|
|
42
|
+
guide: 'guide',
|
|
43
|
+
tvProvider: 'tvProvider',
|
|
44
|
+
oneTwoThree: 'oneTwoThree',
|
|
45
|
+
fourColors: 'fourColors',
|
|
37
46
|
};
|
|
38
47
|
function iosBackend(driver) {
|
|
39
48
|
let size = null;
|
|
@@ -52,7 +61,23 @@ function iosBackend(driver) {
|
|
|
52
61
|
touch: true,
|
|
53
62
|
drag: true,
|
|
54
63
|
multitouch: true,
|
|
55
|
-
|
|
64
|
+
// Advertise what this platform actually accepts so clients can hide
|
|
65
|
+
// controls rather than discover failures at press time.
|
|
66
|
+
buttons: tvos
|
|
67
|
+
? [
|
|
68
|
+
'home',
|
|
69
|
+
'menu',
|
|
70
|
+
'select',
|
|
71
|
+
'up',
|
|
72
|
+
'down',
|
|
73
|
+
'left',
|
|
74
|
+
'right',
|
|
75
|
+
'playPause',
|
|
76
|
+
'pageUp',
|
|
77
|
+
'pageDown',
|
|
78
|
+
'guide',
|
|
79
|
+
]
|
|
80
|
+
: ['home', 'lock'],
|
|
56
81
|
keyboard: true,
|
|
57
82
|
text: true,
|
|
58
83
|
tvRemote: tvos,
|
|
@@ -35,6 +35,7 @@ const android_js_1 = require("../drivers/log-sources/android.js");
|
|
|
35
35
|
const vega_js_1 = require("../drivers/log-sources/vega.js");
|
|
36
36
|
const metro_js_1 = require("../drivers/log-sources/metro.js");
|
|
37
37
|
const metro_discovery_js_1 = require("../drivers/log-sources/metro-discovery.js");
|
|
38
|
+
const bootstrap_js_1 = require("../drivers/bootstrap.js");
|
|
38
39
|
const MAX_BUFFER = 5000;
|
|
39
40
|
const RESTART_DELAY_MS = 2000;
|
|
40
41
|
const WEB_POLL_INTERVAL_MS = 500;
|
|
@@ -145,6 +146,11 @@ class LogCollector {
|
|
|
145
146
|
return;
|
|
146
147
|
try {
|
|
147
148
|
if (this.platform === 'ios' || this.platform === 'tvos') {
|
|
149
|
+
// `simctl spawn ... log stream` has no devicectl equivalent, so OS logs
|
|
150
|
+
// are simulator-only. Metro still streams over the network below, which
|
|
151
|
+
// is the useful source for React Native apps anyway.
|
|
152
|
+
if ((await (0, bootstrap_js_1.detectDeviceKind)(this.deviceId)) === 'physical')
|
|
153
|
+
return;
|
|
148
154
|
this.source = new ios_js_1.IOSLogSource(this.deviceId, this.appId);
|
|
149
155
|
}
|
|
150
156
|
else if (this.platform === 'android') {
|
package/dist/daemon/server.js
CHANGED
|
@@ -84,6 +84,20 @@ function dlog(msg) {
|
|
|
84
84
|
// ── Driver lifecycle ──────────────────────────────────────────────────────────
|
|
85
85
|
let driverPort = 1075;
|
|
86
86
|
let driverPlatform = 'ios';
|
|
87
|
+
/**
|
|
88
|
+
* Host the driver is reachable on. Simulators and every non-Apple platform use
|
|
89
|
+
* the host's loopback; a physical device runs the driver on its own, so we talk
|
|
90
|
+
* to it over the network. Memoised — resolution goes through mDNS.
|
|
91
|
+
*/
|
|
92
|
+
let _driverHost = null;
|
|
93
|
+
async function driverHost() {
|
|
94
|
+
if (_driverHost)
|
|
95
|
+
return _driverHost;
|
|
96
|
+
if (driverPlatform !== 'ios' && driverPlatform !== 'tvos')
|
|
97
|
+
return '127.0.0.1';
|
|
98
|
+
_driverHost = await (0, bootstrap_js_1.resolveDriverHost)(sessionName).catch(() => '127.0.0.1');
|
|
99
|
+
return _driverHost;
|
|
100
|
+
}
|
|
87
101
|
let logCollector = null;
|
|
88
102
|
let inputServer = null;
|
|
89
103
|
let inputPort = null;
|
|
@@ -109,7 +123,7 @@ async function startInputServerForPlatform() {
|
|
|
109
123
|
makeBackend = () => (0, input_backends_js_1.androidBackend)(driver);
|
|
110
124
|
}
|
|
111
125
|
else {
|
|
112
|
-
const driver = new ios_js_1.IOSDriver(driverPort,
|
|
126
|
+
const driver = new ios_js_1.IOSDriver(driverPort, await driverHost(), sessionName, driverPlatform, (await (0, bootstrap_js_1.detectDeviceKind)(sessionName)) === 'physical');
|
|
113
127
|
makeBackend = () => (0, input_backends_js_1.iosBackend)(driver);
|
|
114
128
|
// Opt-in native held-touch backend for live drags (iOS only; single-touch).
|
|
115
129
|
if (driverPlatform === 'ios' && process.env.CONDUCTOR_IOS_HID === '1') {
|
|
@@ -154,6 +168,12 @@ async function startVideoServerForPlatform() {
|
|
|
154
168
|
return;
|
|
155
169
|
if (driverPlatform !== 'ios' && driverPlatform !== 'tvos')
|
|
156
170
|
return;
|
|
171
|
+
// Capture attaches to the Simulator's framebuffer via SimulatorKit, which has
|
|
172
|
+
// no counterpart on real hardware.
|
|
173
|
+
if ((await (0, bootstrap_js_1.detectDeviceKind)(sessionName)) === 'physical') {
|
|
174
|
+
dlog('video capture is simulator-only — stream server disabled for this device');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
157
177
|
const binary = await (0, bootstrap_js_1.getCaptureBinaryPath)();
|
|
158
178
|
if (!binary) {
|
|
159
179
|
dlog('video capture binary not present — stream server disabled for this device');
|
|
@@ -177,8 +197,9 @@ let _driverStartError = null;
|
|
|
177
197
|
async function ensureDriverRunning() {
|
|
178
198
|
if (_restartInProgress || !_driverStarted)
|
|
179
199
|
return;
|
|
180
|
-
// Vega
|
|
181
|
-
|
|
200
|
+
// Vega and Roku have no driver process/port to health-check — control is
|
|
201
|
+
// host-side (the vega CLI) or over the network (Roku ECP).
|
|
202
|
+
if (driverPlatform === 'vega' || driverPlatform === 'roku')
|
|
182
203
|
return;
|
|
183
204
|
let alive;
|
|
184
205
|
if (driverPlatform === 'android') {
|
|
@@ -188,11 +209,13 @@ async function ensureDriverRunning() {
|
|
|
188
209
|
probe.close();
|
|
189
210
|
}
|
|
190
211
|
else {
|
|
191
|
-
// 'ios', 'tvos', and 'web' all use an HTTP server — port open = alive
|
|
192
|
-
|
|
212
|
+
// 'ios', 'tvos', and 'web' all use an HTTP server — port open = alive.
|
|
213
|
+
// Physical devices answer on the LAN rather than the host's loopback.
|
|
214
|
+
alive = await (0, bootstrap_js_1.isPortOpen)(driverPort, await driverHost());
|
|
193
215
|
}
|
|
194
216
|
if (!alive) {
|
|
195
217
|
if ((driverPlatform === 'ios' || driverPlatform === 'tvos') &&
|
|
218
|
+
(await (0, bootstrap_js_1.detectDeviceKind)(sessionName)) === 'simulator' &&
|
|
196
219
|
!(await (0, bootstrap_js_1.isSimulatorBooted)(sessionName))) {
|
|
197
220
|
dlog(`Simulator ${sessionName} is not booted — skipping driver restart`);
|
|
198
221
|
return;
|
|
@@ -200,7 +223,11 @@ async function ensureDriverRunning() {
|
|
|
200
223
|
_restartInProgress = true;
|
|
201
224
|
dlog(`Driver on port ${driverPort} not responding — restarting`);
|
|
202
225
|
try {
|
|
203
|
-
if (driverPlatform === 'ios')
|
|
226
|
+
if ((driverPlatform === 'ios' || driverPlatform === 'tvos') &&
|
|
227
|
+
(await (0, bootstrap_js_1.detectDeviceKind)(sessionName)) === 'physical') {
|
|
228
|
+
await (0, bootstrap_js_1.startDeviceDriver)(sessionName, driverPlatform, driverPort);
|
|
229
|
+
}
|
|
230
|
+
else if (driverPlatform === 'ios') {
|
|
204
231
|
await (0, bootstrap_js_1.startIOSDriver)(sessionName, driverPort);
|
|
205
232
|
}
|
|
206
233
|
else if (driverPlatform === 'tvos') {
|
|
@@ -317,6 +344,9 @@ async function main() {
|
|
|
317
344
|
else if (driverPlatform === 'vega') {
|
|
318
345
|
dlog('vega: no driver process to stop (control is host-side via the CLI)');
|
|
319
346
|
}
|
|
347
|
+
else if (driverPlatform === 'roku') {
|
|
348
|
+
dlog('roku: no driver process to stop (control is ECP over the network)');
|
|
349
|
+
}
|
|
320
350
|
else if (driverPlatform === 'web') {
|
|
321
351
|
dlog('Stopping web driver');
|
|
322
352
|
try {
|
|
@@ -329,7 +359,10 @@ async function main() {
|
|
|
329
359
|
else {
|
|
330
360
|
dlog(`Stopping driver on port ${driverPort}`);
|
|
331
361
|
try {
|
|
332
|
-
if (driverPlatform === 'ios') {
|
|
362
|
+
if (driverPlatform === 'ios' && (await (0, bootstrap_js_1.detectDeviceKind)(sessionName)) === 'physical') {
|
|
363
|
+
await (0, bootstrap_js_1.stopDeviceDriver)(sessionName, 'ios');
|
|
364
|
+
}
|
|
365
|
+
else if (driverPlatform === 'ios') {
|
|
333
366
|
await (0, bootstrap_js_1.stopIOSDriver)(sessionName);
|
|
334
367
|
}
|
|
335
368
|
else {
|
|
@@ -449,6 +482,12 @@ async function main() {
|
|
|
449
482
|
_driverStarted = true;
|
|
450
483
|
dlog('vega: no driver process to start; collecting logs only');
|
|
451
484
|
}
|
|
485
|
+
else if (platform === 'roku') {
|
|
486
|
+
// Roku has neither a driver process nor a log stream to collect, so
|
|
487
|
+
// the daemon has nothing to do — commands drive the device directly.
|
|
488
|
+
_driverStarted = true;
|
|
489
|
+
dlog('roku: no driver process and no device logs; daemon is idle');
|
|
490
|
+
}
|
|
452
491
|
else {
|
|
453
492
|
await startDriverForPlatform(platform);
|
|
454
493
|
}
|
|
@@ -487,8 +526,8 @@ async function main() {
|
|
|
487
526
|
});
|
|
488
527
|
}
|
|
489
528
|
/**
|
|
490
|
-
* Bring up the driver process for a
|
|
491
|
-
* `_driverStartError`. Extracted so the vega
|
|
529
|
+
* Bring up the driver process for a platform that has one. Sets `_driverStarted` /
|
|
530
|
+
* `_driverStartError`. Extracted so the vega and roku paths can skip it entirely.
|
|
492
531
|
*/
|
|
493
532
|
async function startDriverForPlatform(platform) {
|
|
494
533
|
let driverAlive;
|
|
@@ -500,7 +539,7 @@ async function startDriverForPlatform(platform) {
|
|
|
500
539
|
}
|
|
501
540
|
else {
|
|
502
541
|
// 'ios', 'tvos', and 'web' all use an HTTP server — port open = alive
|
|
503
|
-
driverAlive = await (0, bootstrap_js_1.isPortOpen)(driverPort);
|
|
542
|
+
driverAlive = await (0, bootstrap_js_1.isPortOpen)(driverPort, await driverHost());
|
|
504
543
|
}
|
|
505
544
|
if (driverAlive) {
|
|
506
545
|
_driverStarted = true;
|
|
@@ -523,7 +562,11 @@ async function startDriverForPlatform(platform) {
|
|
|
523
562
|
}
|
|
524
563
|
dlog(`Starting ${platform} driver on port ${driverPort}`);
|
|
525
564
|
try {
|
|
526
|
-
if (platform === 'ios')
|
|
565
|
+
if ((platform === 'ios' || platform === 'tvos') &&
|
|
566
|
+
(await (0, bootstrap_js_1.detectDeviceKind)(sessionName)) === 'physical') {
|
|
567
|
+
await (0, bootstrap_js_1.startDeviceDriver)(sessionName, platform, driverPort);
|
|
568
|
+
}
|
|
569
|
+
else if (platform === 'ios') {
|
|
527
570
|
await (0, bootstrap_js_1.startIOSDriver)(sessionName, driverPort);
|
|
528
571
|
}
|
|
529
572
|
else if (platform === 'tvos') {
|