@react-native-harness/platform-android 1.1.0-rc.1 → 1.1.0-rc.3

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.
Files changed (81) hide show
  1. package/README.md +9 -2
  2. package/dist/__tests__/adb.test.js +283 -10
  3. package/dist/__tests__/avd-config.test.d.ts +2 -0
  4. package/dist/__tests__/avd-config.test.d.ts.map +1 -0
  5. package/dist/__tests__/avd-config.test.js +174 -0
  6. package/dist/__tests__/ci-action.test.d.ts +2 -0
  7. package/dist/__tests__/ci-action.test.d.ts.map +1 -0
  8. package/dist/__tests__/ci-action.test.js +46 -0
  9. package/dist/__tests__/emulator-startup.test.d.ts +2 -0
  10. package/dist/__tests__/emulator-startup.test.d.ts.map +1 -0
  11. package/dist/__tests__/emulator-startup.test.js +19 -0
  12. package/dist/__tests__/environment.test.d.ts +2 -0
  13. package/dist/__tests__/environment.test.d.ts.map +1 -0
  14. package/dist/__tests__/environment.test.js +51 -0
  15. package/dist/__tests__/instance.test.d.ts +2 -0
  16. package/dist/__tests__/instance.test.d.ts.map +1 -0
  17. package/dist/__tests__/instance.test.js +423 -0
  18. package/dist/__tests__/shared-prefs.test.d.ts +2 -0
  19. package/dist/__tests__/shared-prefs.test.d.ts.map +1 -0
  20. package/dist/__tests__/shared-prefs.test.js +87 -0
  21. package/dist/adb.d.ts +23 -0
  22. package/dist/adb.d.ts.map +1 -1
  23. package/dist/adb.js +265 -16
  24. package/dist/app-monitor.d.ts.map +1 -1
  25. package/dist/app-monitor.js +29 -8
  26. package/dist/avd-config.d.ts +41 -0
  27. package/dist/avd-config.d.ts.map +1 -0
  28. package/dist/avd-config.js +173 -0
  29. package/dist/config.d.ts +77 -0
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/config.js +5 -0
  32. package/dist/emulator-startup.d.ts +3 -0
  33. package/dist/emulator-startup.d.ts.map +1 -0
  34. package/dist/emulator-startup.js +17 -0
  35. package/dist/environment.d.ts +28 -0
  36. package/dist/environment.d.ts.map +1 -0
  37. package/dist/environment.js +295 -0
  38. package/dist/errors.d.ts +4 -12
  39. package/dist/errors.d.ts.map +1 -1
  40. package/dist/errors.js +10 -24
  41. package/dist/factory.d.ts.map +1 -1
  42. package/dist/factory.js +3 -0
  43. package/dist/index.d.ts +3 -0
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +3 -0
  46. package/dist/instance.d.ts +6 -0
  47. package/dist/instance.d.ts.map +1 -0
  48. package/dist/instance.js +234 -0
  49. package/dist/runner.d.ts +3 -3
  50. package/dist/runner.d.ts.map +1 -1
  51. package/dist/runner.js +12 -48
  52. package/dist/shared-prefs.d.ts +3 -0
  53. package/dist/shared-prefs.d.ts.map +1 -0
  54. package/dist/shared-prefs.js +92 -0
  55. package/dist/targets.d.ts +1 -1
  56. package/dist/targets.d.ts.map +1 -1
  57. package/dist/targets.js +2 -0
  58. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  59. package/package.json +4 -4
  60. package/src/__tests__/adb.test.ts +419 -15
  61. package/src/__tests__/avd-config.test.ts +206 -0
  62. package/src/__tests__/ci-action.test.ts +81 -0
  63. package/src/__tests__/emulator-startup.test.ts +32 -0
  64. package/src/__tests__/environment.test.ts +87 -0
  65. package/src/__tests__/instance.test.ts +610 -0
  66. package/src/__tests__/shared-prefs.test.ts +144 -0
  67. package/src/adb.ts +423 -16
  68. package/src/app-monitor.ts +58 -18
  69. package/src/avd-config.ts +290 -0
  70. package/src/config.ts +8 -0
  71. package/src/emulator-startup.ts +28 -0
  72. package/src/environment.ts +510 -0
  73. package/src/errors.ts +19 -0
  74. package/src/factory.ts +4 -0
  75. package/src/index.ts +7 -1
  76. package/src/instance.ts +380 -0
  77. package/src/runner.ts +25 -63
  78. package/src/shared-prefs.ts +205 -0
  79. package/src/targets.ts +11 -8
  80. package/tsconfig.json +2 -2
  81. package/tsconfig.lib.json +2 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instance.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/instance.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,423 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
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
+ import { getAndroidEmulatorPlatformInstance, getAndroidPhysicalDevicePlatformInstance, } from '../instance.js';
7
+ import * as adb from '../adb.js';
8
+ import * as avdConfig from '../avd-config.js';
9
+ import * as sharedPrefs from '../shared-prefs.js';
10
+ import { HarnessAppPathError, HarnessEmulatorConfigError } from '../errors.js';
11
+ const harnessConfig = {
12
+ metroPort: DEFAULT_METRO_PORT,
13
+ };
14
+ const harnessConfigWithoutNativeCrashDetection = {
15
+ metroPort: DEFAULT_METRO_PORT,
16
+ detectNativeCrashes: false,
17
+ };
18
+ const init = {
19
+ signal: new AbortController().signal,
20
+ };
21
+ describe('Android platform instance', () => {
22
+ beforeEach(() => {
23
+ vi.restoreAllMocks();
24
+ vi.unstubAllEnvs();
25
+ });
26
+ it('reuses a running emulator and does not shut it down on dispose', async () => {
27
+ const ensureAndroidEmulatorEnvironment = vi
28
+ .spyOn(await import('../environment.js'), 'ensureAndroidEmulatorEnvironment')
29
+ .mockResolvedValue('/tmp/android-sdk');
30
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
31
+ vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
32
+ vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
33
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
34
+ vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
35
+ vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
36
+ vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
37
+ vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
38
+ vi.spyOn(sharedPrefs, 'clearHarnessDebugHttpHost').mockResolvedValue(undefined);
39
+ vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
40
+ const stopEmulator = vi.spyOn(adb, 'stopEmulator').mockResolvedValue();
41
+ const instance = await getAndroidEmulatorPlatformInstance({
42
+ name: 'android',
43
+ device: {
44
+ type: 'emulator',
45
+ name: 'Pixel_8_API_35',
46
+ avd: {
47
+ apiLevel: 35,
48
+ profile: 'pixel_8',
49
+ diskSize: '1G',
50
+ heapSize: '1G',
51
+ },
52
+ },
53
+ bundleId: 'com.harnessplayground',
54
+ activityName: '.MainActivity',
55
+ }, harnessConfig, init);
56
+ await instance.dispose();
57
+ expect(ensureAndroidEmulatorEnvironment).toHaveBeenCalledWith(35);
58
+ expect(stopEmulator).not.toHaveBeenCalled();
59
+ });
60
+ it('creates and boots an emulator when missing and shuts it down on dispose', async () => {
61
+ vi.spyOn(await import('../environment.js'), 'ensureAndroidEmulatorEnvironment').mockResolvedValue('/tmp/android-sdk');
62
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([]);
63
+ vi.spyOn(adb, 'hasAvd').mockResolvedValue(false);
64
+ const createAvd = vi.spyOn(adb, 'createAvd').mockResolvedValue(undefined);
65
+ const startEmulator = vi
66
+ .spyOn(adb, 'startEmulator')
67
+ .mockResolvedValue(undefined);
68
+ vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
69
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
70
+ vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
71
+ vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
72
+ vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
73
+ vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
74
+ vi.spyOn(sharedPrefs, 'clearHarnessDebugHttpHost').mockResolvedValue(undefined);
75
+ vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
76
+ const stopEmulator = vi.spyOn(adb, 'stopEmulator').mockResolvedValue();
77
+ const instance = await getAndroidEmulatorPlatformInstance({
78
+ name: 'android',
79
+ device: {
80
+ type: 'emulator',
81
+ name: 'Pixel_8_API_35',
82
+ avd: {
83
+ apiLevel: 35,
84
+ profile: 'pixel_8',
85
+ diskSize: '1G',
86
+ heapSize: '1G',
87
+ },
88
+ },
89
+ bundleId: 'com.harnessplayground',
90
+ activityName: '.MainActivity',
91
+ }, harnessConfig, init);
92
+ expect(createAvd).toHaveBeenCalledWith({
93
+ name: 'Pixel_8_API_35',
94
+ apiLevel: 35,
95
+ profile: 'pixel_8',
96
+ diskSize: '1G',
97
+ heapSize: '1G',
98
+ });
99
+ expect(startEmulator).toHaveBeenCalledWith('Pixel_8_API_35', undefined);
100
+ await instance.dispose();
101
+ expect(stopEmulator).toHaveBeenCalledWith('emulator-5554');
102
+ });
103
+ it('verifies SDK assets before booting an existing AVD', async () => {
104
+ const ensureAndroidEmulatorEnvironment = vi
105
+ .spyOn(await import('../environment.js'), 'ensureAndroidEmulatorEnvironment')
106
+ .mockResolvedValue('/tmp/android-sdk');
107
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([]);
108
+ vi.spyOn(adb, 'hasAvd').mockResolvedValue(true);
109
+ const createAvd = vi.spyOn(adb, 'createAvd').mockResolvedValue(undefined);
110
+ const startEmulator = vi
111
+ .spyOn(adb, 'startEmulator')
112
+ .mockResolvedValue(undefined);
113
+ vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
114
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
115
+ vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
116
+ vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
117
+ vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
118
+ vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
119
+ await expect(getAndroidEmulatorPlatformInstance({
120
+ name: 'android',
121
+ device: {
122
+ type: 'emulator',
123
+ name: 'Pixel_8_API_35',
124
+ avd: {
125
+ apiLevel: 35,
126
+ profile: 'pixel_8',
127
+ diskSize: '1G',
128
+ heapSize: '1G',
129
+ },
130
+ },
131
+ bundleId: 'com.harnessplayground',
132
+ activityName: '.MainActivity',
133
+ }, harnessConfig, init)).resolves.toBeDefined();
134
+ expect(ensureAndroidEmulatorEnvironment).toHaveBeenCalledWith(35);
135
+ expect(createAvd).not.toHaveBeenCalled();
136
+ expect(startEmulator).toHaveBeenCalledWith('Pixel_8_API_35', undefined);
137
+ });
138
+ it('reuses a compatible cached AVD snapshot when caching is enabled', async () => {
139
+ vi.stubEnv('HARNESS_AVD_CACHING', 'true');
140
+ vi.spyOn(await import('../environment.js'), 'ensureAndroidEmulatorEnvironment').mockResolvedValue('/tmp/android-sdk');
141
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([]);
142
+ vi.spyOn(adb, 'hasAvd').mockResolvedValue(true);
143
+ vi.spyOn(avdConfig, 'readAvdConfig').mockResolvedValue({
144
+ imageSysdir1: 'system-images/android-35/default/arm64-v8a/',
145
+ abiType: 'arm64-v8a',
146
+ hwDeviceName: 'pixel_8',
147
+ diskDataPartitionSize: '1G',
148
+ vmHeapSize: '1G',
149
+ });
150
+ vi.spyOn(adb, 'startEmulator').mockResolvedValue(undefined);
151
+ vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
152
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
153
+ vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
154
+ vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
155
+ vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
156
+ vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
157
+ await expect(getAndroidEmulatorPlatformInstance({
158
+ name: 'android',
159
+ device: {
160
+ type: 'emulator',
161
+ name: 'Pixel_8_API_35',
162
+ avd: {
163
+ apiLevel: 35,
164
+ profile: 'pixel_8',
165
+ diskSize: '1G',
166
+ heapSize: '1G',
167
+ snapshot: { enabled: false },
168
+ },
169
+ },
170
+ bundleId: 'com.harnessplayground',
171
+ activityName: '.MainActivity',
172
+ }, harnessConfig, init)).resolves.toBeDefined();
173
+ expect(adb.startEmulator).toHaveBeenCalledTimes(1);
174
+ expect(adb.startEmulator).toHaveBeenCalledWith('Pixel_8_API_35', 'snapshot-reuse');
175
+ });
176
+ it('recreates an incompatible cached AVD before the real boot', async () => {
177
+ vi.stubEnv('HARNESS_AVD_CACHING', 'true');
178
+ vi.spyOn(await import('../environment.js'), 'ensureAndroidEmulatorEnvironment').mockResolvedValue('/tmp/android-sdk');
179
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([]);
180
+ vi.spyOn(adb, 'hasAvd').mockResolvedValue(true);
181
+ vi.spyOn(avdConfig, 'readAvdConfig').mockResolvedValue({
182
+ imageSysdir1: 'system-images/android-34/default/x86_64/',
183
+ abiType: 'x86_64',
184
+ hwDeviceName: 'pixel_7',
185
+ diskDataPartitionSize: '2G',
186
+ vmHeapSize: '2G',
187
+ });
188
+ const deleteAvd = vi.spyOn(adb, 'deleteAvd').mockResolvedValue(undefined);
189
+ const createAvd = vi.spyOn(adb, 'createAvd').mockResolvedValue(undefined);
190
+ vi.spyOn(adb, 'startEmulator').mockResolvedValue(undefined);
191
+ vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
192
+ const stopEmulator = vi.spyOn(adb, 'stopEmulator').mockResolvedValue();
193
+ const waitForEmulatorDisconnect = vi
194
+ .spyOn(adb, 'waitForEmulatorDisconnect')
195
+ .mockResolvedValue(undefined);
196
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
197
+ vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
198
+ vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
199
+ vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
200
+ vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
201
+ await expect(getAndroidEmulatorPlatformInstance({
202
+ name: 'android',
203
+ device: {
204
+ type: 'emulator',
205
+ name: 'Pixel_8_API_35',
206
+ avd: {
207
+ apiLevel: 35,
208
+ profile: 'pixel_8',
209
+ diskSize: '1G',
210
+ heapSize: '1G',
211
+ snapshot: { enabled: true },
212
+ },
213
+ },
214
+ bundleId: 'com.harnessplayground',
215
+ activityName: '.MainActivity',
216
+ }, harnessConfig, init)).resolves.toBeDefined();
217
+ expect(deleteAvd).toHaveBeenCalledWith('Pixel_8_API_35');
218
+ expect(createAvd).toHaveBeenCalled();
219
+ expect(stopEmulator).toHaveBeenCalledWith('emulator-5554');
220
+ expect(waitForEmulatorDisconnect).toHaveBeenCalledWith('emulator-5554', init.signal);
221
+ expect(adb.startEmulator).toHaveBeenNthCalledWith(1, 'Pixel_8_API_35', 'clean-snapshot-generation');
222
+ expect(adb.startEmulator).toHaveBeenNthCalledWith(2, 'Pixel_8_API_35', 'snapshot-reuse');
223
+ });
224
+ it('generates a snapshot on first run before the test boot', async () => {
225
+ vi.stubEnv('HARNESS_AVD_CACHING', 'true');
226
+ vi.spyOn(await import('../environment.js'), 'ensureAndroidEmulatorEnvironment').mockResolvedValue('/tmp/android-sdk');
227
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([]);
228
+ vi.spyOn(adb, 'hasAvd').mockResolvedValue(false);
229
+ vi.spyOn(adb, 'createAvd').mockResolvedValue(undefined);
230
+ const startEmulator = vi
231
+ .spyOn(adb, 'startEmulator')
232
+ .mockResolvedValue(undefined);
233
+ vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
234
+ const stopEmulator = vi.spyOn(adb, 'stopEmulator').mockResolvedValue();
235
+ const waitForEmulatorDisconnect = vi
236
+ .spyOn(adb, 'waitForEmulatorDisconnect')
237
+ .mockResolvedValue(undefined);
238
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
239
+ vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
240
+ vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
241
+ vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
242
+ vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
243
+ await expect(getAndroidEmulatorPlatformInstance({
244
+ name: 'android',
245
+ device: {
246
+ type: 'emulator',
247
+ name: 'Pixel_8_API_35',
248
+ avd: {
249
+ apiLevel: 35,
250
+ profile: 'pixel_8',
251
+ diskSize: '1G',
252
+ heapSize: '1G',
253
+ snapshot: { enabled: true },
254
+ },
255
+ },
256
+ bundleId: 'com.harnessplayground',
257
+ activityName: '.MainActivity',
258
+ }, harnessConfig, init)).resolves.toBeDefined();
259
+ expect(startEmulator).toHaveBeenNthCalledWith(1, 'Pixel_8_API_35', 'clean-snapshot-generation');
260
+ expect(stopEmulator).toHaveBeenCalledWith('emulator-5554');
261
+ expect(waitForEmulatorDisconnect).toHaveBeenCalledWith('emulator-5554', init.signal);
262
+ expect(startEmulator).toHaveBeenNthCalledWith(2, 'Pixel_8_API_35', 'snapshot-reuse');
263
+ });
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
+ it('throws a HarnessAppPathError when HARNESS_APP_PATH is missing', async () => {
297
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
298
+ vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
299
+ vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
300
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(false);
301
+ await expect(getAndroidEmulatorPlatformInstance({
302
+ name: 'android',
303
+ device: {
304
+ type: 'emulator',
305
+ name: 'Pixel_8_API_35',
306
+ avd: {
307
+ apiLevel: 35,
308
+ profile: 'pixel_8',
309
+ diskSize: '1G',
310
+ heapSize: '1G',
311
+ },
312
+ },
313
+ bundleId: 'com.harnessplayground',
314
+ activityName: '.MainActivity',
315
+ }, harnessConfig, init)).rejects.toBeInstanceOf(HarnessAppPathError);
316
+ });
317
+ it('throws a HarnessAppPathError when HARNESS_APP_PATH points to a missing app', async () => {
318
+ vi.stubEnv('HARNESS_APP_PATH', '/tmp/missing.apk');
319
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
320
+ vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
321
+ vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
322
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(false);
323
+ await expect(getAndroidEmulatorPlatformInstance({
324
+ name: 'android',
325
+ device: {
326
+ type: 'emulator',
327
+ name: 'Pixel_8_API_35',
328
+ avd: {
329
+ apiLevel: 35,
330
+ profile: 'pixel_8',
331
+ diskSize: '1G',
332
+ heapSize: '1G',
333
+ },
334
+ },
335
+ bundleId: 'com.harnessplayground',
336
+ activityName: '.MainActivity',
337
+ }, harnessConfig, init)).rejects.toBeInstanceOf(HarnessAppPathError);
338
+ });
339
+ it('throws a HarnessEmulatorConfigError when the emulator is missing and avd config is absent', async () => {
340
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([]);
341
+ await expect(getAndroidEmulatorPlatformInstance({
342
+ name: 'android',
343
+ device: {
344
+ type: 'emulator',
345
+ name: 'Pixel_8_API_35',
346
+ },
347
+ bundleId: 'com.harnessplayground',
348
+ activityName: '.MainActivity',
349
+ }, harnessConfig, init)).rejects.toBeInstanceOf(HarnessEmulatorConfigError);
350
+ });
351
+ it('returns a noop emulator app monitor when native crash detection is disabled', async () => {
352
+ vi.spyOn(await import('../environment.js'), 'ensureAndroidEmulatorEnvironment').mockResolvedValue('/tmp/android-sdk');
353
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
354
+ vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
355
+ vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
356
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
357
+ vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
358
+ vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
359
+ vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
360
+ vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
361
+ const instance = await getAndroidEmulatorPlatformInstance({
362
+ name: 'android',
363
+ device: {
364
+ type: 'emulator',
365
+ name: 'Pixel_8_API_35',
366
+ avd: {
367
+ apiLevel: 35,
368
+ profile: 'pixel_8',
369
+ diskSize: '1G',
370
+ heapSize: '1G',
371
+ },
372
+ },
373
+ bundleId: 'com.harnessplayground',
374
+ activityName: '.MainActivity',
375
+ }, harnessConfigWithoutNativeCrashDetection, init);
376
+ const listener = vi.fn();
377
+ const appMonitor = instance.createAppMonitor();
378
+ await expect(appMonitor.start()).resolves.toBeUndefined();
379
+ await expect(appMonitor.stop()).resolves.toBeUndefined();
380
+ await expect(appMonitor.dispose()).resolves.toBeUndefined();
381
+ expect(appMonitor.addListener(listener)).toBeUndefined();
382
+ expect(appMonitor.removeListener(listener)).toBeUndefined();
383
+ });
384
+ it('returns a noop physical device app monitor when native crash detection is disabled', async () => {
385
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['012345']);
386
+ vi.spyOn(adb, 'getDeviceInfo').mockResolvedValue({
387
+ manufacturer: 'motorola',
388
+ model: 'moto g72',
389
+ });
390
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
391
+ vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
392
+ vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
393
+ vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
394
+ vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
395
+ await expect(getAndroidPhysicalDevicePlatformInstance({
396
+ name: 'android-device',
397
+ device: {
398
+ type: 'physical',
399
+ manufacturer: 'motorola',
400
+ model: 'moto g72',
401
+ },
402
+ bundleId: 'com.harnessplayground',
403
+ activityName: '.MainActivity',
404
+ }, harnessConfigWithoutNativeCrashDetection)).resolves.toBeDefined();
405
+ const instance = await getAndroidPhysicalDevicePlatformInstance({
406
+ name: 'android-device',
407
+ device: {
408
+ type: 'physical',
409
+ manufacturer: 'motorola',
410
+ model: 'moto g72',
411
+ },
412
+ bundleId: 'com.harnessplayground',
413
+ activityName: '.MainActivity',
414
+ }, harnessConfigWithoutNativeCrashDetection);
415
+ const listener = vi.fn();
416
+ const appMonitor = instance.createAppMonitor();
417
+ await expect(appMonitor.start()).resolves.toBeUndefined();
418
+ await expect(appMonitor.stop()).resolves.toBeUndefined();
419
+ await expect(appMonitor.dispose()).resolves.toBeUndefined();
420
+ expect(appMonitor.addListener(listener)).toBeUndefined();
421
+ expect(appMonitor.removeListener(listener)).toBeUndefined();
422
+ });
423
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=shared-prefs.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-prefs.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/shared-prefs.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,87 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import * as tools from '@react-native-harness/tools';
3
+ import { applyHarnessDebugHttpHost, clearHarnessDebugHttpHost, } from '../shared-prefs.js';
4
+ const bundleId = 'com.example.app';
5
+ const adbId = 'emulator-5554';
6
+ const getWrittenContent = (calls) => {
7
+ const writeCall = calls.find(([, , options]) => {
8
+ if (!options || typeof options !== 'object' || !('stdin' in options)) {
9
+ return false;
10
+ }
11
+ return Boolean(options.stdin);
12
+ });
13
+ const options = writeCall?.[2];
14
+ if (!options || typeof options !== 'object' || !('stdin' in options)) {
15
+ throw new Error('Expected write call options.');
16
+ }
17
+ const content = options.stdin;
18
+ if (!content ||
19
+ typeof content !== 'object' ||
20
+ !('string' in content) ||
21
+ typeof content.string !== 'string') {
22
+ throw new Error('Expected write call with string stdin.');
23
+ }
24
+ return content.string;
25
+ };
26
+ const getWrittenContents = (calls) => calls
27
+ .filter(([, , options]) => {
28
+ if (!options || typeof options !== 'object' || !('stdin' in options)) {
29
+ return false;
30
+ }
31
+ return Boolean(options.stdin);
32
+ })
33
+ .map((call) => getWrittenContent([call]));
34
+ describe('Android shared preferences Metro host override', () => {
35
+ afterEach(() => {
36
+ vi.restoreAllMocks();
37
+ });
38
+ it('handles empty self-closing map files', async () => {
39
+ const spawnSpy = vi
40
+ .spyOn(tools, 'spawn')
41
+ .mockResolvedValueOnce({
42
+ stdout: '<?xml version="1.0" encoding="utf-8"?>\n<map />\n',
43
+ })
44
+ .mockResolvedValueOnce({});
45
+ await applyHarnessDebugHttpHost(adbId, bundleId, 'localhost:9090');
46
+ expect(getWrittenContent(spawnSpy.mock.calls)).toContain('<map>');
47
+ expect(getWrittenContent(spawnSpy.mock.calls)).toContain('</map>');
48
+ expect(getWrittenContent(spawnSpy.mock.calls)).toContain('<string name="debug_http_host">localhost:9090</string>');
49
+ });
50
+ it('restores the previous debug host on cleanup', async () => {
51
+ const spawnSpy = vi
52
+ .spyOn(tools, 'spawn')
53
+ .mockResolvedValueOnce({
54
+ stdout: [
55
+ '<?xml version="1.0" encoding="utf-8"?>',
56
+ '<map>',
57
+ ' <string name="debug_http_host">10.0.2.2:8081</string>',
58
+ '</map>',
59
+ ].join('\n'),
60
+ })
61
+ .mockResolvedValueOnce({})
62
+ .mockResolvedValueOnce({
63
+ stdout: [
64
+ '<?xml version="1.0" encoding="utf-8"?>',
65
+ '<map>',
66
+ ' <string name="debug_http_host">10.0.2.2:8081</string>',
67
+ ' <!-- react-native-harness:debug_http_host:start -->',
68
+ ' <string name="debug_http_host">localhost:9090</string>',
69
+ ' <!-- react-native-harness:debug_http_host:end -->',
70
+ '</map>',
71
+ ].join('\n'),
72
+ })
73
+ .mockResolvedValueOnce({});
74
+ await applyHarnessDebugHttpHost(adbId, bundleId, 'localhost:9090');
75
+ await clearHarnessDebugHttpHost(adbId, bundleId);
76
+ const writes = getWrittenContents(spawnSpy.mock.calls);
77
+ const firstWrite = writes[0];
78
+ const secondWrite = writes[1];
79
+ expect(firstWrite).toEqual(expect.stringContaining('<string name="harness_debug_http_host_backup">10.0.2.2:8081</string>'));
80
+ expect(firstWrite).toEqual(expect.stringContaining('<string name="debug_http_host">localhost:9090</string>'));
81
+ expect(firstWrite).toEqual(expect.not.stringContaining('<string name="debug_http_host">10.0.2.2:8081</string>'));
82
+ expect(secondWrite).toEqual(expect.stringContaining('<string name="debug_http_host">10.0.2.2:8081</string>'));
83
+ expect(secondWrite).toEqual(expect.not.stringContaining('<string name="harness_debug_http_host_backup">10.0.2.2:8081</string>'));
84
+ expect(secondWrite).toEqual(expect.not.stringContaining('<!-- react-native-harness:debug_http_host:start -->'));
85
+ expect(secondWrite).toEqual(expect.not.stringContaining('<string name="debug_http_host">localhost:9090</string>'));
86
+ });
87
+ });
package/dist/adb.d.ts CHANGED
@@ -1,4 +1,19 @@
1
1
  import { type AndroidAppLaunchOptions } from '@react-native-harness/platforms';
2
+ import type { ChildProcessByStdio } from 'node:child_process';
3
+ import type { Readable } from 'node:stream';
4
+ import { type EmulatorBootMode } from './emulator-startup.js';
5
+ export declare const emulatorProcess: {
6
+ startDetachedProcess: (file: string, args: readonly string[]) => ChildProcessByStdio<null, Readable, Readable>;
7
+ };
8
+ export type CreateAvdOptions = {
9
+ name: string;
10
+ apiLevel: number;
11
+ profile: string;
12
+ diskSize: string;
13
+ heapSize: string;
14
+ };
15
+ export declare const getRequiredEmulatorPackages: (apiLevel: number) => string[];
16
+ export declare const verifyAndroidEmulatorSdk: (apiLevel: number) => Promise<void>;
2
17
  export declare const getStartAppArgs: (bundleId: string, activityName: string, options?: AndroidAppLaunchOptions) => string[];
3
18
  export declare const isAppInstalled: (adbId: string, bundleId: string) => Promise<boolean>;
4
19
  export declare const reversePort: (adbId: string, port: number, hostPort?: number) => Promise<void>;
@@ -14,6 +29,14 @@ export type DeviceInfo = {
14
29
  export declare const getDeviceInfo: (adbId: string) => Promise<DeviceInfo | null>;
15
30
  export declare const isBootCompleted: (adbId: string) => Promise<boolean>;
16
31
  export declare const stopEmulator: (adbId: string) => Promise<void>;
32
+ export declare const installApp: (adbId: string, appPath: string) => Promise<void>;
33
+ export declare const hasAvd: (name: string) => Promise<boolean>;
34
+ export declare const createAvd: ({ name, apiLevel, profile, diskSize, heapSize, }: CreateAvdOptions) => Promise<void>;
35
+ export declare const deleteAvd: (name: string) => Promise<void>;
36
+ export declare const startEmulator: (name: string, mode?: EmulatorBootMode) => Promise<void>;
37
+ export declare const waitForEmulator: (name: string, signal: AbortSignal) => Promise<string>;
38
+ export declare const waitForEmulatorDisconnect: (adbId: string, signal: AbortSignal) => Promise<void>;
39
+ export declare const waitForBoot: (name: string, signal: AbortSignal) => Promise<string>;
17
40
  export declare const isAppRunning: (adbId: string, bundleId: string) => Promise<boolean>;
18
41
  export declare const getAppUid: (adbId: string, bundleId: string) => Promise<number>;
19
42
  export declare const setHideErrorDialogs: (adbId: string, hide: boolean) => Promise<void>;
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,eAAO,MAAM,eAAe,GAC1B,UAAU,MAAM,EAChB,cAAc,MAAM,EACpB,UAAU,uBAAuB,KAChC,MAAM,EAoCR,CAAC;AAEF,eAAO,MAAM,cAAc,GACzB,OAAO,MAAM,EACb,UAAU,MAAM,KACf,OAAO,CAAC,OAAO,CAWjB,CAAC;AAEF,eAAO,MAAM,WAAW,GACtB,OAAO,MAAM,EACb,MAAM,MAAM,EACZ,WAAU,MAAa,KACtB,OAAO,CAAC,IAAI,CAQd,CAAC;AAEF,eAAO,MAAM,OAAO,GAClB,OAAO,MAAM,EACb,UAAU,MAAM,KACf,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF,eAAO,MAAM,QAAQ,GACnB,OAAO,MAAM,EACb,UAAU,MAAM,EAChB,cAAc,MAAM,EACpB,UAAU,uBAAuB,KAChC,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF,eAAO,MAAM,YAAY,QAAa,OAAO,CAAC,MAAM,EAAE,CAOrD,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,OAAO,MAAM,KACZ,OAAO,CAAC,MAAM,GAAG,IAAI,CAGvB,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAC3B,OAAO,MAAM,EACb,UAAU,MAAM,KACf,OAAO,CAAC,MAAM,GAAG,IAAI,CASvB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,aAAa,GACxB,OAAO,MAAM,KACZ,OAAO,CAAC,UAAU,GAAG,IAAI,CAI3B,CAAC;AAEF,eAAO,MAAM,eAAe,GAAU,OAAO,MAAM,KAAG,OAAO,CAAC,OAAO,CAGpE,CAAC;AAEF,eAAO,MAAM,YAAY,GAAU,OAAO,MAAM,KAAG,OAAO,CAAC,IAAI,CAE9D,CAAC;AAEF,eAAO,MAAM,YAAY,GACvB,OAAO,MAAM,EACb,UAAU,MAAM,KACf,OAAO,CAAC,OAAO,CAiBjB,CAAC;AAEF,eAAO,MAAM,SAAS,GACpB,OAAO,MAAM,EACb,UAAU,MAAM,KACf,OAAO,CAAC,MAAM,CAoBhB,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,OAAO,MAAM,EACb,MAAM,OAAO,KACZ,OAAO,CAAC,IAAI,CAWd,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAU,OAAO,MAAM,KAAG,OAAO,CAAC,MAAM,CAUtE,CAAC;AAEF,eAAO,MAAM,OAAO,QAAa,OAAO,CAAC,MAAM,EAAE,CAUhD,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,mBAAmB,QAAa,OAAO,CAAC,SAAS,EAAE,CA8B/D,CAAC"}
1
+ {"version":3,"file":"adb.d.ts","sourceRoot":"","sources":["../src/adb.ts"],"names":[],"mappings":"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"}