@react-native-harness/platform-android 1.1.0-rc.2 → 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 (83) 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/adb.d.ts +23 -0
  19. package/dist/adb.d.ts.map +1 -1
  20. package/dist/adb.js +265 -16
  21. package/dist/app-monitor.d.ts.map +1 -1
  22. package/dist/app-monitor.js +27 -7
  23. package/dist/assertions.d.ts +5 -0
  24. package/dist/assertions.d.ts.map +1 -0
  25. package/dist/assertions.js +6 -0
  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/emulator.d.ts +6 -0
  36. package/dist/emulator.d.ts.map +1 -0
  37. package/dist/emulator.js +27 -0
  38. package/dist/environment.d.ts +28 -0
  39. package/dist/environment.d.ts.map +1 -0
  40. package/dist/environment.js +295 -0
  41. package/dist/errors.d.ts +7 -0
  42. package/dist/errors.d.ts.map +1 -0
  43. package/dist/errors.js +14 -0
  44. package/dist/factory.d.ts.map +1 -1
  45. package/dist/factory.js +3 -0
  46. package/dist/index.d.ts +3 -0
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +3 -0
  49. package/dist/instance.d.ts +6 -0
  50. package/dist/instance.d.ts.map +1 -0
  51. package/dist/instance.js +234 -0
  52. package/dist/reader.d.ts +6 -0
  53. package/dist/reader.d.ts.map +1 -0
  54. package/dist/reader.js +57 -0
  55. package/dist/runner.d.ts +2 -2
  56. package/dist/runner.d.ts.map +1 -1
  57. package/dist/runner.js +12 -52
  58. package/dist/targets.d.ts +1 -1
  59. package/dist/targets.d.ts.map +1 -1
  60. package/dist/targets.js +2 -0
  61. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  62. package/dist/types.d.ts +381 -0
  63. package/dist/types.d.ts.map +1 -0
  64. package/dist/types.js +107 -0
  65. package/package.json +4 -4
  66. package/src/__tests__/adb.test.ts +419 -15
  67. package/src/__tests__/avd-config.test.ts +206 -0
  68. package/src/__tests__/ci-action.test.ts +81 -0
  69. package/src/__tests__/emulator-startup.test.ts +32 -0
  70. package/src/__tests__/environment.test.ts +87 -0
  71. package/src/__tests__/instance.test.ts +610 -0
  72. package/src/adb.ts +423 -16
  73. package/src/app-monitor.ts +56 -18
  74. package/src/avd-config.ts +290 -0
  75. package/src/config.ts +8 -0
  76. package/src/emulator-startup.ts +28 -0
  77. package/src/environment.ts +510 -0
  78. package/src/errors.ts +19 -0
  79. package/src/factory.ts +4 -0
  80. package/src/index.ts +7 -1
  81. package/src/instance.ts +380 -0
  82. package/src/runner.ts +23 -69
  83. package/src/targets.ts +11 -8
@@ -0,0 +1,610 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import {
3
+ DEFAULT_METRO_PORT,
4
+ type Config as HarnessConfig,
5
+ } from '@react-native-harness/config';
6
+ import fs from 'node:fs';
7
+ import os from 'node:os';
8
+ import path from 'node:path';
9
+ import {
10
+ getAndroidEmulatorPlatformInstance,
11
+ getAndroidPhysicalDevicePlatformInstance,
12
+ } from '../instance.js';
13
+ import * as adb from '../adb.js';
14
+ import * as avdConfig from '../avd-config.js';
15
+ import * as sharedPrefs from '../shared-prefs.js';
16
+ import { HarnessAppPathError, HarnessEmulatorConfigError } from '../errors.js';
17
+
18
+ const harnessConfig = {
19
+ metroPort: DEFAULT_METRO_PORT,
20
+ } as HarnessConfig;
21
+ const harnessConfigWithoutNativeCrashDetection = {
22
+ metroPort: DEFAULT_METRO_PORT,
23
+ detectNativeCrashes: false,
24
+ } as HarnessConfig;
25
+ const init = {
26
+ signal: new AbortController().signal,
27
+ };
28
+
29
+ describe('Android platform instance', () => {
30
+ beforeEach(() => {
31
+ vi.restoreAllMocks();
32
+ vi.unstubAllEnvs();
33
+ });
34
+
35
+ it('reuses a running emulator and does not shut it down on dispose', async () => {
36
+ const ensureAndroidEmulatorEnvironment = vi
37
+ .spyOn(
38
+ await import('../environment.js'),
39
+ 'ensureAndroidEmulatorEnvironment'
40
+ )
41
+ .mockResolvedValue('/tmp/android-sdk');
42
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
43
+ vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
44
+ vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
45
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
46
+ vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
47
+ vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
48
+ vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
49
+ vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
50
+ undefined
51
+ );
52
+ vi.spyOn(sharedPrefs, 'clearHarnessDebugHttpHost').mockResolvedValue(
53
+ undefined
54
+ );
55
+ vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
56
+ const stopEmulator = vi.spyOn(adb, 'stopEmulator').mockResolvedValue();
57
+
58
+ const instance = await getAndroidEmulatorPlatformInstance(
59
+ {
60
+ name: 'android',
61
+ device: {
62
+ type: 'emulator',
63
+ name: 'Pixel_8_API_35',
64
+ avd: {
65
+ apiLevel: 35,
66
+ profile: 'pixel_8',
67
+ diskSize: '1G',
68
+ heapSize: '1G',
69
+ },
70
+ },
71
+ bundleId: 'com.harnessplayground',
72
+ activityName: '.MainActivity',
73
+ },
74
+ harnessConfig,
75
+ init
76
+ );
77
+
78
+ await instance.dispose();
79
+
80
+ expect(ensureAndroidEmulatorEnvironment).toHaveBeenCalledWith(35);
81
+ expect(stopEmulator).not.toHaveBeenCalled();
82
+ });
83
+
84
+ it('creates and boots an emulator when missing and shuts it down on dispose', async () => {
85
+ vi.spyOn(
86
+ await import('../environment.js'),
87
+ 'ensureAndroidEmulatorEnvironment'
88
+ ).mockResolvedValue('/tmp/android-sdk');
89
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([]);
90
+ vi.spyOn(adb, 'hasAvd').mockResolvedValue(false);
91
+ const createAvd = vi.spyOn(adb, 'createAvd').mockResolvedValue(undefined);
92
+ const startEmulator = vi
93
+ .spyOn(adb, 'startEmulator')
94
+ .mockResolvedValue(undefined);
95
+ vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
96
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
97
+ vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
98
+ vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
99
+ vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
100
+ vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
101
+ undefined
102
+ );
103
+ vi.spyOn(sharedPrefs, 'clearHarnessDebugHttpHost').mockResolvedValue(
104
+ undefined
105
+ );
106
+ vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
107
+ const stopEmulator = vi.spyOn(adb, 'stopEmulator').mockResolvedValue();
108
+
109
+ const instance = await getAndroidEmulatorPlatformInstance(
110
+ {
111
+ name: 'android',
112
+ device: {
113
+ type: 'emulator',
114
+ name: 'Pixel_8_API_35',
115
+ avd: {
116
+ apiLevel: 35,
117
+ profile: 'pixel_8',
118
+ diskSize: '1G',
119
+ heapSize: '1G',
120
+ },
121
+ },
122
+ bundleId: 'com.harnessplayground',
123
+ activityName: '.MainActivity',
124
+ },
125
+ harnessConfig,
126
+ init
127
+ );
128
+
129
+ expect(createAvd).toHaveBeenCalledWith({
130
+ name: 'Pixel_8_API_35',
131
+ apiLevel: 35,
132
+ profile: 'pixel_8',
133
+ diskSize: '1G',
134
+ heapSize: '1G',
135
+ });
136
+ expect(startEmulator).toHaveBeenCalledWith('Pixel_8_API_35', undefined);
137
+
138
+ await instance.dispose();
139
+
140
+ expect(stopEmulator).toHaveBeenCalledWith('emulator-5554');
141
+ });
142
+
143
+ it('verifies SDK assets before booting an existing AVD', async () => {
144
+ const ensureAndroidEmulatorEnvironment = vi
145
+ .spyOn(
146
+ await import('../environment.js'),
147
+ 'ensureAndroidEmulatorEnvironment'
148
+ )
149
+ .mockResolvedValue('/tmp/android-sdk');
150
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([]);
151
+ vi.spyOn(adb, 'hasAvd').mockResolvedValue(true);
152
+ const createAvd = vi.spyOn(adb, 'createAvd').mockResolvedValue(undefined);
153
+ const startEmulator = vi
154
+ .spyOn(adb, 'startEmulator')
155
+ .mockResolvedValue(undefined);
156
+ vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
157
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
158
+ vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
159
+ vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
160
+ vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
161
+ vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
162
+ undefined
163
+ );
164
+
165
+ await expect(
166
+ getAndroidEmulatorPlatformInstance(
167
+ {
168
+ name: 'android',
169
+ device: {
170
+ type: 'emulator',
171
+ name: 'Pixel_8_API_35',
172
+ avd: {
173
+ apiLevel: 35,
174
+ profile: 'pixel_8',
175
+ diskSize: '1G',
176
+ heapSize: '1G',
177
+ },
178
+ },
179
+ bundleId: 'com.harnessplayground',
180
+ activityName: '.MainActivity',
181
+ },
182
+ harnessConfig,
183
+ init
184
+ )
185
+ ).resolves.toBeDefined();
186
+
187
+ expect(ensureAndroidEmulatorEnvironment).toHaveBeenCalledWith(35);
188
+ expect(createAvd).not.toHaveBeenCalled();
189
+ expect(startEmulator).toHaveBeenCalledWith('Pixel_8_API_35', undefined);
190
+ });
191
+
192
+ it('reuses a compatible cached AVD snapshot when caching is enabled', async () => {
193
+ vi.stubEnv('HARNESS_AVD_CACHING', 'true');
194
+ vi.spyOn(
195
+ await import('../environment.js'),
196
+ 'ensureAndroidEmulatorEnvironment'
197
+ ).mockResolvedValue('/tmp/android-sdk');
198
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([]);
199
+ vi.spyOn(adb, 'hasAvd').mockResolvedValue(true);
200
+ vi.spyOn(avdConfig, 'readAvdConfig').mockResolvedValue({
201
+ imageSysdir1: 'system-images/android-35/default/arm64-v8a/',
202
+ abiType: 'arm64-v8a',
203
+ hwDeviceName: 'pixel_8',
204
+ diskDataPartitionSize: '1G',
205
+ vmHeapSize: '1G',
206
+ });
207
+ vi.spyOn(adb, 'startEmulator').mockResolvedValue(undefined);
208
+ vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
209
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
210
+ vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
211
+ vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
212
+ vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
213
+ vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
214
+ undefined
215
+ );
216
+
217
+ await expect(
218
+ getAndroidEmulatorPlatformInstance(
219
+ {
220
+ name: 'android',
221
+ device: {
222
+ type: 'emulator',
223
+ name: 'Pixel_8_API_35',
224
+ avd: {
225
+ apiLevel: 35,
226
+ profile: 'pixel_8',
227
+ diskSize: '1G',
228
+ heapSize: '1G',
229
+ snapshot: { enabled: false },
230
+ },
231
+ },
232
+ bundleId: 'com.harnessplayground',
233
+ activityName: '.MainActivity',
234
+ },
235
+ harnessConfig,
236
+ init
237
+ )
238
+ ).resolves.toBeDefined();
239
+
240
+ expect(adb.startEmulator).toHaveBeenCalledTimes(1);
241
+ expect(adb.startEmulator).toHaveBeenCalledWith(
242
+ 'Pixel_8_API_35',
243
+ 'snapshot-reuse'
244
+ );
245
+ });
246
+
247
+ it('recreates an incompatible cached AVD before the real boot', async () => {
248
+ vi.stubEnv('HARNESS_AVD_CACHING', 'true');
249
+ vi.spyOn(
250
+ await import('../environment.js'),
251
+ 'ensureAndroidEmulatorEnvironment'
252
+ ).mockResolvedValue('/tmp/android-sdk');
253
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([]);
254
+ vi.spyOn(adb, 'hasAvd').mockResolvedValue(true);
255
+ vi.spyOn(avdConfig, 'readAvdConfig').mockResolvedValue({
256
+ imageSysdir1: 'system-images/android-34/default/x86_64/',
257
+ abiType: 'x86_64',
258
+ hwDeviceName: 'pixel_7',
259
+ diskDataPartitionSize: '2G',
260
+ vmHeapSize: '2G',
261
+ });
262
+ const deleteAvd = vi.spyOn(adb, 'deleteAvd').mockResolvedValue(undefined);
263
+ const createAvd = vi.spyOn(adb, 'createAvd').mockResolvedValue(undefined);
264
+ vi.spyOn(adb, 'startEmulator').mockResolvedValue(undefined);
265
+ vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
266
+ const stopEmulator = vi.spyOn(adb, 'stopEmulator').mockResolvedValue();
267
+ const waitForEmulatorDisconnect = vi
268
+ .spyOn(adb, 'waitForEmulatorDisconnect')
269
+ .mockResolvedValue(undefined);
270
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
271
+ vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
272
+ vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
273
+ vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
274
+ vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
275
+ undefined
276
+ );
277
+
278
+ await expect(
279
+ getAndroidEmulatorPlatformInstance(
280
+ {
281
+ name: 'android',
282
+ device: {
283
+ type: 'emulator',
284
+ name: 'Pixel_8_API_35',
285
+ avd: {
286
+ apiLevel: 35,
287
+ profile: 'pixel_8',
288
+ diskSize: '1G',
289
+ heapSize: '1G',
290
+ snapshot: { enabled: true },
291
+ },
292
+ },
293
+ bundleId: 'com.harnessplayground',
294
+ activityName: '.MainActivity',
295
+ },
296
+ harnessConfig,
297
+ init
298
+ )
299
+ ).resolves.toBeDefined();
300
+
301
+ expect(deleteAvd).toHaveBeenCalledWith('Pixel_8_API_35');
302
+ expect(createAvd).toHaveBeenCalled();
303
+ expect(stopEmulator).toHaveBeenCalledWith('emulator-5554');
304
+ expect(waitForEmulatorDisconnect).toHaveBeenCalledWith(
305
+ 'emulator-5554',
306
+ init.signal
307
+ );
308
+ expect(adb.startEmulator).toHaveBeenNthCalledWith(
309
+ 1,
310
+ 'Pixel_8_API_35',
311
+ 'clean-snapshot-generation'
312
+ );
313
+ expect(adb.startEmulator).toHaveBeenNthCalledWith(
314
+ 2,
315
+ 'Pixel_8_API_35',
316
+ 'snapshot-reuse'
317
+ );
318
+ });
319
+
320
+ it('generates a snapshot on first run before the test boot', async () => {
321
+ vi.stubEnv('HARNESS_AVD_CACHING', 'true');
322
+ vi.spyOn(
323
+ await import('../environment.js'),
324
+ 'ensureAndroidEmulatorEnvironment'
325
+ ).mockResolvedValue('/tmp/android-sdk');
326
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([]);
327
+ vi.spyOn(adb, 'hasAvd').mockResolvedValue(false);
328
+ vi.spyOn(adb, 'createAvd').mockResolvedValue(undefined);
329
+ const startEmulator = vi
330
+ .spyOn(adb, 'startEmulator')
331
+ .mockResolvedValue(undefined);
332
+ vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
333
+ const stopEmulator = vi.spyOn(adb, 'stopEmulator').mockResolvedValue();
334
+ const waitForEmulatorDisconnect = vi
335
+ .spyOn(adb, 'waitForEmulatorDisconnect')
336
+ .mockResolvedValue(undefined);
337
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
338
+ vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
339
+ vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
340
+ vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
341
+ vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
342
+ undefined
343
+ );
344
+
345
+ await expect(
346
+ getAndroidEmulatorPlatformInstance(
347
+ {
348
+ name: 'android',
349
+ device: {
350
+ type: 'emulator',
351
+ name: 'Pixel_8_API_35',
352
+ avd: {
353
+ apiLevel: 35,
354
+ profile: 'pixel_8',
355
+ diskSize: '1G',
356
+ heapSize: '1G',
357
+ snapshot: { enabled: true },
358
+ },
359
+ },
360
+ bundleId: 'com.harnessplayground',
361
+ activityName: '.MainActivity',
362
+ },
363
+ harnessConfig,
364
+ init
365
+ )
366
+ ).resolves.toBeDefined();
367
+
368
+ expect(startEmulator).toHaveBeenNthCalledWith(
369
+ 1,
370
+ 'Pixel_8_API_35',
371
+ 'clean-snapshot-generation'
372
+ );
373
+ expect(stopEmulator).toHaveBeenCalledWith('emulator-5554');
374
+ expect(waitForEmulatorDisconnect).toHaveBeenCalledWith(
375
+ 'emulator-5554',
376
+ init.signal
377
+ );
378
+ expect(startEmulator).toHaveBeenNthCalledWith(
379
+ 2,
380
+ 'Pixel_8_API_35',
381
+ 'snapshot-reuse'
382
+ );
383
+ });
384
+
385
+ it('installs the app from HARNESS_APP_PATH when missing', async () => {
386
+ const appPath = path.join(os.tmpdir(), 'HarnessPlayground.apk');
387
+ fs.writeFileSync(appPath, 'apk');
388
+ vi.stubEnv('HARNESS_APP_PATH', appPath);
389
+ vi.spyOn(
390
+ await import('../environment.js'),
391
+ 'ensureAndroidEmulatorEnvironment'
392
+ ).mockResolvedValue('/tmp/android-sdk');
393
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
394
+ vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
395
+ vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
396
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(false);
397
+ const installApp = vi.spyOn(adb, 'installApp').mockResolvedValue(undefined);
398
+ vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
399
+ vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
400
+ vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
401
+ vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
402
+ undefined
403
+ );
404
+
405
+ await expect(
406
+ getAndroidEmulatorPlatformInstance(
407
+ {
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
+ },
422
+ harnessConfig,
423
+ init
424
+ )
425
+ ).resolves.toBeDefined();
426
+
427
+ expect(installApp).toHaveBeenCalledWith('emulator-5554', appPath);
428
+
429
+ fs.rmSync(appPath, { force: true });
430
+ });
431
+
432
+ it('throws a HarnessAppPathError when HARNESS_APP_PATH is missing', async () => {
433
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
434
+ vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
435
+ vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
436
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(false);
437
+
438
+ await expect(
439
+ getAndroidEmulatorPlatformInstance(
440
+ {
441
+ name: 'android',
442
+ device: {
443
+ type: 'emulator',
444
+ name: 'Pixel_8_API_35',
445
+ avd: {
446
+ apiLevel: 35,
447
+ profile: 'pixel_8',
448
+ diskSize: '1G',
449
+ heapSize: '1G',
450
+ },
451
+ },
452
+ bundleId: 'com.harnessplayground',
453
+ activityName: '.MainActivity',
454
+ },
455
+ harnessConfig,
456
+ init
457
+ )
458
+ ).rejects.toBeInstanceOf(HarnessAppPathError);
459
+ });
460
+
461
+ it('throws a HarnessAppPathError when HARNESS_APP_PATH points to a missing app', async () => {
462
+ vi.stubEnv('HARNESS_APP_PATH', '/tmp/missing.apk');
463
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
464
+ vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
465
+ vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
466
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(false);
467
+
468
+ await expect(
469
+ getAndroidEmulatorPlatformInstance(
470
+ {
471
+ name: 'android',
472
+ device: {
473
+ type: 'emulator',
474
+ name: 'Pixel_8_API_35',
475
+ avd: {
476
+ apiLevel: 35,
477
+ profile: 'pixel_8',
478
+ diskSize: '1G',
479
+ heapSize: '1G',
480
+ },
481
+ },
482
+ bundleId: 'com.harnessplayground',
483
+ activityName: '.MainActivity',
484
+ },
485
+ harnessConfig,
486
+ init
487
+ )
488
+ ).rejects.toBeInstanceOf(HarnessAppPathError);
489
+ });
490
+
491
+ it('throws a HarnessEmulatorConfigError when the emulator is missing and avd config is absent', async () => {
492
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([]);
493
+
494
+ await expect(
495
+ getAndroidEmulatorPlatformInstance(
496
+ {
497
+ name: 'android',
498
+ device: {
499
+ type: 'emulator',
500
+ name: 'Pixel_8_API_35',
501
+ },
502
+ bundleId: 'com.harnessplayground',
503
+ activityName: '.MainActivity',
504
+ },
505
+ harnessConfig,
506
+ init
507
+ )
508
+ ).rejects.toBeInstanceOf(HarnessEmulatorConfigError);
509
+ });
510
+
511
+ it('returns a noop emulator app monitor when native crash detection is disabled', async () => {
512
+ vi.spyOn(
513
+ await import('../environment.js'),
514
+ 'ensureAndroidEmulatorEnvironment'
515
+ ).mockResolvedValue('/tmp/android-sdk');
516
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
517
+ vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
518
+ vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
519
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
520
+ vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
521
+ vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
522
+ vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
523
+ vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
524
+ undefined
525
+ );
526
+
527
+ const instance = await getAndroidEmulatorPlatformInstance(
528
+ {
529
+ name: 'android',
530
+ device: {
531
+ type: 'emulator',
532
+ name: 'Pixel_8_API_35',
533
+ avd: {
534
+ apiLevel: 35,
535
+ profile: 'pixel_8',
536
+ diskSize: '1G',
537
+ heapSize: '1G',
538
+ },
539
+ },
540
+ bundleId: 'com.harnessplayground',
541
+ activityName: '.MainActivity',
542
+ },
543
+ harnessConfigWithoutNativeCrashDetection,
544
+ init
545
+ );
546
+
547
+ const listener = vi.fn();
548
+ const appMonitor = instance.createAppMonitor();
549
+
550
+ await expect(appMonitor.start()).resolves.toBeUndefined();
551
+ await expect(appMonitor.stop()).resolves.toBeUndefined();
552
+ await expect(appMonitor.dispose()).resolves.toBeUndefined();
553
+ expect(appMonitor.addListener(listener)).toBeUndefined();
554
+ expect(appMonitor.removeListener(listener)).toBeUndefined();
555
+ });
556
+
557
+ it('returns a noop physical device app monitor when native crash detection is disabled', async () => {
558
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['012345']);
559
+ vi.spyOn(adb, 'getDeviceInfo').mockResolvedValue({
560
+ manufacturer: 'motorola',
561
+ model: 'moto g72',
562
+ });
563
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
564
+ vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
565
+ vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
566
+ vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
567
+ vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
568
+ undefined
569
+ );
570
+
571
+ await expect(
572
+ getAndroidPhysicalDevicePlatformInstance(
573
+ {
574
+ name: 'android-device',
575
+ device: {
576
+ type: 'physical',
577
+ manufacturer: 'motorola',
578
+ model: 'moto g72',
579
+ },
580
+ bundleId: 'com.harnessplayground',
581
+ activityName: '.MainActivity',
582
+ },
583
+ harnessConfigWithoutNativeCrashDetection
584
+ )
585
+ ).resolves.toBeDefined();
586
+
587
+ const instance = await getAndroidPhysicalDevicePlatformInstance(
588
+ {
589
+ name: 'android-device',
590
+ device: {
591
+ type: 'physical',
592
+ manufacturer: 'motorola',
593
+ model: 'moto g72',
594
+ },
595
+ bundleId: 'com.harnessplayground',
596
+ activityName: '.MainActivity',
597
+ },
598
+ harnessConfigWithoutNativeCrashDetection
599
+ );
600
+
601
+ const listener = vi.fn();
602
+ const appMonitor = instance.createAppMonitor();
603
+
604
+ await expect(appMonitor.start()).resolves.toBeUndefined();
605
+ await expect(appMonitor.stop()).resolves.toBeUndefined();
606
+ await expect(appMonitor.dispose()).resolves.toBeUndefined();
607
+ expect(appMonitor.addListener(listener)).toBeUndefined();
608
+ expect(appMonitor.removeListener(listener)).toBeUndefined();
609
+ });
610
+ });