@houwert/conductor 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/assert-not-visible.js +3 -1
- package/dist/commands/assert-visible.js +3 -1
- package/dist/commands/back.js +4 -0
- package/dist/commands/capture-ui.js +4 -2
- package/dist/commands/clipboard.js +9 -0
- package/dist/commands/crashes.js +5 -0
- package/dist/commands/delete-device.js +6 -1
- package/dist/commands/download-app.js +4 -0
- package/dist/commands/erase-text.js +2 -1
- package/dist/commands/focused.js +3 -1
- package/dist/commands/foreground-app.js +2 -1
- package/dist/commands/gestures.js +7 -0
- package/dist/commands/hide-keyboard.js +4 -0
- package/dist/commands/inspect.js +4 -3
- package/dist/commands/install-app.js +11 -0
- package/dist/commands/launch-app.js +6 -0
- package/dist/commands/list-apps.js +5 -0
- package/dist/commands/list-devices.js +16 -0
- package/dist/commands/memory.js +5 -0
- package/dist/commands/press-key.js +32 -4
- package/dist/commands/profile.js +5 -0
- package/dist/commands/screenshot.js +3 -1
- package/dist/commands/scroll-until-visible.js +3 -1
- package/dist/commands/scroll.js +2 -1
- package/dist/commands/start-device.js +60 -3
- package/dist/commands/stop-app.js +4 -0
- package/dist/commands/stop-device.js +8 -3
- package/dist/commands/swipe.js +2 -1
- package/dist/commands/tap.js +9 -6
- package/dist/commands/uninstall-app.js +4 -0
- package/dist/commands/workspace.js +4 -1
- package/dist/daemon/log-collector.js +9 -1
- package/dist/daemon/server.js +70 -50
- package/dist/drivers/bootstrap.js +12 -0
- package/dist/drivers/flow-runner.js +38 -8
- package/dist/drivers/ios.js +5 -2
- package/dist/drivers/log-sources/metro-discovery.js +43 -0
- package/dist/drivers/log-sources/vega.js +77 -0
- package/dist/drivers/vega/automation-client.js +79 -0
- package/dist/drivers/vega/cli.js +199 -0
- package/dist/drivers/vega/connection.js +48 -0
- package/dist/drivers/vega/input.js +100 -0
- package/dist/drivers/vega/page-source-parser.js +229 -0
- package/dist/drivers/vega.js +165 -0
- package/dist/enum-options.js +15 -3
- package/dist/index.js +7 -2
- package/dist/runner.js +31 -0
- package/package.json +1 -1
- package/skills/conductor-device-interact/SKILL.md +4 -3
- package/skills/conductor-device-setup/SKILL.md +16 -2
|
@@ -10,7 +10,7 @@ exports.pickSystemImage = pickSystemImage;
|
|
|
10
10
|
exports.buildAvdmanagerCreateArgs = buildAvdmanagerCreateArgs;
|
|
11
11
|
exports.startDevice = startDevice;
|
|
12
12
|
exports.HELP = ` start-device
|
|
13
|
-
--platform <ios|android|tvos|web> Boot a simulator/emulator,
|
|
13
|
+
--platform <ios|android|tvos|web|vega> Boot a simulator/emulator, start the web driver (Playwright), or boot/attach a Vega VVD
|
|
14
14
|
--os-version <n> iOS/tvOS version (e.g. 18) or Android API level (e.g. 33)
|
|
15
15
|
--avd <name> Android AVD name (default: first available; created if missing + --device-type)
|
|
16
16
|
--name <name> Set a custom name on the device after creation (iOS/tvOS/web)
|
|
@@ -28,6 +28,7 @@ const protocol_js_1 = require("../daemon/protocol.js");
|
|
|
28
28
|
const bootstrap_js_1 = require("../drivers/bootstrap.js");
|
|
29
29
|
const output_js_1 = require("../output.js");
|
|
30
30
|
const utils_js_1 = require("../utils.js");
|
|
31
|
+
const cli_js_1 = require("../drivers/vega/cli.js");
|
|
31
32
|
const IOS_BOOT_TIMEOUT_MS = 120000;
|
|
32
33
|
const ANDROID_BOOT_TIMEOUT_MS = 120000;
|
|
33
34
|
const POLL_MS = 1000;
|
|
@@ -694,10 +695,64 @@ async function startWebDriver(opts, browser, name) {
|
|
|
694
695
|
(0, output_js_1.printSuccess)(`Web driver ready: ${displayName} (${session})`, opts);
|
|
695
696
|
return 0;
|
|
696
697
|
}
|
|
698
|
+
// ── Vega (Amazon Fire TV) ───────────────────────────────────────────────────
|
|
699
|
+
const VEGA_BOOT_TIMEOUT_MS = 180000;
|
|
700
|
+
/** Pick the target VVD from a device list: by name if given, else first virtual, else first. */
|
|
701
|
+
function pickVegaDevice(devices, deviceName) {
|
|
702
|
+
return ((deviceName ? devices.find((d) => d.serial === deviceName) : undefined) ??
|
|
703
|
+
devices.find((d) => d.isVirtual) ??
|
|
704
|
+
devices[0]);
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Boot (or attach to) a Vega Virtual Device. If one is already running we attach;
|
|
708
|
+
* otherwise we launch it via `vega virtual-device start` and poll `vega device
|
|
709
|
+
* list` until it appears — mirroring how the Android emulator is booted. Then we
|
|
710
|
+
* prewarm the log daemon.
|
|
711
|
+
*/
|
|
712
|
+
async function startVega(opts, deviceName) {
|
|
713
|
+
const cli = new cli_js_1.VegaCli();
|
|
714
|
+
let devices;
|
|
715
|
+
try {
|
|
716
|
+
devices = await cli.listDevices();
|
|
717
|
+
}
|
|
718
|
+
catch {
|
|
719
|
+
(0, output_js_1.printError)('The Vega SDK CLI (`vega`/`kepler`) was not found. Install it and ensure it is on ' +
|
|
720
|
+
'PATH (or set CONDUCTOR_VEGA_CLI), then retry.', opts);
|
|
721
|
+
return 1;
|
|
722
|
+
}
|
|
723
|
+
let device = pickVegaDevice(devices, deviceName);
|
|
724
|
+
if (!device) {
|
|
725
|
+
console.log(`No running Vega device — booting a VVD${deviceName ? ` (${deviceName})` : ''} ` +
|
|
726
|
+
`via \`vega virtual-device start\`...`);
|
|
727
|
+
try {
|
|
728
|
+
cli.spawnVirtualDeviceStart(deviceName);
|
|
729
|
+
}
|
|
730
|
+
catch (e) {
|
|
731
|
+
(0, output_js_1.printError)(`Failed to start a Vega Virtual Device: ${e instanceof Error ? e.message : String(e)}`, opts);
|
|
732
|
+
return 1;
|
|
733
|
+
}
|
|
734
|
+
const deadline = Date.now() + VEGA_BOOT_TIMEOUT_MS;
|
|
735
|
+
while (Date.now() < deadline && !device) {
|
|
736
|
+
await (0, utils_js_1.sleep)(POLL_MS);
|
|
737
|
+
const current = await cli.listDevices().catch(() => []);
|
|
738
|
+
device = pickVegaDevice(current, deviceName);
|
|
739
|
+
}
|
|
740
|
+
if (!device) {
|
|
741
|
+
(0, output_js_1.printError)(`Vega Virtual Device did not become ready within ${VEGA_BOOT_TIMEOUT_MS / 1000}s. ` +
|
|
742
|
+
`Check \`vega virtual-device list\` — you may need to create one first, or pass ` +
|
|
743
|
+
`--name <vvd>.`, opts);
|
|
744
|
+
return 1;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
// Prewarm the daemon so device/Metro log collection is already running.
|
|
748
|
+
await (0, runner_js_1.prewarmDriver)(`vega:${device.serial}`);
|
|
749
|
+
(0, output_js_1.printSuccess)(`Vega device ready: ${device.serial}`, opts);
|
|
750
|
+
return 0;
|
|
751
|
+
}
|
|
697
752
|
// ── Entry point ───────────────────────────────────────────────────────────────
|
|
698
753
|
async function startDevice(platform, opts, flags) {
|
|
699
754
|
if (!platform) {
|
|
700
|
-
(0, output_js_1.printError)('start-device requires --platform ios|android|tvos|web', opts);
|
|
755
|
+
(0, output_js_1.printError)('start-device requires --platform ios|android|tvos|web|vega', opts);
|
|
701
756
|
return 1;
|
|
702
757
|
}
|
|
703
758
|
switch (platform.toLowerCase()) {
|
|
@@ -709,8 +764,10 @@ async function startDevice(platform, opts, flags) {
|
|
|
709
764
|
return startAndroid(flags.avd, opts, flags.deviceType, flags.osVersion, flags.systemImage);
|
|
710
765
|
case 'web':
|
|
711
766
|
return startWebDriver(opts, flags.browser, flags.name);
|
|
767
|
+
case 'vega':
|
|
768
|
+
return startVega(opts, flags.name);
|
|
712
769
|
default:
|
|
713
|
-
(0, output_js_1.printError)(`Unknown platform "${platform}". Use ios, android, tvos, or
|
|
770
|
+
(0, output_js_1.printError)(`Unknown platform "${platform}". Use ios, android, tvos, web, or vega.`, opts);
|
|
714
771
|
return 1;
|
|
715
772
|
}
|
|
716
773
|
}
|
|
@@ -9,6 +9,7 @@ const output_js_1 = require("../output.js");
|
|
|
9
9
|
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
|
+
const vega_js_1 = require("../drivers/vega.js");
|
|
12
13
|
async function stopApp(appId, opts = {}, sessionName = 'default') {
|
|
13
14
|
const session = await (0, session_js_1.getSession)(sessionName);
|
|
14
15
|
const resolvedAppId = appId ?? session.appId;
|
|
@@ -23,6 +24,9 @@ async function stopApp(appId, opts = {}, sessionName = 'default') {
|
|
|
23
24
|
else if (driver instanceof web_js_1.WebDriver) {
|
|
24
25
|
await driver.terminateApp();
|
|
25
26
|
}
|
|
27
|
+
else if (driver instanceof vega_js_1.VegaDriver) {
|
|
28
|
+
await driver.stopApp(resolvedAppId);
|
|
29
|
+
}
|
|
26
30
|
else if (driver instanceof android_js_1.AndroidDriver) {
|
|
27
31
|
await driver.stopApp(resolvedAppId);
|
|
28
32
|
}
|
|
@@ -3,7 +3,7 @@ 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> Scope to a single platform
|
|
6
|
+
--platform <ios|tvos|android|web|vega> 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");
|
|
@@ -35,6 +35,7 @@ async function stopDevice(nameOrId, opts, flags) {
|
|
|
35
35
|
const includeTvOS = !platform || platform === 'tvos';
|
|
36
36
|
const includeAndroid = !platform || platform === 'android';
|
|
37
37
|
const includeWeb = !platform || platform === 'web';
|
|
38
|
+
const includeVega = !platform || platform === 'vega';
|
|
38
39
|
const stopped = [];
|
|
39
40
|
// ── --all mode ───────────────────────────────────────────────────────────
|
|
40
41
|
if (flags.all) {
|
|
@@ -48,6 +49,8 @@ async function stopDevice(nameOrId, opts, flags) {
|
|
|
48
49
|
continue;
|
|
49
50
|
if (d.platform === 'web' && !includeWeb)
|
|
50
51
|
continue;
|
|
52
|
+
if (d.platform === 'vega' && !includeVega)
|
|
53
|
+
continue;
|
|
51
54
|
try {
|
|
52
55
|
if (d.platform === 'ios' || d.platform === 'tvos') {
|
|
53
56
|
await shutdownSimulator(d.id);
|
|
@@ -55,7 +58,8 @@ async function stopDevice(nameOrId, opts, flags) {
|
|
|
55
58
|
else if (d.platform === 'android') {
|
|
56
59
|
await killEmulator(d.id);
|
|
57
60
|
}
|
|
58
|
-
else if (d.platform === 'web') {
|
|
61
|
+
else if (d.platform === 'web' || d.platform === 'vega') {
|
|
62
|
+
// Vega VVD lifecycle is owned by Amazon's tooling — we only stop our log daemon.
|
|
59
63
|
await (0, client_js_1.stopDaemon)(d.id);
|
|
60
64
|
}
|
|
61
65
|
stopped.push({ id: d.id, name: d.name, platform: d.platform });
|
|
@@ -96,7 +100,8 @@ async function stopDevice(nameOrId, opts, flags) {
|
|
|
96
100
|
else if (match.platform === 'android') {
|
|
97
101
|
await killEmulator(match.id);
|
|
98
102
|
}
|
|
99
|
-
else if (match.platform === 'web') {
|
|
103
|
+
else if (match.platform === 'web' || match.platform === 'vega') {
|
|
104
|
+
// Vega VVD lifecycle is owned by Amazon's tooling — we only stop our log daemon.
|
|
100
105
|
await (0, client_js_1.stopDaemon)(match.id);
|
|
101
106
|
}
|
|
102
107
|
}
|
package/dist/commands/swipe.js
CHANGED
|
@@ -12,6 +12,7 @@ const output_js_1 = require("../output.js");
|
|
|
12
12
|
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
|
+
const vega_js_1 = require("../drivers/vega.js");
|
|
15
16
|
const utils_js_1 = require("../utils.js");
|
|
16
17
|
function parseCoordPair(s) {
|
|
17
18
|
const [xs, ys] = s.split(',').map((p) => p.trim());
|
|
@@ -70,7 +71,7 @@ async function swipe(direction, opts = {}, sessionName = 'default', flags = {})
|
|
|
70
71
|
}
|
|
71
72
|
await driver.swipe(startX, startY, endX, endY, durationMs);
|
|
72
73
|
}
|
|
73
|
-
else if (driver instanceof android_js_1.AndroidDriver) {
|
|
74
|
+
else if (driver instanceof android_js_1.AndroidDriver || driver instanceof vega_js_1.VegaDriver) {
|
|
74
75
|
const { widthPixels: w, heightPixels: h } = await driver.deviceInfo();
|
|
75
76
|
const durationMs = flags.duration ?? 500;
|
|
76
77
|
if (flags.start && flags.end) {
|
package/dist/commands/tap.js
CHANGED
|
@@ -22,6 +22,7 @@ const output_js_1 = require("../output.js");
|
|
|
22
22
|
const ios_js_1 = require("../drivers/ios.js");
|
|
23
23
|
const android_js_1 = require("../drivers/android.js");
|
|
24
24
|
const web_js_1 = require("../drivers/web.js");
|
|
25
|
+
const vega_js_1 = require("../drivers/vega.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
|
const snapshot_store_js_1 = require("../snapshot-store.js");
|
|
@@ -68,6 +69,10 @@ async function tap(query, opts = {}, sessionName = 'default', flags = {}) {
|
|
|
68
69
|
else if (driver instanceof web_js_1.WebDriver) {
|
|
69
70
|
el = await (0, wait_js_1.waitForWebElement)(() => driver.viewHierarchy(), sel);
|
|
70
71
|
}
|
|
72
|
+
else if (driver instanceof vega_js_1.VegaDriver) {
|
|
73
|
+
// Vega emits uiautomator-style XML, so it reuses the Android resolver.
|
|
74
|
+
el = await (0, wait_js_1.waitForAndroidElement)(() => driver.viewHierarchy(), sel);
|
|
75
|
+
}
|
|
71
76
|
else if (driver instanceof android_js_1.AndroidDriver) {
|
|
72
77
|
el = await (0, wait_js_1.waitForAndroidElement)(() => driver.viewHierarchy(), sel);
|
|
73
78
|
}
|
|
@@ -75,14 +80,12 @@ async function tap(query, opts = {}, sessionName = 'default', flags = {}) {
|
|
|
75
80
|
return;
|
|
76
81
|
}
|
|
77
82
|
if (flags.longPress) {
|
|
78
|
-
if (driver instanceof
|
|
79
|
-
await driver.
|
|
80
|
-
}
|
|
81
|
-
else if (driver instanceof web_js_1.WebDriver) {
|
|
82
|
-
await driver.tap(el.centerX, el.centerY, 1.5);
|
|
83
|
+
if (driver instanceof android_js_1.AndroidDriver) {
|
|
84
|
+
await driver.swipe(el.centerX, el.centerY, el.centerX, el.centerY, 1500);
|
|
83
85
|
}
|
|
84
86
|
else {
|
|
85
|
-
|
|
87
|
+
// iOS, web, and vega express a long press as a held tap.
|
|
88
|
+
await driver.tap(el.centerX, el.centerY, 1.5);
|
|
86
89
|
}
|
|
87
90
|
}
|
|
88
91
|
else if (flags.doubleTap) {
|
|
@@ -8,6 +8,7 @@ const output_js_1 = require("../output.js");
|
|
|
8
8
|
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
|
+
const vega_js_1 = require("../drivers/vega.js");
|
|
11
12
|
async function uninstallApp(appId, opts = {}, sessionName = 'default') {
|
|
12
13
|
if (!appId) {
|
|
13
14
|
(0, output_js_1.printError)('uninstall-app requires <appId>', opts);
|
|
@@ -20,6 +21,9 @@ async function uninstallApp(appId, opts = {}, sessionName = 'default') {
|
|
|
20
21
|
else if (driver instanceof web_js_1.WebDriver) {
|
|
21
22
|
throw new Error('uninstall-app is not supported on web');
|
|
22
23
|
}
|
|
24
|
+
else if (driver instanceof vega_js_1.VegaDriver) {
|
|
25
|
+
throw new Error('uninstall-app is not supported on vega (Amazon Fire TV)');
|
|
26
|
+
}
|
|
23
27
|
else if (driver instanceof android_js_1.AndroidDriver) {
|
|
24
28
|
await driver.uninstallApp(appId);
|
|
25
29
|
}
|
|
@@ -112,7 +112,10 @@ async function workspaceInfo(opts = {}) {
|
|
|
112
112
|
const devices = await (0, list_devices_js_1.discoverBootedDevices)().catch(() => []);
|
|
113
113
|
let metroPort = null;
|
|
114
114
|
for (const d of devices) {
|
|
115
|
-
if (d.platform !== 'ios' &&
|
|
115
|
+
if (d.platform !== 'ios' &&
|
|
116
|
+
d.platform !== 'tvos' &&
|
|
117
|
+
d.platform !== 'android' &&
|
|
118
|
+
d.platform !== 'vega')
|
|
116
119
|
continue;
|
|
117
120
|
const port = await (0, metro_discovery_js_1.discoverMetroPortForDevice)(d.platform, d.id).catch(() => null);
|
|
118
121
|
if (port) {
|
|
@@ -32,6 +32,7 @@ const http_1 = __importDefault(require("http"));
|
|
|
32
32
|
const types_js_1 = require("../drivers/log-sources/types.js");
|
|
33
33
|
const ios_js_1 = require("../drivers/log-sources/ios.js");
|
|
34
34
|
const android_js_1 = require("../drivers/log-sources/android.js");
|
|
35
|
+
const vega_js_1 = require("../drivers/log-sources/vega.js");
|
|
35
36
|
const metro_js_1 = require("../drivers/log-sources/metro.js");
|
|
36
37
|
const metro_discovery_js_1 = require("../drivers/log-sources/metro-discovery.js");
|
|
37
38
|
const MAX_BUFFER = 5000;
|
|
@@ -75,7 +76,10 @@ class LogCollector {
|
|
|
75
76
|
// Metro is always auto-discovered. Discovery retries forever with a
|
|
76
77
|
// backoff ceiling — the app may be launched long after the daemon starts,
|
|
77
78
|
// and the cost per attempt is tiny (a few spawns + one HTTP call).
|
|
78
|
-
if (this.platform === 'ios' ||
|
|
79
|
+
if (this.platform === 'ios' ||
|
|
80
|
+
this.platform === 'tvos' ||
|
|
81
|
+
this.platform === 'android' ||
|
|
82
|
+
this.platform === 'vega') {
|
|
79
83
|
this.startMetroAutoDiscovery();
|
|
80
84
|
}
|
|
81
85
|
}
|
|
@@ -146,6 +150,10 @@ class LogCollector {
|
|
|
146
150
|
else if (this.platform === 'android') {
|
|
147
151
|
this.source = new android_js_1.AndroidLogSource(this.deviceId, this.appId);
|
|
148
152
|
}
|
|
153
|
+
else if (this.platform === 'vega') {
|
|
154
|
+
// deviceId is `vega:<serial>`; VegaCli wants the bare serial.
|
|
155
|
+
this.source = new vega_js_1.VegaLogSource(this.deviceId.replace(/^vega:/, ''));
|
|
156
|
+
}
|
|
149
157
|
else {
|
|
150
158
|
return; // Unsupported platform for device log collection
|
|
151
159
|
}
|
package/dist/daemon/server.js
CHANGED
|
@@ -85,6 +85,9 @@ let _driverStartError = null;
|
|
|
85
85
|
async function ensureDriverRunning() {
|
|
86
86
|
if (_restartInProgress || !_driverStarted)
|
|
87
87
|
return;
|
|
88
|
+
// Vega has no driver process/port to health-check — control is host-side via the CLI.
|
|
89
|
+
if (driverPlatform === 'vega')
|
|
90
|
+
return;
|
|
88
91
|
let alive;
|
|
89
92
|
if (driverPlatform === 'android') {
|
|
90
93
|
const probe = new android_js_1.AndroidDriver(sessionName, driverPort);
|
|
@@ -197,6 +200,9 @@ async function main() {
|
|
|
197
200
|
if (driverPlatform === 'tvos') {
|
|
198
201
|
dlog('tvOS: leaving driver running to preserve app state');
|
|
199
202
|
}
|
|
203
|
+
else if (driverPlatform === 'vega') {
|
|
204
|
+
dlog('vega: no driver process to stop (control is host-side via the CLI)');
|
|
205
|
+
}
|
|
200
206
|
else if (driverPlatform === 'web') {
|
|
201
207
|
dlog('Stopping web driver');
|
|
202
208
|
try {
|
|
@@ -321,59 +327,14 @@ async function main() {
|
|
|
321
327
|
driverPlatform = platform;
|
|
322
328
|
driverPort = await (0, bootstrap_js_1.getDriverPort)(platform, sessionName);
|
|
323
329
|
dlog(`Platform: ${platform}, port: ${driverPort}`);
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
await probe.connect();
|
|
328
|
-
driverAlive = await probe.isAlive().catch(() => false);
|
|
329
|
-
probe.close();
|
|
330
|
-
}
|
|
331
|
-
else {
|
|
332
|
-
// 'ios', 'tvos', and 'web' all use an HTTP server — port open = alive
|
|
333
|
-
driverAlive = await (0, bootstrap_js_1.isPortOpen)(driverPort);
|
|
334
|
-
}
|
|
335
|
-
if (driverAlive) {
|
|
330
|
+
// Vega has no driver process — control is host-side via the vega CLI.
|
|
331
|
+
// The daemon exists only to collect device + Metro logs.
|
|
332
|
+
if (platform === 'vega') {
|
|
336
333
|
_driverStarted = true;
|
|
337
|
-
dlog(
|
|
334
|
+
dlog('vega: no driver process to start; collecting logs only');
|
|
338
335
|
}
|
|
339
336
|
else {
|
|
340
|
-
|
|
341
|
-
// iOS/tvOS: xcodebuild installs silently via DependentProductPaths.
|
|
342
|
-
// Web: ensure Playwright browser binary is installed.
|
|
343
|
-
if (platform === 'android') {
|
|
344
|
-
dlog(`Installing Android driver on ${sessionName}`);
|
|
345
|
-
await (0, bootstrap_js_1.installDriver)(sessionName);
|
|
346
|
-
dlog(`Driver installation complete`);
|
|
347
|
-
}
|
|
348
|
-
else if (platform === 'web' && !cdpUrl) {
|
|
349
|
-
// Only install Playwright browser when launching standalone.
|
|
350
|
-
// In CDP mode we attach to the host app's browser (e.g. Electron).
|
|
351
|
-
const browser = (0, bootstrap_js_1.webBrowserName)(sessionName);
|
|
352
|
-
await (0, bootstrap_js_1.ensurePlaywrightBrowser)(browser, dlog);
|
|
353
|
-
}
|
|
354
|
-
dlog(`Starting ${platform} driver on port ${driverPort}`);
|
|
355
|
-
try {
|
|
356
|
-
if (platform === 'ios') {
|
|
357
|
-
await (0, bootstrap_js_1.startIOSDriver)(sessionName, driverPort);
|
|
358
|
-
}
|
|
359
|
-
else if (platform === 'tvos') {
|
|
360
|
-
// First install — the runner takes foreground; ask it to hand
|
|
361
|
-
// focus back to whatever app the user had open.
|
|
362
|
-
await (0, bootstrap_js_1.startTvOSDriver)(sessionName, driverPort, /* restoreFocusAfterLaunch */ true);
|
|
363
|
-
}
|
|
364
|
-
else if (platform === 'web') {
|
|
365
|
-
await (0, web_server_js_1.startWebServer)(driverPort, (0, bootstrap_js_1.webBrowserName)(sessionName), dlog, cdpUrl, cdpTargetId);
|
|
366
|
-
}
|
|
367
|
-
else {
|
|
368
|
-
await (0, bootstrap_js_1.startAndroidDriver)(sessionName, driverPort);
|
|
369
|
-
}
|
|
370
|
-
_driverStarted = true;
|
|
371
|
-
dlog(`Driver started successfully`);
|
|
372
|
-
}
|
|
373
|
-
catch (err) {
|
|
374
|
-
_driverStartError = err instanceof Error ? err.message : String(err);
|
|
375
|
-
dlog(`Driver startup error: ${_driverStartError}`);
|
|
376
|
-
}
|
|
337
|
+
await startDriverForPlatform(platform);
|
|
377
338
|
}
|
|
378
339
|
// Start collecting logs once the driver is (or was already) running.
|
|
379
340
|
if (_driverStarted) {
|
|
@@ -394,6 +355,65 @@ async function main() {
|
|
|
394
355
|
}
|
|
395
356
|
});
|
|
396
357
|
}
|
|
358
|
+
/**
|
|
359
|
+
* Bring up the driver process for a non-vega platform. Sets `_driverStarted` /
|
|
360
|
+
* `_driverStartError`. Extracted so the vega path can skip it entirely.
|
|
361
|
+
*/
|
|
362
|
+
async function startDriverForPlatform(platform) {
|
|
363
|
+
let driverAlive;
|
|
364
|
+
if (platform === 'android') {
|
|
365
|
+
const probe = new android_js_1.AndroidDriver(sessionName, driverPort);
|
|
366
|
+
await probe.connect();
|
|
367
|
+
driverAlive = await probe.isAlive().catch(() => false);
|
|
368
|
+
probe.close();
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
// 'ios', 'tvos', and 'web' all use an HTTP server — port open = alive
|
|
372
|
+
driverAlive = await (0, bootstrap_js_1.isPortOpen)(driverPort);
|
|
373
|
+
}
|
|
374
|
+
if (driverAlive) {
|
|
375
|
+
_driverStarted = true;
|
|
376
|
+
dlog(`Driver already running on port ${driverPort}`);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
// Android: install APKs before starting the driver.
|
|
380
|
+
// iOS/tvOS: xcodebuild installs silently via DependentProductPaths.
|
|
381
|
+
// Web: ensure Playwright browser binary is installed.
|
|
382
|
+
if (platform === 'android') {
|
|
383
|
+
dlog(`Installing Android driver on ${sessionName}`);
|
|
384
|
+
await (0, bootstrap_js_1.installDriver)(sessionName);
|
|
385
|
+
dlog(`Driver installation complete`);
|
|
386
|
+
}
|
|
387
|
+
else if (platform === 'web' && !cdpUrl) {
|
|
388
|
+
// Only install Playwright browser when launching standalone.
|
|
389
|
+
// In CDP mode we attach to the host app's browser (e.g. Electron).
|
|
390
|
+
const browser = (0, bootstrap_js_1.webBrowserName)(sessionName);
|
|
391
|
+
await (0, bootstrap_js_1.ensurePlaywrightBrowser)(browser, dlog);
|
|
392
|
+
}
|
|
393
|
+
dlog(`Starting ${platform} driver on port ${driverPort}`);
|
|
394
|
+
try {
|
|
395
|
+
if (platform === 'ios') {
|
|
396
|
+
await (0, bootstrap_js_1.startIOSDriver)(sessionName, driverPort);
|
|
397
|
+
}
|
|
398
|
+
else if (platform === 'tvos') {
|
|
399
|
+
// First install — the runner takes foreground; ask it to hand
|
|
400
|
+
// focus back to whatever app the user had open.
|
|
401
|
+
await (0, bootstrap_js_1.startTvOSDriver)(sessionName, driverPort, /* restoreFocusAfterLaunch */ true);
|
|
402
|
+
}
|
|
403
|
+
else if (platform === 'web') {
|
|
404
|
+
await (0, web_server_js_1.startWebServer)(driverPort, (0, bootstrap_js_1.webBrowserName)(sessionName), dlog, cdpUrl, cdpTargetId);
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
await (0, bootstrap_js_1.startAndroidDriver)(sessionName, driverPort);
|
|
408
|
+
}
|
|
409
|
+
_driverStarted = true;
|
|
410
|
+
dlog(`Driver started successfully`);
|
|
411
|
+
}
|
|
412
|
+
catch (err) {
|
|
413
|
+
_driverStartError = err instanceof Error ? err.message : String(err);
|
|
414
|
+
dlog(`Driver startup error: ${_driverStartError}`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
397
417
|
main().catch((err) => {
|
|
398
418
|
console.error('Daemon error:', err);
|
|
399
419
|
process.exit(1);
|
|
@@ -53,6 +53,11 @@ async function detectPlatform(deviceId) {
|
|
|
53
53
|
_platformCache.set(deviceId, 'web');
|
|
54
54
|
return 'web';
|
|
55
55
|
}
|
|
56
|
+
// Vega (Amazon Fire TV): "vega:<serial>" (e.g. "vega:VirtualDevice")
|
|
57
|
+
if (deviceId === 'vega' || deviceId.startsWith('vega:')) {
|
|
58
|
+
_platformCache.set(deviceId, 'vega');
|
|
59
|
+
return 'vega';
|
|
60
|
+
}
|
|
56
61
|
// Check if it looks like an iOS/tvOS simulator UUID (8-4-4-4-12 hex chars)
|
|
57
62
|
const iosUuidRe = /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i;
|
|
58
63
|
if (iosUuidRe.test(deviceId)) {
|
|
@@ -83,6 +88,7 @@ const IOS_BASE_PORT = 1075;
|
|
|
83
88
|
const TVOS_BASE_PORT = 2075;
|
|
84
89
|
const ANDROID_BASE_PORT = 3763;
|
|
85
90
|
const WEB_BASE_PORT = 4075;
|
|
91
|
+
const VEGA_BASE_PORT = 5075;
|
|
86
92
|
const PORT_FILE = path_1.default.join(os_1.default.homedir(), '.conductor', 'ports.json');
|
|
87
93
|
const PORT_LOCK = PORT_FILE + '.lock';
|
|
88
94
|
const PORT_LOCK_TIMEOUT_MS = 5000;
|
|
@@ -97,6 +103,7 @@ function readPortState() {
|
|
|
97
103
|
nextTvosPort: TVOS_BASE_PORT,
|
|
98
104
|
nextAndroidPort: ANDROID_BASE_PORT,
|
|
99
105
|
nextWebPort: WEB_BASE_PORT,
|
|
106
|
+
nextVegaPort: VEGA_BASE_PORT,
|
|
100
107
|
};
|
|
101
108
|
}
|
|
102
109
|
}
|
|
@@ -145,6 +152,8 @@ async function getDriverPort(platform, deviceId) {
|
|
|
145
152
|
state.nextTvosPort = TVOS_BASE_PORT;
|
|
146
153
|
if (state.nextWebPort === undefined)
|
|
147
154
|
state.nextWebPort = WEB_BASE_PORT;
|
|
155
|
+
if (state.nextVegaPort === undefined)
|
|
156
|
+
state.nextVegaPort = VEGA_BASE_PORT;
|
|
148
157
|
let port;
|
|
149
158
|
if (platform === 'ios') {
|
|
150
159
|
port = state.nextIosPort++;
|
|
@@ -155,6 +164,9 @@ async function getDriverPort(platform, deviceId) {
|
|
|
155
164
|
else if (platform === 'web') {
|
|
156
165
|
port = state.nextWebPort++;
|
|
157
166
|
}
|
|
167
|
+
else if (platform === 'vega') {
|
|
168
|
+
port = state.nextVegaPort++;
|
|
169
|
+
}
|
|
158
170
|
else {
|
|
159
171
|
port = state.nextAndroidPort++;
|
|
160
172
|
}
|
|
@@ -17,6 +17,7 @@ const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
|
17
17
|
const ios_js_1 = require("./ios.js");
|
|
18
18
|
const android_js_1 = require("./android.js");
|
|
19
19
|
const web_js_1 = require("./web.js");
|
|
20
|
+
const vega_js_1 = require("./vega.js");
|
|
20
21
|
const wait_js_1 = require("./wait.js");
|
|
21
22
|
const direct_ios_selector_js_1 = require("./direct-ios-selector.js");
|
|
22
23
|
const perf_hooks_1 = require("perf_hooks");
|
|
@@ -25,6 +26,20 @@ const utils_js_1 = require("../utils.js");
|
|
|
25
26
|
function fmtMs(ms) {
|
|
26
27
|
return ms < 1000 ? `${Math.round(ms)}ms` : `${(ms / 1000).toFixed(1)}s`;
|
|
27
28
|
}
|
|
29
|
+
// vega (Amazon Fire TV) remote key names → VegaDriver button values, for flow pressKey.
|
|
30
|
+
const VEGA_FLOW_BUTTONS = {
|
|
31
|
+
BACK: 'back',
|
|
32
|
+
HOME: 'home',
|
|
33
|
+
'REMOTE DPAD UP': 'up',
|
|
34
|
+
'REMOTE DPAD DOWN': 'down',
|
|
35
|
+
'REMOTE DPAD LEFT': 'left',
|
|
36
|
+
'REMOTE DPAD RIGHT': 'right',
|
|
37
|
+
'REMOTE DPAD CENTER': 'select',
|
|
38
|
+
ENTER: 'select',
|
|
39
|
+
RETURN: 'select',
|
|
40
|
+
VOLUME_UP: 'volumeUp',
|
|
41
|
+
VOLUME_DOWN: 'volumeDown',
|
|
42
|
+
};
|
|
28
43
|
function toElementSelector(sel) {
|
|
29
44
|
// A bare string matches text OR id (Maestro's query semantics).
|
|
30
45
|
// Only object form with explicit `text:` or `id:` keys forces one field.
|
|
@@ -507,7 +522,13 @@ async function executeCommand(cmd, driver, opts) {
|
|
|
507
522
|
}
|
|
508
523
|
}
|
|
509
524
|
function getConductorObj(driver, output) {
|
|
510
|
-
const platform = driver instanceof ios_js_1.IOSDriver
|
|
525
|
+
const platform = driver instanceof ios_js_1.IOSDriver
|
|
526
|
+
? 'ios'
|
|
527
|
+
: driver instanceof web_js_1.WebDriver
|
|
528
|
+
? 'web'
|
|
529
|
+
: driver instanceof vega_js_1.VegaDriver
|
|
530
|
+
? 'vega'
|
|
531
|
+
: 'android';
|
|
511
532
|
return {
|
|
512
533
|
platform,
|
|
513
534
|
copiedText: output['__copiedText'] ?? '',
|
|
@@ -634,16 +655,14 @@ async function executeCommandBody(key, val, driver, opts) {
|
|
|
634
655
|
const n = typeof val === 'number'
|
|
635
656
|
? val
|
|
636
657
|
: (val?.charactersToErase ?? 50);
|
|
637
|
-
if (driver instanceof
|
|
638
|
-
await driver.eraseAllText(n);
|
|
639
|
-
}
|
|
640
|
-
else if (driver instanceof web_js_1.WebDriver) {
|
|
641
|
-
await driver.eraseAllText(n);
|
|
642
|
-
}
|
|
643
|
-
else {
|
|
658
|
+
if (driver instanceof ios_js_1.IOSDriver) {
|
|
644
659
|
for (let i = 0; i < n; i++)
|
|
645
660
|
await driver.pressKey('delete');
|
|
646
661
|
}
|
|
662
|
+
else {
|
|
663
|
+
// Android, web, and vega all expose eraseAllText.
|
|
664
|
+
await driver.eraseAllText(n);
|
|
665
|
+
}
|
|
647
666
|
break;
|
|
648
667
|
}
|
|
649
668
|
case 'inputRandomText': {
|
|
@@ -782,6 +801,8 @@ async function executeCommandBody(key, val, driver, opts) {
|
|
|
782
801
|
await driver.back();
|
|
783
802
|
else if (driver instanceof web_js_1.WebDriver)
|
|
784
803
|
await driver.goBack();
|
|
804
|
+
else if (driver instanceof vega_js_1.VegaDriver)
|
|
805
|
+
await driver.back();
|
|
785
806
|
// iOS has no hardware back button — noop
|
|
786
807
|
break;
|
|
787
808
|
}
|
|
@@ -1055,6 +1076,12 @@ async function executeCommandBody(key, val, driver, opts) {
|
|
|
1055
1076
|
};
|
|
1056
1077
|
await driver.pressKey(WEB_KEY_MAP[keyName] ?? keyName);
|
|
1057
1078
|
}
|
|
1079
|
+
else if (driver instanceof vega_js_1.VegaDriver) {
|
|
1080
|
+
const button = VEGA_FLOW_BUTTONS[keyName];
|
|
1081
|
+
if (!button)
|
|
1082
|
+
throw new Error(`pressKey: key "${val}" is not supported on vega`);
|
|
1083
|
+
await driver.pressButton(button);
|
|
1084
|
+
}
|
|
1058
1085
|
else {
|
|
1059
1086
|
const keycode = ANDROID_KEYCODES[keyName];
|
|
1060
1087
|
if (keycode === undefined)
|
|
@@ -1072,6 +1099,9 @@ async function executeCommandBody(key, val, driver, opts) {
|
|
|
1072
1099
|
else if (driver instanceof web_js_1.WebDriver) {
|
|
1073
1100
|
// No virtual keyboard on web — noop
|
|
1074
1101
|
}
|
|
1102
|
+
else if (driver instanceof vega_js_1.VegaDriver) {
|
|
1103
|
+
// No reliable keyboard-hide primitive on vega — noop
|
|
1104
|
+
}
|
|
1075
1105
|
else {
|
|
1076
1106
|
await driver.pressKeyEvent(111); // KEYCODE_ESCAPE
|
|
1077
1107
|
}
|
package/dist/drivers/ios.js
CHANGED
|
@@ -160,8 +160,11 @@ class IOSDriver {
|
|
|
160
160
|
await this.post('pressKey', { key });
|
|
161
161
|
this.invalidateHierarchyCache();
|
|
162
162
|
}
|
|
163
|
-
async pressButton(button) {
|
|
164
|
-
await this.post('pressButton', {
|
|
163
|
+
async pressButton(button, duration) {
|
|
164
|
+
await this.post('pressButton', {
|
|
165
|
+
button,
|
|
166
|
+
...(duration !== undefined ? { duration } : {}),
|
|
167
|
+
});
|
|
165
168
|
this.invalidateHierarchyCache();
|
|
166
169
|
}
|
|
167
170
|
async launchApp(bundleId, args) {
|
|
@@ -26,6 +26,10 @@ exports.targetsForDevice = targetsForDevice;
|
|
|
26
26
|
const child_process_1 = require("child_process");
|
|
27
27
|
const metro_js_1 = require("./metro.js");
|
|
28
28
|
const sdk_js_1 = require("../../android/sdk.js");
|
|
29
|
+
const cli_js_1 = require("../vega/cli.js");
|
|
30
|
+
// Standard Metro dev-server ports to probe for the Vega VVD, which — unlike
|
|
31
|
+
// iOS/Android — exposes no host-visible socket mapping we can read.
|
|
32
|
+
const VEGA_METRO_CANDIDATE_PORTS = [8081, 8082];
|
|
29
33
|
/**
|
|
30
34
|
* Heuristic to reject ports that obviously aren't a Metro dev server (system
|
|
31
35
|
* ports, well-known services). Candidates that pass are always verified via
|
|
@@ -62,6 +66,33 @@ async function discoverMetroPortForDevice(platform, deviceId) {
|
|
|
62
66
|
if (platform === 'ios' || platform === 'tvos') {
|
|
63
67
|
return discoverMetroPortIOS(deviceId);
|
|
64
68
|
}
|
|
69
|
+
if (platform === 'vega') {
|
|
70
|
+
return discoverMetroPortVega(deviceId);
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Locate the Metro port for a Vega VVD. There is no host-visible socket mapping,
|
|
76
|
+
* so probe the standard Metro ports and accept the first that is a real Metro
|
|
77
|
+
* server hosting a target for this device (matched by display name when known).
|
|
78
|
+
*/
|
|
79
|
+
async function discoverMetroPortVega(deviceId) {
|
|
80
|
+
const displayName = await getDeviceDisplayName('vega', deviceId);
|
|
81
|
+
for (const port of VEGA_METRO_CANDIDATE_PORTS) {
|
|
82
|
+
try {
|
|
83
|
+
const targets = await (0, metro_js_1.fetchTargets)(port, 'localhost');
|
|
84
|
+
if (targets.length === 0)
|
|
85
|
+
continue;
|
|
86
|
+
// Prefer a name match; fall back to any target (single-device v1).
|
|
87
|
+
if (!displayName || targets.some((t) => deviceNameMatches(t.deviceName, displayName))) {
|
|
88
|
+
return port;
|
|
89
|
+
}
|
|
90
|
+
return port;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// not Metro on this port — try the next
|
|
94
|
+
}
|
|
95
|
+
}
|
|
65
96
|
return null;
|
|
66
97
|
}
|
|
67
98
|
async function discoverMetroPortAndroid(deviceId) {
|
|
@@ -199,6 +230,18 @@ async function getDeviceDisplayName(platform, deviceId) {
|
|
|
199
230
|
return null;
|
|
200
231
|
}
|
|
201
232
|
}
|
|
233
|
+
if (platform === 'vega') {
|
|
234
|
+
// deviceId is `vega:<serial>`; resolve the VVD's reported description.
|
|
235
|
+
const serial = deviceId.replace(/^vega:/, '');
|
|
236
|
+
try {
|
|
237
|
+
const devices = await new cli_js_1.VegaCli().listDevices();
|
|
238
|
+
const match = devices.find((d) => d.serial === serial);
|
|
239
|
+
return match?.description ?? serial;
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
return serial;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
202
245
|
return null;
|
|
203
246
|
}
|
|
204
247
|
/**
|