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/README.md
CHANGED
package/dist/adapters.js
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { remote } from 'webdriverio';
|
|
4
3
|
import { errorMessage, parseServerUrl, sleep } from './utils.js';
|
|
5
4
|
export const DEFAULT_SERVER_URL = 'http://127.0.0.1:4723';
|
|
6
5
|
export const DEFAULT_ANDROID_APP = 'com.example.app';
|
|
7
6
|
export const DEFAULT_IOS_BUNDLE = 'com.example.app';
|
|
8
|
-
export const DEFAULT_ANDROID_DEVICE = 'emulator-5554';
|
|
9
|
-
export const DEFAULT_IOS_DEVICE = 'iPhone 17 Pro';
|
|
10
|
-
const MINI_PNG_BASE64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO7Z0x8AAAAASUVORK5CYII=';
|
|
11
|
-
const MINI_PNG = Buffer.from(MINI_PNG_BASE64, 'base64');
|
|
12
7
|
export const ACCESSIBILITY_ID = 'accessibility id';
|
|
13
8
|
export const XPATH = 'xpath';
|
|
14
9
|
export const ELEMENT_ID = 'id';
|
|
@@ -25,6 +20,18 @@ function envBool(preferred, legacy, defaultValue = false) {
|
|
|
25
20
|
}
|
|
26
21
|
return ['1', 'true', 'yes', 'on'].includes(raw.trim().toLowerCase());
|
|
27
22
|
}
|
|
23
|
+
function envNumber(preferred, defaultValue) {
|
|
24
|
+
const raw = process.env[preferred];
|
|
25
|
+
if (raw === undefined) {
|
|
26
|
+
return defaultValue;
|
|
27
|
+
}
|
|
28
|
+
const parsed = Number(raw);
|
|
29
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : defaultValue;
|
|
30
|
+
}
|
|
31
|
+
export function resolveWebdriverConnectionTimeout(platform) {
|
|
32
|
+
const defaultTimeout = platform === 'ios' ? 240000 : 60000;
|
|
33
|
+
return envNumber('VISOR_WEBDRIVER_CONNECTION_TIMEOUT_MS', defaultTimeout);
|
|
34
|
+
}
|
|
28
35
|
function pngDimensions(filePath) {
|
|
29
36
|
try {
|
|
30
37
|
const header = fs.readFileSync(filePath).subarray(0, 24);
|
|
@@ -50,6 +57,9 @@ export function formatDriverCreationError(platform, appId, attachToRunning, erro
|
|
|
50
57
|
: '';
|
|
51
58
|
return `Failed to create WebdriverIO Appium session: ${message}. On iOS, --app-id must be the exact installed bundle identifier for the target app (${targetApp}). Android package names do not carry over automatically.${attachHint}`;
|
|
52
59
|
}
|
|
60
|
+
if (platform === 'ios' && (message.includes('CoreSimulatorService') || message.includes('simctl'))) {
|
|
61
|
+
return `Failed to create WebdriverIO Appium session: ${message}. iOS simulator access failed before the command ran; verify \`xcrun simctl list\` works from the same shell and that Appium is not running in a sandboxed process.`;
|
|
62
|
+
}
|
|
53
63
|
return `Failed to create WebdriverIO Appium session: ${message}`;
|
|
54
64
|
}
|
|
55
65
|
export function parseTarget(target) {
|
|
@@ -297,11 +307,13 @@ export class RealAppiumAdapter {
|
|
|
297
307
|
envBool('VISOR_ATTACH_TO_RUNNING', 'PATF_ATTACH_TO_RUNNING', false);
|
|
298
308
|
const server = parseServerUrl(this.serverUrl);
|
|
299
309
|
const capabilities = {};
|
|
310
|
+
if (!this.device) {
|
|
311
|
+
throw new Error('A running device must be selected before creating an Appium session.');
|
|
312
|
+
}
|
|
300
313
|
if (this.platform === 'android') {
|
|
301
314
|
capabilities.platformName = 'Android';
|
|
302
315
|
capabilities['appium:automationName'] = 'UiAutomator2';
|
|
303
|
-
capabilities['appium:udid'] =
|
|
304
|
-
this.device ?? env('VISOR_ANDROID_DEVICE', 'PATF_ANDROID_DEVICE', DEFAULT_ANDROID_DEVICE);
|
|
316
|
+
capabilities['appium:udid'] = this.device;
|
|
305
317
|
capabilities['appium:appPackage'] =
|
|
306
318
|
this.appId ?? env('VISOR_ANDROID_APP_PACKAGE', 'PATF_ANDROID_APP_PACKAGE', DEFAULT_ANDROID_APP);
|
|
307
319
|
capabilities['appium:appActivity'] = env('VISOR_ANDROID_APP_ACTIVITY', 'PATF_ANDROID_APP_ACTIVITY', '.MainActivity');
|
|
@@ -316,8 +328,7 @@ export class RealAppiumAdapter {
|
|
|
316
328
|
else {
|
|
317
329
|
capabilities.platformName = 'iOS';
|
|
318
330
|
capabilities['appium:automationName'] = 'XCUITest';
|
|
319
|
-
capabilities['appium:
|
|
320
|
-
this.device ?? env('VISOR_IOS_DEVICE', 'PATF_IOS_DEVICE', DEFAULT_IOS_DEVICE);
|
|
331
|
+
capabilities['appium:udid'] = this.device;
|
|
321
332
|
capabilities['appium:bundleId'] =
|
|
322
333
|
this.appId ?? env('VISOR_IOS_BUNDLE_ID', 'PATF_IOS_BUNDLE_ID', DEFAULT_IOS_BUNDLE);
|
|
323
334
|
capabilities['appium:newCommandTimeout'] = 60;
|
|
@@ -330,13 +341,16 @@ export class RealAppiumAdapter {
|
|
|
330
341
|
}
|
|
331
342
|
}
|
|
332
343
|
try {
|
|
344
|
+
const { remote } = await import('webdriverio');
|
|
333
345
|
return await remote({
|
|
334
346
|
protocol: server.protocol,
|
|
335
347
|
hostname: server.host,
|
|
336
348
|
port: server.port,
|
|
337
349
|
path: server.pathname,
|
|
338
350
|
capabilities,
|
|
339
|
-
logLevel: 'error'
|
|
351
|
+
logLevel: 'error',
|
|
352
|
+
connectionRetryCount: envNumber('VISOR_WEBDRIVER_CONNECTION_RETRY_COUNT', 0),
|
|
353
|
+
connectionRetryTimeout: resolveWebdriverConnectionTimeout(this.platform)
|
|
340
354
|
});
|
|
341
355
|
}
|
|
342
356
|
catch (error) {
|
|
@@ -421,90 +435,7 @@ export class RealAppiumAdapter {
|
|
|
421
435
|
await driver.releaseActions();
|
|
422
436
|
}
|
|
423
437
|
}
|
|
424
|
-
export
|
|
425
|
-
platform;
|
|
426
|
-
constructor(platform) {
|
|
427
|
-
this.platform = platform;
|
|
428
|
-
}
|
|
429
|
-
capability() {
|
|
430
|
-
return {
|
|
431
|
-
platform: this.platform,
|
|
432
|
-
commands: ['navigate', 'tap', 'act', 'scroll', 'screenshot', 'wait', 'source']
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
async navigate(args) {
|
|
436
|
-
return { action: 'navigate', platform: this.platform, args };
|
|
437
|
-
}
|
|
438
|
-
async tap(args) {
|
|
439
|
-
resolveTapMode(args);
|
|
440
|
-
return { action: 'tap', platform: this.platform, args };
|
|
441
|
-
}
|
|
442
|
-
async act(args) {
|
|
443
|
-
return { action: 'act', platform: this.platform, args };
|
|
444
|
-
}
|
|
445
|
-
async scroll(args) {
|
|
446
|
-
const { direction, percent } = resolveScrollOptions(args);
|
|
447
|
-
return {
|
|
448
|
-
action: 'scroll',
|
|
449
|
-
platform: this.platform,
|
|
450
|
-
args: { direction, percent }
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
async screenshot(args) {
|
|
454
|
-
const label = String(args.label ?? 'capture');
|
|
455
|
-
const filePath = path.resolve(typeof args.path === 'string' ? args.path : `${label}.png`);
|
|
456
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
457
|
-
fs.writeFileSync(filePath, MINI_PNG);
|
|
458
|
-
const { width, height } = pngDimensions(filePath);
|
|
459
|
-
return {
|
|
460
|
-
action: 'screenshot',
|
|
461
|
-
platform: this.platform,
|
|
462
|
-
args: {
|
|
463
|
-
label,
|
|
464
|
-
file: path.basename(filePath),
|
|
465
|
-
path: filePath,
|
|
466
|
-
width,
|
|
467
|
-
height
|
|
468
|
-
}
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
async wait(args) {
|
|
472
|
-
const ms = Number(args.ms ?? 0);
|
|
473
|
-
if (ms < 0) {
|
|
474
|
-
throw new Error('wait requires non-negative ms');
|
|
475
|
-
}
|
|
476
|
-
return { action: 'wait', platform: this.platform, args: { ms } };
|
|
477
|
-
}
|
|
478
|
-
async source(args) {
|
|
479
|
-
const label = String(args.label ?? 'source');
|
|
480
|
-
const filePath = path.resolve(typeof args.path === 'string' ? args.path : `${label}.xml`);
|
|
481
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
482
|
-
const content = `<hierarchy platform="${this.platform}"><node text="mock" /></hierarchy>\n`;
|
|
483
|
-
fs.writeFileSync(filePath, content, 'utf8');
|
|
484
|
-
return {
|
|
485
|
-
action: 'source',
|
|
486
|
-
platform: this.platform,
|
|
487
|
-
args: {
|
|
488
|
-
label,
|
|
489
|
-
file: path.basename(filePath),
|
|
490
|
-
path: filePath,
|
|
491
|
-
format: 'xml',
|
|
492
|
-
bytes: fs.statSync(filePath).size
|
|
493
|
-
}
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
async exists(target) {
|
|
497
|
-
const lowered = target.toLowerCase();
|
|
498
|
-
return !lowered.includes('missing') && !lowered.includes('not_found');
|
|
499
|
-
}
|
|
500
|
-
async close() {
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
export async function getAdapter(platform, serverUrl = DEFAULT_SERVER_URL, device, useMock = false, appId, attachToRunning = false) {
|
|
438
|
+
export async function getAdapter(platform, serverUrl = DEFAULT_SERVER_URL, device, appId, attachToRunning = false) {
|
|
505
439
|
const normalized = platform.toLowerCase();
|
|
506
|
-
if (useMock) {
|
|
507
|
-
return new MockAdapter(normalized);
|
|
508
|
-
}
|
|
509
440
|
return RealAppiumAdapter.create(normalized, serverUrl, device, appId, attachToRunning);
|
|
510
441
|
}
|
package/dist/appiumLifecycle.js
CHANGED