@houwert/conductor 0.14.0 → 0.16.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/memory.js +23 -2
- package/dist/commands/screenshot.js +4 -3
- package/dist/commands/start-device.js +173 -9
- package/dist/daemon/web-server.js +25 -2
- package/dist/drivers/android.js +4 -2
- package/dist/drivers/ios.js +1 -1
- package/dist/drivers/web.js +3 -2
- package/dist/index.js +4 -1
- package/package.json +1 -1
package/dist/commands/memory.js
CHANGED
|
@@ -37,12 +37,29 @@ async function resolveDeviceId(sessionName) {
|
|
|
37
37
|
const session = await (0, session_js_1.getSession)(sessionName);
|
|
38
38
|
return session.deviceId ?? (await (0, runner_js_1.detectFirstDevice)());
|
|
39
39
|
}
|
|
40
|
-
async function resolveAppId(explicit, sessionName, deviceId) {
|
|
40
|
+
async function resolveAppId(explicit, sessionName, deviceId, platform) {
|
|
41
41
|
if (explicit)
|
|
42
42
|
return explicit;
|
|
43
43
|
// No arg: always resolve from the live foreground app, not the session file.
|
|
44
44
|
// The session's appId reflects the last `launch-app` call, which can be stale
|
|
45
45
|
// if the user switched apps on the device by other means.
|
|
46
|
+
//
|
|
47
|
+
// On Android, foreground-app detection only needs `adb shell dumpsys` — going
|
|
48
|
+
// through getDriver() would force the gRPC daemon to start (and require the
|
|
49
|
+
// driver APK to be installed), which is wasteful for a read-only memory dump.
|
|
50
|
+
if (platform === 'android') {
|
|
51
|
+
const dump = await (0, runner_js_1.spawnCommand)((0, sdk_js_1.resolveAndroidTool)('adb'), ['-s', deviceId, 'shell', 'dumpsys', 'activity', 'activities'], { env: (0, sdk_js_1.androidSpawnEnv)() });
|
|
52
|
+
if (dump.success) {
|
|
53
|
+
// Different Android versions print this differently:
|
|
54
|
+
// API 28-: mResumedActivity: ActivityRecord{... pkg/.Activity ...}
|
|
55
|
+
// API 29+: topResumedActivity=ActivityRecord{... pkg/.Activity ...}
|
|
56
|
+
// ResumedActivity: ActivityRecord{... pkg/.Activity ...}
|
|
57
|
+
const m = dump.stdout.match(/(?:m|top)?ResumedActivity[=:].*?([a-zA-Z][a-zA-Z0-9_]*(?:\.[a-zA-Z][a-zA-Z0-9_]*)+)\//);
|
|
58
|
+
if (m)
|
|
59
|
+
return m[1];
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
46
63
|
try {
|
|
47
64
|
const driver = await (0, runner_js_1.getDriver)(sessionName);
|
|
48
65
|
if (driver instanceof android_js_1.AndroidDriver)
|
|
@@ -1104,13 +1121,17 @@ async function memory(appIdArg, opts = {}, sessionName = 'default', memOpts = {}
|
|
|
1104
1121
|
report = await collectWeb(deviceId, sessionName, memOpts);
|
|
1105
1122
|
}
|
|
1106
1123
|
else {
|
|
1107
|
-
const appId = await resolveAppId(appIdArg, sessionName, deviceId);
|
|
1124
|
+
const appId = await resolveAppId(appIdArg, sessionName, deviceId, platform);
|
|
1108
1125
|
if (platform === 'android') {
|
|
1109
1126
|
report = await collectAndroid(deviceId, appId, memOpts);
|
|
1110
1127
|
}
|
|
1111
1128
|
else {
|
|
1112
1129
|
report = await collectIOS(deviceId, platform, appId, memOpts);
|
|
1113
1130
|
}
|
|
1131
|
+
if (!appId) {
|
|
1132
|
+
report.notes ?? (report.notes = []);
|
|
1133
|
+
report.notes.push('No foreground app detected — pass an app id (e.g. `conductor memory com.example.app --all`) to get per-app memory, objects, and leaks.');
|
|
1134
|
+
}
|
|
1114
1135
|
}
|
|
1115
1136
|
report.capturedAt = new Date().toISOString();
|
|
1116
1137
|
// Diff current report against a saved snapshot.
|
|
@@ -5,19 +5,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.HELP = void 0;
|
|
7
7
|
exports.screenshot = screenshot;
|
|
8
|
-
exports.HELP = ` take-screenshot [--output <path>]
|
|
8
|
+
exports.HELP = ` take-screenshot [--output <path>] [--full-page]
|
|
9
|
+
Take screenshot (--full-page: web only, capture entire scrollable page)`;
|
|
9
10
|
const path_1 = __importDefault(require("path"));
|
|
10
11
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
11
12
|
const runner_js_1 = require("../runner.js");
|
|
12
13
|
const output_js_1 = require("../output.js");
|
|
13
|
-
async function screenshot(outputPath, opts = {}, sessionName = 'default') {
|
|
14
|
+
async function screenshot(outputPath, opts = {}, sessionName = 'default', fullPage = false) {
|
|
14
15
|
const timestamp = Date.now();
|
|
15
16
|
const defaultName = `screenshot-${timestamp}.png`;
|
|
16
17
|
const resolvedPath = outputPath
|
|
17
18
|
? path_1.default.resolve(outputPath)
|
|
18
19
|
: path_1.default.resolve(process.cwd(), defaultName);
|
|
19
20
|
const result = await (0, runner_js_1.runDirect)(async (driver) => {
|
|
20
|
-
const buf = await driver.screenshot();
|
|
21
|
+
const buf = await driver.screenshot({ fullPage });
|
|
21
22
|
await promises_1.default.writeFile(resolvedPath, buf);
|
|
22
23
|
}, sessionName);
|
|
23
24
|
if (result.success) {
|
|
@@ -4,13 +4,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.HELP = void 0;
|
|
7
|
+
exports.pickAndroidArch = pickAndroidArch;
|
|
8
|
+
exports.parseInstalledSystemImages = parseInstalledSystemImages;
|
|
9
|
+
exports.pickSystemImage = pickSystemImage;
|
|
10
|
+
exports.buildAvdmanagerCreateArgs = buildAvdmanagerCreateArgs;
|
|
7
11
|
exports.startDevice = startDevice;
|
|
8
12
|
exports.HELP = ` start-device
|
|
9
13
|
--platform <ios|android|tvos|web> Boot a simulator/emulator, or start the web driver (Playwright)
|
|
10
14
|
--os-version <n> iOS/tvOS version (e.g. 18) or Android API level (e.g. 33)
|
|
11
|
-
--avd <name> Android AVD name (default: first available)
|
|
15
|
+
--avd <name> Android AVD name (default: first available; created if missing + --device-type)
|
|
12
16
|
--name <name> Set a custom name on the device after creation (iOS/tvOS/web)
|
|
13
|
-
--device-type <name> iOS/tvOS device type (e.g. "iPhone 16 Pro", "Apple TV 4K")
|
|
17
|
+
--device-type <name> iOS/tvOS device type (e.g. "iPhone 16 Pro", "Apple TV 4K") or
|
|
18
|
+
Android device profile (e.g. "pixel_7"); creates if needed
|
|
19
|
+
--system-image <id> Android only: override auto-picked system image
|
|
20
|
+
(e.g. "system-images;android-34;google_apis;arm64-v8a")
|
|
14
21
|
--browser <chromium|firefox|webkit> Web only: which Playwright browser to launch (default: chromium)`;
|
|
15
22
|
const fs_1 = __importDefault(require("fs"));
|
|
16
23
|
const child_process_1 = require("child_process");
|
|
@@ -390,6 +397,9 @@ async function startTvOS(osVersion, opts, name, deviceType) {
|
|
|
390
397
|
return 0;
|
|
391
398
|
}
|
|
392
399
|
// ── Android ───────────────────────────────────────────────────────────────────
|
|
400
|
+
// TODO: Replace bare command names with the resolver from `android-sdk-path-resolver-68477d`
|
|
401
|
+
// once it lands on main. For now we rely on `emulator`/`adb`/`avdmanager`/`sdkmanager`
|
|
402
|
+
// being on PATH.
|
|
393
403
|
async function listAVDs() {
|
|
394
404
|
const result = await (0, runner_js_1.spawnCommand)((0, sdk_js_1.resolveAndroidTool)('emulator'), ['-list-avds'], {
|
|
395
405
|
env: (0, sdk_js_1.androidSpawnEnv)(),
|
|
@@ -401,6 +411,139 @@ async function listAVDs() {
|
|
|
401
411
|
.map((l) => l.trim())
|
|
402
412
|
.filter(Boolean);
|
|
403
413
|
}
|
|
414
|
+
/** Pick the Android system-image arch tag based on the host CPU. */
|
|
415
|
+
function pickAndroidArch(arch = process.arch) {
|
|
416
|
+
return arch === 'arm64' ? 'arm64-v8a' : 'x86_64';
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Parse `sdkmanager --list_installed` (or --list) output, returning installed
|
|
420
|
+
* system-image package paths like "system-images;android-34;google_apis;arm64-v8a".
|
|
421
|
+
*/
|
|
422
|
+
function parseInstalledSystemImages(stdout) {
|
|
423
|
+
const images = [];
|
|
424
|
+
for (const rawLine of stdout.split('\n')) {
|
|
425
|
+
const line = rawLine.trim();
|
|
426
|
+
if (!line.startsWith('system-images;'))
|
|
427
|
+
continue;
|
|
428
|
+
// Format is "<path> | <version> | <description> | <location>" with leading whitespace
|
|
429
|
+
const path = line.split(/[|\s]+/)[0];
|
|
430
|
+
if (path && !images.includes(path))
|
|
431
|
+
images.push(path);
|
|
432
|
+
}
|
|
433
|
+
return images;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Pick the best installed system image matching the requested API level and arch.
|
|
437
|
+
* Prefers `google_apis` over `default` (no Play store dependency on Apple Silicon
|
|
438
|
+
* where Play images often lack arm64). Returns undefined if no match.
|
|
439
|
+
*/
|
|
440
|
+
function pickSystemImage(installed, apiLevel, arch) {
|
|
441
|
+
const filtered = installed.filter((img) => {
|
|
442
|
+
const parts = img.split(';');
|
|
443
|
+
// parts: ["system-images", "android-<api>", "<variant>", "<arch>"]
|
|
444
|
+
if (parts.length < 4)
|
|
445
|
+
return false;
|
|
446
|
+
if (parts[3] !== arch)
|
|
447
|
+
return false;
|
|
448
|
+
if (apiLevel && parts[1] !== `android-${apiLevel}`)
|
|
449
|
+
return false;
|
|
450
|
+
return true;
|
|
451
|
+
});
|
|
452
|
+
if (filtered.length === 0)
|
|
453
|
+
return undefined;
|
|
454
|
+
// Variant preference: google_apis > google_apis_playstore > default > others
|
|
455
|
+
const rank = (img) => {
|
|
456
|
+
const variant = img.split(';')[2];
|
|
457
|
+
if (variant === 'google_apis')
|
|
458
|
+
return 0;
|
|
459
|
+
if (variant === 'google_apis_playstore')
|
|
460
|
+
return 1;
|
|
461
|
+
if (variant === 'default')
|
|
462
|
+
return 2;
|
|
463
|
+
return 3;
|
|
464
|
+
};
|
|
465
|
+
filtered.sort((a, b) => {
|
|
466
|
+
const r = rank(a) - rank(b);
|
|
467
|
+
if (r !== 0)
|
|
468
|
+
return r;
|
|
469
|
+
// Then prefer higher API level
|
|
470
|
+
const ai = parseInt(a.split(';')[1].replace('android-', ''), 10) || 0;
|
|
471
|
+
const bi = parseInt(b.split(';')[1].replace('android-', ''), 10) || 0;
|
|
472
|
+
return bi - ai;
|
|
473
|
+
});
|
|
474
|
+
return filtered[0];
|
|
475
|
+
}
|
|
476
|
+
/** Build the avdmanager argv for AVD creation — pure, exported for tests. */
|
|
477
|
+
function buildAvdmanagerCreateArgs(avdName, systemImage, deviceProfile) {
|
|
478
|
+
return ['create', 'avd', '-n', avdName, '-k', systemImage, '-d', deviceProfile];
|
|
479
|
+
}
|
|
480
|
+
async function listInstalledSystemImages() {
|
|
481
|
+
const result = await (0, runner_js_1.spawnCommand)('sdkmanager', ['--list_installed']);
|
|
482
|
+
if (!result.success) {
|
|
483
|
+
throw new Error(`sdkmanager --list_installed failed: ${result.stderr.trim() || result.stdout.trim()}`);
|
|
484
|
+
}
|
|
485
|
+
return parseInstalledSystemImages(result.stdout);
|
|
486
|
+
}
|
|
487
|
+
/** Run avdmanager create avd, piping "no\n" so it skips the custom hardware prompt. */
|
|
488
|
+
async function spawnAvdmanagerCreate(args) {
|
|
489
|
+
await new Promise((resolve, reject) => {
|
|
490
|
+
const proc = (0, child_process_1.spawn)('avdmanager', args, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
491
|
+
let stderr = '';
|
|
492
|
+
proc.stdout.on('data', () => {
|
|
493
|
+
/* discard */
|
|
494
|
+
});
|
|
495
|
+
proc.stderr.on('data', (chunk) => {
|
|
496
|
+
stderr += chunk.toString();
|
|
497
|
+
});
|
|
498
|
+
proc.on('error', (err) => reject(err));
|
|
499
|
+
proc.on('close', (code) => {
|
|
500
|
+
if (code === 0)
|
|
501
|
+
resolve();
|
|
502
|
+
else
|
|
503
|
+
reject(new Error(`avdmanager exited ${code}: ${stderr.trim()}`));
|
|
504
|
+
});
|
|
505
|
+
proc.stdin.end('no\n');
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
async function createAndroidAVD(avdName, deviceProfile, apiLevel, systemImageOverride) {
|
|
509
|
+
const arch = pickAndroidArch();
|
|
510
|
+
let systemImage;
|
|
511
|
+
if (systemImageOverride) {
|
|
512
|
+
systemImage = systemImageOverride;
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
let installed;
|
|
516
|
+
try {
|
|
517
|
+
installed = await listInstalledSystemImages();
|
|
518
|
+
}
|
|
519
|
+
catch (e) {
|
|
520
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
521
|
+
throw new Error(`Could not list installed Android system images (${msg}). ` +
|
|
522
|
+
`Ensure the Android SDK command-line tools are installed and on PATH.`);
|
|
523
|
+
}
|
|
524
|
+
const picked = pickSystemImage(installed, apiLevel, arch);
|
|
525
|
+
if (!picked) {
|
|
526
|
+
const apiHint = apiLevel ?? '34';
|
|
527
|
+
throw new Error(`No installed Android system image matches arch=${arch}` +
|
|
528
|
+
(apiLevel ? `, api=${apiLevel}` : '') +
|
|
529
|
+
`.\nInstall one with:\n sdkmanager "system-images;android-${apiHint};google_apis;${arch}"`);
|
|
530
|
+
}
|
|
531
|
+
systemImage = picked;
|
|
532
|
+
}
|
|
533
|
+
const args = buildAvdmanagerCreateArgs(avdName, systemImage, deviceProfile);
|
|
534
|
+
try {
|
|
535
|
+
await spawnAvdmanagerCreate(args);
|
|
536
|
+
}
|
|
537
|
+
catch (e) {
|
|
538
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
539
|
+
throw new Error(`Failed to create AVD "${avdName}": ${msg}`);
|
|
540
|
+
}
|
|
541
|
+
// Validate the AVD now appears in emulator -list-avds
|
|
542
|
+
const avds = await listAVDs();
|
|
543
|
+
if (!avds.includes(avdName)) {
|
|
544
|
+
throw new Error(`AVD "${avdName}" was not registered after creation (not in emulator -list-avds).`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
404
547
|
async function waitForAndroidBoot(avdName) {
|
|
405
548
|
const deadline = Date.now() + ANDROID_BOOT_TIMEOUT_MS;
|
|
406
549
|
const connectedBefore = new Set();
|
|
@@ -434,7 +577,7 @@ async function waitForAndroidBoot(avdName) {
|
|
|
434
577
|
}
|
|
435
578
|
throw new Error(`Android emulator (${avdName}) did not appear within ${ANDROID_BOOT_TIMEOUT_MS / 1000}s`);
|
|
436
579
|
}
|
|
437
|
-
async function startAndroid(avdName, opts) {
|
|
580
|
+
async function startAndroid(avdName, opts, deviceType, osVersion, systemImage) {
|
|
438
581
|
let avds;
|
|
439
582
|
try {
|
|
440
583
|
avds = await listAVDs();
|
|
@@ -443,13 +586,34 @@ async function startAndroid(avdName, opts) {
|
|
|
443
586
|
(0, output_js_1.printError)(`Failed to list AVDs: ${e instanceof Error ? e.message : String(e)}`, opts);
|
|
444
587
|
return 1;
|
|
445
588
|
}
|
|
446
|
-
|
|
447
|
-
|
|
589
|
+
let target = avdName ?? avds[0];
|
|
590
|
+
// If the requested AVD doesn't exist (or none exist) and a device profile was
|
|
591
|
+
// provided, create the AVD before booting.
|
|
592
|
+
const needsCreate = (avds.length === 0 || (avdName !== undefined && !avds.includes(avdName))) &&
|
|
593
|
+
deviceType !== undefined;
|
|
594
|
+
if (needsCreate) {
|
|
595
|
+
if (!avdName) {
|
|
596
|
+
(0, output_js_1.printError)('--avd <name> is required when creating an AVD with --device-type.', opts);
|
|
597
|
+
return 1;
|
|
598
|
+
}
|
|
599
|
+
console.log(`No AVD "${avdName}" found. Creating one with device profile "${deviceType}"...`);
|
|
600
|
+
try {
|
|
601
|
+
await createAndroidAVD(avdName, deviceType, osVersion, systemImage);
|
|
602
|
+
}
|
|
603
|
+
catch (e) {
|
|
604
|
+
(0, output_js_1.printError)(e instanceof Error ? e.message : String(e), opts);
|
|
605
|
+
return 1;
|
|
606
|
+
}
|
|
607
|
+
target = avdName;
|
|
608
|
+
}
|
|
609
|
+
else if (avds.length === 0) {
|
|
610
|
+
(0, output_js_1.printError)('No Android AVDs found. Pass --device-type <profile> --avd <name> to create one, ' +
|
|
611
|
+
'or create one in Android Studio → Device Manager.', opts);
|
|
448
612
|
return 1;
|
|
449
613
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
614
|
+
else if (!avds.includes(target)) {
|
|
615
|
+
(0, output_js_1.printError)(`AVD "${target}" not found. Available: ${avds.join(', ')}. ` +
|
|
616
|
+
`Pass --device-type to create it.`, opts);
|
|
453
617
|
return 1;
|
|
454
618
|
}
|
|
455
619
|
console.log(`Launching emulator: ${target}...`);
|
|
@@ -524,7 +688,7 @@ async function startDevice(platform, opts, flags) {
|
|
|
524
688
|
case 'tvos':
|
|
525
689
|
return startTvOS(flags.osVersion, opts, flags.name, flags.deviceType);
|
|
526
690
|
case 'android':
|
|
527
|
-
return startAndroid(flags.avd, opts);
|
|
691
|
+
return startAndroid(flags.avd, opts, flags.deviceType, flags.osVersion, flags.systemImage);
|
|
528
692
|
case 'web':
|
|
529
693
|
return startWebDriver(opts, flags.browser, flags.name);
|
|
530
694
|
default:
|
|
@@ -437,6 +437,24 @@ let _server = null;
|
|
|
437
437
|
let _cdpMode = false;
|
|
438
438
|
let _cdpPort = 0;
|
|
439
439
|
let _pageTargetId = null;
|
|
440
|
+
/**
|
|
441
|
+
* Default User-Agent applied to created contexts. Derived from the launched
|
|
442
|
+
* browser's UA with "HeadlessChrome" rewritten to "Chrome" so remote servers
|
|
443
|
+
* don't see automation/headless markers in the UA string.
|
|
444
|
+
*/
|
|
445
|
+
let _defaultUserAgent;
|
|
446
|
+
async function deriveDefaultUserAgent(browser) {
|
|
447
|
+
try {
|
|
448
|
+
const tmpCtx = await browser.newContext();
|
|
449
|
+
const tmpPage = await tmpCtx.newPage();
|
|
450
|
+
const ua = await tmpPage.evaluate(() => globalThis.navigator.userAgent);
|
|
451
|
+
await tmpCtx.close().catch(() => { });
|
|
452
|
+
return ua.replace(/HeadlessChrome/g, 'Chrome');
|
|
453
|
+
}
|
|
454
|
+
catch {
|
|
455
|
+
return undefined;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
440
458
|
/** Find a free TCP port by briefly binding to port 0. */
|
|
441
459
|
function findFreePort() {
|
|
442
460
|
return new Promise((resolve, reject) => {
|
|
@@ -557,8 +575,10 @@ async function startWebServer(port, browserName = 'chromium', dlog = () => { },
|
|
|
557
575
|
? [`--remote-debugging-port=${_cdpPort}`, '--disable-search-engine-choice-screen']
|
|
558
576
|
: undefined,
|
|
559
577
|
});
|
|
578
|
+
_defaultUserAgent = await deriveDefaultUserAgent(_browser);
|
|
560
579
|
_context = await _browser.newContext({
|
|
561
580
|
viewport: DEFAULT_VIEWPORT,
|
|
581
|
+
userAgent: _defaultUserAgent,
|
|
562
582
|
});
|
|
563
583
|
_page = await _context.newPage();
|
|
564
584
|
attachConsoleListeners(_page);
|
|
@@ -692,6 +712,7 @@ async function recreateBrowserContext(dlog) {
|
|
|
692
712
|
_page = null;
|
|
693
713
|
_context = await _browser.newContext({
|
|
694
714
|
viewport: DEFAULT_VIEWPORT,
|
|
715
|
+
userAgent: _defaultUserAgent,
|
|
695
716
|
});
|
|
696
717
|
_page = await _context.newPage();
|
|
697
718
|
attachConsoleListeners(_page);
|
|
@@ -791,7 +812,9 @@ async function handleRequest(req, res, dlog) {
|
|
|
791
812
|
return;
|
|
792
813
|
}
|
|
793
814
|
case '/screenshot': {
|
|
794
|
-
const
|
|
815
|
+
const fullPageRaw = parsedUrl.query.fullPage;
|
|
816
|
+
const fullPage = fullPageRaw === '1' || fullPageRaw === 'true';
|
|
817
|
+
const buf = await (await getPage(dlog)).screenshot({ type: 'png', fullPage });
|
|
795
818
|
res.writeHead(200, {
|
|
796
819
|
'Content-Type': 'image/png',
|
|
797
820
|
'Content-Length': buf.length,
|
|
@@ -1077,7 +1100,7 @@ async function handleRequest(req, res, dlog) {
|
|
|
1077
1100
|
await _context.close().catch(() => { });
|
|
1078
1101
|
_context = await _browser.newContext({
|
|
1079
1102
|
viewport: { width: vpWidth, height: vpHeight },
|
|
1080
|
-
userAgent: vpUserAgent,
|
|
1103
|
+
userAgent: vpUserAgent ?? _defaultUserAgent,
|
|
1081
1104
|
deviceScaleFactor: vpScaleFactor,
|
|
1082
1105
|
isMobile: vpIsMobile,
|
|
1083
1106
|
hasTouch: vpIsMobile,
|
package/dist/drivers/android.js
CHANGED
|
@@ -142,7 +142,7 @@ class AndroidDriver {
|
|
|
142
142
|
const resp = await this.call('viewHierarchy', {});
|
|
143
143
|
return resp.hierarchy;
|
|
144
144
|
}
|
|
145
|
-
async screenshot() {
|
|
145
|
+
async screenshot(_opts = {}) {
|
|
146
146
|
const resp = await this.call('screenshot', {});
|
|
147
147
|
return Buffer.from(resp.bytes);
|
|
148
148
|
}
|
|
@@ -203,7 +203,9 @@ class AndroidDriver {
|
|
|
203
203
|
}
|
|
204
204
|
async getForegroundApp() {
|
|
205
205
|
const output = await this.adbOutput(['shell', 'dumpsys', 'activity', 'activities']);
|
|
206
|
-
|
|
206
|
+
// API 28- prints `mResumedActivity:`; API 29+ uses `ResumedActivity:` /
|
|
207
|
+
// `topResumedActivity=`. Accept all three so this works across versions.
|
|
208
|
+
const match = output.match(/(?:m|top)?ResumedActivity[=:].*?([a-zA-Z][a-zA-Z0-9_]*(?:\.[a-zA-Z][a-zA-Z0-9_]*)+)\//);
|
|
207
209
|
if (!match)
|
|
208
210
|
throw new Error('Could not determine foreground app');
|
|
209
211
|
return match[1];
|
package/dist/drivers/ios.js
CHANGED
|
@@ -308,7 +308,7 @@ class IOSDriver {
|
|
|
308
308
|
}
|
|
309
309
|
return JSON.parse(data.toString('utf-8'));
|
|
310
310
|
}
|
|
311
|
-
async screenshot() {
|
|
311
|
+
async screenshot(_opts = {}) {
|
|
312
312
|
const { status, data } = await this.request('GET', '/screenshot');
|
|
313
313
|
if (status < 200 || status >= 300) {
|
|
314
314
|
throw new Error(`iOS driver screenshot failed (HTTP ${status})`);
|
package/dist/drivers/web.js
CHANGED
|
@@ -121,8 +121,9 @@ class WebDriver {
|
|
|
121
121
|
async viewHierarchy() {
|
|
122
122
|
return this.get('viewHierarchy');
|
|
123
123
|
}
|
|
124
|
-
async screenshot() {
|
|
125
|
-
const
|
|
124
|
+
async screenshot(opts = {}) {
|
|
125
|
+
const path = opts.fullPage ? '/screenshot?fullPage=1' : '/screenshot';
|
|
126
|
+
const { status, data } = await this.request('GET', path);
|
|
126
127
|
if (status < 200 || status >= 300) {
|
|
127
128
|
throw new Error(`Web driver screenshot failed (HTTP ${status})`);
|
|
128
129
|
}
|
package/dist/index.js
CHANGED
|
@@ -162,6 +162,7 @@ async function main() {
|
|
|
162
162
|
'name',
|
|
163
163
|
'device-name',
|
|
164
164
|
'device-type',
|
|
165
|
+
'system-image',
|
|
165
166
|
'browser',
|
|
166
167
|
'from',
|
|
167
168
|
'to',
|
|
@@ -249,6 +250,7 @@ async function main() {
|
|
|
249
250
|
avd: argv['avd'],
|
|
250
251
|
name: argv['name'],
|
|
251
252
|
deviceType: argv['device-type'],
|
|
253
|
+
systemImage: argv['system-image'],
|
|
252
254
|
browser: argv['browser'],
|
|
253
255
|
});
|
|
254
256
|
break;
|
|
@@ -459,7 +461,8 @@ async function main() {
|
|
|
459
461
|
}
|
|
460
462
|
case 'take-screenshot': {
|
|
461
463
|
const outPath = argv['output'];
|
|
462
|
-
|
|
464
|
+
const fullPage = Boolean(argv['full-page']);
|
|
465
|
+
exitCode = await (0, screenshot_js_1.screenshot)(outPath, opts, sessionName, fullPage);
|
|
463
466
|
break;
|
|
464
467
|
}
|
|
465
468
|
case 'capture-ui': {
|