@react-native-harness/platform-android 1.3.0 → 1.4.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.
Files changed (62) hide show
  1. package/dist/__tests__/adb-wait.test.d.ts +2 -0
  2. package/dist/__tests__/adb-wait.test.d.ts.map +1 -0
  3. package/dist/__tests__/adb-wait.test.js +69 -0
  4. package/dist/__tests__/app-session.test.d.ts +2 -0
  5. package/dist/__tests__/app-session.test.d.ts.map +1 -0
  6. package/dist/__tests__/app-session.test.js +57 -0
  7. package/dist/__tests__/avd-config.test.js +58 -0
  8. package/dist/__tests__/crash-diagnostics.test.d.ts +2 -0
  9. package/dist/__tests__/crash-diagnostics.test.d.ts.map +1 -0
  10. package/dist/__tests__/crash-diagnostics.test.js +119 -0
  11. package/dist/__tests__/crash-reporter.test.d.ts +2 -0
  12. package/dist/__tests__/crash-reporter.test.d.ts.map +1 -0
  13. package/dist/__tests__/crash-reporter.test.js +150 -0
  14. package/dist/__tests__/emulator-startup.test.js +30 -3
  15. package/dist/__tests__/instance.test.js +139 -24
  16. package/dist/adb.d.ts +7 -1
  17. package/dist/adb.d.ts.map +1 -1
  18. package/dist/adb.js +72 -23
  19. package/dist/app-session.d.ts +26 -0
  20. package/dist/app-session.d.ts.map +1 -0
  21. package/dist/app-session.js +214 -0
  22. package/dist/avd-config.d.ts +1 -0
  23. package/dist/avd-config.d.ts.map +1 -1
  24. package/dist/avd-config.js +11 -0
  25. package/dist/crash-diagnostics.d.ts +40 -0
  26. package/dist/crash-diagnostics.d.ts.map +1 -0
  27. package/dist/crash-diagnostics.js +256 -0
  28. package/dist/crash-reporter.d.ts +12 -0
  29. package/dist/crash-reporter.d.ts.map +1 -0
  30. package/dist/crash-reporter.js +150 -0
  31. package/dist/emulator-startup.d.ts +23 -0
  32. package/dist/emulator-startup.d.ts.map +1 -1
  33. package/dist/emulator-startup.js +58 -0
  34. package/dist/index.d.ts +1 -0
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +1 -0
  37. package/dist/instance.d.ts +1 -1
  38. package/dist/instance.d.ts.map +1 -1
  39. package/dist/instance.js +51 -72
  40. package/dist/runner.d.ts +2 -2
  41. package/dist/runner.d.ts.map +1 -1
  42. package/dist/runner.js +1 -1
  43. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  44. package/package.json +4 -4
  45. package/src/__tests__/adb-wait.test.ts +95 -0
  46. package/src/__tests__/app-session.test.ts +76 -0
  47. package/src/__tests__/avd-config.test.ts +70 -0
  48. package/src/__tests__/crash-diagnostics.test.ts +144 -0
  49. package/src/__tests__/crash-reporter.test.ts +167 -0
  50. package/src/__tests__/emulator-startup.test.ts +53 -6
  51. package/src/__tests__/instance.test.ts +226 -84
  52. package/src/adb.ts +129 -63
  53. package/src/app-session.ts +316 -0
  54. package/src/avd-config.ts +17 -0
  55. package/src/crash-diagnostics.ts +471 -0
  56. package/src/crash-reporter.ts +263 -0
  57. package/src/emulator-startup.ts +74 -0
  58. package/src/index.ts +1 -0
  59. package/src/instance.ts +84 -105
  60. package/src/runner.ts +11 -11
  61. package/test/setup.ts +1 -0
  62. package/src/app-monitor.ts +0 -577
@@ -3,6 +3,7 @@ import {
3
3
  DEFAULT_METRO_PORT,
4
4
  type Config as HarnessConfig,
5
5
  } from '@react-native-harness/config';
6
+ import type { Subprocess } from '@react-native-harness/tools';
6
7
  import {
7
8
  getAndroidEmulatorPlatformInstance,
8
9
  getAndroidPhysicalDevicePlatformInstance,
@@ -11,6 +12,25 @@ import * as adb from '../adb.js';
11
12
  import * as avdConfig from '../avd-config.js';
12
13
  import * as sharedPrefs from '../shared-prefs.js';
13
14
  import { HarnessAppPathError, HarnessEmulatorConfigError } from '../errors.js';
15
+ import * as emulatorStartup from '../emulator-startup.js';
16
+ const { getEmulatorCpuCores } = emulatorStartup;
17
+
18
+ const createLogcatProcess = (lines: string[] = []): Subprocess => {
19
+ // Mirrors nano-spawn's real Subprocess, which is both a Promise and an
20
+ // async iterable, since app-session.ts relies on it being thenable.
21
+ const process = Object.assign(Promise.resolve(undefined), {
22
+ nodeChildProcess: Promise.resolve({
23
+ kill: vi.fn(),
24
+ }),
25
+ [Symbol.asyncIterator]: async function* () {
26
+ for (const line of lines) {
27
+ yield line;
28
+ }
29
+ },
30
+ });
31
+
32
+ return process as unknown as Subprocess;
33
+ };
14
34
 
15
35
  const harnessConfig = {
16
36
  metroPort: DEFAULT_METRO_PORT,
@@ -27,17 +47,22 @@ describe('Android platform instance', () => {
27
47
  beforeEach(async () => {
28
48
  vi.restoreAllMocks();
29
49
  vi.unstubAllEnvs();
50
+ vi.useRealTimers();
30
51
  vi.spyOn(
31
52
  await import('../environment.js'),
32
- 'ensureAndroidEmulatorAvailable',
53
+ 'ensureAndroidEmulatorAvailable'
33
54
  ).mockResolvedValue('/tmp/android-sdk');
55
+ vi.spyOn(
56
+ emulatorStartup,
57
+ 'warnIfEmulatorAccelerationUnavailable'
58
+ ).mockResolvedValue(undefined);
34
59
  });
35
60
 
36
61
  it('reuses a running emulator and does not shut it down on dispose', async () => {
37
62
  const ensureAndroidEmulatorEnvironment = vi
38
63
  .spyOn(
39
64
  await import('../environment.js'),
40
- 'ensureAndroidEmulatorEnvironment',
65
+ 'ensureAndroidEmulatorEnvironment'
41
66
  )
42
67
  .mockResolvedValue('/tmp/android-sdk');
43
68
  vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
@@ -47,11 +72,12 @@ describe('Android platform instance', () => {
47
72
  vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
48
73
  vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
49
74
  vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
75
+ vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
50
76
  vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
51
- undefined,
77
+ undefined
52
78
  );
53
79
  vi.spyOn(sharedPrefs, 'clearHarnessDebugHttpHost').mockResolvedValue(
54
- undefined,
80
+ undefined
55
81
  );
56
82
  vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
57
83
  const stopEmulator = vi.spyOn(adb, 'stopEmulator').mockResolvedValue();
@@ -73,7 +99,7 @@ describe('Android platform instance', () => {
73
99
  activityName: '.MainActivity',
74
100
  },
75
101
  harnessConfig,
76
- init,
102
+ init
77
103
  );
78
104
 
79
105
  await instance.dispose();
@@ -85,7 +111,7 @@ describe('Android platform instance', () => {
85
111
  it('creates and boots an emulator when missing and shuts it down on dispose', async () => {
86
112
  vi.spyOn(
87
113
  await import('../environment.js'),
88
- 'ensureAndroidEmulatorEnvironment',
114
+ 'ensureAndroidEmulatorEnvironment'
89
115
  ).mockResolvedValue('/tmp/android-sdk');
90
116
  vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([]);
91
117
  vi.spyOn(adb, 'hasAvd').mockResolvedValue(false);
@@ -98,11 +124,12 @@ describe('Android platform instance', () => {
98
124
  vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
99
125
  vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
100
126
  vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
127
+ vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
101
128
  vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
102
- undefined,
129
+ undefined
103
130
  );
104
131
  vi.spyOn(sharedPrefs, 'clearHarnessDebugHttpHost').mockResolvedValue(
105
- undefined,
132
+ undefined
106
133
  );
107
134
  vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
108
135
  const stopEmulator = vi.spyOn(adb, 'stopEmulator').mockResolvedValue();
@@ -124,7 +151,7 @@ describe('Android platform instance', () => {
124
151
  activityName: '.MainActivity',
125
152
  },
126
153
  harnessConfig,
127
- init,
154
+ init
128
155
  );
129
156
 
130
157
  expect(createAvd).toHaveBeenCalledWith({
@@ -145,7 +172,7 @@ describe('Android platform instance', () => {
145
172
  const ensureAndroidEmulatorEnvironment = vi
146
173
  .spyOn(
147
174
  await import('../environment.js'),
148
- 'ensureAndroidEmulatorEnvironment',
175
+ 'ensureAndroidEmulatorEnvironment'
149
176
  )
150
177
  .mockResolvedValue('/tmp/android-sdk');
151
178
  vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([]);
@@ -159,8 +186,9 @@ describe('Android platform instance', () => {
159
186
  vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
160
187
  vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
161
188
  vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
189
+ vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
162
190
  vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
163
- undefined,
191
+ undefined
164
192
  );
165
193
 
166
194
  await expect(
@@ -181,8 +209,8 @@ describe('Android platform instance', () => {
181
209
  activityName: '.MainActivity',
182
210
  },
183
211
  harnessConfig,
184
- init,
185
- ),
212
+ init
213
+ )
186
214
  ).resolves.toBeDefined();
187
215
 
188
216
  expect(ensureAndroidEmulatorEnvironment).toHaveBeenCalledWith(35);
@@ -194,11 +222,11 @@ describe('Android platform instance', () => {
194
222
  vi.stubEnv('HARNESS_AVD_CACHING', 'true');
195
223
  vi.spyOn(
196
224
  await import('../environment.js'),
197
- 'getHostAndroidSystemImageArch',
225
+ 'getHostAndroidSystemImageArch'
198
226
  ).mockReturnValue('arm64-v8a');
199
227
  vi.spyOn(
200
228
  await import('../environment.js'),
201
- 'ensureAndroidEmulatorEnvironment',
229
+ 'ensureAndroidEmulatorEnvironment'
202
230
  ).mockResolvedValue('/tmp/android-sdk');
203
231
  vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([]);
204
232
  vi.spyOn(adb, 'hasAvd').mockResolvedValue(true);
@@ -208,6 +236,7 @@ describe('Android platform instance', () => {
208
236
  hwDeviceName: 'pixel_8',
209
237
  diskDataPartitionSize: '1G',
210
238
  vmHeapSize: '1G',
239
+ hwCpuNcore: String(getEmulatorCpuCores()),
211
240
  });
212
241
  vi.spyOn(adb, 'startEmulator').mockResolvedValue(undefined);
213
242
  vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
@@ -215,8 +244,9 @@ describe('Android platform instance', () => {
215
244
  vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
216
245
  vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
217
246
  vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
247
+ vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
218
248
  vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
219
- undefined,
249
+ undefined
220
250
  );
221
251
 
222
252
  await expect(
@@ -238,14 +268,14 @@ describe('Android platform instance', () => {
238
268
  activityName: '.MainActivity',
239
269
  },
240
270
  harnessConfig,
241
- init,
242
- ),
271
+ init
272
+ )
243
273
  ).resolves.toBeDefined();
244
274
 
245
275
  expect(adb.startEmulator).toHaveBeenCalledTimes(1);
246
276
  expect(adb.startEmulator).toHaveBeenCalledWith(
247
277
  'Pixel_8_API_35',
248
- 'snapshot-reuse',
278
+ 'snapshot-reuse'
249
279
  );
250
280
  });
251
281
 
@@ -253,7 +283,7 @@ describe('Android platform instance', () => {
253
283
  vi.stubEnv('HARNESS_AVD_CACHING', 'true');
254
284
  vi.spyOn(
255
285
  await import('../environment.js'),
256
- 'ensureAndroidEmulatorEnvironment',
286
+ 'ensureAndroidEmulatorEnvironment'
257
287
  ).mockResolvedValue('/tmp/android-sdk');
258
288
  vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([]);
259
289
  vi.spyOn(adb, 'hasAvd').mockResolvedValue(true);
@@ -276,8 +306,9 @@ describe('Android platform instance', () => {
276
306
  vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
277
307
  vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
278
308
  vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
309
+ vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
279
310
  vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
280
- undefined,
311
+ undefined
281
312
  );
282
313
 
283
314
  await expect(
@@ -299,8 +330,8 @@ describe('Android platform instance', () => {
299
330
  activityName: '.MainActivity',
300
331
  },
301
332
  harnessConfig,
302
- init,
303
- ),
333
+ init
334
+ )
304
335
  ).resolves.toBeDefined();
305
336
 
306
337
  expect(deleteAvd).toHaveBeenCalledWith('Pixel_8_API_35');
@@ -308,17 +339,17 @@ describe('Android platform instance', () => {
308
339
  expect(stopEmulator).toHaveBeenCalledWith('emulator-5554');
309
340
  expect(waitForEmulatorDisconnect).toHaveBeenCalledWith(
310
341
  'emulator-5554',
311
- init.signal,
342
+ init.signal
312
343
  );
313
344
  expect(adb.startEmulator).toHaveBeenNthCalledWith(
314
345
  1,
315
346
  'Pixel_8_API_35',
316
- 'clean-snapshot-generation',
347
+ 'clean-snapshot-generation'
317
348
  );
318
349
  expect(adb.startEmulator).toHaveBeenNthCalledWith(
319
350
  2,
320
351
  'Pixel_8_API_35',
321
- 'snapshot-reuse',
352
+ 'snapshot-reuse'
322
353
  );
323
354
  });
324
355
 
@@ -326,7 +357,7 @@ describe('Android platform instance', () => {
326
357
  vi.stubEnv('HARNESS_AVD_CACHING', 'true');
327
358
  vi.spyOn(
328
359
  await import('../environment.js'),
329
- 'ensureAndroidEmulatorEnvironment',
360
+ 'ensureAndroidEmulatorEnvironment'
330
361
  ).mockResolvedValue('/tmp/android-sdk');
331
362
  vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([]);
332
363
  vi.spyOn(adb, 'hasAvd').mockResolvedValue(false);
@@ -343,8 +374,9 @@ describe('Android platform instance', () => {
343
374
  vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
344
375
  vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
345
376
  vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
377
+ vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
346
378
  vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
347
- undefined,
379
+ undefined
348
380
  );
349
381
 
350
382
  await expect(
@@ -366,24 +398,24 @@ describe('Android platform instance', () => {
366
398
  activityName: '.MainActivity',
367
399
  },
368
400
  harnessConfig,
369
- init,
370
- ),
401
+ init
402
+ )
371
403
  ).resolves.toBeDefined();
372
404
 
373
405
  expect(startEmulator).toHaveBeenNthCalledWith(
374
406
  1,
375
407
  'Pixel_8_API_35',
376
- 'clean-snapshot-generation',
408
+ 'clean-snapshot-generation'
377
409
  );
378
410
  expect(stopEmulator).toHaveBeenCalledWith('emulator-5554');
379
411
  expect(waitForEmulatorDisconnect).toHaveBeenCalledWith(
380
412
  'emulator-5554',
381
- init.signal,
413
+ init.signal
382
414
  );
383
415
  expect(startEmulator).toHaveBeenNthCalledWith(
384
416
  2,
385
417
  'Pixel_8_API_35',
386
- 'snapshot-reuse',
418
+ 'snapshot-reuse'
387
419
  );
388
420
  });
389
421
 
@@ -411,8 +443,8 @@ describe('Android platform instance', () => {
411
443
  activityName: '.MainActivity',
412
444
  },
413
445
  harnessConfig,
414
- init,
415
- ),
446
+ init
447
+ )
416
448
  ).rejects.toBeInstanceOf(HarnessAppPathError);
417
449
  });
418
450
 
@@ -441,8 +473,8 @@ describe('Android platform instance', () => {
441
473
  activityName: '.MainActivity',
442
474
  },
443
475
  harnessConfig,
444
- init,
445
- ),
476
+ init
477
+ )
446
478
  ).rejects.toBeInstanceOf(HarnessAppPathError);
447
479
  });
448
480
 
@@ -461,15 +493,16 @@ describe('Android platform instance', () => {
461
493
  activityName: '.MainActivity',
462
494
  },
463
495
  harnessConfig,
464
- init,
465
- ),
496
+ init
497
+ )
466
498
  ).rejects.toBeInstanceOf(HarnessEmulatorConfigError);
467
499
  });
468
500
 
469
- it('returns a noop emulator app monitor when native crash detection is disabled', async () => {
501
+ it('creates a real emulator app session when native crash detection is disabled', async () => {
502
+ vi.useFakeTimers();
470
503
  vi.spyOn(
471
504
  await import('../environment.js'),
472
- 'ensureAndroidEmulatorEnvironment',
505
+ 'ensureAndroidEmulatorEnvironment'
473
506
  ).mockResolvedValue('/tmp/android-sdk');
474
507
  vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
475
508
  vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
@@ -478,9 +511,16 @@ describe('Android platform instance', () => {
478
511
  vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
479
512
  vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
480
513
  vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
514
+ vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
481
515
  vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
482
- undefined,
516
+ undefined
483
517
  );
518
+ const stopApp = vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
519
+ const startApp = vi.spyOn(adb, 'startApp').mockResolvedValue(undefined);
520
+ const getAppPid = vi.spyOn(adb, 'getAppPid').mockResolvedValue(4321);
521
+ const startLogcat = vi
522
+ .spyOn(adb, 'startLogcat')
523
+ .mockReturnValue(createLogcatProcess());
484
524
 
485
525
  const instance = await getAndroidEmulatorPlatformInstance(
486
526
  {
@@ -499,20 +539,113 @@ describe('Android platform instance', () => {
499
539
  activityName: '.MainActivity',
500
540
  },
501
541
  harnessConfigWithoutNativeCrashDetection,
502
- init,
542
+ init
503
543
  );
504
544
 
505
545
  const listener = vi.fn();
506
- const appMonitor = instance.createAppMonitor();
546
+ const appSession = await instance.createAppSession();
547
+ appSession.addListener(listener);
548
+ await vi.advanceTimersByTimeAsync(0);
507
549
 
508
- await expect(appMonitor.start()).resolves.toBeUndefined();
509
- await expect(appMonitor.stop()).resolves.toBeUndefined();
510
- await expect(appMonitor.dispose()).resolves.toBeUndefined();
511
- expect(appMonitor.addListener(listener)).toBeUndefined();
512
- expect(appMonitor.removeListener(listener)).toBeUndefined();
550
+ expect(startLogcat).toHaveBeenCalledWith(
551
+ 'emulator-5554',
552
+ [
553
+ 'logcat',
554
+ '-v',
555
+ 'threadtime',
556
+ '-b',
557
+ 'crash',
558
+ '--uid=10234',
559
+ '-T',
560
+ '01-01 00:00:00.000',
561
+ ],
562
+ { signal: expect.any(AbortSignal) }
563
+ );
564
+ expect(startLogcat.mock.invocationCallOrder[0]).toBeLessThan(
565
+ startApp.mock.invocationCallOrder[0]
566
+ );
567
+ await expect(appSession.getState()).resolves.toEqual({
568
+ status: 'running',
569
+ pid: 4321,
570
+ });
571
+ expect(getAppPid).toHaveBeenCalled();
572
+ expect(listener).not.toHaveBeenCalled();
573
+ expect(appSession.removeListener(listener)).toBeUndefined();
574
+ await expect(appSession.dispose()).resolves.toBeUndefined();
575
+ expect(stopApp).toHaveBeenCalled();
513
576
  });
514
577
 
515
- it('returns a noop physical device app monitor when native crash detection is disabled', async () => {
578
+ it('reports an early Android crash from logcat before a PID poll succeeds', async () => {
579
+ vi.useFakeTimers();
580
+ vi.spyOn(
581
+ await import('../environment.js'),
582
+ 'ensureAndroidEmulatorEnvironment'
583
+ ).mockResolvedValue('/tmp/android-sdk');
584
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
585
+ vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
586
+ vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
587
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
588
+ vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
589
+ vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
590
+ vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
591
+ vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
592
+ vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
593
+ undefined
594
+ );
595
+ vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
596
+ vi.spyOn(adb, 'startApp').mockResolvedValue(undefined);
597
+ vi.spyOn(adb, 'getAppPid').mockResolvedValue(null);
598
+ vi.spyOn(adb, 'startLogcat').mockReturnValue(
599
+ createLogcatProcess([
600
+ '--------- beginning of crash',
601
+ 'Process: com.harnessplayground, PID: 7777',
602
+ 'FATAL EXCEPTION: main',
603
+ ])
604
+ );
605
+
606
+ const instance = await getAndroidEmulatorPlatformInstance(
607
+ {
608
+ name: 'android',
609
+ device: {
610
+ type: 'emulator',
611
+ name: 'Pixel_8_API_35',
612
+ avd: {
613
+ apiLevel: 35,
614
+ profile: 'pixel_8',
615
+ diskSize: '1G',
616
+ heapSize: '1G',
617
+ },
618
+ },
619
+ bundleId: 'com.harnessplayground',
620
+ activityName: '.MainActivity',
621
+ },
622
+ harnessConfig,
623
+ init
624
+ );
625
+
626
+ const listener = vi.fn();
627
+ const appSession = await instance.createAppSession();
628
+ appSession.addListener(listener);
629
+
630
+ await vi.advanceTimersByTimeAsync(0);
631
+
632
+ expect(appSession.getLogs().map((entry) => entry.line)).toEqual([
633
+ '--------- beginning of crash',
634
+ 'Process: com.harnessplayground, PID: 7777',
635
+ 'FATAL EXCEPTION: main',
636
+ ]);
637
+ await expect(appSession.getState()).resolves.toMatchObject({
638
+ status: 'exited',
639
+ pid: 7777,
640
+ reason: 'observed-exit',
641
+ });
642
+ expect(listener).toHaveBeenCalledWith({ type: 'app_exited' });
643
+
644
+ await appSession.dispose();
645
+ });
646
+
647
+ it('creates a real physical-device app session when native crash detection is disabled', async () => {
648
+ vi.useFakeTimers();
516
649
  vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['012345']);
517
650
  vi.spyOn(adb, 'getDeviceInfo').mockResolvedValue({
518
651
  manufacturer: 'motorola',
@@ -522,25 +655,16 @@ describe('Android platform instance', () => {
522
655
  vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
523
656
  vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
524
657
  vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
658
+ vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
525
659
  vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
526
- undefined,
660
+ undefined
527
661
  );
528
-
529
- await expect(
530
- getAndroidPhysicalDevicePlatformInstance(
531
- {
532
- name: 'android-device',
533
- device: {
534
- type: 'physical',
535
- manufacturer: 'motorola',
536
- model: 'moto g72',
537
- },
538
- bundleId: 'com.harnessplayground',
539
- activityName: '.MainActivity',
540
- },
541
- harnessConfigWithoutNativeCrashDetection,
542
- ),
543
- ).resolves.toBeDefined();
662
+ vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
663
+ vi.spyOn(adb, 'startApp').mockResolvedValue(undefined);
664
+ vi.spyOn(adb, 'getAppPid').mockResolvedValue(8765);
665
+ const startLogcat = vi
666
+ .spyOn(adb, 'startLogcat')
667
+ .mockReturnValue(createLogcatProcess());
544
668
 
545
669
  const instance = await getAndroidPhysicalDevicePlatformInstance(
546
670
  {
@@ -553,23 +677,41 @@ describe('Android platform instance', () => {
553
677
  bundleId: 'com.harnessplayground',
554
678
  activityName: '.MainActivity',
555
679
  },
556
- harnessConfigWithoutNativeCrashDetection,
680
+ harnessConfigWithoutNativeCrashDetection
557
681
  );
558
682
 
559
683
  const listener = vi.fn();
560
- const appMonitor = instance.createAppMonitor();
684
+ const appSession = await instance.createAppSession();
685
+ appSession.addListener(listener);
686
+ await vi.advanceTimersByTimeAsync(0);
561
687
 
562
- await expect(appMonitor.start()).resolves.toBeUndefined();
563
- await expect(appMonitor.stop()).resolves.toBeUndefined();
564
- await expect(appMonitor.dispose()).resolves.toBeUndefined();
565
- expect(appMonitor.addListener(listener)).toBeUndefined();
566
- expect(appMonitor.removeListener(listener)).toBeUndefined();
688
+ expect(startLogcat).toHaveBeenCalledWith(
689
+ '012345',
690
+ [
691
+ 'logcat',
692
+ '-v',
693
+ 'threadtime',
694
+ '-b',
695
+ 'crash',
696
+ '--uid=10234',
697
+ '-T',
698
+ '01-01 00:00:00.000',
699
+ ],
700
+ { signal: expect.any(AbortSignal) }
701
+ );
702
+ await expect(appSession.getState()).resolves.toEqual({
703
+ status: 'running',
704
+ pid: 8765,
705
+ });
706
+ expect(listener).not.toHaveBeenCalled();
707
+ expect(appSession.removeListener(listener)).toBeUndefined();
708
+ await expect(appSession.dispose()).resolves.toBeUndefined();
567
709
  });
568
710
 
569
711
  it('grants permissions when permissions are enabled for emulator', async () => {
570
712
  vi.spyOn(
571
713
  await import('../environment.js'),
572
- 'ensureAndroidEmulatorEnvironment',
714
+ 'ensureAndroidEmulatorEnvironment'
573
715
  ).mockResolvedValue('/tmp/android-sdk');
574
716
  vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
575
717
  vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
@@ -579,7 +721,7 @@ describe('Android platform instance', () => {
579
721
  vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
580
722
  vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
581
723
  vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
582
- undefined,
724
+ undefined
583
725
  );
584
726
  const grantPermissions = vi
585
727
  .spyOn(adb, 'grantPermissions')
@@ -607,19 +749,19 @@ describe('Android platform instance', () => {
607
749
  activityName: '.MainActivity',
608
750
  },
609
751
  harnessConfigWithPermissions,
610
- init,
752
+ init
611
753
  );
612
754
 
613
755
  expect(grantPermissions).toHaveBeenCalledWith(
614
756
  'emulator-5554',
615
- 'com.harnessplayground',
757
+ 'com.harnessplayground'
616
758
  );
617
759
  });
618
760
 
619
761
  it('does not grant permissions when permissions are disabled for emulator', async () => {
620
762
  vi.spyOn(
621
763
  await import('../environment.js'),
622
- 'ensureAndroidEmulatorEnvironment',
764
+ 'ensureAndroidEmulatorEnvironment'
623
765
  ).mockResolvedValue('/tmp/android-sdk');
624
766
  vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
625
767
  vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
@@ -629,7 +771,7 @@ describe('Android platform instance', () => {
629
771
  vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
630
772
  vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
631
773
  vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
632
- undefined,
774
+ undefined
633
775
  );
634
776
  const grantPermissions = vi
635
777
  .spyOn(adb, 'grantPermissions')
@@ -652,7 +794,7 @@ describe('Android platform instance', () => {
652
794
  activityName: '.MainActivity',
653
795
  },
654
796
  harnessConfig,
655
- init,
797
+ init
656
798
  );
657
799
 
658
800
  expect(grantPermissions).not.toHaveBeenCalled();
@@ -669,7 +811,7 @@ describe('Android platform instance', () => {
669
811
  vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
670
812
  vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
671
813
  vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
672
- undefined,
814
+ undefined
673
815
  );
674
816
  const grantPermissions = vi
675
817
  .spyOn(adb, 'grantPermissions')
@@ -691,12 +833,12 @@ describe('Android platform instance', () => {
691
833
  bundleId: 'com.harnessplayground',
692
834
  activityName: '.MainActivity',
693
835
  },
694
- harnessConfigWithPermissions,
836
+ harnessConfigWithPermissions
695
837
  );
696
838
 
697
839
  expect(grantPermissions).toHaveBeenCalledWith(
698
840
  '012345',
699
- 'com.harnessplayground',
841
+ 'com.harnessplayground'
700
842
  );
701
843
  });
702
844
  });