@houwert/conductor 0.29.2 → 0.30.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 +5 -0
- package/dist/commands/launch-app.js +3 -2
- package/dist/commands/list-apps.js +5 -0
- package/dist/commands/list-devices.js +12 -0
- package/dist/commands/memory.js +5 -0
- package/dist/commands/press-key.js +15 -0
- package/dist/commands/profile-frames.js +4 -2
- package/dist/commands/profile.js +111 -7
- 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 +12 -1
- package/dist/commands/swipe.js +4 -1
- package/dist/commands/tap.js +3 -2
- package/dist/commands/uninstall-app.js +4 -0
- package/dist/daemon/server.js +14 -4
- package/dist/drivers/bootstrap.js +10 -0
- package/dist/drivers/flow-runner.js +16 -3
- 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 +32 -0
- package/package.json +1 -1
- package/skills/conductor-device-interact/SKILL.md +4 -3
- package/skills/conductor-device-setup/SKILL.md +35 -2
- package/skills/conductor-profiler/SKILL.md +9 -4
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
|
}
|
|
@@ -30,6 +30,11 @@ async function installApp(appPath, opts = {}, sessionName = 'default') {
|
|
|
30
30
|
(0, output_js_1.printError)('install-app is not supported on web. Use launch-app with a URL instead.', opts);
|
|
31
31
|
return 1;
|
|
32
32
|
}
|
|
33
|
+
else if (platform === 'roku') {
|
|
34
|
+
(0, output_js_1.printError)('install-app is not supported on roku — sideload the channel through the ' +
|
|
35
|
+
"device's developer web server (http://<device-ip>).", opts);
|
|
36
|
+
return 1;
|
|
37
|
+
}
|
|
33
38
|
else if (platform === 'vega') {
|
|
34
39
|
// Vega installs a `.vpkg` through Amazon's CLI; strip the `vega:` id prefix.
|
|
35
40
|
try {
|
|
@@ -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) {
|
|
@@ -29,6 +29,11 @@ async function listApps(opts = {}, sessionName = 'default') {
|
|
|
29
29
|
(0, output_js_1.printError)('list-apps is not supported on web. Use foreground-app to get the current URL.', opts);
|
|
30
30
|
return 1;
|
|
31
31
|
}
|
|
32
|
+
else if (platform === 'roku') {
|
|
33
|
+
// ECP can name only the *active* channel, not enumerate installed ones.
|
|
34
|
+
(0, output_js_1.printError)('list-apps is not supported on roku. Use foreground-app to get the active channel id.', opts);
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
32
37
|
else if (platform === 'vega') {
|
|
33
38
|
// Vega is driven through Amazon's CLI, not adb; strip the `vega:` id prefix.
|
|
34
39
|
appIds = (await new cli_js_1.VegaCli(deviceId.replace(/^vega:/, '')).listInstalledApps()).sort();
|
|
@@ -16,6 +16,7 @@ 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");
|
|
20
21
|
// Captured during discoverAvailableDevices so listDevices() can report it.
|
|
21
22
|
// Module-scoped because the discover function returns Device[]; bolting an
|
|
@@ -124,6 +125,17 @@ async function discoverBootedDevices() {
|
|
|
124
125
|
catch {
|
|
125
126
|
/* vega CLI not installed */
|
|
126
127
|
}
|
|
128
|
+
// Roku: the pinned CONDUCTOR_ROKU_HOST device, plus SSDP hits when
|
|
129
|
+
// CONDUCTOR_ROKU_DISCOVERY is on. Physical hardware only — Roku has no emulator.
|
|
130
|
+
for (const d of await (0, discovery_js_1.discoverRokuDevices)().catch(() => [])) {
|
|
131
|
+
devices.push({
|
|
132
|
+
id: `roku:${d.host}`,
|
|
133
|
+
name: d.friendlyName || d.modelName,
|
|
134
|
+
platform: 'roku',
|
|
135
|
+
status: 'booted',
|
|
136
|
+
formFactor: 'tv',
|
|
137
|
+
});
|
|
138
|
+
}
|
|
127
139
|
// External CDP endpoints (e.g. an Electron app started with
|
|
128
140
|
// --remote-debugging-port): one device per discovered webview/tile.
|
|
129
141
|
devices.push(...(await (0, cdp_discovery_js_1.discoverCdpDevices)()));
|
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);
|
|
@@ -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,9 @@ 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',
|
|
57
62
|
];
|
|
58
63
|
// iOS XCTest pressKey accepts these values (maps to XCUIKeyboardKey)
|
|
59
64
|
const IOS_KEY_MAP = {
|
|
@@ -131,6 +136,9 @@ exports.ANDROID_KEYCODE = {
|
|
|
131
136
|
'TV Input HDMI 1': 243,
|
|
132
137
|
'TV Input HDMI 2': 244,
|
|
133
138
|
'TV Input HDMI 3': 245,
|
|
139
|
+
'Remote Info': 165,
|
|
140
|
+
'Remote Instant Replay': 273, // KEYCODE_MEDIA_SKIP_BACKWARD
|
|
141
|
+
'Remote Search': 84,
|
|
134
142
|
};
|
|
135
143
|
/**
|
|
136
144
|
* Send `key` on an already-connected driver. Split out of `pressKey` so
|
|
@@ -191,6 +199,13 @@ async function dispatchKey(driver, matched, holdSeconds) {
|
|
|
191
199
|
}
|
|
192
200
|
// Keys not mapped on vega are silently ignored
|
|
193
201
|
}
|
|
202
|
+
else if (driver instanceof roku_js_1.RokuDriver) {
|
|
203
|
+
// Keys not mapped on roku are silently ignored, as on tvOS and vega — but a
|
|
204
|
+
// mapped key that the device rejects must still fail.
|
|
205
|
+
if ((0, key_mapping_js_1.rokuEcpKey)(matched)) {
|
|
206
|
+
await driver.pressKeyNamed(matched);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
194
209
|
else if (driver instanceof android_js_1.AndroidDriver) {
|
|
195
210
|
const code = exports.ANDROID_KEYCODE[matched];
|
|
196
211
|
if (code !== undefined) {
|
|
@@ -647,8 +647,10 @@ async function profileFramesReport(opts, sessionName, frameOpts) {
|
|
|
647
647
|
if (frameOpts.trackSec !== undefined && repeat === 1) {
|
|
648
648
|
notes.push({
|
|
649
649
|
code: 'single-window',
|
|
650
|
-
message: 'One window carries no run-to-run variance, so
|
|
651
|
-
'
|
|
650
|
+
message: 'One window carries no run-to-run variance, so nothing here can be called a change. ' +
|
|
651
|
+
'Measured on a Fire TV Stick, five identical captures of the same screen ranged ' +
|
|
652
|
+
'37.8-72.9% janky and 6.2-13.0ms issueDraw p50 — wide enough to invent a regression ' +
|
|
653
|
+
'or hide one. Treat --repeat 5 as the default on TV, not an option.',
|
|
652
654
|
});
|
|
653
655
|
}
|
|
654
656
|
const report = {
|