@react-native-harness/platform-android 1.1.0 → 1.2.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.
@@ -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
+ }
@@ -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,CA+BvB,CAAC"}
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
@@ -1,4 +1,5 @@
1
1
  import { type AndroidAppLaunchOptions } from '@react-native-harness/platforms';
2
+ import { type Subprocess } from '@react-native-harness/tools';
2
3
  import type { ChildProcessByStdio } from 'node:child_process';
3
4
  import type { Readable } from 'node:stream';
4
5
  import { type EmulatorBootMode } from './emulator-startup.js';
@@ -30,6 +31,7 @@ export declare const getDeviceInfo: (adbId: string) => Promise<DeviceInfo | null
30
31
  export declare const isBootCompleted: (adbId: string) => Promise<boolean>;
31
32
  export declare const stopEmulator: (adbId: string) => Promise<void>;
32
33
  export declare const installApp: (adbId: string, appPath: string) => Promise<void>;
34
+ export declare const uninstallApp: (adbId: string, bundleId: string) => Promise<void>;
33
35
  export declare const hasAvd: (name: string) => Promise<boolean>;
34
36
  export declare const createAvd: ({ name, apiLevel, profile, diskSize, heapSize, }: CreateAvdOptions) => Promise<void>;
35
37
  export declare const deleteAvd: (name: string) => Promise<void>;
@@ -41,6 +43,7 @@ export declare const isAppRunning: (adbId: string, bundleId: string) => Promise<
41
43
  export declare const getAppUid: (adbId: string, bundleId: string) => Promise<number>;
42
44
  export declare const setHideErrorDialogs: (adbId: string, hide: boolean) => Promise<void>;
43
45
  export declare const getLogcatTimestamp: (adbId: string) => Promise<string>;
46
+ export declare const startLogcat: (adbId: string, args: readonly string[]) => Subprocess;
44
47
  export declare const getAvds: () => Promise<string[]>;
45
48
  export type AdbDevice = {
46
49
  id: string;
@@ -48,4 +51,5 @@ export type AdbDevice = {
48
51
  manufacturer: string;
49
52
  };
50
53
  export declare const getConnectedDevices: () => Promise<AdbDevice[]>;
54
+ export declare const grantPermissions: (adbId: string, bundleId: string) => Promise<void>;
51
55
  //# 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":"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"}
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);
@@ -430,6 +490,10 @@ export const getLogcatTimestamp = async (adbId) => {
430
490
  ]);
431
491
  return stdout.trim().replace(/^'+|'+$/g, '');
432
492
  };
493
+ export const startLogcat = (adbId, args) => spawn(getAdbBinaryPath(), ['-s', adbId, ...args], {
494
+ stdout: 'pipe',
495
+ stderr: 'pipe',
496
+ });
433
497
  export const getAvds = async () => {
434
498
  try {
435
499
  const emulatorBinaryPath = await ensureEmulatorInstalled();
@@ -468,3 +532,62 @@ export const getConnectedDevices = async () => {
468
532
  }
469
533
  return devices;
470
534
  };
535
+ export const grantPermissions = async (adbId, bundleId) => {
536
+ androidAdbLogger.debug('grantPermissions:start %o', {
537
+ adbId,
538
+ bundleId,
539
+ });
540
+ const isInstalled = await isAppInstalled(adbId, bundleId);
541
+ if (!isInstalled) {
542
+ throw new AdbAppNotInstalledError(bundleId, adbId);
543
+ }
544
+ const [requestedPermissions, dangerousPermissions] = await Promise.all([
545
+ getRequestedPermissions(adbId, bundleId),
546
+ getDangerousPermissions(adbId),
547
+ ]);
548
+ const permissions = requestedPermissions.filter((permission) => dangerousPermissions.has(permission));
549
+ androidAdbLogger.debug('grantPermissions:resolved %o', {
550
+ adbId,
551
+ bundleId,
552
+ requestedPermissions,
553
+ permissions,
554
+ });
555
+ if (permissions.length === 0) {
556
+ androidAdbLogger.debug('grantPermissions:skip %o', {
557
+ adbId,
558
+ bundleId,
559
+ });
560
+ return;
561
+ }
562
+ const grantCommands = permissions.map((permission) => [
563
+ '-s',
564
+ adbId,
565
+ 'shell',
566
+ 'pm',
567
+ 'grant',
568
+ bundleId,
569
+ permission,
570
+ ]);
571
+ try {
572
+ androidAdbLogger.debug('grantPermissions:commands %o', {
573
+ adbId,
574
+ bundleId,
575
+ grantCommands,
576
+ });
577
+ await Promise.all(grantCommands.map((args) => spawn(getAdbBinaryPath(), args)));
578
+ androidAdbLogger.debug('grantPermissions:success %o', {
579
+ adbId,
580
+ bundleId,
581
+ permissions,
582
+ });
583
+ }
584
+ catch (error) {
585
+ androidAdbLogger.debug('grantPermissions:error %o', {
586
+ adbId,
587
+ bundleId,
588
+ permissions,
589
+ error,
590
+ });
591
+ throw new AdbPermissionGrantError(bundleId, permissions, adbId);
592
+ }
593
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"app-monitor.d.ts","sourceRoot":"","sources":["../src/app-monitor.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,eAAe,EAErB,MAAM,iCAAiC,CAAC;AAgTzC,QAAA,MAAM,qBAAqB,GACzB,MAAM,MAAM,EACZ,UAAU,MAAM,KACf,eAAe,GAAG,IAwEpB,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,mDAKrC;IACD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CAC3C,KAAG,iBAgLH,CAAC;AAEF,OAAO,EAAE,qBAAqB,EAAE,CAAC;AACjC,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG;IAC3C,eAAe,EAAE,CACf,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;CACtC,CAAC"}
1
+ {"version":3,"file":"app-monitor.d.ts","sourceRoot":"","sources":["../src/app-monitor.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,eAAe,EAErB,MAAM,iCAAiC,CAAC;AA+SzC,QAAA,MAAM,qBAAqB,GACzB,MAAM,MAAM,EACZ,UAAU,MAAM,KACf,eAAe,GAAG,IAwEpB,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,mDAKrC;IACD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CAC3C,KAAG,iBA4KH,CAAC;AAEF,OAAO,EAAE,qBAAqB,EAAE,CAAC;AACjC,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG;IAC3C,eAAe,EAAE,CACf,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;CACtC,CAAC"}
@@ -1,4 +1,4 @@
1
- import { escapeRegExp, getEmitter, logger, spawn, SubprocessError, } from '@react-native-harness/tools';
1
+ import { escapeRegExp, getEmitter, logger, SubprocessError, } from '@react-native-harness/tools';
2
2
  import * as adb from './adb.js';
3
3
  import { androidCrashParser } from './crash-parser.js';
4
4
  const androidAppMonitorLogger = logger.child('android-app-monitor');
@@ -281,10 +281,7 @@ export const createAndroidAppMonitor = ({ adbId, bundleId, appUid, crashArtifact
281
281
  };
282
282
  const startLogcat = async () => {
283
283
  const logcatTimestamp = await adb.getLogcatTimestamp(adbId);
284
- logcatProcess = spawn('adb', ['-s', adbId, ...getLogcatArgs(appUid, logcatTimestamp)], {
285
- stdout: 'pipe',
286
- stderr: 'pipe',
287
- });
284
+ logcatProcess = adb.startLogcat(adbId, getLogcatArgs(appUid, logcatTimestamp));
288
285
  const currentProcess = logcatProcess;
289
286
  if (!currentProcess) {
290
287
  return;
@@ -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;AAYrB,eAAO,MAAM,sBAAsB,GACjC,MAAM,MAAM,EACZ,MAAM,gBAAgB,KACrB,MAAM,EASR,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"}
@@ -4,8 +4,6 @@ const COMMON_EMULATOR_ARGS = [
4
4
  'swiftshader_indirect',
5
5
  '-noaudio',
6
6
  '-no-boot-anim',
7
- '-camera-back',
8
- 'none',
9
7
  ];
10
8
  export const getEmulatorStartupArgs = (name, mode) => {
11
9
  const modeArgs = mode === 'clean-snapshot-generation'
@@ -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;AAkKrB,eAAO,MAAM,kCAAkC,GAC7C,QAAQ,qBAAqB,EAC7B,eAAe,aAAa,EAC5B,MAAM,0BAA0B,KAC/B,OAAO,CAAC,qBAAqB,CAkI/B,CAAC;AAEF,eAAO,MAAM,wCAAwC,GACnD,QAAQ,qBAAqB,EAC7B,eAAe,aAAa,KAC3B,OAAO,CAAC,qBAAqB,CAiE/B,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
- if (!isInstalled) {
145
- const appPath = getHarnessAppPath();
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 ??