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