visor-ai 0.2.4 → 0.2.6
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 +13 -2
- package/dist/adapters.js +98 -82
- package/dist/appiumLifecycle.js +4 -1
- package/dist/cli.js +168 -192
- package/dist/daemon.js +517 -0
- package/dist/devices.js +123 -0
- package/dist/main.js +5 -0
- package/dist/runner.js +7 -3
- package/dist/validator.js +25 -6
- package/package.json +7 -2
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { DEFAULT_SERVER_URL
|
|
2
|
-
import {
|
|
1
|
+
import { DEFAULT_SERVER_URL } from './adapters.js';
|
|
2
|
+
import { DaemonRequestTimeoutError, DaemonUnavailableError, runDaemonAction, runDaemonScenario, startVisorDaemon, statusVisorDaemon, stopVisorDaemon } from './daemon.js';
|
|
3
|
+
import { DeviceSelectionError, resolveRunningDevice } from './devices.js';
|
|
3
4
|
import { makeError } from './errors.js';
|
|
4
5
|
import { writeReports } from './report.js';
|
|
5
|
-
import { determinismCheck
|
|
6
|
+
import { determinismCheck } from './runner.js';
|
|
6
7
|
import { errorMessage, makeId, utcNowIso } from './utils.js';
|
|
7
8
|
import { parseAndValidate } from './validator.js';
|
|
8
9
|
const ACTION_COMMANDS = new Set([
|
|
9
10
|
'tap',
|
|
10
11
|
'navigate',
|
|
11
12
|
'act',
|
|
13
|
+
'scroll',
|
|
12
14
|
'screenshot',
|
|
13
15
|
'wait',
|
|
14
16
|
'source'
|
|
@@ -24,7 +26,6 @@ const ALL_COMMANDS = new Set([
|
|
|
24
26
|
'stop'
|
|
25
27
|
]);
|
|
26
28
|
const GLOBAL_SPEC = {
|
|
27
|
-
platform: 'string',
|
|
28
29
|
device: 'string',
|
|
29
30
|
timeout: 'number',
|
|
30
31
|
output: 'string',
|
|
@@ -32,11 +33,7 @@ const GLOBAL_SPEC = {
|
|
|
32
33
|
seed: 'number',
|
|
33
34
|
'server-url': 'string',
|
|
34
35
|
'app-id': 'string',
|
|
35
|
-
'appium-cmd': 'string',
|
|
36
|
-
'startup-timeout': 'number',
|
|
37
|
-
'no-auto-start-appium': 'boolean',
|
|
38
36
|
attach: 'boolean',
|
|
39
|
-
mock: 'boolean',
|
|
40
37
|
verbose: 'boolean'
|
|
41
38
|
};
|
|
42
39
|
const ACTION_SPEC = {
|
|
@@ -44,6 +41,8 @@ const ACTION_SPEC = {
|
|
|
44
41
|
target: 'string',
|
|
45
42
|
x: 'number',
|
|
46
43
|
y: 'number',
|
|
44
|
+
direction: 'string',
|
|
45
|
+
percent: 'number',
|
|
47
46
|
normalized: 'boolean',
|
|
48
47
|
to: 'string',
|
|
49
48
|
name: 'string',
|
|
@@ -55,40 +54,29 @@ const ACTION_SPEC = {
|
|
|
55
54
|
const COMMAND_SPECS = {
|
|
56
55
|
validate: { format: 'string' },
|
|
57
56
|
run: {
|
|
58
|
-
platform: 'string',
|
|
59
57
|
device: 'string',
|
|
60
58
|
timeout: 'number',
|
|
61
59
|
output: 'string',
|
|
62
60
|
format: 'string',
|
|
63
61
|
'server-url': 'string',
|
|
64
62
|
'app-id': 'string',
|
|
65
|
-
|
|
66
|
-
'startup-timeout': 'number',
|
|
67
|
-
'no-auto-start-appium': 'boolean',
|
|
68
|
-
attach: 'boolean',
|
|
69
|
-
mock: 'boolean'
|
|
63
|
+
attach: 'boolean'
|
|
70
64
|
},
|
|
71
65
|
benchmark: {
|
|
72
66
|
runs: 'number',
|
|
73
67
|
threshold: 'number',
|
|
74
|
-
platform: 'string',
|
|
75
68
|
device: 'string',
|
|
76
69
|
timeout: 'number',
|
|
77
70
|
output: 'string',
|
|
78
71
|
format: 'string',
|
|
79
72
|
'server-url': 'string',
|
|
80
73
|
'app-id': 'string',
|
|
81
|
-
|
|
82
|
-
'startup-timeout': 'number',
|
|
83
|
-
'no-auto-start-appium': 'boolean',
|
|
84
|
-
attach: 'boolean',
|
|
85
|
-
mock: 'boolean'
|
|
74
|
+
attach: 'boolean'
|
|
86
75
|
},
|
|
87
76
|
report: { format: 'string' },
|
|
88
77
|
start: {
|
|
89
78
|
'server-url': 'string',
|
|
90
79
|
'appium-cmd': 'string',
|
|
91
|
-
'startup-timeout': 'number',
|
|
92
80
|
format: 'string'
|
|
93
81
|
},
|
|
94
82
|
status: {
|
|
@@ -103,6 +91,7 @@ const COMMAND_SPECS = {
|
|
|
103
91
|
tap: ACTION_SPEC,
|
|
104
92
|
navigate: ACTION_SPEC,
|
|
105
93
|
act: ACTION_SPEC,
|
|
94
|
+
scroll: ACTION_SPEC,
|
|
106
95
|
screenshot: ACTION_SPEC,
|
|
107
96
|
wait: ACTION_SPEC,
|
|
108
97
|
source: ACTION_SPEC
|
|
@@ -117,17 +106,19 @@ function helpText() {
|
|
|
117
106
|
'',
|
|
118
107
|
'Commands:',
|
|
119
108
|
' validate <scenario>',
|
|
120
|
-
' run <scenario> [--
|
|
109
|
+
' run <scenario> [--output <dir>]',
|
|
121
110
|
' benchmark <scenario> [--runs <n>] [--threshold <percent>]',
|
|
122
111
|
' report [path]',
|
|
123
|
-
' start [--server-url <url>]',
|
|
112
|
+
' start [--server-url <url>] [--appium-cmd <cmd>]',
|
|
124
113
|
' status [--server-url <url>]',
|
|
125
114
|
' stop [--server-url <url>] [--force]',
|
|
126
|
-
' tap|navigate|act|screenshot|wait|source',
|
|
115
|
+
' tap|navigate|act|scroll|screenshot|wait|source',
|
|
127
116
|
'',
|
|
128
117
|
'Examples:',
|
|
129
118
|
' visor validate scenarios/checkout-smoke.json',
|
|
130
|
-
' visor
|
|
119
|
+
' visor start --server-url http://127.0.0.1:4723',
|
|
120
|
+
' visor run scenarios/checkout-smoke.json --output artifacts-test',
|
|
121
|
+
' visor scroll --device emulator-5554 --direction down',
|
|
131
122
|
' node dist/main.js status'
|
|
132
123
|
].join('\n');
|
|
133
124
|
}
|
|
@@ -203,69 +194,31 @@ function parseCommand(argv) {
|
|
|
203
194
|
function warningIssues(issues) {
|
|
204
195
|
return issues.filter((issue) => issue.severity === 'warning');
|
|
205
196
|
}
|
|
206
|
-
function resolvedRuntime(options
|
|
207
|
-
const
|
|
208
|
-
const defaultDevice = platform === 'android' ? 'emulator-5554' : 'iPhone 17 Pro';
|
|
209
|
-
scenario.meta.platform = platform;
|
|
197
|
+
async function resolvedRuntime(options) {
|
|
198
|
+
const selectedDevice = await resolveRunningDevice(typeof options.device === 'string' ? options.device : undefined);
|
|
210
199
|
return {
|
|
211
|
-
platform,
|
|
212
|
-
device:
|
|
200
|
+
platform: selectedDevice.platform,
|
|
201
|
+
device: selectedDevice.id,
|
|
213
202
|
timeout: typeof options.timeout === 'number'
|
|
214
203
|
? options.timeout
|
|
215
|
-
:
|
|
216
|
-
|
|
217
|
-
: 2500,
|
|
218
|
-
output_dir: String(options.output ?? scenario.config.artifactsDir ?? 'artifacts'),
|
|
204
|
+
: 2500,
|
|
205
|
+
output_dir: String(options.output ?? 'artifacts'),
|
|
219
206
|
server_url: String(options['server-url'] ?? DEFAULT_SERVER_URL),
|
|
220
|
-
use_mock: Boolean(options.mock),
|
|
221
207
|
app_id: typeof options['app-id'] === 'string' ? options['app-id'] : undefined,
|
|
222
|
-
attach_to_running: Boolean(options.attach)
|
|
223
|
-
auto_start_appium: !Boolean(options['no-auto-start-appium']),
|
|
224
|
-
appium_cmd: typeof options['appium-cmd'] === 'string' ? options['appium-cmd'] : undefined,
|
|
225
|
-
startup_timeout: typeof options['startup-timeout'] === 'number'
|
|
226
|
-
? options['startup-timeout']
|
|
227
|
-
: DEFAULT_STARTUP_TIMEOUT_SECONDS
|
|
208
|
+
attach_to_running: Boolean(options.attach)
|
|
228
209
|
};
|
|
229
210
|
}
|
|
230
|
-
async function ensureNonMockRuntime(options) {
|
|
231
|
-
if (await isAppiumReachable(options.server_url, 2000)) {
|
|
232
|
-
return {
|
|
233
|
-
serverUrl: options.server_url,
|
|
234
|
-
started: false
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
if (!options.auto_start_appium) {
|
|
238
|
-
throw new Error(`Cannot reach Appium server at ${options.server_url}. Start Appium and ensure ${options.platform} target '${options.device}' is booted.`);
|
|
239
|
-
}
|
|
240
|
-
return startManagedAppium(options.server_url, options.appium_cmd, options.startup_timeout);
|
|
241
|
-
}
|
|
242
|
-
async function stopAutoStartedAppium(runtimeState) {
|
|
243
|
-
if (!runtimeState || !runtimeState.started || typeof runtimeState.serverUrl !== 'string') {
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
try {
|
|
247
|
-
await stopManagedAppium(runtimeState.serverUrl, false);
|
|
248
|
-
}
|
|
249
|
-
catch {
|
|
250
|
-
await stopManagedAppium(runtimeState.serverUrl, true);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
211
|
function actionArgs(command, options) {
|
|
254
212
|
const commonIgnored = new Set([
|
|
255
|
-
'platform',
|
|
256
213
|
'device',
|
|
257
214
|
'format',
|
|
258
215
|
'output',
|
|
259
216
|
'timeout',
|
|
260
217
|
'verbose',
|
|
261
218
|
'server-url',
|
|
262
|
-
'mock',
|
|
263
219
|
'seed',
|
|
264
220
|
'app-id',
|
|
265
|
-
'attach'
|
|
266
|
-
'appium-cmd',
|
|
267
|
-
'startup-timeout',
|
|
268
|
-
'no-auto-start-appium'
|
|
221
|
+
'attach'
|
|
269
222
|
]);
|
|
270
223
|
const args = Object.entries(options).reduce((acc, [key, value]) => {
|
|
271
224
|
if (!commonIgnored.has(key) && value !== undefined) {
|
|
@@ -278,6 +231,25 @@ function actionArgs(command, options) {
|
|
|
278
231
|
}
|
|
279
232
|
return args;
|
|
280
233
|
}
|
|
234
|
+
function isTargetInitializationError(error) {
|
|
235
|
+
const message = errorMessage(error);
|
|
236
|
+
return (message.includes('Failed to create WebdriverIO Appium session') ||
|
|
237
|
+
message.includes('CoreSimulatorService') ||
|
|
238
|
+
message.includes('Could not find a driver for automationName'));
|
|
239
|
+
}
|
|
240
|
+
function targetInitializationNextStep(error) {
|
|
241
|
+
const message = errorMessage(error);
|
|
242
|
+
if (message.includes('CoreSimulatorService') || message.includes('simctl')) {
|
|
243
|
+
return 'Run `xcrun simctl list` from the same shell. If it fails, restart Simulator/CoreSimulator and ensure Appium is not running from a sandboxed process.';
|
|
244
|
+
}
|
|
245
|
+
if (message.includes('Could not find a driver for automationName')) {
|
|
246
|
+
return 'Install the matching Appium driver, verify it with `appium driver list --installed`, then restart `visor start`.';
|
|
247
|
+
}
|
|
248
|
+
if (message.includes('bundle identifier') && message.includes('unknown')) {
|
|
249
|
+
return 'Verify the iOS bundle id is installed on the selected simulator/device, launch it first when using `--attach`, then retry.';
|
|
250
|
+
}
|
|
251
|
+
return 'Verify Appium driver setup, target device state, and app id, then retry.';
|
|
252
|
+
}
|
|
281
253
|
function cmdHelp() {
|
|
282
254
|
const commandId = makeId('cmd');
|
|
283
255
|
const startedAt = utcNowIso();
|
|
@@ -287,7 +259,9 @@ function cmdHelp() {
|
|
|
287
259
|
commands: Array.from(ALL_COMMANDS),
|
|
288
260
|
examples: [
|
|
289
261
|
'visor validate scenarios/checkout-smoke.json',
|
|
290
|
-
'visor
|
|
262
|
+
'visor start --server-url http://127.0.0.1:4723',
|
|
263
|
+
'visor run scenarios/checkout-smoke.json --output artifacts-test',
|
|
264
|
+
'visor scroll --device emulator-5554 --direction down',
|
|
291
265
|
'node dist/main.js status'
|
|
292
266
|
]
|
|
293
267
|
};
|
|
@@ -327,17 +301,36 @@ export async function cmdRun(parsed) {
|
|
|
327
301
|
response.data = { issues };
|
|
328
302
|
return { code: 1, response };
|
|
329
303
|
}
|
|
330
|
-
|
|
331
|
-
let runtimeState;
|
|
332
|
-
let cleanupError;
|
|
304
|
+
let runtime;
|
|
333
305
|
try {
|
|
334
|
-
|
|
335
|
-
|
|
306
|
+
runtime = await resolvedRuntime({
|
|
307
|
+
...parsed.options,
|
|
308
|
+
timeout: typeof parsed.options.timeout === 'number'
|
|
309
|
+
? parsed.options.timeout
|
|
310
|
+
: typeof scenario.config.timeoutMs === 'number'
|
|
311
|
+
? scenario.config.timeoutMs
|
|
312
|
+
: undefined,
|
|
313
|
+
output: parsed.options.output ??
|
|
314
|
+
(typeof scenario.config.artifactsDir === 'string' ? scenario.config.artifactsDir : undefined)
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
if (!(error instanceof DeviceSelectionError)) {
|
|
319
|
+
throw error;
|
|
336
320
|
}
|
|
337
|
-
const
|
|
338
|
-
|
|
321
|
+
const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Device selection failed', error.message, 'Start one target device or rerun with --device <device-id>');
|
|
322
|
+
response.data = { devices: error.devices };
|
|
323
|
+
return { code: 1, response };
|
|
324
|
+
}
|
|
325
|
+
try {
|
|
326
|
+
const result = await runDaemonScenario({
|
|
327
|
+
platform: runtime.platform,
|
|
328
|
+
server_url: runtime.server_url,
|
|
329
|
+
device: runtime.device,
|
|
330
|
+
app_id: runtime.app_id,
|
|
331
|
+
attach_to_running: runtime.attach_to_running
|
|
332
|
+
}, scenario, runtime.device, runtime.timeout, runtime.output_dir);
|
|
339
333
|
const outputs = writeReports(result, runtime.output_dir);
|
|
340
|
-
await stopAutoStartedAppium(runtimeState);
|
|
341
334
|
if (result.status === 'fail' && result.error) {
|
|
342
335
|
const response = envelopeFail(commandId, startedAt, result.error.code, result.error.message, result.error.likely_cause, result.error.next_step);
|
|
343
336
|
response.artifacts = Object.values(outputs);
|
|
@@ -355,17 +348,19 @@ export async function cmdRun(parsed) {
|
|
|
355
348
|
return { code: 0, response };
|
|
356
349
|
}
|
|
357
350
|
catch (error) {
|
|
358
|
-
const
|
|
351
|
+
const isDaemonUnavailable = error instanceof DaemonUnavailableError;
|
|
352
|
+
const isDaemonTimeout = error instanceof DaemonRequestTimeoutError;
|
|
353
|
+
const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', isDaemonUnavailable
|
|
354
|
+
? 'Run requires visor start'
|
|
355
|
+
: isDaemonTimeout
|
|
356
|
+
? 'Timed out waiting for Visor daemon'
|
|
357
|
+
: 'Failed to initialize platform target', errorMessage(error), isDaemonUnavailable
|
|
358
|
+
? 'Run `visor start`, verify the target emulator/simulator is booted, and retry.'
|
|
359
|
+
: isDaemonTimeout
|
|
360
|
+
? 'Inspect .visor/daemon/daemon.log and .visor/appium/*.log, then retry or run `visor stop --force` before restarting.'
|
|
361
|
+
: 'Verify the target emulator/simulator and Appium driver setup, then retry.');
|
|
359
362
|
return { code: 1, response };
|
|
360
363
|
}
|
|
361
|
-
finally {
|
|
362
|
-
try {
|
|
363
|
-
await stopAutoStartedAppium(runtimeState);
|
|
364
|
-
}
|
|
365
|
-
catch (error) {
|
|
366
|
-
cleanupError = error;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
364
|
}
|
|
370
365
|
export async function cmdBenchmark(parsed) {
|
|
371
366
|
const commandId = makeId('cmd');
|
|
@@ -377,62 +372,62 @@ export async function cmdBenchmark(parsed) {
|
|
|
377
372
|
response.data = { issues };
|
|
378
373
|
return { code: 1, response };
|
|
379
374
|
}
|
|
380
|
-
|
|
375
|
+
let runtime;
|
|
376
|
+
try {
|
|
377
|
+
runtime = await resolvedRuntime({
|
|
378
|
+
...parsed.options,
|
|
379
|
+
timeout: typeof parsed.options.timeout === 'number'
|
|
380
|
+
? parsed.options.timeout
|
|
381
|
+
: typeof scenario.config.timeoutMs === 'number'
|
|
382
|
+
? scenario.config.timeoutMs
|
|
383
|
+
: undefined,
|
|
384
|
+
output: parsed.options.output ??
|
|
385
|
+
(typeof scenario.config.artifactsDir === 'string' ? scenario.config.artifactsDir : undefined)
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
if (!(error instanceof DeviceSelectionError)) {
|
|
390
|
+
throw error;
|
|
391
|
+
}
|
|
392
|
+
const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Device selection failed', error.message, 'Start one target device or rerun with --device <device-id>');
|
|
393
|
+
response.data = { devices: error.devices };
|
|
394
|
+
return { code: 1, response };
|
|
395
|
+
}
|
|
381
396
|
const runs = typeof parsed.options.runs === 'number' ? parsed.options.runs : 20;
|
|
382
397
|
const threshold = typeof parsed.options.threshold === 'number' ? parsed.options.threshold : 95;
|
|
383
398
|
const signatures = [];
|
|
384
399
|
const runIds = [];
|
|
385
400
|
let failures = 0;
|
|
386
|
-
let
|
|
387
|
-
let cleanupError;
|
|
388
|
-
if (!runtime.use_mock) {
|
|
401
|
+
for (let index = 0; index < runs; index += 1) {
|
|
389
402
|
try {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const result = await runScenario(scenario, adapter, runtime.device, runtime.timeout, runtime.output_dir);
|
|
402
|
-
writeReports(result, runtime.output_dir);
|
|
403
|
-
signatures.push(result.determinism_signature);
|
|
404
|
-
runIds.push(result.run_id);
|
|
405
|
-
if (result.status !== 'ok') {
|
|
406
|
-
failures += 1;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
catch {
|
|
403
|
+
const result = await runDaemonScenario({
|
|
404
|
+
platform: runtime.platform,
|
|
405
|
+
server_url: runtime.server_url,
|
|
406
|
+
device: runtime.device,
|
|
407
|
+
app_id: runtime.app_id,
|
|
408
|
+
attach_to_running: runtime.attach_to_running
|
|
409
|
+
}, scenario, runtime.device, runtime.timeout, runtime.output_dir);
|
|
410
|
+
writeReports(result, runtime.output_dir);
|
|
411
|
+
signatures.push(result.determinism_signature);
|
|
412
|
+
runIds.push(result.run_id);
|
|
413
|
+
if (result.status !== 'ok') {
|
|
410
414
|
failures += 1;
|
|
411
415
|
}
|
|
412
416
|
}
|
|
413
|
-
}
|
|
414
|
-
finally {
|
|
415
|
-
try {
|
|
416
|
-
await stopAutoStartedAppium(runtimeState);
|
|
417
|
-
}
|
|
418
417
|
catch (error) {
|
|
419
|
-
|
|
418
|
+
if (error instanceof DaemonUnavailableError) {
|
|
419
|
+
const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Benchmark requires visor start', errorMessage(error), 'Run `visor start`, verify the target emulator/simulator is booted, and retry.');
|
|
420
|
+
return { code: 1, response };
|
|
421
|
+
}
|
|
422
|
+
if (error instanceof DaemonRequestTimeoutError) {
|
|
423
|
+
const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Timed out waiting for Visor daemon', errorMessage(error), 'Inspect .visor/daemon/daemon.log and .visor/appium/*.log, then retry or run `visor stop --force` before restarting.');
|
|
424
|
+
return { code: 1, response };
|
|
425
|
+
}
|
|
426
|
+
failures += 1;
|
|
420
427
|
}
|
|
421
428
|
}
|
|
422
429
|
const score = determinismCheck(signatures);
|
|
423
430
|
const passGate = score >= threshold && failures === 0;
|
|
424
|
-
if (cleanupError) {
|
|
425
|
-
const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Benchmark completed but failed to stop auto-started Appium', errorMessage(cleanupError), 'Inspect .visor/appium logs and stop Appium manually');
|
|
426
|
-
response.data = {
|
|
427
|
-
runs,
|
|
428
|
-
threshold,
|
|
429
|
-
determinismScore: score,
|
|
430
|
-
pass: false,
|
|
431
|
-
failures,
|
|
432
|
-
runIds
|
|
433
|
-
};
|
|
434
|
-
return { code: 1, response };
|
|
435
|
-
}
|
|
436
431
|
const response = envelopeOk(commandId, startedAt, [], 'report');
|
|
437
432
|
response.data = {
|
|
438
433
|
runs,
|
|
@@ -460,43 +455,29 @@ export async function cmdReport(parsed) {
|
|
|
460
455
|
export async function cmdAction(command, parsed) {
|
|
461
456
|
const commandId = makeId('cmd');
|
|
462
457
|
const startedAt = utcNowIso();
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
};
|
|
476
|
-
let runtimeState;
|
|
477
|
-
let cleanupError;
|
|
458
|
+
let options;
|
|
459
|
+
try {
|
|
460
|
+
options = await resolvedRuntime(parsed.options);
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
if (!(error instanceof DeviceSelectionError)) {
|
|
464
|
+
throw error;
|
|
465
|
+
}
|
|
466
|
+
const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Device selection failed', error.message, 'Start one target device or rerun with --device <device-id>');
|
|
467
|
+
response.data = { devices: error.devices };
|
|
468
|
+
return { code: 1, response };
|
|
469
|
+
}
|
|
478
470
|
let payload = {};
|
|
479
471
|
let artifacts = [];
|
|
480
472
|
let actionError;
|
|
481
|
-
let adapter;
|
|
482
473
|
try {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
use_mock: options.use_mock,
|
|
491
|
-
app_id: options.app_id,
|
|
492
|
-
attach_to_running: options.attach_to_running,
|
|
493
|
-
auto_start_appium: options.auto_start_appium,
|
|
494
|
-
appium_cmd: options.appium_cmd,
|
|
495
|
-
startup_timeout: options.startup_timeout
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
adapter = await getAdapter(options.platform, options.server_url, options.device, options.use_mock, options.app_id, options.attach_to_running);
|
|
499
|
-
payload = await adapter[command](actionArgs(command, parsed.options));
|
|
474
|
+
payload = await runDaemonAction({
|
|
475
|
+
platform: options.platform,
|
|
476
|
+
server_url: options.server_url,
|
|
477
|
+
device: options.device,
|
|
478
|
+
app_id: options.app_id,
|
|
479
|
+
attach_to_running: options.attach_to_running
|
|
480
|
+
}, command, actionArgs(command, parsed.options));
|
|
500
481
|
const actionPayload = payload.args;
|
|
501
482
|
if (actionPayload && typeof actionPayload === 'object' && !Array.isArray(actionPayload)) {
|
|
502
483
|
const maybePath = actionPayload.path;
|
|
@@ -508,27 +489,23 @@ export async function cmdAction(command, parsed) {
|
|
|
508
489
|
catch (error) {
|
|
509
490
|
actionError = error;
|
|
510
491
|
}
|
|
511
|
-
finally {
|
|
512
|
-
if (adapter) {
|
|
513
|
-
await adapter.close();
|
|
514
|
-
}
|
|
515
|
-
try {
|
|
516
|
-
await stopAutoStartedAppium(runtimeState);
|
|
517
|
-
}
|
|
518
|
-
catch (error) {
|
|
519
|
-
cleanupError = error;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
492
|
if (actionError) {
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
const response = envelopeFail(commandId, startedAt,
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
493
|
+
const isDaemonUnavailable = actionError instanceof DaemonUnavailableError;
|
|
494
|
+
const isDaemonTimeout = actionError instanceof DaemonRequestTimeoutError;
|
|
495
|
+
const isTargetInitialization = isTargetInitializationError(actionError);
|
|
496
|
+
const response = envelopeFail(commandId, startedAt, isDaemonUnavailable || isDaemonTimeout || isTargetInitialization ? 'TARGET_ERROR' : 'ACTION_ERROR', isDaemonUnavailable
|
|
497
|
+
? `${command} requires visor start`
|
|
498
|
+
: isDaemonTimeout
|
|
499
|
+
? `${command} timed out waiting for Visor daemon`
|
|
500
|
+
: isTargetInitialization
|
|
501
|
+
? `${command} failed to initialize platform target`
|
|
502
|
+
: `${command} failed`, errorMessage(actionError), isDaemonUnavailable
|
|
503
|
+
? 'Run `visor start`, verify the target emulator/simulator is booted, and retry.'
|
|
504
|
+
: isDaemonTimeout
|
|
505
|
+
? 'Inspect .visor/daemon/daemon.log and .visor/appium/*.log, then retry or run `visor stop --force` before restarting.'
|
|
506
|
+
: isTargetInitialization
|
|
507
|
+
? targetInitializationNextStep(actionError)
|
|
508
|
+
: 'Check command args and retry');
|
|
532
509
|
response.data = payload;
|
|
533
510
|
return { code: 1, response };
|
|
534
511
|
}
|
|
@@ -540,23 +517,22 @@ export async function cmdStart(parsed) {
|
|
|
540
517
|
const commandId = makeId('cmd');
|
|
541
518
|
const startedAt = utcNowIso();
|
|
542
519
|
try {
|
|
543
|
-
const status = await
|
|
544
|
-
? parsed.options['startup-timeout']
|
|
545
|
-
: DEFAULT_STARTUP_TIMEOUT_SECONDS);
|
|
520
|
+
const status = await startVisorDaemon(String(parsed.options['server-url'] ?? DEFAULT_SERVER_URL), typeof parsed.options['appium-cmd'] === 'string' ? parsed.options['appium-cmd'] : undefined);
|
|
546
521
|
const response = envelopeOk(commandId, startedAt, [], 'run');
|
|
547
522
|
response.data = status;
|
|
548
523
|
return { code: 0, response };
|
|
549
524
|
}
|
|
550
525
|
catch (error) {
|
|
551
|
-
const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Failed to start
|
|
526
|
+
const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Failed to start Visor daemon', errorMessage(error), 'Install Node deps, check --appium-cmd, and inspect .visor/daemon/*.log');
|
|
552
527
|
return { code: 1, response };
|
|
553
528
|
}
|
|
554
529
|
}
|
|
555
530
|
export async function cmdStatus(parsed) {
|
|
556
531
|
const commandId = makeId('cmd');
|
|
557
532
|
const startedAt = utcNowIso();
|
|
558
|
-
const status = await
|
|
559
|
-
const
|
|
533
|
+
const status = await statusVisorDaemon(String(parsed.options['server-url'] ?? DEFAULT_SERVER_URL));
|
|
534
|
+
const daemon = status.daemon;
|
|
535
|
+
const response = envelopeOk(commandId, startedAt, [], daemon?.running ? 'run' : daemon?.unresponsive ? 'stop' : 'start');
|
|
560
536
|
response.data = status;
|
|
561
537
|
return { code: 0, response };
|
|
562
538
|
}
|
|
@@ -564,13 +540,13 @@ export async function cmdStop(parsed) {
|
|
|
564
540
|
const commandId = makeId('cmd');
|
|
565
541
|
const startedAt = utcNowIso();
|
|
566
542
|
try {
|
|
567
|
-
const result = await
|
|
543
|
+
const result = await stopVisorDaemon(String(parsed.options['server-url'] ?? DEFAULT_SERVER_URL), Boolean(parsed.options.force));
|
|
568
544
|
const response = envelopeOk(commandId, startedAt, [], 'none');
|
|
569
545
|
response.data = result;
|
|
570
546
|
return { code: 0, response };
|
|
571
547
|
}
|
|
572
548
|
catch (error) {
|
|
573
|
-
const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Failed to stop
|
|
549
|
+
const response = envelopeFail(commandId, startedAt, 'TARGET_ERROR', 'Failed to stop Visor daemon', errorMessage(error), 'Retry with --force or check process state manually');
|
|
574
550
|
return { code: 1, response };
|
|
575
551
|
}
|
|
576
552
|
}
|