visor-ai 0.2.5 → 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 +0 -1
- package/dist/adapters.js +25 -94
- package/dist/appiumLifecycle.js +4 -1
- package/dist/cli.js +163 -193
- package/dist/daemon.js +517 -0
- package/dist/devices.js +123 -0
- package/dist/main.js +5 -0
- package/dist/runner.js +5 -3
- package/dist/validator.js +6 -5
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
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([
|
|
@@ -25,7 +26,6 @@ const ALL_COMMANDS = new Set([
|
|
|
25
26
|
'stop'
|
|
26
27
|
]);
|
|
27
28
|
const GLOBAL_SPEC = {
|
|
28
|
-
platform: 'string',
|
|
29
29
|
device: 'string',
|
|
30
30
|
timeout: 'number',
|
|
31
31
|
output: 'string',
|
|
@@ -33,11 +33,7 @@ const GLOBAL_SPEC = {
|
|
|
33
33
|
seed: 'number',
|
|
34
34
|
'server-url': 'string',
|
|
35
35
|
'app-id': 'string',
|
|
36
|
-
'appium-cmd': 'string',
|
|
37
|
-
'startup-timeout': 'number',
|
|
38
|
-
'no-auto-start-appium': 'boolean',
|
|
39
36
|
attach: 'boolean',
|
|
40
|
-
mock: 'boolean',
|
|
41
37
|
verbose: 'boolean'
|
|
42
38
|
};
|
|
43
39
|
const ACTION_SPEC = {
|
|
@@ -58,40 +54,29 @@ const ACTION_SPEC = {
|
|
|
58
54
|
const COMMAND_SPECS = {
|
|
59
55
|
validate: { format: 'string' },
|
|
60
56
|
run: {
|
|
61
|
-
platform: 'string',
|
|
62
57
|
device: 'string',
|
|
63
58
|
timeout: 'number',
|
|
64
59
|
output: 'string',
|
|
65
60
|
format: 'string',
|
|
66
61
|
'server-url': 'string',
|
|
67
62
|
'app-id': 'string',
|
|
68
|
-
|
|
69
|
-
'startup-timeout': 'number',
|
|
70
|
-
'no-auto-start-appium': 'boolean',
|
|
71
|
-
attach: 'boolean',
|
|
72
|
-
mock: 'boolean'
|
|
63
|
+
attach: 'boolean'
|
|
73
64
|
},
|
|
74
65
|
benchmark: {
|
|
75
66
|
runs: 'number',
|
|
76
67
|
threshold: 'number',
|
|
77
|
-
platform: 'string',
|
|
78
68
|
device: 'string',
|
|
79
69
|
timeout: 'number',
|
|
80
70
|
output: 'string',
|
|
81
71
|
format: 'string',
|
|
82
72
|
'server-url': 'string',
|
|
83
73
|
'app-id': 'string',
|
|
84
|
-
|
|
85
|
-
'startup-timeout': 'number',
|
|
86
|
-
'no-auto-start-appium': 'boolean',
|
|
87
|
-
attach: 'boolean',
|
|
88
|
-
mock: 'boolean'
|
|
74
|
+
attach: 'boolean'
|
|
89
75
|
},
|
|
90
76
|
report: { format: 'string' },
|
|
91
77
|
start: {
|
|
92
78
|
'server-url': 'string',
|
|
93
79
|
'appium-cmd': 'string',
|
|
94
|
-
'startup-timeout': 'number',
|
|
95
80
|
format: 'string'
|
|
96
81
|
},
|
|
97
82
|
status: {
|
|
@@ -121,18 +106,19 @@ function helpText() {
|
|
|
121
106
|
'',
|
|
122
107
|
'Commands:',
|
|
123
108
|
' validate <scenario>',
|
|
124
|
-
' run <scenario> [--
|
|
109
|
+
' run <scenario> [--output <dir>]',
|
|
125
110
|
' benchmark <scenario> [--runs <n>] [--threshold <percent>]',
|
|
126
111
|
' report [path]',
|
|
127
|
-
' start [--server-url <url>]',
|
|
112
|
+
' start [--server-url <url>] [--appium-cmd <cmd>]',
|
|
128
113
|
' status [--server-url <url>]',
|
|
129
114
|
' stop [--server-url <url>] [--force]',
|
|
130
115
|
' tap|navigate|act|scroll|screenshot|wait|source',
|
|
131
116
|
'',
|
|
132
117
|
'Examples:',
|
|
133
118
|
' visor validate scenarios/checkout-smoke.json',
|
|
134
|
-
' visor
|
|
135
|
-
' 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',
|
|
136
122
|
' node dist/main.js status'
|
|
137
123
|
].join('\n');
|
|
138
124
|
}
|
|
@@ -208,69 +194,31 @@ function parseCommand(argv) {
|
|
|
208
194
|
function warningIssues(issues) {
|
|
209
195
|
return issues.filter((issue) => issue.severity === 'warning');
|
|
210
196
|
}
|
|
211
|
-
function resolvedRuntime(options
|
|
212
|
-
const
|
|
213
|
-
const defaultDevice = platform === 'android' ? 'emulator-5554' : 'iPhone 17 Pro';
|
|
214
|
-
scenario.meta.platform = platform;
|
|
197
|
+
async function resolvedRuntime(options) {
|
|
198
|
+
const selectedDevice = await resolveRunningDevice(typeof options.device === 'string' ? options.device : undefined);
|
|
215
199
|
return {
|
|
216
|
-
platform,
|
|
217
|
-
device:
|
|
200
|
+
platform: selectedDevice.platform,
|
|
201
|
+
device: selectedDevice.id,
|
|
218
202
|
timeout: typeof options.timeout === 'number'
|
|
219
203
|
? options.timeout
|
|
220
|
-
:
|
|
221
|
-
|
|
222
|
-
: 2500,
|
|
223
|
-
output_dir: String(options.output ?? scenario.config.artifactsDir ?? 'artifacts'),
|
|
204
|
+
: 2500,
|
|
205
|
+
output_dir: String(options.output ?? 'artifacts'),
|
|
224
206
|
server_url: String(options['server-url'] ?? DEFAULT_SERVER_URL),
|
|
225
|
-
use_mock: Boolean(options.mock),
|
|
226
207
|
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
|
|
208
|
+
attach_to_running: Boolean(options.attach)
|
|
233
209
|
};
|
|
234
210
|
}
|
|
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
211
|
function actionArgs(command, options) {
|
|
259
212
|
const commonIgnored = new Set([
|
|
260
|
-
'platform',
|
|
261
213
|
'device',
|
|
262
214
|
'format',
|
|
263
215
|
'output',
|
|
264
216
|
'timeout',
|
|
265
217
|
'verbose',
|
|
266
218
|
'server-url',
|
|
267
|
-
'mock',
|
|
268
219
|
'seed',
|
|
269
220
|
'app-id',
|
|
270
|
-
'attach'
|
|
271
|
-
'appium-cmd',
|
|
272
|
-
'startup-timeout',
|
|
273
|
-
'no-auto-start-appium'
|
|
221
|
+
'attach'
|
|
274
222
|
]);
|
|
275
223
|
const args = Object.entries(options).reduce((acc, [key, value]) => {
|
|
276
224
|
if (!commonIgnored.has(key) && value !== undefined) {
|
|
@@ -283,6 +231,25 @@ function actionArgs(command, options) {
|
|
|
283
231
|
}
|
|
284
232
|
return args;
|
|
285
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
|
+
}
|
|
286
253
|
function cmdHelp() {
|
|
287
254
|
const commandId = makeId('cmd');
|
|
288
255
|
const startedAt = utcNowIso();
|
|
@@ -292,8 +259,9 @@ function cmdHelp() {
|
|
|
292
259
|
commands: Array.from(ALL_COMMANDS),
|
|
293
260
|
examples: [
|
|
294
261
|
'visor validate scenarios/checkout-smoke.json',
|
|
295
|
-
'visor
|
|
296
|
-
'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',
|
|
297
265
|
'node dist/main.js status'
|
|
298
266
|
]
|
|
299
267
|
};
|
|
@@ -333,17 +301,36 @@ export async function cmdRun(parsed) {
|
|
|
333
301
|
response.data = { issues };
|
|
334
302
|
return { code: 1, response };
|
|
335
303
|
}
|
|
336
|
-
|
|
337
|
-
let runtimeState;
|
|
338
|
-
let cleanupError;
|
|
304
|
+
let runtime;
|
|
339
305
|
try {
|
|
340
|
-
|
|
341
|
-
|
|
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;
|
|
342
320
|
}
|
|
343
|
-
const
|
|
344
|
-
|
|
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);
|
|
345
333
|
const outputs = writeReports(result, runtime.output_dir);
|
|
346
|
-
await stopAutoStartedAppium(runtimeState);
|
|
347
334
|
if (result.status === 'fail' && result.error) {
|
|
348
335
|
const response = envelopeFail(commandId, startedAt, result.error.code, result.error.message, result.error.likely_cause, result.error.next_step);
|
|
349
336
|
response.artifacts = Object.values(outputs);
|
|
@@ -361,17 +348,19 @@ export async function cmdRun(parsed) {
|
|
|
361
348
|
return { code: 0, response };
|
|
362
349
|
}
|
|
363
350
|
catch (error) {
|
|
364
|
-
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.');
|
|
365
362
|
return { code: 1, response };
|
|
366
363
|
}
|
|
367
|
-
finally {
|
|
368
|
-
try {
|
|
369
|
-
await stopAutoStartedAppium(runtimeState);
|
|
370
|
-
}
|
|
371
|
-
catch (error) {
|
|
372
|
-
cleanupError = error;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
364
|
}
|
|
376
365
|
export async function cmdBenchmark(parsed) {
|
|
377
366
|
const commandId = makeId('cmd');
|
|
@@ -383,62 +372,62 @@ export async function cmdBenchmark(parsed) {
|
|
|
383
372
|
response.data = { issues };
|
|
384
373
|
return { code: 1, response };
|
|
385
374
|
}
|
|
386
|
-
|
|
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
|
+
}
|
|
387
396
|
const runs = typeof parsed.options.runs === 'number' ? parsed.options.runs : 20;
|
|
388
397
|
const threshold = typeof parsed.options.threshold === 'number' ? parsed.options.threshold : 95;
|
|
389
398
|
const signatures = [];
|
|
390
399
|
const runIds = [];
|
|
391
400
|
let failures = 0;
|
|
392
|
-
let
|
|
393
|
-
let cleanupError;
|
|
394
|
-
if (!runtime.use_mock) {
|
|
401
|
+
for (let index = 0; index < runs; index += 1) {
|
|
395
402
|
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 {
|
|
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') {
|
|
416
414
|
failures += 1;
|
|
417
415
|
}
|
|
418
416
|
}
|
|
419
|
-
}
|
|
420
|
-
finally {
|
|
421
|
-
try {
|
|
422
|
-
await stopAutoStartedAppium(runtimeState);
|
|
423
|
-
}
|
|
424
417
|
catch (error) {
|
|
425
|
-
|
|
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;
|
|
426
427
|
}
|
|
427
428
|
}
|
|
428
429
|
const score = determinismCheck(signatures);
|
|
429
430
|
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
431
|
const response = envelopeOk(commandId, startedAt, [], 'report');
|
|
443
432
|
response.data = {
|
|
444
433
|
runs,
|
|
@@ -466,43 +455,29 @@ export async function cmdReport(parsed) {
|
|
|
466
455
|
export async function cmdAction(command, parsed) {
|
|
467
456
|
const commandId = makeId('cmd');
|
|
468
457
|
const startedAt = utcNowIso();
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
};
|
|
482
|
-
let runtimeState;
|
|
483
|
-
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
|
+
}
|
|
484
470
|
let payload = {};
|
|
485
471
|
let artifacts = [];
|
|
486
472
|
let actionError;
|
|
487
|
-
let adapter;
|
|
488
473
|
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));
|
|
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));
|
|
506
481
|
const actionPayload = payload.args;
|
|
507
482
|
if (actionPayload && typeof actionPayload === 'object' && !Array.isArray(actionPayload)) {
|
|
508
483
|
const maybePath = actionPayload.path;
|
|
@@ -514,27 +489,23 @@ export async function cmdAction(command, parsed) {
|
|
|
514
489
|
catch (error) {
|
|
515
490
|
actionError = error;
|
|
516
491
|
}
|
|
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
492
|
if (actionError) {
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
const response = envelopeFail(commandId, startedAt,
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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');
|
|
538
509
|
response.data = payload;
|
|
539
510
|
return { code: 1, response };
|
|
540
511
|
}
|
|
@@ -546,23 +517,22 @@ export async function cmdStart(parsed) {
|
|
|
546
517
|
const commandId = makeId('cmd');
|
|
547
518
|
const startedAt = utcNowIso();
|
|
548
519
|
try {
|
|
549
|
-
const status = await
|
|
550
|
-
? parsed.options['startup-timeout']
|
|
551
|
-
: 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);
|
|
552
521
|
const response = envelopeOk(commandId, startedAt, [], 'run');
|
|
553
522
|
response.data = status;
|
|
554
523
|
return { code: 0, response };
|
|
555
524
|
}
|
|
556
525
|
catch (error) {
|
|
557
|
-
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');
|
|
558
527
|
return { code: 1, response };
|
|
559
528
|
}
|
|
560
529
|
}
|
|
561
530
|
export async function cmdStatus(parsed) {
|
|
562
531
|
const commandId = makeId('cmd');
|
|
563
532
|
const startedAt = utcNowIso();
|
|
564
|
-
const status = await
|
|
565
|
-
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');
|
|
566
536
|
response.data = status;
|
|
567
537
|
return { code: 0, response };
|
|
568
538
|
}
|
|
@@ -570,13 +540,13 @@ export async function cmdStop(parsed) {
|
|
|
570
540
|
const commandId = makeId('cmd');
|
|
571
541
|
const startedAt = utcNowIso();
|
|
572
542
|
try {
|
|
573
|
-
const result = await
|
|
543
|
+
const result = await stopVisorDaemon(String(parsed.options['server-url'] ?? DEFAULT_SERVER_URL), Boolean(parsed.options.force));
|
|
574
544
|
const response = envelopeOk(commandId, startedAt, [], 'none');
|
|
575
545
|
response.data = result;
|
|
576
546
|
return { code: 0, response };
|
|
577
547
|
}
|
|
578
548
|
catch (error) {
|
|
579
|
-
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');
|
|
580
550
|
return { code: 1, response };
|
|
581
551
|
}
|
|
582
552
|
}
|