@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
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ conductor assert-visible "Dashboard"
|
|
|
26
26
|
conductor take-screenshot --output /tmp/screen.png
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
Targets iOS and tvOS simulators, physical iOS/tvOS devices, Android emulators and devices, Amazon Fire TV, and web via Playwright.
|
|
29
|
+
Targets iOS and tvOS simulators, physical iOS/tvOS devices, Android emulators and devices, Amazon Fire TV, Roku, and web via Playwright.
|
|
30
30
|
|
|
31
31
|
## Quick start
|
|
32
32
|
|
|
@@ -12,6 +12,7 @@ const ios_js_1 = require("../drivers/ios.js");
|
|
|
12
12
|
const android_js_1 = require("../drivers/android.js");
|
|
13
13
|
const web_js_1 = require("../drivers/web.js");
|
|
14
14
|
const vega_js_1 = require("../drivers/vega.js");
|
|
15
|
+
const roku_js_1 = require("../drivers/roku.js");
|
|
15
16
|
const wait_js_1 = require("../drivers/wait.js");
|
|
16
17
|
async function assertNotVisible(element, opts = {}, sessionName = 'default', flags = {}) {
|
|
17
18
|
if (!element && !flags.id && !flags.text) {
|
|
@@ -43,7 +44,9 @@ async function assertNotVisible(element, opts = {}, sessionName = 'default', fla
|
|
|
43
44
|
else if (driver instanceof web_js_1.WebDriver) {
|
|
44
45
|
await (0, wait_js_1.waitUntilWebElementGone)(() => driver.viewHierarchy(), sel, flags.timeout);
|
|
45
46
|
}
|
|
46
|
-
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
47
|
+
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
48
|
+
driver instanceof vega_js_1.VegaDriver ||
|
|
49
|
+
driver instanceof roku_js_1.RokuDriver) {
|
|
47
50
|
// Vega emits uiautomator-style XML, so it reuses the Android resolver.
|
|
48
51
|
await (0, wait_js_1.waitUntilAndroidElementGone)(() => driver.viewHierarchy(), sel, flags.timeout);
|
|
49
52
|
}
|
|
@@ -22,6 +22,7 @@ const ios_js_1 = require("../drivers/ios.js");
|
|
|
22
22
|
const android_js_1 = require("../drivers/android.js");
|
|
23
23
|
const web_js_1 = require("../drivers/web.js");
|
|
24
24
|
const vega_js_1 = require("../drivers/vega.js");
|
|
25
|
+
const roku_js_1 = require("../drivers/roku.js");
|
|
25
26
|
const wait_js_1 = require("../drivers/wait.js");
|
|
26
27
|
const direct_ios_selector_js_1 = require("../drivers/direct-ios-selector.js");
|
|
27
28
|
async function assertVisible(element, opts = {}, sessionName = 'default', flags = {}) {
|
|
@@ -56,8 +57,10 @@ async function assertVisible(element, opts = {}, sessionName = 'default', flags
|
|
|
56
57
|
else if (driver instanceof web_js_1.WebDriver) {
|
|
57
58
|
return (0, wait_js_1.waitForWebElement)(() => driver.viewHierarchy(), sel, timeoutMs);
|
|
58
59
|
}
|
|
59
|
-
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
60
|
-
|
|
60
|
+
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
61
|
+
driver instanceof vega_js_1.VegaDriver ||
|
|
62
|
+
driver instanceof roku_js_1.RokuDriver) {
|
|
63
|
+
// Vega and Roku emit uiautomator-style XML, so they reuse the Android resolver.
|
|
61
64
|
return (0, wait_js_1.waitForAndroidElement)(() => driver.viewHierarchy(), sel, timeoutMs);
|
|
62
65
|
}
|
|
63
66
|
};
|
package/dist/commands/back.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
|
async function back(opts = {}, sessionName = 'default') {
|
|
13
14
|
const result = await (0, runner_js_1.runDirect)(async (driver) => {
|
|
14
15
|
if (driver instanceof ios_js_1.IOSDriver) {
|
|
@@ -20,7 +21,7 @@ async function back(opts = {}, sessionName = 'default') {
|
|
|
20
21
|
else if (driver instanceof web_js_1.WebDriver) {
|
|
21
22
|
await driver.goBack();
|
|
22
23
|
}
|
|
23
|
-
else if (driver instanceof vega_js_1.VegaDriver) {
|
|
24
|
+
else if (driver instanceof vega_js_1.VegaDriver || driver instanceof roku_js_1.RokuDriver) {
|
|
24
25
|
await driver.back();
|
|
25
26
|
}
|
|
26
27
|
else if (driver instanceof android_js_1.AndroidDriver) {
|
|
@@ -14,6 +14,7 @@ const ios_js_1 = require("../drivers/ios.js");
|
|
|
14
14
|
const android_js_1 = require("../drivers/android.js");
|
|
15
15
|
const web_js_1 = require("../drivers/web.js");
|
|
16
16
|
const vega_js_1 = require("../drivers/vega.js");
|
|
17
|
+
const roku_js_1 = require("../drivers/roku.js");
|
|
17
18
|
const a11y_js_1 = require("../drivers/a11y.js");
|
|
18
19
|
const snapshot_store_js_1 = require("../snapshot-store.js");
|
|
19
20
|
async function captureUI(outputPath, opts = {}, sessionName = 'default') {
|
|
@@ -67,9 +68,12 @@ async function captureUI(outputPath, opts = {}, sessionName = 'default') {
|
|
|
67
68
|
a11ySnapshot = built.a11ySnapshot;
|
|
68
69
|
screenshotBuf = shot;
|
|
69
70
|
}
|
|
70
|
-
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
72
|
+
driver instanceof vega_js_1.VegaDriver ||
|
|
73
|
+
driver instanceof roku_js_1.RokuDriver) {
|
|
74
|
+
// Vega and Roku emit uiautomator-style XML, so they reuse the Android a11y builder.
|
|
75
|
+
platform =
|
|
76
|
+
driver instanceof vega_js_1.VegaDriver ? 'vega' : driver instanceof roku_js_1.RokuDriver ? 'roku' : 'android';
|
|
73
77
|
const [info, xml, shot] = await Promise.all([
|
|
74
78
|
driver.deviceInfo(),
|
|
75
79
|
driver.viewHierarchy(),
|
|
@@ -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 ANDROID_MSG = 'clipboard is iOS-only. On Android, use `conductor input-text` to type instead.';
|
|
17
18
|
async function clipboardRead(opts = {}, sessionName = 'default') {
|
|
18
19
|
const result = await (0, runner_js_1.runDirect)(async (driver) => {
|
|
@@ -24,6 +25,8 @@ async function clipboardRead(opts = {}, sessionName = 'default') {
|
|
|
24
25
|
throw new Error('clipboard read is not supported on Web');
|
|
25
26
|
if (driver instanceof vega_js_1.VegaDriver)
|
|
26
27
|
throw new Error('clipboard read is not supported on vega');
|
|
28
|
+
if (driver instanceof roku_js_1.RokuDriver)
|
|
29
|
+
throw new Error('clipboard read is not supported on roku');
|
|
27
30
|
return '';
|
|
28
31
|
}, sessionName);
|
|
29
32
|
if (result.success) {
|
|
@@ -52,6 +55,9 @@ async function clipboardWrite(text, opts = {}, sessionName = 'default') {
|
|
|
52
55
|
else if (driver instanceof vega_js_1.VegaDriver) {
|
|
53
56
|
throw new Error('clipboard write is not supported on vega');
|
|
54
57
|
}
|
|
58
|
+
else if (driver instanceof roku_js_1.RokuDriver) {
|
|
59
|
+
throw new Error('clipboard write is not supported on roku');
|
|
60
|
+
}
|
|
55
61
|
}, sessionName);
|
|
56
62
|
if (result.success) {
|
|
57
63
|
(0, output_js_1.printSuccess)('clipboard write — done', opts);
|
|
@@ -81,6 +87,9 @@ async function paste(opts = {}, sessionName = 'default') {
|
|
|
81
87
|
else if (driver instanceof vega_js_1.VegaDriver) {
|
|
82
88
|
throw new Error('paste is not supported on vega');
|
|
83
89
|
}
|
|
90
|
+
else if (driver instanceof roku_js_1.RokuDriver) {
|
|
91
|
+
throw new Error('paste is not supported on roku');
|
|
92
|
+
}
|
|
84
93
|
}, sessionName);
|
|
85
94
|
if (result.success) {
|
|
86
95
|
(0, output_js_1.printSuccess)('paste — done', opts);
|
|
@@ -12,6 +12,7 @@ const ios_js_1 = require("../drivers/ios.js");
|
|
|
12
12
|
const android_js_1 = require("../drivers/android.js");
|
|
13
13
|
const web_js_1 = require("../drivers/web.js");
|
|
14
14
|
const vega_js_1 = require("../drivers/vega.js");
|
|
15
|
+
const roku_js_1 = require("../drivers/roku.js");
|
|
15
16
|
const wait_js_1 = require("../drivers/wait.js");
|
|
16
17
|
const direct_ios_selector_js_1 = require("../drivers/direct-ios-selector.js");
|
|
17
18
|
async function copyTextFrom(query, opts = {}, sessionName = 'default', flags = {}) {
|
|
@@ -33,7 +34,7 @@ async function copyTextFrom(query, opts = {}, sessionName = 'default', flags = {
|
|
|
33
34
|
else if (driver instanceof web_js_1.WebDriver) {
|
|
34
35
|
el = await (0, wait_js_1.waitForWebElement)(() => driver.viewHierarchy(), sel);
|
|
35
36
|
}
|
|
36
|
-
else if (driver instanceof vega_js_1.VegaDriver) {
|
|
37
|
+
else if (driver instanceof vega_js_1.VegaDriver || driver instanceof roku_js_1.RokuDriver) {
|
|
37
38
|
el = await (0, wait_js_1.waitForAndroidElement)(() => driver.viewHierarchy(), sel);
|
|
38
39
|
}
|
|
39
40
|
else if (driver instanceof android_js_1.AndroidDriver) {
|
package/dist/commands/crashes.js
CHANGED
|
@@ -180,6 +180,11 @@ async function crashesList(opts, sessionName, listOpts) {
|
|
|
180
180
|
if (!opts.json)
|
|
181
181
|
console.error('note: crash reports are not supported on vega (Amazon Fire TV)');
|
|
182
182
|
}
|
|
183
|
+
else if (platform === 'roku') {
|
|
184
|
+
// ECP exposes no crash log; Roku's are read from the dev console by hand.
|
|
185
|
+
if (!opts.json)
|
|
186
|
+
console.error('note: crash reports are not supported on roku');
|
|
187
|
+
}
|
|
183
188
|
if (opts.json) {
|
|
184
189
|
(0, output_js_1.printData)({ count: reports.length, reports }, opts);
|
|
185
190
|
}
|
|
@@ -110,6 +110,11 @@ async function deleteDevice(nameOrId, opts, flags) {
|
|
|
110
110
|
(0, output_js_1.printError)("delete-device is not supported on vega (Amazon Fire TV) — manage VVDs via Amazon's tooling.", opts);
|
|
111
111
|
return 1;
|
|
112
112
|
}
|
|
113
|
+
// A Roku is physical hardware conductor never created.
|
|
114
|
+
if (platform === 'roku' || (nameOrId && nameOrId.startsWith('roku:'))) {
|
|
115
|
+
(0, output_js_1.printError)('delete-device is not supported on roku — it is physical hardware.', opts);
|
|
116
|
+
return 1;
|
|
117
|
+
}
|
|
113
118
|
const includeIOS = !platform || platform === 'ios';
|
|
114
119
|
const includeTvOS = !platform || platform === 'tvos';
|
|
115
120
|
const includeAndroid = !platform || platform === 'android';
|
|
@@ -37,6 +37,10 @@ async function downloadApp(appId, output, opts = {}, sessionName = 'default') {
|
|
|
37
37
|
(0, output_js_1.printError)('download-app is not supported on vega (Amazon Fire TV).', opts);
|
|
38
38
|
return 1;
|
|
39
39
|
}
|
|
40
|
+
if (platform === 'roku') {
|
|
41
|
+
(0, output_js_1.printError)('download-app is not supported on roku.', opts);
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
40
44
|
if (platform === 'ios' || platform === 'tvos') {
|
|
41
45
|
// Get the .app bundle path from the simulator
|
|
42
46
|
const getPath = await (0, runner_js_1.spawnCommand)('xcrun', [
|
|
@@ -9,9 +9,12 @@ 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 eraseText(characters, opts = {}, sessionName = 'default') {
|
|
13
14
|
const result = await (0, runner_js_1.runDirect)(async (driver) => {
|
|
14
|
-
if (driver instanceof android_js_1.AndroidDriver ||
|
|
15
|
+
if (driver instanceof android_js_1.AndroidDriver ||
|
|
16
|
+
driver instanceof vega_js_1.VegaDriver ||
|
|
17
|
+
driver instanceof roku_js_1.RokuDriver) {
|
|
15
18
|
await driver.eraseAllText(characters);
|
|
16
19
|
}
|
|
17
20
|
else if (driver instanceof web_js_1.WebDriver) {
|
package/dist/commands/focused.js
CHANGED
|
@@ -12,6 +12,7 @@ const ios_js_1 = require("../drivers/ios.js");
|
|
|
12
12
|
const android_js_1 = require("../drivers/android.js");
|
|
13
13
|
const web_js_1 = require("../drivers/web.js");
|
|
14
14
|
const vega_js_1 = require("../drivers/vega.js");
|
|
15
|
+
const roku_js_1 = require("../drivers/roku.js");
|
|
15
16
|
const element_resolver_js_1 = require("../drivers/element-resolver.js");
|
|
16
17
|
// XCUIElementType rawValue → human-readable name.
|
|
17
18
|
// Source: XCUIElementType enum in XCTest framework.
|
|
@@ -209,8 +210,10 @@ async function queryFocused(driver) {
|
|
|
209
210
|
const node = findFocusedWeb(hierarchy.elements);
|
|
210
211
|
return node ? formatWebElement(node) : null;
|
|
211
212
|
}
|
|
212
|
-
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
213
|
-
|
|
213
|
+
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
214
|
+
driver instanceof vega_js_1.VegaDriver ||
|
|
215
|
+
driver instanceof roku_js_1.RokuDriver) {
|
|
216
|
+
// Vega and Roku emit uiautomator-style XML, so they reuse the Android parser.
|
|
214
217
|
const xml = await driver.viewHierarchy();
|
|
215
218
|
const nodes = (0, element_resolver_js_1.parseAndroidHierarchy)(xml);
|
|
216
219
|
const node = nodes.find((n) => n.focused);
|
|
@@ -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 resolveDeviceId(sessionName) {
|
|
14
15
|
if (sessionName !== 'default')
|
|
15
16
|
return sessionName;
|
|
@@ -41,7 +42,9 @@ async function foregroundApp(opts = {}, sessionName = 'default') {
|
|
|
41
42
|
else if (driver instanceof web_js_1.WebDriver) {
|
|
42
43
|
appId = await driver.runningApp();
|
|
43
44
|
}
|
|
44
|
-
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
45
|
+
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
46
|
+
driver instanceof vega_js_1.VegaDriver ||
|
|
47
|
+
driver instanceof roku_js_1.RokuDriver) {
|
|
45
48
|
appId = await driver.getForegroundApp();
|
|
46
49
|
}
|
|
47
50
|
else {
|
|
@@ -20,6 +20,7 @@ const output_js_1 = require("../output.js");
|
|
|
20
20
|
const ios_js_1 = require("../drivers/ios.js");
|
|
21
21
|
const web_js_1 = require("../drivers/web.js");
|
|
22
22
|
const vega_js_1 = require("../drivers/vega.js");
|
|
23
|
+
const roku_js_1 = require("../drivers/roku.js");
|
|
23
24
|
function parseCenter(s) {
|
|
24
25
|
if (!s)
|
|
25
26
|
return null;
|
|
@@ -111,6 +112,8 @@ async function pinch(opts, sessionName, pinchOpts) {
|
|
|
111
112
|
throw new Error('pinch is not supported on Web');
|
|
112
113
|
if (driver instanceof vega_js_1.VegaDriver)
|
|
113
114
|
throw new Error('pinch is not supported on vega (Amazon Fire TV)');
|
|
115
|
+
if (driver instanceof roku_js_1.RokuDriver)
|
|
116
|
+
throw new Error('pinch is not supported on roku — Roku is D-pad driven, with no touch input');
|
|
114
117
|
const { width, height, cx: defCx, cy: defCy } = await screenCenter(driver);
|
|
115
118
|
const center = parseCenter(pinchOpts.center) ?? { x: defCx, y: defCy };
|
|
116
119
|
// Start span is half the shorter screen dimension; end is scaled.
|
|
@@ -135,6 +138,8 @@ async function rotateGesture(opts, sessionName, rotateOpts) {
|
|
|
135
138
|
throw new Error('rotate-gesture is not supported on Web');
|
|
136
139
|
if (driver instanceof vega_js_1.VegaDriver)
|
|
137
140
|
throw new Error('rotate-gesture is not supported on vega (Amazon Fire TV)');
|
|
141
|
+
if (driver instanceof roku_js_1.RokuDriver)
|
|
142
|
+
throw new Error('rotate-gesture is not supported on roku — Roku is D-pad driven, with no touch input');
|
|
138
143
|
const { width, height, cx: defCx, cy: defCy } = await screenCenter(driver);
|
|
139
144
|
const center = parseCenter(rotateOpts.center) ?? { x: defCx, y: defCy };
|
|
140
145
|
const radius = Math.min(width, height) * 0.25;
|
|
@@ -192,6 +197,8 @@ async function gesture(rawJson, filePath, opts, sessionName) {
|
|
|
192
197
|
throw new Error('gesture is not supported on Web');
|
|
193
198
|
if (driver instanceof vega_js_1.VegaDriver)
|
|
194
199
|
throw new Error('gesture is not supported on vega (Amazon Fire TV)');
|
|
200
|
+
if (driver instanceof roku_js_1.RokuDriver)
|
|
201
|
+
throw new Error('gesture is not supported on roku — Roku is D-pad driven, with no touch input');
|
|
195
202
|
await playPaths(driver, paths);
|
|
196
203
|
}, sessionName);
|
|
197
204
|
if (result.success) {
|
|
@@ -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 hideKeyboard(opts = {}, sessionName = 'default') {
|
|
13
14
|
const result = await (0, runner_js_1.runDirect)(async (driver) => {
|
|
14
15
|
if (driver instanceof ios_js_1.IOSDriver) {
|
|
@@ -22,6 +23,10 @@ async function hideKeyboard(opts = {}, sessionName = 'default') {
|
|
|
22
23
|
else if (driver instanceof vega_js_1.VegaDriver) {
|
|
23
24
|
// Vega has no keyboard-hide primitive — no-op
|
|
24
25
|
}
|
|
26
|
+
else if (driver instanceof roku_js_1.RokuDriver) {
|
|
27
|
+
// Roku dismisses its on-screen keyboard with Back.
|
|
28
|
+
await driver.back();
|
|
29
|
+
}
|
|
25
30
|
else if (driver instanceof android_js_1.AndroidDriver) {
|
|
26
31
|
await driver.pressKeyEvent(111); // KEYCODE_ESCAPE
|
|
27
32
|
}
|
package/dist/commands/inspect.js
CHANGED
|
@@ -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
|
const element_resolver_js_1 = require("../drivers/element-resolver.js");
|
|
14
15
|
const a11y_js_1 = require("../drivers/a11y.js");
|
|
15
16
|
async function inspect(opts = {}, sessionName = 'default', inspectOpts = {}) {
|
|
@@ -29,7 +30,9 @@ async function inspect(opts = {}, sessionName = 'default', inspectOpts = {}) {
|
|
|
29
30
|
const hierarchy = await driver.viewHierarchy(false);
|
|
30
31
|
hit = (0, element_resolver_js_1.findIOSViewAtPoint)(hierarchy.axElement, x, y, tappable);
|
|
31
32
|
}
|
|
32
|
-
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
33
|
+
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
34
|
+
driver instanceof vega_js_1.VegaDriver ||
|
|
35
|
+
driver instanceof roku_js_1.RokuDriver) {
|
|
33
36
|
const xml = await driver.viewHierarchy();
|
|
34
37
|
hit = (0, element_resolver_js_1.findAndroidViewAtPoint)(xml, x, y, tappable);
|
|
35
38
|
}
|
|
@@ -68,7 +71,9 @@ async function inspect(opts = {}, sessionName = 'default', inspectOpts = {}) {
|
|
|
68
71
|
const built = (0, a11y_js_1.buildWebA11y)(vh);
|
|
69
72
|
raw = JSON.stringify({ ...vh, elements: built.hierarchy }, null, 2);
|
|
70
73
|
}
|
|
71
|
-
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
74
|
+
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
75
|
+
driver instanceof vega_js_1.VegaDriver ||
|
|
76
|
+
driver instanceof roku_js_1.RokuDriver) {
|
|
72
77
|
const xml = await driver.viewHierarchy();
|
|
73
78
|
const built = (0, a11y_js_1.buildAndroidA11y)(xml);
|
|
74
79
|
raw = JSON.stringify(built.hierarchy, null, 2);
|
|
@@ -93,7 +98,9 @@ async function inspect(opts = {}, sessionName = 'default', inspectOpts = {}) {
|
|
|
93
98
|
const hierarchy = await driver.viewHierarchy();
|
|
94
99
|
text = (0, element_resolver_js_1.inspectWebToText)(hierarchy);
|
|
95
100
|
}
|
|
96
|
-
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
101
|
+
else if (driver instanceof android_js_1.AndroidDriver ||
|
|
102
|
+
driver instanceof vega_js_1.VegaDriver ||
|
|
103
|
+
driver instanceof roku_js_1.RokuDriver) {
|
|
97
104
|
const xml = await driver.viewHierarchy();
|
|
98
105
|
text = (0, element_resolver_js_1.inspectAndroidToText)(xml);
|
|
99
106
|
}
|
|
@@ -8,6 +8,7 @@ const sdk_js_1 = require("../android/sdk.js");
|
|
|
8
8
|
const session_js_1 = require("../session.js");
|
|
9
9
|
const output_js_1 = require("../output.js");
|
|
10
10
|
const bootstrap_js_1 = require("../drivers/bootstrap.js");
|
|
11
|
+
const devicectl_js_1 = require("../drivers/devicectl.js");
|
|
11
12
|
const cli_js_1 = require("../drivers/vega/cli.js");
|
|
12
13
|
async function resolveDeviceId(sessionName) {
|
|
13
14
|
if (sessionName !== 'default')
|
|
@@ -30,6 +31,11 @@ async function installApp(appPath, opts = {}, sessionName = 'default') {
|
|
|
30
31
|
(0, output_js_1.printError)('install-app is not supported on web. Use launch-app with a URL instead.', opts);
|
|
31
32
|
return 1;
|
|
32
33
|
}
|
|
34
|
+
else if (platform === 'roku') {
|
|
35
|
+
(0, output_js_1.printError)('install-app is not supported on roku — sideload the channel through the ' +
|
|
36
|
+
"device's developer web server (http://<device-ip>).", opts);
|
|
37
|
+
return 1;
|
|
38
|
+
}
|
|
33
39
|
else if (platform === 'vega') {
|
|
34
40
|
// Vega installs a `.vpkg` through Amazon's CLI; strip the `vega:` id prefix.
|
|
35
41
|
try {
|
|
@@ -41,10 +47,21 @@ async function installApp(appPath, opts = {}, sessionName = 'default') {
|
|
|
41
47
|
}
|
|
42
48
|
}
|
|
43
49
|
else if (platform === 'ios' || platform === 'tvos') {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
if ((await (0, bootstrap_js_1.detectDeviceKind)(deviceId)) === 'physical') {
|
|
51
|
+
try {
|
|
52
|
+
await (0, devicectl_js_1.installApp)(deviceId, appPath);
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
(0, output_js_1.printError)(`install-app failed: ${e instanceof Error ? e.message : String(e)}`, opts);
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const result = await (0, runner_js_1.spawnCommand)('xcrun', ['simctl', 'install', deviceId, appPath]);
|
|
61
|
+
if (!result.success) {
|
|
62
|
+
(0, output_js_1.printError)(`install-app failed: ${result.stderr}`, opts);
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
48
65
|
}
|
|
49
66
|
}
|
|
50
67
|
else {
|
|
@@ -28,6 +28,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
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
|
async function launchApp(appId, deviceId, opts = {}, sessionName = 'default', flags = {}) {
|
|
32
33
|
if (!appId) {
|
|
33
34
|
(0, output_js_1.printError)('launch-app requires <appId>', opts);
|
|
@@ -50,7 +51,7 @@ async function launchApp(appId, deviceId, opts = {}, sessionName = 'default', fl
|
|
|
50
51
|
await driver.terminateApp(appId);
|
|
51
52
|
else if (driver instanceof web_js_1.WebDriver)
|
|
52
53
|
await driver.terminateApp();
|
|
53
|
-
else if (driver instanceof vega_js_1.VegaDriver)
|
|
54
|
+
else if (driver instanceof vega_js_1.VegaDriver || driver instanceof roku_js_1.RokuDriver)
|
|
54
55
|
await driver.terminateApp(appId);
|
|
55
56
|
else if (driver instanceof android_js_1.AndroidDriver)
|
|
56
57
|
await driver.stopApp(appId);
|
|
@@ -79,7 +80,7 @@ async function launchApp(appId, deviceId, opts = {}, sessionName = 'default', fl
|
|
|
79
80
|
else if (driver instanceof web_js_1.WebDriver) {
|
|
80
81
|
await driver.launchApp(appId);
|
|
81
82
|
}
|
|
82
|
-
else if (driver instanceof vega_js_1.VegaDriver) {
|
|
83
|
+
else if (driver instanceof vega_js_1.VegaDriver || driver instanceof roku_js_1.RokuDriver) {
|
|
83
84
|
await driver.launchApp(appId, flags.launchArgs);
|
|
84
85
|
}
|
|
85
86
|
else if (driver instanceof android_js_1.AndroidDriver) {
|
|
@@ -8,6 +8,7 @@ const sdk_js_1 = require("../android/sdk.js");
|
|
|
8
8
|
const session_js_1 = require("../session.js");
|
|
9
9
|
const output_js_1 = require("../output.js");
|
|
10
10
|
const bootstrap_js_1 = require("../drivers/bootstrap.js");
|
|
11
|
+
const devicectl_js_1 = require("../drivers/devicectl.js");
|
|
11
12
|
const cli_js_1 = require("../drivers/vega/cli.js");
|
|
12
13
|
async function resolveDeviceId(sessionName) {
|
|
13
14
|
if (sessionName !== 'default')
|
|
@@ -29,10 +30,25 @@ async function listApps(opts = {}, sessionName = 'default') {
|
|
|
29
30
|
(0, output_js_1.printError)('list-apps is not supported on web. Use foreground-app to get the current URL.', opts);
|
|
30
31
|
return 1;
|
|
31
32
|
}
|
|
33
|
+
else if (platform === 'roku') {
|
|
34
|
+
// ECP can name only the *active* channel, not enumerate installed ones.
|
|
35
|
+
(0, output_js_1.printError)('list-apps is not supported on roku. Use foreground-app to get the active channel id.', opts);
|
|
36
|
+
return 1;
|
|
37
|
+
}
|
|
32
38
|
else if (platform === 'vega') {
|
|
33
39
|
// Vega is driven through Amazon's CLI, not adb; strip the `vega:` id prefix.
|
|
34
40
|
appIds = (await new cli_js_1.VegaCli(deviceId.replace(/^vega:/, '')).listInstalledApps()).sort();
|
|
35
41
|
}
|
|
42
|
+
else if ((platform === 'ios' || platform === 'tvos') &&
|
|
43
|
+
(await (0, bootstrap_js_1.detectDeviceKind)(deviceId)) === 'physical') {
|
|
44
|
+
try {
|
|
45
|
+
appIds = (await (0, devicectl_js_1.listApps)(deviceId)).map((a) => a.id).sort();
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
(0, output_js_1.printError)(`list-apps failed: ${e instanceof Error ? e.message : String(e)}`, opts);
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
36
52
|
else if (platform === 'ios' || platform === 'tvos') {
|
|
37
53
|
const result = await (0, runner_js_1.spawnCommand)('bash', [
|
|
38
54
|
'-c',
|
|
@@ -16,7 +16,9 @@ const bootstrap_js_1 = require("../drivers/bootstrap.js");
|
|
|
16
16
|
const client_js_1 = require("../daemon/client.js");
|
|
17
17
|
const protocol_js_1 = require("../daemon/protocol.js");
|
|
18
18
|
const cli_js_1 = require("../drivers/vega/cli.js");
|
|
19
|
+
const discovery_js_1 = require("../drivers/roku/discovery.js");
|
|
19
20
|
const cdp_discovery_js_1 = require("../drivers/cdp-discovery.js");
|
|
21
|
+
const devicectl_js_1 = require("../drivers/devicectl.js");
|
|
20
22
|
// Captured during discoverAvailableDevices so listDevices() can report it.
|
|
21
23
|
// Module-scoped because the discover function returns Device[]; bolting an
|
|
22
24
|
// extra return field onto the public type would ripple beyond this fix.
|
|
@@ -109,6 +111,18 @@ async function discoverBootedDevices() {
|
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
}
|
|
114
|
+
// Physical iOS/tvOS devices: paired and reachable is the closest analogue to
|
|
115
|
+
// a booted simulator — that's when conductor can actually drive them.
|
|
116
|
+
for (const d of await (0, devicectl_js_1.listPhysicalDevices)()) {
|
|
117
|
+
if (!d.available)
|
|
118
|
+
continue;
|
|
119
|
+
devices.push({
|
|
120
|
+
id: d.identifier,
|
|
121
|
+
name: d.name,
|
|
122
|
+
platform: d.platform,
|
|
123
|
+
status: d.developerModeEnabled ? 'connected' : 'developer mode off',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
112
126
|
// Vega (Amazon Fire TV): booted devices reported by the vega CLI. Best-effort —
|
|
113
127
|
// the CLI is absent unless the Vega SDK is installed.
|
|
114
128
|
try {
|
|
@@ -124,6 +138,17 @@ async function discoverBootedDevices() {
|
|
|
124
138
|
catch {
|
|
125
139
|
/* vega CLI not installed */
|
|
126
140
|
}
|
|
141
|
+
// Roku: the pinned CONDUCTOR_ROKU_HOST device, plus SSDP hits when
|
|
142
|
+
// CONDUCTOR_ROKU_DISCOVERY is on. Physical hardware only — Roku has no emulator.
|
|
143
|
+
for (const d of await (0, discovery_js_1.discoverRokuDevices)().catch(() => [])) {
|
|
144
|
+
devices.push({
|
|
145
|
+
id: `roku:${d.host}`,
|
|
146
|
+
name: d.friendlyName || d.modelName,
|
|
147
|
+
platform: 'roku',
|
|
148
|
+
status: 'booted',
|
|
149
|
+
formFactor: 'tv',
|
|
150
|
+
});
|
|
151
|
+
}
|
|
127
152
|
// External CDP endpoints (e.g. an Electron app started with
|
|
128
153
|
// --remote-debugging-port): one device per discovered webview/tile.
|
|
129
154
|
devices.push(...(await (0, cdp_discovery_js_1.discoverCdpDevices)()));
|
|
@@ -154,6 +179,18 @@ async function discoverAvailableDevices() {
|
|
|
154
179
|
// ignore parse errors
|
|
155
180
|
}
|
|
156
181
|
}
|
|
182
|
+
// Physical devices that aren't currently reachable — listed so users can see
|
|
183
|
+
// why a device they expect is missing.
|
|
184
|
+
for (const d of await (0, devicectl_js_1.listPhysicalDevices)()) {
|
|
185
|
+
if (d.available)
|
|
186
|
+
continue;
|
|
187
|
+
devices.push({
|
|
188
|
+
id: d.identifier,
|
|
189
|
+
name: d.name,
|
|
190
|
+
platform: d.platform,
|
|
191
|
+
status: 'unavailable',
|
|
192
|
+
});
|
|
193
|
+
}
|
|
157
194
|
// Android: list available AVDs
|
|
158
195
|
const emu = await (0, runner_js_1.spawnCommand)((0, sdk_js_1.resolveAndroidTool)('emulator'), ['-list-avds'], {
|
|
159
196
|
env: (0, sdk_js_1.androidSpawnEnv)(),
|
package/dist/commands/memory.js
CHANGED
|
@@ -1121,6 +1121,11 @@ async function memory(appIdArg, opts = {}, sessionName = 'default', memOpts = {}
|
|
|
1121
1121
|
(0, output_js_1.printError)('memory is not yet supported on vega (Amazon Fire TV)', opts);
|
|
1122
1122
|
return 1;
|
|
1123
1123
|
}
|
|
1124
|
+
if (platform === 'roku') {
|
|
1125
|
+
// ECP exposes no memory counters, and there is no host-side tooling to ask.
|
|
1126
|
+
(0, output_js_1.printError)('memory is not supported on roku', opts);
|
|
1127
|
+
return 1;
|
|
1128
|
+
}
|
|
1124
1129
|
let report;
|
|
1125
1130
|
if (platform === 'web') {
|
|
1126
1131
|
report = await collectWeb(deviceId, sessionName, memOpts);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ANDROID_KEYCODE = exports.VALID_KEYS = exports.HELP = void 0;
|
|
3
|
+
exports.ANDROID_KEYCODE = exports.TVOS_REMOTE_BUTTONS = exports.VALID_KEYS = exports.HELP = void 0;
|
|
4
4
|
exports.dispatchKey = dispatchKey;
|
|
5
5
|
exports.pressKey = pressKey;
|
|
6
6
|
exports.HELP = ` press-key <key> Press a key (Enter, Backspace, Home, ...)
|
|
@@ -19,6 +19,8 @@ const ios_js_1 = require("../drivers/ios.js");
|
|
|
19
19
|
const android_js_1 = require("../drivers/android.js");
|
|
20
20
|
const web_js_1 = require("../drivers/web.js");
|
|
21
21
|
const vega_js_1 = require("../drivers/vega.js");
|
|
22
|
+
const roku_js_1 = require("../drivers/roku.js");
|
|
23
|
+
const key_mapping_js_1 = require("../drivers/roku/key-mapping.js");
|
|
22
24
|
exports.VALID_KEYS = [
|
|
23
25
|
'Enter',
|
|
24
26
|
'Backspace',
|
|
@@ -54,6 +56,15 @@ exports.VALID_KEYS = [
|
|
|
54
56
|
'TV Input HDMI 1',
|
|
55
57
|
'TV Input HDMI 2',
|
|
56
58
|
'TV Input HDMI 3',
|
|
59
|
+
'Remote Info',
|
|
60
|
+
'Remote Instant Replay',
|
|
61
|
+
'Remote Search',
|
|
62
|
+
'Remote Page Up',
|
|
63
|
+
'Remote Page Down',
|
|
64
|
+
'Remote Guide',
|
|
65
|
+
'Remote TV Provider',
|
|
66
|
+
'Remote One Two Three',
|
|
67
|
+
'Remote Four Colors',
|
|
57
68
|
];
|
|
58
69
|
// iOS XCTest pressKey accepts these values (maps to XCUIKeyboardKey)
|
|
59
70
|
const IOS_KEY_MAP = {
|
|
@@ -69,7 +80,9 @@ const IOS_BUTTON_MAP = {
|
|
|
69
80
|
Power: 'lock',
|
|
70
81
|
};
|
|
71
82
|
// tvOS remote: map key names to pressButton values
|
|
72
|
-
|
|
83
|
+
// Page Up/Down and Guide need tvOS 14.3; the last three need tvOS 18.1. The
|
|
84
|
+
// driver reports a precondition error when the device's OS is older.
|
|
85
|
+
exports.TVOS_REMOTE_BUTTONS = {
|
|
73
86
|
'Remote Dpad Up': 'up',
|
|
74
87
|
'Remote Dpad Down': 'down',
|
|
75
88
|
'Remote Dpad Left': 'left',
|
|
@@ -77,6 +90,12 @@ const TVOS_REMOTE_BUTTONS = {
|
|
|
77
90
|
'Remote Dpad Center': 'select',
|
|
78
91
|
'Remote Menu': 'menu',
|
|
79
92
|
'Remote Media Play Pause': 'playPause',
|
|
93
|
+
'Remote Page Up': 'pageUp',
|
|
94
|
+
'Remote Page Down': 'pageDown',
|
|
95
|
+
'Remote Guide': 'guide',
|
|
96
|
+
'Remote TV Provider': 'tvProvider',
|
|
97
|
+
'Remote One Two Three': 'oneTwoThree',
|
|
98
|
+
'Remote Four Colors': 'fourColors',
|
|
80
99
|
};
|
|
81
100
|
// vega (Amazon Fire TV) remote: map key names to VegaDriver button values.
|
|
82
101
|
const VEGA_REMOTE_BUTTONS = {
|
|
@@ -131,6 +150,13 @@ exports.ANDROID_KEYCODE = {
|
|
|
131
150
|
'TV Input HDMI 1': 243,
|
|
132
151
|
'TV Input HDMI 2': 244,
|
|
133
152
|
'TV Input HDMI 3': 245,
|
|
153
|
+
'Remote Info': 165,
|
|
154
|
+
'Remote Instant Replay': 273, // KEYCODE_MEDIA_SKIP_BACKWARD
|
|
155
|
+
'Remote Search': 84,
|
|
156
|
+
// Paging keys — the fast way through a long list. Whether a given app honours
|
|
157
|
+
// them varies, the same as on tvOS.
|
|
158
|
+
'Remote Page Up': 92,
|
|
159
|
+
'Remote Page Down': 93,
|
|
134
160
|
};
|
|
135
161
|
/**
|
|
136
162
|
* Send `key` on an already-connected driver. Split out of `pressKey` so
|
|
@@ -140,7 +166,7 @@ exports.ANDROID_KEYCODE = {
|
|
|
140
166
|
async function dispatchKey(driver, matched, holdSeconds) {
|
|
141
167
|
if (driver instanceof ios_js_1.IOSDriver) {
|
|
142
168
|
if (driver.platform === 'tvos') {
|
|
143
|
-
const tvosButton = TVOS_REMOTE_BUTTONS[matched];
|
|
169
|
+
const tvosButton = exports.TVOS_REMOTE_BUTTONS[matched];
|
|
144
170
|
const iosButton = IOS_BUTTON_MAP[matched];
|
|
145
171
|
if (tvosButton) {
|
|
146
172
|
await driver.pressButton(tvosButton, holdSeconds);
|
|
@@ -191,6 +217,13 @@ async function dispatchKey(driver, matched, holdSeconds) {
|
|
|
191
217
|
}
|
|
192
218
|
// Keys not mapped on vega are silently ignored
|
|
193
219
|
}
|
|
220
|
+
else if (driver instanceof roku_js_1.RokuDriver) {
|
|
221
|
+
// Keys not mapped on roku are silently ignored, as on tvOS and vega — but a
|
|
222
|
+
// mapped key that the device rejects must still fail.
|
|
223
|
+
if ((0, key_mapping_js_1.rokuEcpKey)(matched)) {
|
|
224
|
+
await driver.pressKeyNamed(matched);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
194
227
|
else if (driver instanceof android_js_1.AndroidDriver) {
|
|
195
228
|
const code = exports.ANDROID_KEYCODE[matched];
|
|
196
229
|
if (code !== undefined) {
|
package/dist/commands/profile.js
CHANGED
|
@@ -244,6 +244,10 @@ async function profileCpu(opts, sessionName, profileOpts) {
|
|
|
244
244
|
else if (platform === 'android') {
|
|
245
245
|
entries = await recordAndroidCpu(sessionName, profileOpts.appId, profileOpts.durationSec, out, profileOpts.report ? { top: profileOpts.top ?? 30 } : undefined);
|
|
246
246
|
}
|
|
247
|
+
else if (platform === 'roku') {
|
|
248
|
+
(0, output_js_1.printError)('profile cpu is not supported on roku — ECP exposes no CPU counters.', opts);
|
|
249
|
+
return 1;
|
|
250
|
+
}
|
|
247
251
|
else if (platform === 'vega') {
|
|
248
252
|
// Vega is Amazon's own OS, not Android — no simpleperf, no dumpsys. A
|
|
249
253
|
// physical Fire TV Stick runs Fire OS (Android) over adb and is a
|