@react-native-harness/platform-android 1.3.0 → 1.4.0-rc.1

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 (37) hide show
  1. package/dist/__tests__/crash-diagnostics.test.d.ts +2 -0
  2. package/dist/__tests__/crash-diagnostics.test.d.ts.map +1 -0
  3. package/dist/__tests__/crash-diagnostics.test.js +119 -0
  4. package/dist/__tests__/crash-reporter.test.d.ts +2 -0
  5. package/dist/__tests__/crash-reporter.test.d.ts.map +1 -0
  6. package/dist/__tests__/crash-reporter.test.js +150 -0
  7. package/dist/__tests__/instance.test.js +133 -24
  8. package/dist/adb.d.ts +4 -0
  9. package/dist/adb.d.ts.map +1 -1
  10. package/dist/adb.js +42 -2
  11. package/dist/app-session.d.ts +17 -0
  12. package/dist/app-session.d.ts.map +1 -0
  13. package/dist/app-session.js +209 -0
  14. package/dist/crash-diagnostics.d.ts +40 -0
  15. package/dist/crash-diagnostics.d.ts.map +1 -0
  16. package/dist/crash-diagnostics.js +256 -0
  17. package/dist/crash-reporter.d.ts +12 -0
  18. package/dist/crash-reporter.d.ts.map +1 -0
  19. package/dist/crash-reporter.js +150 -0
  20. package/dist/instance.d.ts +1 -1
  21. package/dist/instance.d.ts.map +1 -1
  22. package/dist/instance.js +32 -59
  23. package/dist/runner.d.ts.map +1 -1
  24. package/dist/runner.js +1 -1
  25. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  26. package/package.json +4 -4
  27. package/src/__tests__/crash-diagnostics.test.ts +144 -0
  28. package/src/__tests__/crash-reporter.test.ts +167 -0
  29. package/src/__tests__/instance.test.ts +174 -31
  30. package/src/adb.ts +61 -2
  31. package/src/app-session.ts +300 -0
  32. package/src/crash-diagnostics.ts +471 -0
  33. package/src/crash-reporter.ts +263 -0
  34. package/src/instance.ts +56 -91
  35. package/src/runner.ts +7 -3
  36. package/test/setup.ts +1 -0
  37. package/src/app-monitor.ts +0 -577
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=crash-diagnostics.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crash-diagnostics.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/crash-diagnostics.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,119 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { collectDropboxArtifacts, filterExitInfo, getBestDropboxArtifact, parseDropboxOutput, } from '../crash-diagnostics.js';
3
+ const javaCrashDropbox = `
4
+ Drop box contents: 1 entries
5
+ Max entries: 1000
6
+ ========================================
7
+ 2026-03-12 10:30:45.123 data_app_crash (text, 500 bytes)
8
+ Process: com.harnessplayground
9
+ PID: 7777
10
+ Package: com.harnessplayground v1 (1.0)
11
+ java.lang.RuntimeException: boom
12
+ \tat com.harnessplayground.MainActivity.onCreate(MainActivity.kt:38)
13
+ `;
14
+ const nativeCrashDropbox = `
15
+ ========================================
16
+ 2026-03-12 10:31:10.456 data_app_native_crash (text, 800 bytes)
17
+ Process: com.harnessplayground
18
+ PID: 8888
19
+ signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xdeadbeef
20
+ Abort message: 'JNI DETECTED ERROR IN APPLICATION'
21
+ backtrace:
22
+ #00 pc 00001234 /data/app/lib/libapp.so
23
+ `;
24
+ const deviceJavaCrashDropbox = `
25
+ Drop box contents: 8 entries
26
+ Searching for: data_app_crash
27
+
28
+ ========================================
29
+ 2026-05-29 10:22:34 data_app_crash (text, 1328 bytes)
30
+ SystemUptimeMs: 79467172
31
+ Process: com.harnessplayground
32
+ PID: 586
33
+ Package: com.harnessplayground v1 (1.0)
34
+ java.lang.RuntimeException: Intentional asynchronous Kotlin crash
35
+ \tat com.harnessplayground.PlaygroundCrashModule.crashFromKotlinAsync(PlaygroundCrashModule.kt:44)
36
+ `;
37
+ describe('collectDropboxArtifacts', () => {
38
+ it('matches harness playground crashes from merged per-tag dropbox output', async () => {
39
+ const artifacts = await collectDropboxArtifacts({
40
+ bundleId: 'com.harnessplayground',
41
+ getDropboxOutput: async () => deviceJavaCrashDropbox,
42
+ occurredAt: Date.now(),
43
+ pid: 586,
44
+ });
45
+ expect(artifacts).toHaveLength(1);
46
+ expect(artifacts[0]).toMatchObject({
47
+ artifactType: 'dropbox-crash',
48
+ pid: 586,
49
+ processName: 'com.harnessplayground',
50
+ });
51
+ });
52
+ it('ignores Dropbox entries older than the session start time', async () => {
53
+ const artifacts = await collectDropboxArtifacts({
54
+ bundleId: 'com.harnessplayground',
55
+ getDropboxOutput: async () => deviceJavaCrashDropbox,
56
+ minOccurredAt: Date.parse('2026-05-29T10:22:35.000Z'),
57
+ occurredAt: Date.parse('2026-05-29T10:22:36.000Z'),
58
+ });
59
+ expect(artifacts).toHaveLength(0);
60
+ });
61
+ });
62
+ describe('parseDropboxOutput', () => {
63
+ it('parses java and native dropbox entries', () => {
64
+ const entries = parseDropboxOutput(`${javaCrashDropbox}\n${nativeCrashDropbox}`);
65
+ expect(entries).toHaveLength(2);
66
+ expect(entries[0]).toMatchObject({
67
+ tag: 'data_app_crash',
68
+ pid: 7777,
69
+ });
70
+ expect(entries[1]).toMatchObject({
71
+ tag: 'data_app_native_crash',
72
+ pid: 8888,
73
+ });
74
+ expect(entries[1]?.content).toContain('SIGSEGV');
75
+ });
76
+ });
77
+ describe('getBestDropboxArtifact', () => {
78
+ it('prefers the highest-scored artifact', () => {
79
+ const entries = parseDropboxOutput(`${javaCrashDropbox}\n${nativeCrashDropbox}`);
80
+ const artifacts = entries.map((entry, index) => ({
81
+ artifactType: entry.tag === 'data_app_native_crash'
82
+ ? 'dropbox-native-crash'
83
+ : 'dropbox-crash',
84
+ dropboxTag: entry.tag,
85
+ occurredAt: Date.now(),
86
+ score: index === 0 ? 100 : 300,
87
+ summary: entry.content,
88
+ pid: entry.pid,
89
+ processName: 'com.harnessplayground',
90
+ }));
91
+ expect(getBestDropboxArtifact(artifacts)?.dropboxTag).toBe('data_app_native_crash');
92
+ });
93
+ });
94
+ describe('filterExitInfo', () => {
95
+ it('returns matching exit-info records for the target package and pid', () => {
96
+ const output = `
97
+ ApplicationExitInfo #0:
98
+ package=com.harnessplayground pid=7777 realUid=10123
99
+ reason=4 (APP CRASH(NATIVE))
100
+ timestamp=2026-03-12 10:31:10.456
101
+ ApplicationExitInfo #1:
102
+ package=com.other.app pid=9999 realUid=10199
103
+ reason=10 (USER REQUESTED)
104
+ `;
105
+ const filtered = filterExitInfo({
106
+ output,
107
+ bundleId: 'com.harnessplayground',
108
+ pid: 7777,
109
+ });
110
+ expect(filtered).toContain('com.harnessplayground pid=7777');
111
+ expect(filtered).not.toContain('com.other.app');
112
+ });
113
+ it('returns null when no exit-info records exist', () => {
114
+ expect(filterExitInfo({
115
+ output: 'No exit info records for com.harnessplayground',
116
+ bundleId: 'com.harnessplayground',
117
+ })).toBeNull();
118
+ });
119
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=crash-reporter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crash-reporter.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/crash-reporter.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,150 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { createAndroidCrashReporter } from '../crash-reporter.js';
3
+ const javaCrashDropbox = `
4
+ ========================================
5
+ 2026-03-12 10:30:45.123 data_app_crash (text, 500 bytes)
6
+ Process: com.harnessplayground
7
+ PID: 7777
8
+ Package: com.harnessplayground v1 (1.0)
9
+ java.lang.RuntimeException: boom
10
+ \tat com.harnessplayground.MainActivity.onCreate(MainActivity.kt:38)
11
+ `;
12
+ const nativeCrashDropbox = `
13
+ ========================================
14
+ 2026-03-12 10:31:10.456 data_app_native_crash (text, 800 bytes)
15
+ Process: com.harnessplayground
16
+ PID: 8888
17
+ signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xdeadbeef
18
+ Abort message: 'JNI DETECTED ERROR IN APPLICATION'
19
+ backtrace:
20
+ #00 pc 00001234 /data/app/lib/libapp.so
21
+ `;
22
+ describe('createAndroidCrashReporter', () => {
23
+ afterEach(() => {
24
+ vi.useRealTimers();
25
+ });
26
+ it('persists the current logcat crash block with the test file path', async () => {
27
+ vi.useFakeTimers();
28
+ const persistArtifact = vi.fn(() => '/tmp/.harness/crash-reports/logcat.txt');
29
+ const reporter = createAndroidCrashReporter({
30
+ bundleId: 'com.harnessplayground',
31
+ crashArtifactWriter: {
32
+ runTimestamp: '2026-03-12T11-35-08-000Z',
33
+ persistArtifact,
34
+ },
35
+ getLogs: () => [
36
+ { line: 'ordinary crash-buffer line', occurredAt: 1_000 },
37
+ { line: '--------- beginning of crash', occurredAt: 1_100 },
38
+ {
39
+ line: 'Process: com.harnessplayground, PID: 7777',
40
+ occurredAt: 1_100,
41
+ },
42
+ { line: 'FATAL EXCEPTION: main', occurredAt: 1_100 },
43
+ ],
44
+ });
45
+ const detailsPromise = reporter.getCrashDetails({
46
+ occurredAt: 1_100,
47
+ pid: 7777,
48
+ testFilePath: '/test/native-crash.test.ts',
49
+ });
50
+ await vi.advanceTimersByTimeAsync(100);
51
+ const details = await detailsPromise;
52
+ expect(details).toMatchObject({
53
+ artifactType: 'logcat',
54
+ artifactPath: '/tmp/.harness/crash-reports/logcat.txt',
55
+ pid: 7777,
56
+ processName: 'com.harnessplayground',
57
+ });
58
+ expect(persistArtifact).toHaveBeenCalledWith({
59
+ artifactKind: 'logcat',
60
+ testFilePath: '/test/native-crash.test.ts',
61
+ source: {
62
+ kind: 'text',
63
+ fileName: 'logcat.txt',
64
+ text: [
65
+ '--------- beginning of crash',
66
+ 'Process: com.harnessplayground, PID: 7777',
67
+ 'FATAL EXCEPTION: main',
68
+ '',
69
+ ].join('\n'),
70
+ },
71
+ });
72
+ });
73
+ it('adds dropbox artifacts as enrichment when logcat evidence is available', async () => {
74
+ vi.useFakeTimers();
75
+ const persistArtifact = vi
76
+ .fn()
77
+ .mockImplementation(({ artifactKind }) => {
78
+ return `/tmp/.harness/crash-reports/${artifactKind}.txt`;
79
+ });
80
+ const reporter = createAndroidCrashReporter({
81
+ bundleId: 'com.harnessplayground',
82
+ crashArtifactWriter: {
83
+ runTimestamp: '2026-03-12T11-35-08-000Z',
84
+ persistArtifact,
85
+ },
86
+ getLogs: () => [
87
+ { line: '--------- beginning of crash', occurredAt: 1_100 },
88
+ {
89
+ line: 'Process: com.harnessplayground, PID: 7777',
90
+ occurredAt: 1_100,
91
+ },
92
+ { line: 'FATAL EXCEPTION: main', occurredAt: 1_100 },
93
+ ],
94
+ getDropboxOutput: async () => javaCrashDropbox,
95
+ getExitInfo: async () => 'ApplicationExitInfo #0:\n package=com.harnessplayground pid=7777 reason=4 (APP CRASH(NATIVE))',
96
+ });
97
+ const detailsPromise = reporter.getCrashDetails({
98
+ occurredAt: 1_100,
99
+ pid: 7777,
100
+ testFilePath: '/test/native-crash.test.ts',
101
+ });
102
+ await vi.advanceTimersByTimeAsync(100);
103
+ const details = await detailsPromise;
104
+ expect(details).toMatchObject({
105
+ artifactType: 'logcat',
106
+ artifactPath: '/tmp/.harness/crash-reports/logcat.txt',
107
+ });
108
+ expect(details?.enrichmentArtifacts).toEqual(expect.arrayContaining([
109
+ {
110
+ artifactType: 'dropbox-crash',
111
+ artifactPath: '/tmp/.harness/crash-reports/dropbox-crash.txt',
112
+ },
113
+ {
114
+ artifactType: 'exit-info',
115
+ artifactPath: '/tmp/.harness/crash-reports/exit-info.txt',
116
+ },
117
+ ]));
118
+ });
119
+ it('falls back to native dropbox evidence when logcat is empty', async () => {
120
+ vi.useFakeTimers();
121
+ const persistArtifact = vi
122
+ .fn()
123
+ .mockImplementation(({ artifactKind }) => {
124
+ return `/tmp/.harness/crash-reports/${artifactKind}.txt`;
125
+ });
126
+ const reporter = createAndroidCrashReporter({
127
+ bundleId: 'com.harnessplayground',
128
+ crashArtifactWriter: {
129
+ runTimestamp: '2026-03-12T11-35-08-000Z',
130
+ persistArtifact,
131
+ },
132
+ getLogs: () => [],
133
+ getDropboxOutput: async () => nativeCrashDropbox,
134
+ });
135
+ const detailsPromise = reporter.getCrashDetails({
136
+ occurredAt: 1_100,
137
+ pid: 8888,
138
+ testFilePath: '/test/native-crash.test.ts',
139
+ });
140
+ await vi.advanceTimersByTimeAsync(100);
141
+ const details = await detailsPromise;
142
+ expect(details).toMatchObject({
143
+ artifactType: 'dropbox-native-crash',
144
+ artifactPath: '/tmp/.harness/crash-reports/dropbox-native-crash.txt',
145
+ pid: 8888,
146
+ signal: 'SIGSEGV',
147
+ processName: 'com.harnessplayground',
148
+ });
149
+ });
150
+ });
@@ -5,6 +5,19 @@ import * as adb from '../adb.js';
5
5
  import * as avdConfig from '../avd-config.js';
6
6
  import * as sharedPrefs from '../shared-prefs.js';
7
7
  import { HarnessAppPathError, HarnessEmulatorConfigError } from '../errors.js';
8
+ const createLogcatProcess = (lines = []) => {
9
+ const process = {
10
+ nodeChildProcess: Promise.resolve({
11
+ kill: vi.fn(),
12
+ }),
13
+ [Symbol.asyncIterator]: async function* () {
14
+ for (const line of lines) {
15
+ yield line;
16
+ }
17
+ },
18
+ };
19
+ return process;
20
+ };
8
21
  const harnessConfig = {
9
22
  metroPort: DEFAULT_METRO_PORT,
10
23
  };
@@ -19,6 +32,7 @@ describe('Android platform instance', () => {
19
32
  beforeEach(async () => {
20
33
  vi.restoreAllMocks();
21
34
  vi.unstubAllEnvs();
35
+ vi.useRealTimers();
22
36
  vi.spyOn(await import('../environment.js'), 'ensureAndroidEmulatorAvailable').mockResolvedValue('/tmp/android-sdk');
23
37
  });
24
38
  it('reuses a running emulator and does not shut it down on dispose', async () => {
@@ -32,6 +46,7 @@ describe('Android platform instance', () => {
32
46
  vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
33
47
  vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
34
48
  vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
49
+ vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
35
50
  vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
36
51
  vi.spyOn(sharedPrefs, 'clearHarnessDebugHttpHost').mockResolvedValue(undefined);
37
52
  vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
@@ -68,6 +83,7 @@ describe('Android platform instance', () => {
68
83
  vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
69
84
  vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
70
85
  vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
86
+ vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
71
87
  vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
72
88
  vi.spyOn(sharedPrefs, 'clearHarnessDebugHttpHost').mockResolvedValue(undefined);
73
89
  vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
@@ -113,6 +129,7 @@ describe('Android platform instance', () => {
113
129
  vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
114
130
  vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
115
131
  vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
132
+ vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
116
133
  vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
117
134
  await expect(getAndroidEmulatorPlatformInstance({
118
135
  name: 'android',
@@ -152,6 +169,7 @@ describe('Android platform instance', () => {
152
169
  vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
153
170
  vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
154
171
  vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
172
+ vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
155
173
  vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
156
174
  await expect(getAndroidEmulatorPlatformInstance({
157
175
  name: 'android',
@@ -196,6 +214,7 @@ describe('Android platform instance', () => {
196
214
  vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
197
215
  vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
198
216
  vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
217
+ vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
199
218
  vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
200
219
  await expect(getAndroidEmulatorPlatformInstance({
201
220
  name: 'android',
@@ -238,6 +257,7 @@ describe('Android platform instance', () => {
238
257
  vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
239
258
  vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
240
259
  vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
260
+ vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
241
261
  vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
242
262
  await expect(getAndroidEmulatorPlatformInstance({
243
263
  name: 'android',
@@ -315,7 +335,8 @@ describe('Android platform instance', () => {
315
335
  activityName: '.MainActivity',
316
336
  }, harnessConfig, init)).rejects.toBeInstanceOf(HarnessEmulatorConfigError);
317
337
  });
318
- it('returns a noop emulator app monitor when native crash detection is disabled', async () => {
338
+ it('creates a real emulator app session when native crash detection is disabled', async () => {
339
+ vi.useFakeTimers();
319
340
  vi.spyOn(await import('../environment.js'), 'ensureAndroidEmulatorEnvironment').mockResolvedValue('/tmp/android-sdk');
320
341
  vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
321
342
  vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
@@ -324,7 +345,14 @@ describe('Android platform instance', () => {
324
345
  vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
325
346
  vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
326
347
  vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
348
+ vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
327
349
  vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
350
+ const stopApp = vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
351
+ const startApp = vi.spyOn(adb, 'startApp').mockResolvedValue(undefined);
352
+ const getAppPid = vi.spyOn(adb, 'getAppPid').mockResolvedValue(4321);
353
+ const startLogcat = vi
354
+ .spyOn(adb, 'startLogcat')
355
+ .mockReturnValue(createLogcatProcess());
328
356
  const instance = await getAndroidEmulatorPlatformInstance({
329
357
  name: 'android',
330
358
  device: {
@@ -341,14 +369,84 @@ describe('Android platform instance', () => {
341
369
  activityName: '.MainActivity',
342
370
  }, harnessConfigWithoutNativeCrashDetection, init);
343
371
  const listener = vi.fn();
344
- const appMonitor = instance.createAppMonitor();
345
- await expect(appMonitor.start()).resolves.toBeUndefined();
346
- await expect(appMonitor.stop()).resolves.toBeUndefined();
347
- await expect(appMonitor.dispose()).resolves.toBeUndefined();
348
- expect(appMonitor.addListener(listener)).toBeUndefined();
349
- expect(appMonitor.removeListener(listener)).toBeUndefined();
372
+ const appSession = await instance.createAppSession();
373
+ appSession.addListener(listener);
374
+ await vi.advanceTimersByTimeAsync(0);
375
+ expect(startLogcat).toHaveBeenCalledWith('emulator-5554', [
376
+ 'logcat',
377
+ '-v',
378
+ 'threadtime',
379
+ '-b',
380
+ 'crash',
381
+ '--uid=10234',
382
+ '-T',
383
+ '01-01 00:00:00.000',
384
+ ]);
385
+ expect(startLogcat.mock.invocationCallOrder[0]).toBeLessThan(startApp.mock.invocationCallOrder[0]);
386
+ await expect(appSession.getState()).resolves.toEqual({
387
+ status: 'running',
388
+ pid: 4321,
389
+ });
390
+ expect(getAppPid).toHaveBeenCalled();
391
+ expect(listener).not.toHaveBeenCalled();
392
+ expect(appSession.removeListener(listener)).toBeUndefined();
393
+ await expect(appSession.dispose()).resolves.toBeUndefined();
394
+ expect(stopApp).toHaveBeenCalled();
395
+ });
396
+ it('reports an early Android crash from logcat before a PID poll succeeds', async () => {
397
+ vi.useFakeTimers();
398
+ vi.spyOn(await import('../environment.js'), 'ensureAndroidEmulatorEnvironment').mockResolvedValue('/tmp/android-sdk');
399
+ vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
400
+ vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
401
+ vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
402
+ vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
403
+ vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
404
+ vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
405
+ vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
406
+ vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
407
+ vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
408
+ vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
409
+ vi.spyOn(adb, 'startApp').mockResolvedValue(undefined);
410
+ vi.spyOn(adb, 'getAppPid').mockResolvedValue(null);
411
+ vi.spyOn(adb, 'startLogcat').mockReturnValue(createLogcatProcess([
412
+ '--------- beginning of crash',
413
+ 'Process: com.harnessplayground, PID: 7777',
414
+ 'FATAL EXCEPTION: main',
415
+ ]));
416
+ const instance = await getAndroidEmulatorPlatformInstance({
417
+ name: 'android',
418
+ device: {
419
+ type: 'emulator',
420
+ name: 'Pixel_8_API_35',
421
+ avd: {
422
+ apiLevel: 35,
423
+ profile: 'pixel_8',
424
+ diskSize: '1G',
425
+ heapSize: '1G',
426
+ },
427
+ },
428
+ bundleId: 'com.harnessplayground',
429
+ activityName: '.MainActivity',
430
+ }, harnessConfig, init);
431
+ const listener = vi.fn();
432
+ const appSession = await instance.createAppSession();
433
+ appSession.addListener(listener);
434
+ await vi.advanceTimersByTimeAsync(0);
435
+ expect(appSession.getLogs().map((entry) => entry.line)).toEqual([
436
+ '--------- beginning of crash',
437
+ 'Process: com.harnessplayground, PID: 7777',
438
+ 'FATAL EXCEPTION: main',
439
+ ]);
440
+ await expect(appSession.getState()).resolves.toMatchObject({
441
+ status: 'exited',
442
+ pid: 7777,
443
+ reason: 'observed-exit',
444
+ });
445
+ expect(listener).toHaveBeenCalledWith({ type: 'app_exited' });
446
+ await appSession.dispose();
350
447
  });
351
- it('returns a noop physical device app monitor when native crash detection is disabled', async () => {
448
+ it('creates a real physical-device app session when native crash detection is disabled', async () => {
449
+ vi.useFakeTimers();
352
450
  vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['012345']);
353
451
  vi.spyOn(adb, 'getDeviceInfo').mockResolvedValue({
354
452
  manufacturer: 'motorola',
@@ -358,17 +456,14 @@ describe('Android platform instance', () => {
358
456
  vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
359
457
  vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
360
458
  vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
459
+ vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
361
460
  vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
362
- await expect(getAndroidPhysicalDevicePlatformInstance({
363
- name: 'android-device',
364
- device: {
365
- type: 'physical',
366
- manufacturer: 'motorola',
367
- model: 'moto g72',
368
- },
369
- bundleId: 'com.harnessplayground',
370
- activityName: '.MainActivity',
371
- }, harnessConfigWithoutNativeCrashDetection)).resolves.toBeDefined();
461
+ vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
462
+ vi.spyOn(adb, 'startApp').mockResolvedValue(undefined);
463
+ vi.spyOn(adb, 'getAppPid').mockResolvedValue(8765);
464
+ const startLogcat = vi
465
+ .spyOn(adb, 'startLogcat')
466
+ .mockReturnValue(createLogcatProcess());
372
467
  const instance = await getAndroidPhysicalDevicePlatformInstance({
373
468
  name: 'android-device',
374
469
  device: {
@@ -380,12 +475,26 @@ describe('Android platform instance', () => {
380
475
  activityName: '.MainActivity',
381
476
  }, harnessConfigWithoutNativeCrashDetection);
382
477
  const listener = vi.fn();
383
- const appMonitor = instance.createAppMonitor();
384
- await expect(appMonitor.start()).resolves.toBeUndefined();
385
- await expect(appMonitor.stop()).resolves.toBeUndefined();
386
- await expect(appMonitor.dispose()).resolves.toBeUndefined();
387
- expect(appMonitor.addListener(listener)).toBeUndefined();
388
- expect(appMonitor.removeListener(listener)).toBeUndefined();
478
+ const appSession = await instance.createAppSession();
479
+ appSession.addListener(listener);
480
+ await vi.advanceTimersByTimeAsync(0);
481
+ expect(startLogcat).toHaveBeenCalledWith('012345', [
482
+ 'logcat',
483
+ '-v',
484
+ 'threadtime',
485
+ '-b',
486
+ 'crash',
487
+ '--uid=10234',
488
+ '-T',
489
+ '01-01 00:00:00.000',
490
+ ]);
491
+ await expect(appSession.getState()).resolves.toEqual({
492
+ status: 'running',
493
+ pid: 8765,
494
+ });
495
+ expect(listener).not.toHaveBeenCalled();
496
+ expect(appSession.removeListener(listener)).toBeUndefined();
497
+ await expect(appSession.dispose()).resolves.toBeUndefined();
389
498
  });
390
499
  it('grants permissions when permissions are enabled for emulator', async () => {
391
500
  vi.spyOn(await import('../environment.js'), 'ensureAndroidEmulatorEnvironment').mockResolvedValue('/tmp/android-sdk');
package/dist/adb.d.ts CHANGED
@@ -40,10 +40,14 @@ export declare const waitForEmulator: (name: string, signal: AbortSignal) => Pro
40
40
  export declare const waitForEmulatorDisconnect: (adbId: string, signal: AbortSignal) => Promise<void>;
41
41
  export declare const waitForBoot: (name: string, signal: AbortSignal) => Promise<string>;
42
42
  export declare const isAppRunning: (adbId: string, bundleId: string) => Promise<boolean>;
43
+ export declare const getAppPid: (adbId: string, bundleId: string) => Promise<number | null>;
43
44
  export declare const getAppUid: (adbId: string, bundleId: string) => Promise<number>;
44
45
  export declare const setHideErrorDialogs: (adbId: string, hide: boolean) => Promise<void>;
45
46
  export declare const getLogcatTimestamp: (adbId: string) => Promise<string>;
46
47
  export declare const startLogcat: (adbId: string, args: readonly string[]) => Subprocess;
48
+ export declare const DROPBOX_CRASH_TAGS: readonly ["data_app_crash", "data_app_native_crash"];
49
+ export declare const getDropboxPrint: (adbId: string, tags?: readonly string[]) => Promise<string>;
50
+ export declare const getActivityExitInfo: (adbId: string, bundleId: string) => Promise<string>;
47
51
  export declare const getAvds: () => Promise<string[]>;
48
52
  export type AdbDevice = {
49
53
  id: string;
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;AAC/E,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAgB5C,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,uBAAuB,CAAC;AA2C/B,eAAO,MAAM,eAAe;iCAElB,MAAM,QACN,SAAS,MAAM,EAAE,KACtB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC;CAKjD,CAAC;AAqEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,eAAO,MAAM,2BAA2B,GAAI,UAAU,MAAM,KAAG,MAAM,EAMpE,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,UAAU,MAAM,KACf,OAAO,CAAC,IAAI,CAEd,CAAC;AAgEF,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,CASd,CAAC;AAEF,eAAO,MAAM,QAAQ,GACnB,OAAO,MAAM,EACb,UAAU,MAAM,EAChB,cAAc,MAAM,EACpB,UAAU,uBAAuB,KAChC,OAAO,CAAC,IAAI,CAMd,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,CASvB,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAC3B,OAAO,MAAM,EACb,UAAU,MAAM,KACf,OAAO,CAAC,MAAM,GAAG,IAAI,CASvB,CAAC;AAMF,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;AA2EF,eAAO,MAAM,eAAe,GAAU,OAAO,MAAM,KAAG,OAAO,CAAC,OAAO,CAWpE,CAAC;AAEF,eAAO,MAAM,YAAY,GAAU,OAAO,MAAM,KAAG,OAAO,CAAC,IAAI,CAE9D,CAAC;AAEF,eAAO,MAAM,UAAU,GACrB,OAAO,MAAM,EACb,SAAS,MAAM,KACd,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF,eAAO,MAAM,YAAY,GACvB,OAAO,MAAM,EACb,UAAU,MAAM,KACf,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF,eAAO,MAAM,MAAM,GAAU,MAAM,MAAM,KAAG,OAAO,CAAC,OAAO,CAG1D,CAAC;AAEF,eAAO,MAAM,SAAS,GAAU,kDAM7B,gBAAgB,KAAG,OAAO,CAAC,IAAI,CAkBjC,CAAC;AAEF,eAAO,MAAM,SAAS,GAAU,MAAM,MAAM,KAAG,OAAO,CAAC,IAAI,CAkB1D,CAAC;AAEF,eAAO,MAAM,aAAa,GACxB,MAAM,MAAM,EACZ,OAAM,gBAAiC,KACtC,OAAO,CAAC,IAAI,CAgFd,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,MAAM,MAAM,EACZ,QAAQ,WAAW,KAClB,OAAO,CAAC,MAAM,CAoBhB,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACpC,OAAO,MAAM,EACb,QAAQ,WAAW,KAClB,OAAO,CAAC,IAAI,CAYd,CAAC;AAEF,eAAO,MAAM,WAAW,GACtB,MAAM,MAAM,EACZ,QAAQ,WAAW,KAClB,OAAO,CAAC,MAAM,CAwBhB,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,WAAW,GACtB,OAAO,MAAM,EACb,MAAM,SAAS,MAAM,EAAE,KACtB,UAIC,CAAC;AAEL,eAAO,MAAM,OAAO,QAAa,OAAO,CAAC,MAAM,EAAE,CAWhD,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;AAEF,eAAO,MAAM,gBAAgB,GAC3B,OAAO,MAAM,EACb,UAAU,MAAM,KACf,OAAO,CAAC,IAAI,CAqEd,CAAC"}
1
+ {"version":3,"file":"adb.d.ts","sourceRoot":"","sources":["../src/adb.ts"],"names":[],"mappings":"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"}
package/dist/adb.js CHANGED
@@ -432,6 +432,9 @@ export const waitForBoot = async (name, signal) => {
432
432
  throw signal.reason;
433
433
  };
434
434
  export const isAppRunning = async (adbId, bundleId) => {
435
+ return (await getAppPid(adbId, bundleId)) != null;
436
+ };
437
+ export const getAppPid = async (adbId, bundleId) => {
435
438
  try {
436
439
  const { stdout } = await spawn(getAdbBinaryPath(), [
437
440
  '-s',
@@ -440,11 +443,15 @@ export const isAppRunning = async (adbId, bundleId) => {
440
443
  'pidof',
441
444
  bundleId,
442
445
  ]);
443
- return stdout.trim() !== '';
446
+ const pid = stdout
447
+ .trim()
448
+ .split(/\s+/)
449
+ .find((value) => value !== '');
450
+ return pid ? Number(pid) : null;
444
451
  }
445
452
  catch (error) {
446
453
  if (error instanceof SubprocessError && error.exitCode === 1) {
447
- return false;
454
+ return null;
448
455
  }
449
456
  throw error;
450
457
  }
@@ -494,6 +501,39 @@ export const startLogcat = (adbId, args) => spawn(getAdbBinaryPath(), ['-s', adb
494
501
  stdout: 'pipe',
495
502
  stderr: 'pipe',
496
503
  });
504
+ export const DROPBOX_CRASH_TAGS = [
505
+ 'data_app_crash',
506
+ 'data_app_native_crash',
507
+ ];
508
+ export const getDropboxPrint = async (adbId, tags = DROPBOX_CRASH_TAGS) => {
509
+ // Android treats multiple args after --print as one search string, so each
510
+ // tag must be queried separately and merged on the host.
511
+ const outputs = await Promise.all(tags.map(async (tag) => {
512
+ const { stdout } = await spawn(getAdbBinaryPath(), [
513
+ '-s',
514
+ adbId,
515
+ 'shell',
516
+ 'dumpsys',
517
+ 'dropbox',
518
+ '--print',
519
+ tag,
520
+ ]);
521
+ return stdout;
522
+ }));
523
+ return outputs.join('\n');
524
+ };
525
+ export const getActivityExitInfo = async (adbId, bundleId) => {
526
+ const { stdout } = await spawn(getAdbBinaryPath(), [
527
+ '-s',
528
+ adbId,
529
+ 'shell',
530
+ 'dumpsys',
531
+ 'activity',
532
+ 'exit-info',
533
+ bundleId,
534
+ ]);
535
+ return stdout;
536
+ };
497
537
  export const getAvds = async () => {
498
538
  try {
499
539
  const emulatorBinaryPath = await ensureEmulatorInstalled();
@@ -0,0 +1,17 @@
1
+ import { type AppSession, type CrashArtifactWriter } from '@react-native-harness/platforms';
2
+ import { type Subprocess } from '@react-native-harness/tools';
3
+ type CreateAndroidAppSessionOptions = {
4
+ appUid: number;
5
+ bundleId: string;
6
+ startApp: () => Promise<void>;
7
+ stopApp: () => Promise<void>;
8
+ getAppPid: () => Promise<number | null>;
9
+ getLogcatTimestamp: () => Promise<string>;
10
+ startLogcat: (args: readonly string[]) => Subprocess;
11
+ getDropboxOutput?: () => Promise<string>;
12
+ getExitInfo?: () => Promise<string>;
13
+ crashArtifactWriter?: CrashArtifactWriter;
14
+ };
15
+ export declare const createAndroidAppSession: ({ appUid, bundleId, startApp, stopApp, getAppPid, getLogcatTimestamp, startLogcat, getDropboxOutput, getExitInfo, crashArtifactWriter, }: CreateAndroidAppSessionOptions) => Promise<AppSession>;
16
+ export {};
17
+ //# sourceMappingURL=app-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-session.d.ts","sourceRoot":"","sources":["../src/app-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,UAAU,EAEf,KAAK,mBAAmB,EACzB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,6BAA6B,CAAC;AAkErC,KAAK,8BAA8B,GAAG;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,SAAS,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxC,kBAAkB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,WAAW,EAAE,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,UAAU,CAAC;IACrD,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACpC,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CAC3C,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAU,0IAW3C,8BAA8B,KAAG,OAAO,CAAC,UAAU,CAsMrD,CAAC"}