@react-native-harness/platform-android 1.1.0 → 1.2.0-rc.1
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/__tests__/adb.test.js +93 -1
- package/dist/__tests__/emulator-startup.test.js +1 -0
- package/dist/__tests__/instance.test.js +130 -0
- package/dist/adb-errors.d.ts +16 -0
- package/dist/adb-errors.d.ts.map +1 -0
- package/dist/adb-errors.js +35 -0
- package/dist/adb-id.d.ts.map +1 -1
- package/dist/adb-id.js +3 -0
- package/dist/adb.d.ts +2 -0
- package/dist/adb.d.ts.map +1 -1
- package/dist/adb.js +120 -1
- package/dist/emulator-startup.d.ts.map +1 -1
- package/dist/emulator-startup.js +0 -2
- package/dist/instance.d.ts.map +1 -1
- package/dist/instance.js +25 -2
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/__tests__/adb.test.ts +102 -0
- package/src/__tests__/emulator-startup.test.ts +3 -0
- package/src/__tests__/instance.test.ts +188 -0
- package/src/adb-errors.ts +47 -0
- package/src/adb-id.ts +4 -0
- package/src/adb.ts +160 -1
- package/src/emulator-startup.ts +0 -2
- package/src/instance.ts +30 -2
|
@@ -2,7 +2,7 @@ import { EventEmitter } from 'node:events';
|
|
|
2
2
|
import { PassThrough } from 'node:stream';
|
|
3
3
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
4
|
import { SubprocessError } from '@react-native-harness/tools';
|
|
5
|
-
import { createAvd, deleteAvd, emulatorProcess, getAppUid, getLogcatTimestamp, getStartAppArgs, hasAvd, installApp, startEmulator, waitForBoot, waitForEmulatorDisconnect, } from '../adb.js';
|
|
5
|
+
import { createAvd, deleteAvd, emulatorProcess, getAppUid, getLogcatTimestamp, getStartAppArgs, hasAvd, installApp, startEmulator, uninstallApp, waitForBoot, waitForEmulatorDisconnect, } from '../adb.js';
|
|
6
6
|
import * as tools from '@react-native-harness/tools';
|
|
7
7
|
import * as environment from '../environment.js';
|
|
8
8
|
import * as avdConfig from '../avd-config.js';
|
|
@@ -101,6 +101,18 @@ describe('getStartAppArgs', () => {
|
|
|
101
101
|
'/tmp/app.apk',
|
|
102
102
|
]);
|
|
103
103
|
});
|
|
104
|
+
it('uninstalls the app via adb', async () => {
|
|
105
|
+
const spawnSpy = vi
|
|
106
|
+
.spyOn(tools, 'spawn')
|
|
107
|
+
.mockResolvedValueOnce({});
|
|
108
|
+
await uninstallApp('emulator-5554', 'com.example.app');
|
|
109
|
+
expect(spawnSpy).toHaveBeenCalledWith(expect.stringMatching(/adb$/), [
|
|
110
|
+
'-s',
|
|
111
|
+
'emulator-5554',
|
|
112
|
+
'uninstall',
|
|
113
|
+
'com.example.app',
|
|
114
|
+
]);
|
|
115
|
+
});
|
|
104
116
|
it('creates an AVD and appends config overrides', async () => {
|
|
105
117
|
vi.spyOn(avdConfig, 'readAvdConfig').mockResolvedValue({});
|
|
106
118
|
const spawnSpy = vi
|
|
@@ -414,4 +426,84 @@ describe('getStartAppArgs', () => {
|
|
|
414
426
|
await expect(waitPromise).resolves.toBeUndefined();
|
|
415
427
|
expect(spawnSpy).toHaveBeenCalledTimes(2);
|
|
416
428
|
});
|
|
429
|
+
it('grants requested dangerous permissions to an app', async () => {
|
|
430
|
+
const { grantPermissions } = await import('../adb.js');
|
|
431
|
+
const spawnSpy = vi
|
|
432
|
+
.spyOn(tools, 'spawn')
|
|
433
|
+
.mockResolvedValueOnce({
|
|
434
|
+
stdout: 'package:com.example.app\n',
|
|
435
|
+
})
|
|
436
|
+
.mockResolvedValueOnce({
|
|
437
|
+
stdout: `requested permissions:\n android.permission.CAMERA\n android.permission.INTERNET\n android.permission.ACCESS_FINE_LOCATION\ninstall permissions:\n`,
|
|
438
|
+
})
|
|
439
|
+
.mockResolvedValueOnce({
|
|
440
|
+
stdout: `Dangerous Permissions:\n permission:android.permission.CAMERA\n permission:android.permission.ACCESS_FINE_LOCATION\n`,
|
|
441
|
+
})
|
|
442
|
+
.mockResolvedValue({
|
|
443
|
+
stdout: '',
|
|
444
|
+
});
|
|
445
|
+
await grantPermissions('emulator-5554', 'com.example.app');
|
|
446
|
+
expect(spawnSpy).toHaveBeenCalledTimes(5);
|
|
447
|
+
expect(spawnSpy).toHaveBeenNthCalledWith(1, expect.any(String), [
|
|
448
|
+
'-s',
|
|
449
|
+
'emulator-5554',
|
|
450
|
+
'shell',
|
|
451
|
+
'pm',
|
|
452
|
+
'list',
|
|
453
|
+
'packages',
|
|
454
|
+
'com.example.app',
|
|
455
|
+
]);
|
|
456
|
+
expect(spawnSpy).toHaveBeenNthCalledWith(2, expect.any(String), [
|
|
457
|
+
'-s',
|
|
458
|
+
'emulator-5554',
|
|
459
|
+
'shell',
|
|
460
|
+
'dumpsys',
|
|
461
|
+
'package',
|
|
462
|
+
'com.example.app',
|
|
463
|
+
]);
|
|
464
|
+
expect(spawnSpy).toHaveBeenNthCalledWith(3, expect.any(String), [
|
|
465
|
+
'-s',
|
|
466
|
+
'emulator-5554',
|
|
467
|
+
'shell',
|
|
468
|
+
'pm',
|
|
469
|
+
'list',
|
|
470
|
+
'permissions',
|
|
471
|
+
'-g',
|
|
472
|
+
'-d',
|
|
473
|
+
]);
|
|
474
|
+
expect(spawnSpy).toHaveBeenNthCalledWith(4, expect.any(String), [
|
|
475
|
+
'-s',
|
|
476
|
+
'emulator-5554',
|
|
477
|
+
'shell',
|
|
478
|
+
'pm',
|
|
479
|
+
'grant',
|
|
480
|
+
'com.example.app',
|
|
481
|
+
'android.permission.CAMERA',
|
|
482
|
+
]);
|
|
483
|
+
expect(spawnSpy).toHaveBeenNthCalledWith(5, expect.any(String), [
|
|
484
|
+
'-s',
|
|
485
|
+
'emulator-5554',
|
|
486
|
+
'shell',
|
|
487
|
+
'pm',
|
|
488
|
+
'grant',
|
|
489
|
+
'com.example.app',
|
|
490
|
+
'android.permission.ACCESS_FINE_LOCATION',
|
|
491
|
+
]);
|
|
492
|
+
});
|
|
493
|
+
it('does nothing when the app has no grantable dangerous permissions', async () => {
|
|
494
|
+
const { grantPermissions } = await import('../adb.js');
|
|
495
|
+
const spawnSpy = vi
|
|
496
|
+
.spyOn(tools, 'spawn')
|
|
497
|
+
.mockResolvedValueOnce({
|
|
498
|
+
stdout: 'package:com.example.app\n',
|
|
499
|
+
})
|
|
500
|
+
.mockResolvedValueOnce({
|
|
501
|
+
stdout: `requested permissions:\n android.permission.INTERNET\ninstall permissions:\n`,
|
|
502
|
+
})
|
|
503
|
+
.mockResolvedValueOnce({
|
|
504
|
+
stdout: `Dangerous Permissions:\n permission:android.permission.CAMERA\n`,
|
|
505
|
+
});
|
|
506
|
+
await grantPermissions('emulator-5554', 'com.example.app');
|
|
507
|
+
expect(spawnSpy).toHaveBeenCalledTimes(3);
|
|
508
|
+
});
|
|
417
509
|
});
|
|
@@ -7,6 +7,7 @@ describe('emulator startup modes', () => {
|
|
|
7
7
|
'-no-snapshot-load',
|
|
8
8
|
'-no-snapshot-save',
|
|
9
9
|
]));
|
|
10
|
+
expect(getEmulatorStartupArgs('Pixel_8_API_35', 'default-boot')).not.toEqual(expect.arrayContaining(['-camera-back', 'none']));
|
|
10
11
|
});
|
|
11
12
|
it('builds clean snapshot generation args', () => {
|
|
12
13
|
expect(getEmulatorStartupArgs('Pixel_8_API_35', 'clean-snapshot-generation')).toEqual(expect.arrayContaining(['@Pixel_8_API_35', '-no-snapshot-load']));
|
|
@@ -293,6 +293,42 @@ describe('Android platform instance', () => {
|
|
|
293
293
|
expect(installApp).toHaveBeenCalledWith('emulator-5554', appPath);
|
|
294
294
|
fs.rmSync(appPath, { force: true });
|
|
295
295
|
});
|
|
296
|
+
it('reinstalls the app from HARNESS_APP_PATH when already installed', async () => {
|
|
297
|
+
const appPath = path.join(os.tmpdir(), 'HarnessPlayground-installed.apk');
|
|
298
|
+
fs.writeFileSync(appPath, 'apk');
|
|
299
|
+
vi.stubEnv('HARNESS_APP_PATH', appPath);
|
|
300
|
+
vi.spyOn(await import('../environment.js'), 'ensureAndroidEmulatorEnvironment').mockResolvedValue('/tmp/android-sdk');
|
|
301
|
+
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
|
|
302
|
+
vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
|
|
303
|
+
vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
|
|
304
|
+
vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
|
|
305
|
+
const uninstallApp = vi
|
|
306
|
+
.spyOn(adb, 'uninstallApp')
|
|
307
|
+
.mockResolvedValue(undefined);
|
|
308
|
+
const installApp = vi.spyOn(adb, 'installApp').mockResolvedValue(undefined);
|
|
309
|
+
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
310
|
+
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
311
|
+
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
312
|
+
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
|
|
313
|
+
await expect(getAndroidEmulatorPlatformInstance({
|
|
314
|
+
name: 'android',
|
|
315
|
+
device: {
|
|
316
|
+
type: 'emulator',
|
|
317
|
+
name: 'Pixel_8_API_35',
|
|
318
|
+
avd: {
|
|
319
|
+
apiLevel: 35,
|
|
320
|
+
profile: 'pixel_8',
|
|
321
|
+
diskSize: '1G',
|
|
322
|
+
heapSize: '1G',
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
bundleId: 'com.harnessplayground',
|
|
326
|
+
activityName: '.MainActivity',
|
|
327
|
+
}, harnessConfig, init)).resolves.toBeDefined();
|
|
328
|
+
expect(uninstallApp).toHaveBeenCalledWith('emulator-5554', 'com.harnessplayground');
|
|
329
|
+
expect(installApp).toHaveBeenCalledWith('emulator-5554', appPath);
|
|
330
|
+
fs.rmSync(appPath, { force: true });
|
|
331
|
+
});
|
|
296
332
|
it('throws a HarnessAppPathError when HARNESS_APP_PATH is missing', async () => {
|
|
297
333
|
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
|
|
298
334
|
vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
|
|
@@ -420,4 +456,98 @@ describe('Android platform instance', () => {
|
|
|
420
456
|
expect(appMonitor.addListener(listener)).toBeUndefined();
|
|
421
457
|
expect(appMonitor.removeListener(listener)).toBeUndefined();
|
|
422
458
|
});
|
|
459
|
+
it('grants permissions when permissions are enabled for emulator', async () => {
|
|
460
|
+
vi.spyOn(await import('../environment.js'), 'ensureAndroidEmulatorEnvironment').mockResolvedValue('/tmp/android-sdk');
|
|
461
|
+
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
|
|
462
|
+
vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
|
|
463
|
+
vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
|
|
464
|
+
vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
|
|
465
|
+
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
466
|
+
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
467
|
+
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
468
|
+
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
|
|
469
|
+
const grantPermissions = vi
|
|
470
|
+
.spyOn(adb, 'grantPermissions')
|
|
471
|
+
.mockResolvedValue(undefined);
|
|
472
|
+
const harnessConfigWithPermissions = {
|
|
473
|
+
...harnessConfig,
|
|
474
|
+
permissions: true,
|
|
475
|
+
};
|
|
476
|
+
await getAndroidEmulatorPlatformInstance({
|
|
477
|
+
name: 'android',
|
|
478
|
+
device: {
|
|
479
|
+
type: 'emulator',
|
|
480
|
+
name: 'Pixel_8_API_35',
|
|
481
|
+
avd: {
|
|
482
|
+
apiLevel: 35,
|
|
483
|
+
profile: 'pixel_8',
|
|
484
|
+
diskSize: '1G',
|
|
485
|
+
heapSize: '1G',
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
bundleId: 'com.harnessplayground',
|
|
489
|
+
activityName: '.MainActivity',
|
|
490
|
+
}, harnessConfigWithPermissions, init);
|
|
491
|
+
expect(grantPermissions).toHaveBeenCalledWith('emulator-5554', 'com.harnessplayground');
|
|
492
|
+
});
|
|
493
|
+
it('does not grant permissions when permissions are disabled for emulator', async () => {
|
|
494
|
+
vi.spyOn(await import('../environment.js'), 'ensureAndroidEmulatorEnvironment').mockResolvedValue('/tmp/android-sdk');
|
|
495
|
+
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
|
|
496
|
+
vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
|
|
497
|
+
vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
|
|
498
|
+
vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
|
|
499
|
+
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
500
|
+
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
501
|
+
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
502
|
+
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
|
|
503
|
+
const grantPermissions = vi
|
|
504
|
+
.spyOn(adb, 'grantPermissions')
|
|
505
|
+
.mockResolvedValue(undefined);
|
|
506
|
+
await getAndroidEmulatorPlatformInstance({
|
|
507
|
+
name: 'android',
|
|
508
|
+
device: {
|
|
509
|
+
type: 'emulator',
|
|
510
|
+
name: 'Pixel_8_API_35',
|
|
511
|
+
avd: {
|
|
512
|
+
apiLevel: 35,
|
|
513
|
+
profile: 'pixel_8',
|
|
514
|
+
diskSize: '1G',
|
|
515
|
+
heapSize: '1G',
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
bundleId: 'com.harnessplayground',
|
|
519
|
+
activityName: '.MainActivity',
|
|
520
|
+
}, harnessConfig, init);
|
|
521
|
+
expect(grantPermissions).not.toHaveBeenCalled();
|
|
522
|
+
});
|
|
523
|
+
it('grants permissions when permissions are enabled for physical device', async () => {
|
|
524
|
+
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['012345']);
|
|
525
|
+
vi.spyOn(adb, 'getDeviceInfo').mockResolvedValue({
|
|
526
|
+
manufacturer: 'motorola',
|
|
527
|
+
model: 'moto g72',
|
|
528
|
+
});
|
|
529
|
+
vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
|
|
530
|
+
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
531
|
+
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
532
|
+
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
533
|
+
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
|
|
534
|
+
const grantPermissions = vi
|
|
535
|
+
.spyOn(adb, 'grantPermissions')
|
|
536
|
+
.mockResolvedValue(undefined);
|
|
537
|
+
const harnessConfigWithPermissions = {
|
|
538
|
+
...harnessConfig,
|
|
539
|
+
permissions: true,
|
|
540
|
+
};
|
|
541
|
+
await getAndroidPhysicalDevicePlatformInstance({
|
|
542
|
+
name: 'android-device',
|
|
543
|
+
device: {
|
|
544
|
+
type: 'physical',
|
|
545
|
+
manufacturer: 'motorola',
|
|
546
|
+
model: 'moto g72',
|
|
547
|
+
},
|
|
548
|
+
bundleId: 'com.harnessplayground',
|
|
549
|
+
activityName: '.MainActivity',
|
|
550
|
+
}, harnessConfigWithPermissions);
|
|
551
|
+
expect(grantPermissions).toHaveBeenCalledWith('012345', 'com.harnessplayground');
|
|
552
|
+
});
|
|
423
553
|
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare class AdbError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
export declare class AdbDeviceNotFoundError extends AdbError {
|
|
5
|
+
constructor(adbId: string);
|
|
6
|
+
}
|
|
7
|
+
export declare class AdbAppNotInstalledError extends AdbError {
|
|
8
|
+
constructor(bundleId: string, adbId: string);
|
|
9
|
+
}
|
|
10
|
+
export declare class AdbPermissionGrantError extends AdbError {
|
|
11
|
+
constructor(bundleId: string, permissions: string[], adbId: string);
|
|
12
|
+
}
|
|
13
|
+
export declare class AdbBinaryNotFoundError extends AdbError {
|
|
14
|
+
constructor();
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=adb-errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adb-errors.d.ts","sourceRoot":"","sources":["../src/adb-errors.ts"],"names":[],"mappings":"AAAA,qBAAa,QAAS,SAAQ,KAAK;gBACrB,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,sBAAuB,SAAQ,QAAQ;gBACtC,KAAK,EAAE,MAAM;CAO1B;AAED,qBAAa,uBAAwB,SAAQ,QAAQ;gBACvC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;CAO5C;AAED,qBAAa,uBAAwB,SAAQ,QAAQ;gBACvC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM;CAQnE;AAED,qBAAa,sBAAuB,SAAQ,QAAQ;;CAQnD"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export class AdbError extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = 'AdbError';
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export class AdbDeviceNotFoundError extends AdbError {
|
|
8
|
+
constructor(adbId) {
|
|
9
|
+
super(`Android device "${adbId}" not found or not connected. ` +
|
|
10
|
+
`Run "adb devices" to see available devices.`);
|
|
11
|
+
this.name = 'AdbDeviceNotFoundError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class AdbAppNotInstalledError extends AdbError {
|
|
15
|
+
constructor(bundleId, adbId) {
|
|
16
|
+
super(`App "${bundleId}" is not installed on device "${adbId}". ` +
|
|
17
|
+
`Install the app before running tests.`);
|
|
18
|
+
this.name = 'AdbAppNotInstalledError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class AdbPermissionGrantError extends AdbError {
|
|
22
|
+
constructor(bundleId, permissions, adbId) {
|
|
23
|
+
const permissionList = permissions.join(', ');
|
|
24
|
+
super(`Failed to grant permissions [${permissionList}] to "${bundleId}" on device "${adbId}". ` +
|
|
25
|
+
`Verify the app is installed and the device supports these permissions.`);
|
|
26
|
+
this.name = 'AdbPermissionGrantError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class AdbBinaryNotFoundError extends AdbError {
|
|
30
|
+
constructor() {
|
|
31
|
+
super(`adb binary not found or not accessible. ` +
|
|
32
|
+
`Ensure Android SDK is properly installed and ANDROID_HOME is set.`);
|
|
33
|
+
this.name = 'AdbBinaryNotFoundError';
|
|
34
|
+
}
|
|
35
|
+
}
|
package/dist/adb-id.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adb-id.d.ts","sourceRoot":"","sources":["../src/adb-id.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,aAAa,EACd,MAAM,aAAa,CAAC;AAErB,eAAO,MAAM,eAAe,GAAI,OAAO,MAAM,KAAG,OAE/C,CAAC;AAEF,eAAO,MAAM,QAAQ,GACnB,QAAQ,aAAa,KACpB,OAAO,CAAC,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"adb-id.d.ts","sourceRoot":"","sources":["../src/adb-id.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,aAAa,EACd,MAAM,aAAa,CAAC;AAErB,eAAO,MAAM,eAAe,GAAI,OAAO,MAAM,KAAG,OAE/C,CAAC;AAEF,eAAO,MAAM,QAAQ,GACnB,QAAQ,aAAa,KACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAmCvB,CAAC"}
|
package/dist/adb-id.js
CHANGED
|
@@ -7,6 +7,9 @@ export const getAdbId = async (device) => {
|
|
|
7
7
|
const adbIds = await adb.getDeviceIds();
|
|
8
8
|
for (const adbId of adbIds) {
|
|
9
9
|
if (isAndroidDeviceEmulator(device)) {
|
|
10
|
+
if (!isAdbIdEmulator(adbId)) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
10
13
|
const emulatorName = await adb.getEmulatorName(adbId);
|
|
11
14
|
if (emulatorName === device.name) {
|
|
12
15
|
return adbId;
|
package/dist/adb.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export declare const getDeviceInfo: (adbId: string) => Promise<DeviceInfo | null
|
|
|
30
30
|
export declare const isBootCompleted: (adbId: string) => Promise<boolean>;
|
|
31
31
|
export declare const stopEmulator: (adbId: string) => Promise<void>;
|
|
32
32
|
export declare const installApp: (adbId: string, appPath: string) => Promise<void>;
|
|
33
|
+
export declare const uninstallApp: (adbId: string, bundleId: string) => Promise<void>;
|
|
33
34
|
export declare const hasAvd: (name: string) => Promise<boolean>;
|
|
34
35
|
export declare const createAvd: ({ name, apiLevel, profile, diskSize, heapSize, }: CreateAvdOptions) => Promise<void>;
|
|
35
36
|
export declare const deleteAvd: (name: string) => Promise<void>;
|
|
@@ -48,4 +49,5 @@ export type AdbDevice = {
|
|
|
48
49
|
manufacturer: string;
|
|
49
50
|
};
|
|
50
51
|
export declare const getConnectedDevices: () => Promise<AdbDevice[]>;
|
|
52
|
+
export declare const grantPermissions: (adbId: string, bundleId: string) => Promise<void>;
|
|
51
53
|
//# sourceMappingURL=adb.d.ts.map
|
package/dist/adb.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adb.d.ts","sourceRoot":"","sources":["../src/adb.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAG/E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAgB5C,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"adb.d.ts","sourceRoot":"","sources":["../src/adb.ts"],"names":[],"mappings":"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"}
|
package/dist/adb.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { spawn, SubprocessError } from '@react-native-harness/tools';
|
|
1
|
+
import { logger, spawn, SubprocessError } from '@react-native-harness/tools';
|
|
2
2
|
import { spawn as nodeSpawn } from 'node:child_process';
|
|
3
3
|
import { access, rm } from 'node:fs/promises';
|
|
4
4
|
import { getAvdConfigPath, getAvdDirectory, readAvdConfig, } from './avd-config.js';
|
|
5
5
|
import { ensureAndroidEmulatorAvailable, ensureAndroidSdkPackages, getAdbBinaryPath, getAndroidSystemImagePackage, getAvdManagerBinaryPath, getEmulatorBinaryPath, getHostAndroidSystemImageArch, getRequiredAndroidSdkPackages, } from './environment.js';
|
|
6
6
|
import { getEmulatorStartupArgs, } from './emulator-startup.js';
|
|
7
|
+
import { AdbAppNotInstalledError, AdbPermissionGrantError, } from './adb-errors.js';
|
|
7
8
|
const wait = async (ms) => {
|
|
8
9
|
await new Promise((resolve) => {
|
|
9
10
|
setTimeout(resolve, ms);
|
|
@@ -27,6 +28,7 @@ const waitWithSignal = async (ms, signal) => {
|
|
|
27
28
|
};
|
|
28
29
|
const EMULATOR_STARTUP_OBSERVATION_TIMEOUT_MS = 5000;
|
|
29
30
|
const EMULATOR_OUTPUT_BUFFER_LIMIT = 16 * 1024;
|
|
31
|
+
const androidAdbLogger = logger.child('android-adb');
|
|
30
32
|
export const emulatorProcess = {
|
|
31
33
|
startDetachedProcess: (file, args) => nodeSpawn(file, args, {
|
|
32
34
|
detached: true,
|
|
@@ -219,6 +221,61 @@ export const getDeviceInfo = async (adbId) => {
|
|
|
219
221
|
const model = await getShellProperty(adbId, 'ro.product.model');
|
|
220
222
|
return { manufacturer, model };
|
|
221
223
|
};
|
|
224
|
+
const getRequestedPermissions = async (adbId, bundleId) => {
|
|
225
|
+
const { stdout } = await spawn(getAdbBinaryPath(), [
|
|
226
|
+
'-s',
|
|
227
|
+
adbId,
|
|
228
|
+
'shell',
|
|
229
|
+
'dumpsys',
|
|
230
|
+
'package',
|
|
231
|
+
bundleId,
|
|
232
|
+
]);
|
|
233
|
+
const requestedPermissions = new Set();
|
|
234
|
+
const lines = stdout.split('\n');
|
|
235
|
+
let inRequestedPermissionsSection = false;
|
|
236
|
+
for (const line of lines) {
|
|
237
|
+
const trimmedLine = line.trim();
|
|
238
|
+
if (trimmedLine === 'requested permissions:') {
|
|
239
|
+
inRequestedPermissionsSection = true;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (!inRequestedPermissionsSection) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (trimmedLine === '') {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (trimmedLine.endsWith(':')) {
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
if (/^[a-zA-Z0-9_.]+$/.test(trimmedLine)) {
|
|
252
|
+
requestedPermissions.add(trimmedLine);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
return [...requestedPermissions];
|
|
258
|
+
};
|
|
259
|
+
const getDangerousPermissions = async (adbId) => {
|
|
260
|
+
const { stdout } = await spawn(getAdbBinaryPath(), [
|
|
261
|
+
'-s',
|
|
262
|
+
adbId,
|
|
263
|
+
'shell',
|
|
264
|
+
'pm',
|
|
265
|
+
'list',
|
|
266
|
+
'permissions',
|
|
267
|
+
'-g',
|
|
268
|
+
'-d',
|
|
269
|
+
]);
|
|
270
|
+
const dangerousPermissions = new Set();
|
|
271
|
+
for (const match of stdout.matchAll(/permission:([a-zA-Z0-9_.]+)/g)) {
|
|
272
|
+
const permission = match[1]?.trim();
|
|
273
|
+
if (permission) {
|
|
274
|
+
dangerousPermissions.add(permission);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return dangerousPermissions;
|
|
278
|
+
};
|
|
222
279
|
export const isBootCompleted = async (adbId) => {
|
|
223
280
|
try {
|
|
224
281
|
const bootCompleted = await getShellProperty(adbId, 'sys.boot_completed');
|
|
@@ -237,6 +294,9 @@ export const stopEmulator = async (adbId) => {
|
|
|
237
294
|
export const installApp = async (adbId, appPath) => {
|
|
238
295
|
await spawn(getAdbBinaryPath(), ['-s', adbId, 'install', '-r', appPath]);
|
|
239
296
|
};
|
|
297
|
+
export const uninstallApp = async (adbId, bundleId) => {
|
|
298
|
+
await spawn(getAdbBinaryPath(), ['-s', adbId, 'uninstall', bundleId]);
|
|
299
|
+
};
|
|
240
300
|
export const hasAvd = async (name) => {
|
|
241
301
|
const avds = await getAvds();
|
|
242
302
|
return avds.includes(name);
|
|
@@ -468,3 +528,62 @@ export const getConnectedDevices = async () => {
|
|
|
468
528
|
}
|
|
469
529
|
return devices;
|
|
470
530
|
};
|
|
531
|
+
export const grantPermissions = async (adbId, bundleId) => {
|
|
532
|
+
androidAdbLogger.debug('grantPermissions:start %o', {
|
|
533
|
+
adbId,
|
|
534
|
+
bundleId,
|
|
535
|
+
});
|
|
536
|
+
const isInstalled = await isAppInstalled(adbId, bundleId);
|
|
537
|
+
if (!isInstalled) {
|
|
538
|
+
throw new AdbAppNotInstalledError(bundleId, adbId);
|
|
539
|
+
}
|
|
540
|
+
const [requestedPermissions, dangerousPermissions] = await Promise.all([
|
|
541
|
+
getRequestedPermissions(adbId, bundleId),
|
|
542
|
+
getDangerousPermissions(adbId),
|
|
543
|
+
]);
|
|
544
|
+
const permissions = requestedPermissions.filter((permission) => dangerousPermissions.has(permission));
|
|
545
|
+
androidAdbLogger.debug('grantPermissions:resolved %o', {
|
|
546
|
+
adbId,
|
|
547
|
+
bundleId,
|
|
548
|
+
requestedPermissions,
|
|
549
|
+
permissions,
|
|
550
|
+
});
|
|
551
|
+
if (permissions.length === 0) {
|
|
552
|
+
androidAdbLogger.debug('grantPermissions:skip %o', {
|
|
553
|
+
adbId,
|
|
554
|
+
bundleId,
|
|
555
|
+
});
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const grantCommands = permissions.map((permission) => [
|
|
559
|
+
'-s',
|
|
560
|
+
adbId,
|
|
561
|
+
'shell',
|
|
562
|
+
'pm',
|
|
563
|
+
'grant',
|
|
564
|
+
bundleId,
|
|
565
|
+
permission,
|
|
566
|
+
]);
|
|
567
|
+
try {
|
|
568
|
+
androidAdbLogger.debug('grantPermissions:commands %o', {
|
|
569
|
+
adbId,
|
|
570
|
+
bundleId,
|
|
571
|
+
grantCommands,
|
|
572
|
+
});
|
|
573
|
+
await Promise.all(grantCommands.map((args) => spawn(getAdbBinaryPath(), args)));
|
|
574
|
+
androidAdbLogger.debug('grantPermissions:success %o', {
|
|
575
|
+
adbId,
|
|
576
|
+
bundleId,
|
|
577
|
+
permissions,
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
catch (error) {
|
|
581
|
+
androidAdbLogger.debug('grantPermissions:error %o', {
|
|
582
|
+
adbId,
|
|
583
|
+
bundleId,
|
|
584
|
+
permissions,
|
|
585
|
+
error,
|
|
586
|
+
});
|
|
587
|
+
throw new AdbPermissionGrantError(bundleId, permissions, adbId);
|
|
588
|
+
}
|
|
589
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emulator-startup.d.ts","sourceRoot":"","sources":["../src/emulator-startup.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GACxB,cAAc,GACd,2BAA2B,GAC3B,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"emulator-startup.d.ts","sourceRoot":"","sources":["../src/emulator-startup.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GACxB,cAAc,GACd,2BAA2B,GAC3B,gBAAgB,CAAC;AAUrB,eAAO,MAAM,sBAAsB,GACjC,MAAM,MAAM,EACZ,MAAM,gBAAgB,KACrB,MAAM,EASR,CAAC"}
|
package/dist/emulator-startup.js
CHANGED
package/dist/instance.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../src/instance.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,0BAA0B,EAC/B,qBAAqB,EACtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE5E,OAAO,EACL,qBAAqB,EAGtB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../src/instance.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,0BAA0B,EAC/B,qBAAqB,EACtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE5E,OAAO,EACL,qBAAqB,EAGtB,MAAM,aAAa,CAAC;AAgLrB,eAAO,MAAM,kCAAkC,GAC7C,QAAQ,qBAAqB,EAC7B,eAAe,aAAa,EAC5B,MAAM,0BAA0B,KAC/B,OAAO,CAAC,qBAAqB,CA2I/B,CAAC;AAEF,eAAO,MAAM,wCAAwC,GACnD,QAAQ,qBAAqB,EAC7B,eAAe,aAAa,KAC3B,OAAO,CAAC,qBAAqB,CAsE/B,CAAC"}
|
package/dist/instance.js
CHANGED
|
@@ -29,6 +29,16 @@ const getHarnessAppPath = () => {
|
|
|
29
29
|
}
|
|
30
30
|
return appPath;
|
|
31
31
|
};
|
|
32
|
+
const getOptionalHarnessAppPath = () => {
|
|
33
|
+
const appPath = process.env.HARNESS_APP_PATH;
|
|
34
|
+
if (!appPath) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
if (!fs.existsSync(appPath)) {
|
|
38
|
+
throw new HarnessAppPathError('invalid', appPath);
|
|
39
|
+
}
|
|
40
|
+
return appPath;
|
|
41
|
+
};
|
|
32
42
|
const configureAndroidRuntime = async (adbId, config, harnessConfig) => {
|
|
33
43
|
const metroPort = harnessConfig.metroPort;
|
|
34
44
|
await Promise.all([
|
|
@@ -98,6 +108,7 @@ const prepareCachedAvd = async ({ emulatorConfig, signal, }) => {
|
|
|
98
108
|
export const getAndroidEmulatorPlatformInstance = async (config, harnessConfig, init) => {
|
|
99
109
|
assertAndroidDeviceEmulator(config.device);
|
|
100
110
|
const detectNativeCrashes = harnessConfig.detectNativeCrashes ?? true;
|
|
111
|
+
const permissionsEnabled = harnessConfig.permissions ?? false;
|
|
101
112
|
const emulatorConfig = config.device;
|
|
102
113
|
const emulatorName = emulatorConfig.name;
|
|
103
114
|
const avdConfig = emulatorConfig.avd;
|
|
@@ -141,11 +152,19 @@ export const getAndroidEmulatorPlatformInstance = async (config, harnessConfig,
|
|
|
141
152
|
}
|
|
142
153
|
androidInstanceLogger.debug('waiting for Android emulator %s to finish booting', adbId);
|
|
143
154
|
const isInstalled = await adb.isAppInstalled(adbId, config.bundleId);
|
|
144
|
-
|
|
145
|
-
|
|
155
|
+
const appPath = getOptionalHarnessAppPath();
|
|
156
|
+
if (isInstalled && appPath) {
|
|
157
|
+
await adb.uninstallApp(adbId, config.bundleId);
|
|
146
158
|
await adb.installApp(adbId, appPath);
|
|
147
159
|
}
|
|
160
|
+
else if (!isInstalled) {
|
|
161
|
+
const installPath = appPath ?? getHarnessAppPath();
|
|
162
|
+
await adb.installApp(adbId, installPath);
|
|
163
|
+
}
|
|
148
164
|
const appUid = await configureAndroidRuntime(adbId, config, harnessConfig);
|
|
165
|
+
if (permissionsEnabled) {
|
|
166
|
+
await adb.grantPermissions(adbId, config.bundleId);
|
|
167
|
+
}
|
|
149
168
|
return {
|
|
150
169
|
startApp: async (options) => {
|
|
151
170
|
await adb.startApp(adbId, config.bundleId, config.activityName, options ??
|
|
@@ -187,6 +206,7 @@ export const getAndroidEmulatorPlatformInstance = async (config, harnessConfig,
|
|
|
187
206
|
export const getAndroidPhysicalDevicePlatformInstance = async (config, harnessConfig) => {
|
|
188
207
|
assertAndroidDevicePhysical(config.device);
|
|
189
208
|
const detectNativeCrashes = harnessConfig.detectNativeCrashes ?? true;
|
|
209
|
+
const permissionsEnabled = harnessConfig.permissions ?? false;
|
|
190
210
|
const adbId = await getAdbId(config.device);
|
|
191
211
|
if (!adbId) {
|
|
192
212
|
throw new DeviceNotFoundError(getDeviceName(config.device));
|
|
@@ -196,6 +216,9 @@ export const getAndroidPhysicalDevicePlatformInstance = async (config, harnessCo
|
|
|
196
216
|
throw new AppNotInstalledError(config.bundleId, getDeviceName(config.device));
|
|
197
217
|
}
|
|
198
218
|
const appUid = await configureAndroidRuntime(adbId, config, harnessConfig);
|
|
219
|
+
if (permissionsEnabled) {
|
|
220
|
+
await adb.grantPermissions(adbId, config.bundleId);
|
|
221
|
+
}
|
|
199
222
|
return {
|
|
200
223
|
startApp: async (options) => {
|
|
201
224
|
await adb.startApp(adbId, config.bundleId, config.activityName, options ??
|