@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.
- package/dist/__tests__/adb-id.test.d.ts +2 -0
- package/dist/__tests__/adb-id.test.d.ts.map +1 -0
- package/dist/__tests__/adb-id.test.js +41 -0
- package/dist/__tests__/adb.test.js +148 -13
- package/dist/__tests__/app-monitor.test.js +52 -75
- package/dist/__tests__/emulator-startup.test.js +1 -0
- package/dist/__tests__/instance.test.js +97 -36
- 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 +4 -0
- package/dist/adb.d.ts.map +1 -1
- package/dist/adb.js +124 -1
- package/dist/app-monitor.d.ts.map +1 -1
- package/dist/app-monitor.js +2 -5
- 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/eslint.config.mjs +1 -0
- package/package.json +5 -4
- package/src/__tests__/adb-id.test.ts +51 -0
- package/src/__tests__/emulator-startup.test.ts +3 -0
- package/src/__tests__/instance.test.ts +143 -51
- package/src/adb-errors.ts +47 -0
- package/src/adb-id.ts +4 -0
- package/src/adb.ts +174 -1
- package/src/app-monitor.ts +3 -8
- package/src/emulator-startup.ts +0 -2
- package/src/instance.ts +30 -2
- package/test/setup.ts +68 -0
- package/vite.config.ts +19 -0
- package/src/__tests__/adb.test.ts +0 -598
- package/src/__tests__/app-monitor.test.ts +0 -273
- package/src/__tests__/environment.test.ts +0 -212
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adb-id.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/adb-id.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { getAdbId, isAdbIdEmulator } from '../adb-id.js';
|
|
3
|
+
import * as adb from '../adb.js';
|
|
4
|
+
describe('adb id resolution', () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
vi.restoreAllMocks();
|
|
7
|
+
});
|
|
8
|
+
it('identifies emulator adb ids', () => {
|
|
9
|
+
expect(isAdbIdEmulator('emulator-5554')).toBe(true);
|
|
10
|
+
expect(isAdbIdEmulator('device-serial-001')).toBe(false);
|
|
11
|
+
});
|
|
12
|
+
it('skips non-emulator ids when resolving an emulator device', async () => {
|
|
13
|
+
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([
|
|
14
|
+
'device-serial-001',
|
|
15
|
+
'emulator-5554',
|
|
16
|
+
]);
|
|
17
|
+
vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Test_AVD_API_35');
|
|
18
|
+
await expect(getAdbId({
|
|
19
|
+
type: 'emulator',
|
|
20
|
+
name: 'Test_AVD_API_35',
|
|
21
|
+
})).resolves.toBe('emulator-5554');
|
|
22
|
+
});
|
|
23
|
+
it('resolves matching physical device by manufacturer and model', async () => {
|
|
24
|
+
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([
|
|
25
|
+
'device-serial-001',
|
|
26
|
+
'emulator-5554',
|
|
27
|
+
]);
|
|
28
|
+
vi.spyOn(adb, 'getDeviceInfo').mockImplementation(async (adbId) => {
|
|
29
|
+
const results = {
|
|
30
|
+
'device-serial-001': { manufacturer: 'Acme', model: 'Model A1' },
|
|
31
|
+
'emulator-5554': { manufacturer: 'Emulator', model: 'Emulator Model' },
|
|
32
|
+
};
|
|
33
|
+
return results[adbId] || null;
|
|
34
|
+
});
|
|
35
|
+
await expect(getAdbId({
|
|
36
|
+
type: 'physical',
|
|
37
|
+
manufacturer: 'acme',
|
|
38
|
+
model: 'model a1',
|
|
39
|
+
})).resolves.toBe('device-serial-001');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
2
5
|
import { PassThrough } from 'node:stream';
|
|
3
6
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
7
|
import { SubprocessError } from '@react-native-harness/tools';
|
|
5
|
-
|
|
8
|
+
vi.unmock('../adb.js');
|
|
9
|
+
import { createAvd, deleteAvd, emulatorProcess, getAppUid, getLogcatTimestamp, getStartAppArgs, hasAvd, installApp, startLogcat, startEmulator, uninstallApp, waitForBoot, waitForEmulatorDisconnect, } from '../adb.js';
|
|
6
10
|
import * as tools from '@react-native-harness/tools';
|
|
7
11
|
import * as environment from '../environment.js';
|
|
8
12
|
import * as avdConfig from '../avd-config.js';
|
|
9
13
|
const createAbortError = () => new DOMException('The operation was aborted', 'AbortError');
|
|
14
|
+
let sdkRoot;
|
|
10
15
|
const createMockChildProcess = () => {
|
|
11
16
|
const process = new EventEmitter();
|
|
12
17
|
process.stdout = new PassThrough();
|
|
@@ -16,6 +21,14 @@ const createMockChildProcess = () => {
|
|
|
16
21
|
};
|
|
17
22
|
beforeEach(() => {
|
|
18
23
|
vi.restoreAllMocks();
|
|
24
|
+
vi.unstubAllEnvs();
|
|
25
|
+
sdkRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'android-sdk-'));
|
|
26
|
+
fs.mkdirSync(path.join(sdkRoot, 'emulator'), { recursive: true });
|
|
27
|
+
fs.writeFileSync(path.join(sdkRoot, 'emulator', 'emulator'), '');
|
|
28
|
+
fs.mkdirSync(path.join(sdkRoot, 'platform-tools'), { recursive: true });
|
|
29
|
+
fs.writeFileSync(path.join(sdkRoot, 'platform-tools', 'adb'), '');
|
|
30
|
+
vi.stubEnv('ANDROID_HOME', sdkRoot);
|
|
31
|
+
vi.spyOn(environment, 'ensureAndroidEmulatorAvailable').mockResolvedValue(sdkRoot);
|
|
19
32
|
});
|
|
20
33
|
describe('getStartAppArgs', () => {
|
|
21
34
|
it('maps supported extras to adb am start flags', () => {
|
|
@@ -81,8 +94,31 @@ describe('getStartAppArgs', () => {
|
|
|
81
94
|
"+'%m-%d %H:%M:%S.000'",
|
|
82
95
|
]);
|
|
83
96
|
});
|
|
97
|
+
it('starts logcat using the resolved adb binary path', async () => {
|
|
98
|
+
const subprocess = {};
|
|
99
|
+
const spawnSpy = vi.spyOn(tools, 'spawn').mockReturnValue(subprocess);
|
|
100
|
+
expect(startLogcat('emulator-5554', [
|
|
101
|
+
'logcat',
|
|
102
|
+
'-v',
|
|
103
|
+
'threadtime',
|
|
104
|
+
'-b',
|
|
105
|
+
'crash',
|
|
106
|
+
])).toBe(subprocess);
|
|
107
|
+
expect(spawnSpy).toHaveBeenCalledWith(expect.stringMatching(/adb$/), [
|
|
108
|
+
'-s',
|
|
109
|
+
'emulator-5554',
|
|
110
|
+
'logcat',
|
|
111
|
+
'-v',
|
|
112
|
+
'threadtime',
|
|
113
|
+
'-b',
|
|
114
|
+
'crash',
|
|
115
|
+
], {
|
|
116
|
+
stdout: 'pipe',
|
|
117
|
+
stderr: 'pipe',
|
|
118
|
+
});
|
|
119
|
+
});
|
|
84
120
|
it('checks whether an AVD exists', async () => {
|
|
85
|
-
vi.spyOn(tools, 'spawn').
|
|
121
|
+
vi.spyOn(tools, 'spawn').mockResolvedValue({
|
|
86
122
|
stdout: 'Pixel_6_API_33\nPixel_8_API_35\n',
|
|
87
123
|
});
|
|
88
124
|
await expect(hasAvd('Pixel_8_API_35')).resolves.toBe(true);
|
|
@@ -101,6 +137,18 @@ describe('getStartAppArgs', () => {
|
|
|
101
137
|
'/tmp/app.apk',
|
|
102
138
|
]);
|
|
103
139
|
});
|
|
140
|
+
it('uninstalls the app via adb', async () => {
|
|
141
|
+
const spawnSpy = vi
|
|
142
|
+
.spyOn(tools, 'spawn')
|
|
143
|
+
.mockResolvedValueOnce({});
|
|
144
|
+
await uninstallApp('emulator-5554', 'com.example.app');
|
|
145
|
+
expect(spawnSpy).toHaveBeenCalledWith(expect.stringMatching(/adb$/), [
|
|
146
|
+
'-s',
|
|
147
|
+
'emulator-5554',
|
|
148
|
+
'uninstall',
|
|
149
|
+
'com.example.app',
|
|
150
|
+
]);
|
|
151
|
+
});
|
|
104
152
|
it('creates an AVD and appends config overrides', async () => {
|
|
105
153
|
vi.spyOn(avdConfig, 'readAvdConfig').mockResolvedValue({});
|
|
106
154
|
const spawnSpy = vi
|
|
@@ -353,17 +401,24 @@ describe('getStartAppArgs', () => {
|
|
|
353
401
|
});
|
|
354
402
|
it('aborts while waiting for boot completion', async () => {
|
|
355
403
|
vi.useFakeTimers();
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
404
|
+
vi.spyOn(tools, 'spawn').mockImplementation((async (_file, args) => {
|
|
405
|
+
if (args?.[0] === 'devices') {
|
|
406
|
+
return {
|
|
407
|
+
stdout: 'List of devices attached\nemulator-5554\tdevice\n',
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
if (args?.includes('emu') && args?.includes('avd') && args?.includes('name')) {
|
|
411
|
+
return {
|
|
412
|
+
stdout: 'Pixel_8_API_35\n',
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
if (args?.includes('getprop')) {
|
|
416
|
+
return {
|
|
417
|
+
stdout: '0\n',
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
throw new Error(`Unexpected adb call: ${args?.join(' ')}`);
|
|
421
|
+
}));
|
|
367
422
|
const controller = new AbortController();
|
|
368
423
|
const waitPromise = waitForBoot('Pixel_8_API_35', controller.signal);
|
|
369
424
|
await vi.advanceTimersByTimeAsync(1000);
|
|
@@ -414,4 +469,84 @@ describe('getStartAppArgs', () => {
|
|
|
414
469
|
await expect(waitPromise).resolves.toBeUndefined();
|
|
415
470
|
expect(spawnSpy).toHaveBeenCalledTimes(2);
|
|
416
471
|
});
|
|
472
|
+
it('grants requested dangerous permissions to an app', async () => {
|
|
473
|
+
const { grantPermissions } = await import('../adb.js');
|
|
474
|
+
const spawnSpy = vi
|
|
475
|
+
.spyOn(tools, 'spawn')
|
|
476
|
+
.mockResolvedValueOnce({
|
|
477
|
+
stdout: 'package:com.example.app\n',
|
|
478
|
+
})
|
|
479
|
+
.mockResolvedValueOnce({
|
|
480
|
+
stdout: `requested permissions:\n android.permission.CAMERA\n android.permission.INTERNET\n android.permission.ACCESS_FINE_LOCATION\ninstall permissions:\n`,
|
|
481
|
+
})
|
|
482
|
+
.mockResolvedValueOnce({
|
|
483
|
+
stdout: `Dangerous Permissions:\n permission:android.permission.CAMERA\n permission:android.permission.ACCESS_FINE_LOCATION\n`,
|
|
484
|
+
})
|
|
485
|
+
.mockResolvedValue({
|
|
486
|
+
stdout: '',
|
|
487
|
+
});
|
|
488
|
+
await grantPermissions('emulator-5554', 'com.example.app');
|
|
489
|
+
expect(spawnSpy).toHaveBeenCalledTimes(5);
|
|
490
|
+
expect(spawnSpy).toHaveBeenNthCalledWith(1, expect.any(String), [
|
|
491
|
+
'-s',
|
|
492
|
+
'emulator-5554',
|
|
493
|
+
'shell',
|
|
494
|
+
'pm',
|
|
495
|
+
'list',
|
|
496
|
+
'packages',
|
|
497
|
+
'com.example.app',
|
|
498
|
+
]);
|
|
499
|
+
expect(spawnSpy).toHaveBeenNthCalledWith(2, expect.any(String), [
|
|
500
|
+
'-s',
|
|
501
|
+
'emulator-5554',
|
|
502
|
+
'shell',
|
|
503
|
+
'dumpsys',
|
|
504
|
+
'package',
|
|
505
|
+
'com.example.app',
|
|
506
|
+
]);
|
|
507
|
+
expect(spawnSpy).toHaveBeenNthCalledWith(3, expect.any(String), [
|
|
508
|
+
'-s',
|
|
509
|
+
'emulator-5554',
|
|
510
|
+
'shell',
|
|
511
|
+
'pm',
|
|
512
|
+
'list',
|
|
513
|
+
'permissions',
|
|
514
|
+
'-g',
|
|
515
|
+
'-d',
|
|
516
|
+
]);
|
|
517
|
+
expect(spawnSpy).toHaveBeenNthCalledWith(4, expect.any(String), [
|
|
518
|
+
'-s',
|
|
519
|
+
'emulator-5554',
|
|
520
|
+
'shell',
|
|
521
|
+
'pm',
|
|
522
|
+
'grant',
|
|
523
|
+
'com.example.app',
|
|
524
|
+
'android.permission.CAMERA',
|
|
525
|
+
]);
|
|
526
|
+
expect(spawnSpy).toHaveBeenNthCalledWith(5, expect.any(String), [
|
|
527
|
+
'-s',
|
|
528
|
+
'emulator-5554',
|
|
529
|
+
'shell',
|
|
530
|
+
'pm',
|
|
531
|
+
'grant',
|
|
532
|
+
'com.example.app',
|
|
533
|
+
'android.permission.ACCESS_FINE_LOCATION',
|
|
534
|
+
]);
|
|
535
|
+
});
|
|
536
|
+
it('does nothing when the app has no grantable dangerous permissions', async () => {
|
|
537
|
+
const { grantPermissions } = await import('../adb.js');
|
|
538
|
+
const spawnSpy = vi
|
|
539
|
+
.spyOn(tools, 'spawn')
|
|
540
|
+
.mockResolvedValueOnce({
|
|
541
|
+
stdout: 'package:com.example.app\n',
|
|
542
|
+
})
|
|
543
|
+
.mockResolvedValueOnce({
|
|
544
|
+
stdout: `requested permissions:\n android.permission.INTERNET\ninstall permissions:\n`,
|
|
545
|
+
})
|
|
546
|
+
.mockResolvedValueOnce({
|
|
547
|
+
stdout: `Dangerous Permissions:\n permission:android.permission.CAMERA\n`,
|
|
548
|
+
});
|
|
549
|
+
await grantPermissions('emulator-5554', 'com.example.app');
|
|
550
|
+
expect(spawnSpy).toHaveBeenCalledTimes(3);
|
|
551
|
+
});
|
|
417
552
|
});
|
|
@@ -3,8 +3,8 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { createAndroidAppMonitor, createAndroidLogEvent } from '../app-monitor.js';
|
|
6
|
-
import * as tools from '@react-native-harness/tools';
|
|
7
6
|
import { createCrashArtifactWriter } from '@react-native-harness/tools';
|
|
7
|
+
import * as adb from '../adb.js';
|
|
8
8
|
const createMockSubprocess = () => ({
|
|
9
9
|
nodeChildProcess: Promise.resolve({
|
|
10
10
|
kill: vi.fn(),
|
|
@@ -55,15 +55,12 @@ describe('createAndroidLogEvent', () => {
|
|
|
55
55
|
});
|
|
56
56
|
});
|
|
57
57
|
it('starts logcat from the current device timestamp', async () => {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
return createMockSubprocess();
|
|
66
|
-
}));
|
|
58
|
+
const getLogcatTimestampSpy = vi
|
|
59
|
+
.spyOn(adb, 'getLogcatTimestamp')
|
|
60
|
+
.mockResolvedValue('03-12 11:35:08.000');
|
|
61
|
+
const startLogcatSpy = vi
|
|
62
|
+
.spyOn(adb, 'startLogcat')
|
|
63
|
+
.mockReturnValue(createMockSubprocess());
|
|
67
64
|
const monitor = createAndroidAppMonitor({
|
|
68
65
|
adbId: 'emulator-5554',
|
|
69
66
|
bundleId: 'com.harnessplayground',
|
|
@@ -71,9 +68,8 @@ describe('createAndroidLogEvent', () => {
|
|
|
71
68
|
});
|
|
72
69
|
await monitor.start();
|
|
73
70
|
await monitor.stop();
|
|
74
|
-
expect(
|
|
75
|
-
|
|
76
|
-
'emulator-5554',
|
|
71
|
+
expect(getLogcatTimestampSpy).toHaveBeenCalledWith('emulator-5554');
|
|
72
|
+
expect(startLogcatSpy).toHaveBeenCalledWith('emulator-5554', [
|
|
77
73
|
'logcat',
|
|
78
74
|
'-v',
|
|
79
75
|
'threadtime',
|
|
@@ -82,34 +78,24 @@ describe('createAndroidLogEvent', () => {
|
|
|
82
78
|
'--uid=10234',
|
|
83
79
|
'-T',
|
|
84
80
|
'03-12 11:35:08.000',
|
|
85
|
-
]
|
|
86
|
-
stdout: 'pipe',
|
|
87
|
-
stderr: 'pipe',
|
|
88
|
-
});
|
|
81
|
+
]);
|
|
89
82
|
});
|
|
90
83
|
it('hydrates crash details with stack lines that arrive after the first crash event', async () => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
line: '03-12 10:44:40.422 13861 13861 E AndroidRuntime: at com.harnessplayground.MainActivity.onCreate(MainActivity.kt:42)',
|
|
109
|
-
delayMs: 25,
|
|
110
|
-
},
|
|
111
|
-
]);
|
|
112
|
-
}));
|
|
84
|
+
vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('03-12 10:44:40.000');
|
|
85
|
+
vi.spyOn(adb, 'startLogcat').mockReturnValue(createStreamingSubprocess([
|
|
86
|
+
{ line: '--------- beginning of crash' },
|
|
87
|
+
{
|
|
88
|
+
line: '03-12 10:44:40.420 13861 13861 E AndroidRuntime: Process: com.harnessplayground, PID: 13861',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
line: '03-12 10:44:40.421 13861 13861 E AndroidRuntime: java.lang.RuntimeException: boom',
|
|
92
|
+
delayMs: 25,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
line: '03-12 10:44:40.422 13861 13861 E AndroidRuntime: at com.harnessplayground.MainActivity.onCreate(MainActivity.kt:42)',
|
|
96
|
+
delayMs: 25,
|
|
97
|
+
},
|
|
98
|
+
]));
|
|
113
99
|
const monitor = createAndroidAppMonitor({
|
|
114
100
|
adbId: 'emulator-5554',
|
|
115
101
|
bundleId: 'com.harnessplayground',
|
|
@@ -130,24 +116,17 @@ describe('createAndroidLogEvent', () => {
|
|
|
130
116
|
]);
|
|
131
117
|
});
|
|
132
118
|
it('persists resolved Android crash blocks into .harness', async () => {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
line: '03-12 10:44:40.421 13861 13861 E AndroidRuntime: java.lang.RuntimeException: boom',
|
|
147
|
-
delayMs: 20,
|
|
148
|
-
},
|
|
149
|
-
]);
|
|
150
|
-
}));
|
|
119
|
+
vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('03-12 10:44:40.000');
|
|
120
|
+
vi.spyOn(adb, 'startLogcat').mockReturnValue(createStreamingSubprocess([
|
|
121
|
+
{ line: '--------- beginning of crash' },
|
|
122
|
+
{
|
|
123
|
+
line: '03-12 10:44:40.420 13861 13861 E AndroidRuntime: Process: com.harnessplayground, PID: 13861',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
line: '03-12 10:44:40.421 13861 13861 E AndroidRuntime: java.lang.RuntimeException: boom',
|
|
127
|
+
delayMs: 20,
|
|
128
|
+
},
|
|
129
|
+
]));
|
|
151
130
|
const monitor = createAndroidAppMonitor({
|
|
152
131
|
adbId: 'emulator-5554',
|
|
153
132
|
bundleId: 'com.harnessplayground',
|
|
@@ -171,21 +150,13 @@ describe('createAndroidLogEvent', () => {
|
|
|
171
150
|
expect(fs.readFileSync(details.artifactPath, 'utf8')).toContain('RuntimeException: boom');
|
|
172
151
|
});
|
|
173
152
|
it('can be started again after timestamp lookup fails', async () => {
|
|
174
|
-
const spawnSpy = vi.spyOn(tools, 'spawn');
|
|
175
153
|
const timestampError = new Error('date failed');
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
return {
|
|
184
|
-
stdout: '03-12 11:35:08.000\n',
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
return createMockSubprocess();
|
|
188
|
-
}));
|
|
154
|
+
vi.spyOn(adb, 'getLogcatTimestamp')
|
|
155
|
+
.mockRejectedValueOnce(timestampError)
|
|
156
|
+
.mockResolvedValueOnce('03-12 11:35:08.000');
|
|
157
|
+
const startLogcatSpy = vi
|
|
158
|
+
.spyOn(adb, 'startLogcat')
|
|
159
|
+
.mockReturnValue(createMockSubprocess());
|
|
189
160
|
const monitor = createAndroidAppMonitor({
|
|
190
161
|
adbId: 'emulator-5554',
|
|
191
162
|
bundleId: 'com.harnessplayground',
|
|
@@ -194,9 +165,15 @@ describe('createAndroidLogEvent', () => {
|
|
|
194
165
|
await expect(monitor.start()).rejects.toThrow(timestampError);
|
|
195
166
|
await expect(monitor.start()).resolves.toBeUndefined();
|
|
196
167
|
await monitor.stop();
|
|
197
|
-
expect(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
168
|
+
expect(startLogcatSpy).toHaveBeenCalledWith('emulator-5554', [
|
|
169
|
+
'logcat',
|
|
170
|
+
'-v',
|
|
171
|
+
'threadtime',
|
|
172
|
+
'-b',
|
|
173
|
+
'crash',
|
|
174
|
+
'--uid=10234',
|
|
175
|
+
'-T',
|
|
176
|
+
'03-12 11:35:08.000',
|
|
177
|
+
]);
|
|
201
178
|
});
|
|
202
179
|
});
|
|
@@ -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']));
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import { DEFAULT_METRO_PORT, } from '@react-native-harness/config';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import os from 'node:os';
|
|
5
|
-
import path from 'node:path';
|
|
6
3
|
import { getAndroidEmulatorPlatformInstance, getAndroidPhysicalDevicePlatformInstance, } from '../instance.js';
|
|
7
4
|
import * as adb from '../adb.js';
|
|
8
5
|
import * as avdConfig from '../avd-config.js';
|
|
@@ -19,9 +16,10 @@ const init = {
|
|
|
19
16
|
signal: new AbortController().signal,
|
|
20
17
|
};
|
|
21
18
|
describe('Android platform instance', () => {
|
|
22
|
-
beforeEach(() => {
|
|
19
|
+
beforeEach(async () => {
|
|
23
20
|
vi.restoreAllMocks();
|
|
24
21
|
vi.unstubAllEnvs();
|
|
22
|
+
vi.spyOn(await import('../environment.js'), 'ensureAndroidEmulatorAvailable').mockResolvedValue('/tmp/android-sdk');
|
|
25
23
|
});
|
|
26
24
|
it('reuses a running emulator and does not shut it down on dispose', async () => {
|
|
27
25
|
const ensureAndroidEmulatorEnvironment = vi
|
|
@@ -137,6 +135,7 @@ describe('Android platform instance', () => {
|
|
|
137
135
|
});
|
|
138
136
|
it('reuses a compatible cached AVD snapshot when caching is enabled', async () => {
|
|
139
137
|
vi.stubEnv('HARNESS_AVD_CACHING', 'true');
|
|
138
|
+
vi.spyOn(await import('../environment.js'), 'getHostAndroidSystemImageArch').mockReturnValue('arm64-v8a');
|
|
140
139
|
vi.spyOn(await import('../environment.js'), 'ensureAndroidEmulatorEnvironment').mockResolvedValue('/tmp/android-sdk');
|
|
141
140
|
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([]);
|
|
142
141
|
vi.spyOn(adb, 'hasAvd').mockResolvedValue(true);
|
|
@@ -261,38 +260,6 @@ describe('Android platform instance', () => {
|
|
|
261
260
|
expect(waitForEmulatorDisconnect).toHaveBeenCalledWith('emulator-5554', init.signal);
|
|
262
261
|
expect(startEmulator).toHaveBeenNthCalledWith(2, 'Pixel_8_API_35', 'snapshot-reuse');
|
|
263
262
|
});
|
|
264
|
-
it('installs the app from HARNESS_APP_PATH when missing', async () => {
|
|
265
|
-
const appPath = path.join(os.tmpdir(), 'HarnessPlayground.apk');
|
|
266
|
-
fs.writeFileSync(appPath, 'apk');
|
|
267
|
-
vi.stubEnv('HARNESS_APP_PATH', appPath);
|
|
268
|
-
vi.spyOn(await import('../environment.js'), 'ensureAndroidEmulatorEnvironment').mockResolvedValue('/tmp/android-sdk');
|
|
269
|
-
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
|
|
270
|
-
vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
|
|
271
|
-
vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
|
|
272
|
-
vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(false);
|
|
273
|
-
const installApp = vi.spyOn(adb, 'installApp').mockResolvedValue(undefined);
|
|
274
|
-
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
275
|
-
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
276
|
-
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
277
|
-
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
|
|
278
|
-
await expect(getAndroidEmulatorPlatformInstance({
|
|
279
|
-
name: 'android',
|
|
280
|
-
device: {
|
|
281
|
-
type: 'emulator',
|
|
282
|
-
name: 'Pixel_8_API_35',
|
|
283
|
-
avd: {
|
|
284
|
-
apiLevel: 35,
|
|
285
|
-
profile: 'pixel_8',
|
|
286
|
-
diskSize: '1G',
|
|
287
|
-
heapSize: '1G',
|
|
288
|
-
},
|
|
289
|
-
},
|
|
290
|
-
bundleId: 'com.harnessplayground',
|
|
291
|
-
activityName: '.MainActivity',
|
|
292
|
-
}, harnessConfig, init)).resolves.toBeDefined();
|
|
293
|
-
expect(installApp).toHaveBeenCalledWith('emulator-5554', appPath);
|
|
294
|
-
fs.rmSync(appPath, { force: true });
|
|
295
|
-
});
|
|
296
263
|
it('throws a HarnessAppPathError when HARNESS_APP_PATH is missing', async () => {
|
|
297
264
|
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
|
|
298
265
|
vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
|
|
@@ -420,4 +387,98 @@ describe('Android platform instance', () => {
|
|
|
420
387
|
expect(appMonitor.addListener(listener)).toBeUndefined();
|
|
421
388
|
expect(appMonitor.removeListener(listener)).toBeUndefined();
|
|
422
389
|
});
|
|
390
|
+
it('grants permissions when permissions are enabled for emulator', async () => {
|
|
391
|
+
vi.spyOn(await import('../environment.js'), 'ensureAndroidEmulatorEnvironment').mockResolvedValue('/tmp/android-sdk');
|
|
392
|
+
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
|
|
393
|
+
vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
|
|
394
|
+
vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
|
|
395
|
+
vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
|
|
396
|
+
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
397
|
+
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
398
|
+
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
399
|
+
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
|
|
400
|
+
const grantPermissions = vi
|
|
401
|
+
.spyOn(adb, 'grantPermissions')
|
|
402
|
+
.mockResolvedValue(undefined);
|
|
403
|
+
const harnessConfigWithPermissions = {
|
|
404
|
+
...harnessConfig,
|
|
405
|
+
permissions: true,
|
|
406
|
+
};
|
|
407
|
+
await getAndroidEmulatorPlatformInstance({
|
|
408
|
+
name: 'android',
|
|
409
|
+
device: {
|
|
410
|
+
type: 'emulator',
|
|
411
|
+
name: 'Pixel_8_API_35',
|
|
412
|
+
avd: {
|
|
413
|
+
apiLevel: 35,
|
|
414
|
+
profile: 'pixel_8',
|
|
415
|
+
diskSize: '1G',
|
|
416
|
+
heapSize: '1G',
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
bundleId: 'com.harnessplayground',
|
|
420
|
+
activityName: '.MainActivity',
|
|
421
|
+
}, harnessConfigWithPermissions, init);
|
|
422
|
+
expect(grantPermissions).toHaveBeenCalledWith('emulator-5554', 'com.harnessplayground');
|
|
423
|
+
});
|
|
424
|
+
it('does not grant permissions when permissions are disabled for emulator', async () => {
|
|
425
|
+
vi.spyOn(await import('../environment.js'), 'ensureAndroidEmulatorEnvironment').mockResolvedValue('/tmp/android-sdk');
|
|
426
|
+
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
|
|
427
|
+
vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
|
|
428
|
+
vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
|
|
429
|
+
vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
|
|
430
|
+
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
431
|
+
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
432
|
+
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
433
|
+
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
|
|
434
|
+
const grantPermissions = vi
|
|
435
|
+
.spyOn(adb, 'grantPermissions')
|
|
436
|
+
.mockResolvedValue(undefined);
|
|
437
|
+
await getAndroidEmulatorPlatformInstance({
|
|
438
|
+
name: 'android',
|
|
439
|
+
device: {
|
|
440
|
+
type: 'emulator',
|
|
441
|
+
name: 'Pixel_8_API_35',
|
|
442
|
+
avd: {
|
|
443
|
+
apiLevel: 35,
|
|
444
|
+
profile: 'pixel_8',
|
|
445
|
+
diskSize: '1G',
|
|
446
|
+
heapSize: '1G',
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
bundleId: 'com.harnessplayground',
|
|
450
|
+
activityName: '.MainActivity',
|
|
451
|
+
}, harnessConfig, init);
|
|
452
|
+
expect(grantPermissions).not.toHaveBeenCalled();
|
|
453
|
+
});
|
|
454
|
+
it('grants permissions when permissions are enabled for physical device', async () => {
|
|
455
|
+
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['012345']);
|
|
456
|
+
vi.spyOn(adb, 'getDeviceInfo').mockResolvedValue({
|
|
457
|
+
manufacturer: 'motorola',
|
|
458
|
+
model: 'moto g72',
|
|
459
|
+
});
|
|
460
|
+
vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
|
|
461
|
+
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
462
|
+
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
463
|
+
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
464
|
+
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
|
|
465
|
+
const grantPermissions = vi
|
|
466
|
+
.spyOn(adb, 'grantPermissions')
|
|
467
|
+
.mockResolvedValue(undefined);
|
|
468
|
+
const harnessConfigWithPermissions = {
|
|
469
|
+
...harnessConfig,
|
|
470
|
+
permissions: true,
|
|
471
|
+
};
|
|
472
|
+
await getAndroidPhysicalDevicePlatformInstance({
|
|
473
|
+
name: 'android-device',
|
|
474
|
+
device: {
|
|
475
|
+
type: 'physical',
|
|
476
|
+
manufacturer: 'motorola',
|
|
477
|
+
model: 'moto g72',
|
|
478
|
+
},
|
|
479
|
+
bundleId: 'com.harnessplayground',
|
|
480
|
+
activityName: '.MainActivity',
|
|
481
|
+
}, harnessConfigWithPermissions);
|
|
482
|
+
expect(grantPermissions).toHaveBeenCalledWith('012345', 'com.harnessplayground');
|
|
483
|
+
});
|
|
423
484
|
});
|
|
@@ -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"}
|