@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.
- package/dist/__tests__/crash-diagnostics.test.d.ts +2 -0
- package/dist/__tests__/crash-diagnostics.test.d.ts.map +1 -0
- package/dist/__tests__/crash-diagnostics.test.js +119 -0
- package/dist/__tests__/crash-reporter.test.d.ts +2 -0
- package/dist/__tests__/crash-reporter.test.d.ts.map +1 -0
- package/dist/__tests__/crash-reporter.test.js +150 -0
- package/dist/__tests__/instance.test.js +133 -24
- package/dist/adb.d.ts +4 -0
- package/dist/adb.d.ts.map +1 -1
- package/dist/adb.js +42 -2
- package/dist/app-session.d.ts +17 -0
- package/dist/app-session.d.ts.map +1 -0
- package/dist/app-session.js +209 -0
- package/dist/crash-diagnostics.d.ts +40 -0
- package/dist/crash-diagnostics.d.ts.map +1 -0
- package/dist/crash-diagnostics.js +256 -0
- package/dist/crash-reporter.d.ts +12 -0
- package/dist/crash-reporter.d.ts.map +1 -0
- package/dist/crash-reporter.js +150 -0
- package/dist/instance.d.ts +1 -1
- package/dist/instance.d.ts.map +1 -1
- package/dist/instance.js +32 -59
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/__tests__/crash-diagnostics.test.ts +144 -0
- package/src/__tests__/crash-reporter.test.ts +167 -0
- package/src/__tests__/instance.test.ts +174 -31
- package/src/adb.ts +61 -2
- package/src/app-session.ts +300 -0
- package/src/crash-diagnostics.ts +471 -0
- package/src/crash-reporter.ts +263 -0
- package/src/instance.ts +56 -91
- package/src/runner.ts +7 -3
- package/test/setup.ts +1 -0
- package/src/app-monitor.ts +0 -577
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
collectDropboxArtifacts,
|
|
4
|
+
filterExitInfo,
|
|
5
|
+
getBestDropboxArtifact,
|
|
6
|
+
parseDropboxOutput,
|
|
7
|
+
} from '../crash-diagnostics.js';
|
|
8
|
+
|
|
9
|
+
const javaCrashDropbox = `
|
|
10
|
+
Drop box contents: 1 entries
|
|
11
|
+
Max entries: 1000
|
|
12
|
+
========================================
|
|
13
|
+
2026-03-12 10:30:45.123 data_app_crash (text, 500 bytes)
|
|
14
|
+
Process: com.harnessplayground
|
|
15
|
+
PID: 7777
|
|
16
|
+
Package: com.harnessplayground v1 (1.0)
|
|
17
|
+
java.lang.RuntimeException: boom
|
|
18
|
+
\tat com.harnessplayground.MainActivity.onCreate(MainActivity.kt:38)
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
const nativeCrashDropbox = `
|
|
22
|
+
========================================
|
|
23
|
+
2026-03-12 10:31:10.456 data_app_native_crash (text, 800 bytes)
|
|
24
|
+
Process: com.harnessplayground
|
|
25
|
+
PID: 8888
|
|
26
|
+
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xdeadbeef
|
|
27
|
+
Abort message: 'JNI DETECTED ERROR IN APPLICATION'
|
|
28
|
+
backtrace:
|
|
29
|
+
#00 pc 00001234 /data/app/lib/libapp.so
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
const deviceJavaCrashDropbox = `
|
|
33
|
+
Drop box contents: 8 entries
|
|
34
|
+
Searching for: data_app_crash
|
|
35
|
+
|
|
36
|
+
========================================
|
|
37
|
+
2026-05-29 10:22:34 data_app_crash (text, 1328 bytes)
|
|
38
|
+
SystemUptimeMs: 79467172
|
|
39
|
+
Process: com.harnessplayground
|
|
40
|
+
PID: 586
|
|
41
|
+
Package: com.harnessplayground v1 (1.0)
|
|
42
|
+
java.lang.RuntimeException: Intentional asynchronous Kotlin crash
|
|
43
|
+
\tat com.harnessplayground.PlaygroundCrashModule.crashFromKotlinAsync(PlaygroundCrashModule.kt:44)
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
describe('collectDropboxArtifacts', () => {
|
|
47
|
+
it('matches harness playground crashes from merged per-tag dropbox output', async () => {
|
|
48
|
+
const artifacts = await collectDropboxArtifacts({
|
|
49
|
+
bundleId: 'com.harnessplayground',
|
|
50
|
+
getDropboxOutput: async () => deviceJavaCrashDropbox,
|
|
51
|
+
occurredAt: Date.now(),
|
|
52
|
+
pid: 586,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(artifacts).toHaveLength(1);
|
|
56
|
+
expect(artifacts[0]).toMatchObject({
|
|
57
|
+
artifactType: 'dropbox-crash',
|
|
58
|
+
pid: 586,
|
|
59
|
+
processName: 'com.harnessplayground',
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('ignores Dropbox entries older than the session start time', async () => {
|
|
64
|
+
const artifacts = await collectDropboxArtifacts({
|
|
65
|
+
bundleId: 'com.harnessplayground',
|
|
66
|
+
getDropboxOutput: async () => deviceJavaCrashDropbox,
|
|
67
|
+
minOccurredAt: Date.parse('2026-05-29T10:22:35.000Z'),
|
|
68
|
+
occurredAt: Date.parse('2026-05-29T10:22:36.000Z'),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(artifacts).toHaveLength(0);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('parseDropboxOutput', () => {
|
|
76
|
+
it('parses java and native dropbox entries', () => {
|
|
77
|
+
const entries = parseDropboxOutput(`${javaCrashDropbox}\n${nativeCrashDropbox}`);
|
|
78
|
+
|
|
79
|
+
expect(entries).toHaveLength(2);
|
|
80
|
+
expect(entries[0]).toMatchObject({
|
|
81
|
+
tag: 'data_app_crash',
|
|
82
|
+
pid: 7777,
|
|
83
|
+
});
|
|
84
|
+
expect(entries[1]).toMatchObject({
|
|
85
|
+
tag: 'data_app_native_crash',
|
|
86
|
+
pid: 8888,
|
|
87
|
+
});
|
|
88
|
+
expect(entries[1]?.content).toContain('SIGSEGV');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('getBestDropboxArtifact', () => {
|
|
93
|
+
it('prefers the highest-scored artifact', () => {
|
|
94
|
+
const entries = parseDropboxOutput(`${javaCrashDropbox}\n${nativeCrashDropbox}`);
|
|
95
|
+
const artifacts = entries.map((entry, index) => ({
|
|
96
|
+
artifactType:
|
|
97
|
+
entry.tag === 'data_app_native_crash'
|
|
98
|
+
? ('dropbox-native-crash' as const)
|
|
99
|
+
: ('dropbox-crash' as const),
|
|
100
|
+
dropboxTag: entry.tag,
|
|
101
|
+
occurredAt: Date.now(),
|
|
102
|
+
score: index === 0 ? 100 : 300,
|
|
103
|
+
summary: entry.content,
|
|
104
|
+
pid: entry.pid,
|
|
105
|
+
processName: 'com.harnessplayground',
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
expect(getBestDropboxArtifact(artifacts)?.dropboxTag).toBe(
|
|
109
|
+
'data_app_native_crash'
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('filterExitInfo', () => {
|
|
115
|
+
it('returns matching exit-info records for the target package and pid', () => {
|
|
116
|
+
const output = `
|
|
117
|
+
ApplicationExitInfo #0:
|
|
118
|
+
package=com.harnessplayground pid=7777 realUid=10123
|
|
119
|
+
reason=4 (APP CRASH(NATIVE))
|
|
120
|
+
timestamp=2026-03-12 10:31:10.456
|
|
121
|
+
ApplicationExitInfo #1:
|
|
122
|
+
package=com.other.app pid=9999 realUid=10199
|
|
123
|
+
reason=10 (USER REQUESTED)
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
const filtered = filterExitInfo({
|
|
127
|
+
output,
|
|
128
|
+
bundleId: 'com.harnessplayground',
|
|
129
|
+
pid: 7777,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(filtered).toContain('com.harnessplayground pid=7777');
|
|
133
|
+
expect(filtered).not.toContain('com.other.app');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('returns null when no exit-info records exist', () => {
|
|
137
|
+
expect(
|
|
138
|
+
filterExitInfo({
|
|
139
|
+
output: 'No exit info records for com.harnessplayground',
|
|
140
|
+
bundleId: 'com.harnessplayground',
|
|
141
|
+
})
|
|
142
|
+
).toBeNull();
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { createAndroidCrashReporter } from '../crash-reporter.js';
|
|
3
|
+
|
|
4
|
+
const javaCrashDropbox = `
|
|
5
|
+
========================================
|
|
6
|
+
2026-03-12 10:30:45.123 data_app_crash (text, 500 bytes)
|
|
7
|
+
Process: com.harnessplayground
|
|
8
|
+
PID: 7777
|
|
9
|
+
Package: com.harnessplayground v1 (1.0)
|
|
10
|
+
java.lang.RuntimeException: boom
|
|
11
|
+
\tat com.harnessplayground.MainActivity.onCreate(MainActivity.kt:38)
|
|
12
|
+
`;
|
|
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
|
+
|
|
25
|
+
describe('createAndroidCrashReporter', () => {
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
vi.useRealTimers();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('persists the current logcat crash block with the test file path', async () => {
|
|
31
|
+
vi.useFakeTimers();
|
|
32
|
+
const persistArtifact = vi.fn(
|
|
33
|
+
() => '/tmp/.harness/crash-reports/logcat.txt'
|
|
34
|
+
);
|
|
35
|
+
const reporter = createAndroidCrashReporter({
|
|
36
|
+
bundleId: 'com.harnessplayground',
|
|
37
|
+
crashArtifactWriter: {
|
|
38
|
+
runTimestamp: '2026-03-12T11-35-08-000Z',
|
|
39
|
+
persistArtifact,
|
|
40
|
+
},
|
|
41
|
+
getLogs: () => [
|
|
42
|
+
{ line: 'ordinary crash-buffer line', occurredAt: 1_000 },
|
|
43
|
+
{ line: '--------- beginning of crash', occurredAt: 1_100 },
|
|
44
|
+
{
|
|
45
|
+
line: 'Process: com.harnessplayground, PID: 7777',
|
|
46
|
+
occurredAt: 1_100,
|
|
47
|
+
},
|
|
48
|
+
{ line: 'FATAL EXCEPTION: main', occurredAt: 1_100 },
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const detailsPromise = reporter.getCrashDetails({
|
|
53
|
+
occurredAt: 1_100,
|
|
54
|
+
pid: 7777,
|
|
55
|
+
testFilePath: '/test/native-crash.test.ts',
|
|
56
|
+
});
|
|
57
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
58
|
+
const details = await detailsPromise;
|
|
59
|
+
|
|
60
|
+
expect(details).toMatchObject({
|
|
61
|
+
artifactType: 'logcat',
|
|
62
|
+
artifactPath: '/tmp/.harness/crash-reports/logcat.txt',
|
|
63
|
+
pid: 7777,
|
|
64
|
+
processName: 'com.harnessplayground',
|
|
65
|
+
});
|
|
66
|
+
expect(persistArtifact).toHaveBeenCalledWith({
|
|
67
|
+
artifactKind: 'logcat',
|
|
68
|
+
testFilePath: '/test/native-crash.test.ts',
|
|
69
|
+
source: {
|
|
70
|
+
kind: 'text',
|
|
71
|
+
fileName: 'logcat.txt',
|
|
72
|
+
text: [
|
|
73
|
+
'--------- beginning of crash',
|
|
74
|
+
'Process: com.harnessplayground, PID: 7777',
|
|
75
|
+
'FATAL EXCEPTION: main',
|
|
76
|
+
'',
|
|
77
|
+
].join('\n'),
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('adds dropbox artifacts as enrichment when logcat evidence is available', async () => {
|
|
83
|
+
vi.useFakeTimers();
|
|
84
|
+
const persistArtifact = vi
|
|
85
|
+
.fn()
|
|
86
|
+
.mockImplementation(({ artifactKind }: { artifactKind: string }) => {
|
|
87
|
+
return `/tmp/.harness/crash-reports/${artifactKind}.txt`;
|
|
88
|
+
});
|
|
89
|
+
const reporter = createAndroidCrashReporter({
|
|
90
|
+
bundleId: 'com.harnessplayground',
|
|
91
|
+
crashArtifactWriter: {
|
|
92
|
+
runTimestamp: '2026-03-12T11-35-08-000Z',
|
|
93
|
+
persistArtifact,
|
|
94
|
+
},
|
|
95
|
+
getLogs: () => [
|
|
96
|
+
{ line: '--------- beginning of crash', occurredAt: 1_100 },
|
|
97
|
+
{
|
|
98
|
+
line: 'Process: com.harnessplayground, PID: 7777',
|
|
99
|
+
occurredAt: 1_100,
|
|
100
|
+
},
|
|
101
|
+
{ line: 'FATAL EXCEPTION: main', occurredAt: 1_100 },
|
|
102
|
+
],
|
|
103
|
+
getDropboxOutput: async () => javaCrashDropbox,
|
|
104
|
+
getExitInfo: async () =>
|
|
105
|
+
'ApplicationExitInfo #0:\n package=com.harnessplayground pid=7777 reason=4 (APP CRASH(NATIVE))',
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const detailsPromise = reporter.getCrashDetails({
|
|
109
|
+
occurredAt: 1_100,
|
|
110
|
+
pid: 7777,
|
|
111
|
+
testFilePath: '/test/native-crash.test.ts',
|
|
112
|
+
});
|
|
113
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
114
|
+
const details = await detailsPromise;
|
|
115
|
+
|
|
116
|
+
expect(details).toMatchObject({
|
|
117
|
+
artifactType: 'logcat',
|
|
118
|
+
artifactPath: '/tmp/.harness/crash-reports/logcat.txt',
|
|
119
|
+
});
|
|
120
|
+
expect(details?.enrichmentArtifacts).toEqual(
|
|
121
|
+
expect.arrayContaining([
|
|
122
|
+
{
|
|
123
|
+
artifactType: 'dropbox-crash',
|
|
124
|
+
artifactPath: '/tmp/.harness/crash-reports/dropbox-crash.txt',
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
artifactType: 'exit-info',
|
|
128
|
+
artifactPath: '/tmp/.harness/crash-reports/exit-info.txt',
|
|
129
|
+
},
|
|
130
|
+
])
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('falls back to native dropbox evidence when logcat is empty', async () => {
|
|
135
|
+
vi.useFakeTimers();
|
|
136
|
+
const persistArtifact = vi
|
|
137
|
+
.fn()
|
|
138
|
+
.mockImplementation(({ artifactKind }: { artifactKind: string }) => {
|
|
139
|
+
return `/tmp/.harness/crash-reports/${artifactKind}.txt`;
|
|
140
|
+
});
|
|
141
|
+
const reporter = createAndroidCrashReporter({
|
|
142
|
+
bundleId: 'com.harnessplayground',
|
|
143
|
+
crashArtifactWriter: {
|
|
144
|
+
runTimestamp: '2026-03-12T11-35-08-000Z',
|
|
145
|
+
persistArtifact,
|
|
146
|
+
},
|
|
147
|
+
getLogs: () => [],
|
|
148
|
+
getDropboxOutput: async () => nativeCrashDropbox,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const detailsPromise = reporter.getCrashDetails({
|
|
152
|
+
occurredAt: 1_100,
|
|
153
|
+
pid: 8888,
|
|
154
|
+
testFilePath: '/test/native-crash.test.ts',
|
|
155
|
+
});
|
|
156
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
157
|
+
const details = await detailsPromise;
|
|
158
|
+
|
|
159
|
+
expect(details).toMatchObject({
|
|
160
|
+
artifactType: 'dropbox-native-crash',
|
|
161
|
+
artifactPath: '/tmp/.harness/crash-reports/dropbox-native-crash.txt',
|
|
162
|
+
pid: 8888,
|
|
163
|
+
signal: 'SIGSEGV',
|
|
164
|
+
processName: 'com.harnessplayground',
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -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,
|
|
@@ -12,6 +13,21 @@ import * as avdConfig from '../avd-config.js';
|
|
|
12
13
|
import * as sharedPrefs from '../shared-prefs.js';
|
|
13
14
|
import { HarnessAppPathError, HarnessEmulatorConfigError } from '../errors.js';
|
|
14
15
|
|
|
16
|
+
const createLogcatProcess = (lines: string[] = []): Subprocess => {
|
|
17
|
+
const process = {
|
|
18
|
+
nodeChildProcess: Promise.resolve({
|
|
19
|
+
kill: vi.fn(),
|
|
20
|
+
}),
|
|
21
|
+
[Symbol.asyncIterator]: async function* () {
|
|
22
|
+
for (const line of lines) {
|
|
23
|
+
yield line;
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return process as unknown as Subprocess;
|
|
29
|
+
};
|
|
30
|
+
|
|
15
31
|
const harnessConfig = {
|
|
16
32
|
metroPort: DEFAULT_METRO_PORT,
|
|
17
33
|
} as HarnessConfig;
|
|
@@ -27,6 +43,7 @@ describe('Android platform instance', () => {
|
|
|
27
43
|
beforeEach(async () => {
|
|
28
44
|
vi.restoreAllMocks();
|
|
29
45
|
vi.unstubAllEnvs();
|
|
46
|
+
vi.useRealTimers();
|
|
30
47
|
vi.spyOn(
|
|
31
48
|
await import('../environment.js'),
|
|
32
49
|
'ensureAndroidEmulatorAvailable',
|
|
@@ -47,6 +64,9 @@ describe('Android platform instance', () => {
|
|
|
47
64
|
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
48
65
|
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
49
66
|
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
67
|
+
vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue(
|
|
68
|
+
'01-01 00:00:00.000',
|
|
69
|
+
);
|
|
50
70
|
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
|
|
51
71
|
undefined,
|
|
52
72
|
);
|
|
@@ -98,6 +118,9 @@ describe('Android platform instance', () => {
|
|
|
98
118
|
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
99
119
|
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
100
120
|
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
121
|
+
vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue(
|
|
122
|
+
'01-01 00:00:00.000',
|
|
123
|
+
);
|
|
101
124
|
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
|
|
102
125
|
undefined,
|
|
103
126
|
);
|
|
@@ -159,6 +182,9 @@ describe('Android platform instance', () => {
|
|
|
159
182
|
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
160
183
|
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
161
184
|
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
185
|
+
vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue(
|
|
186
|
+
'01-01 00:00:00.000',
|
|
187
|
+
);
|
|
162
188
|
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
|
|
163
189
|
undefined,
|
|
164
190
|
);
|
|
@@ -215,6 +241,9 @@ describe('Android platform instance', () => {
|
|
|
215
241
|
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
216
242
|
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
217
243
|
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
244
|
+
vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue(
|
|
245
|
+
'01-01 00:00:00.000',
|
|
246
|
+
);
|
|
218
247
|
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
|
|
219
248
|
undefined,
|
|
220
249
|
);
|
|
@@ -276,6 +305,9 @@ describe('Android platform instance', () => {
|
|
|
276
305
|
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
277
306
|
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
278
307
|
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
308
|
+
vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue(
|
|
309
|
+
'01-01 00:00:00.000',
|
|
310
|
+
);
|
|
279
311
|
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
|
|
280
312
|
undefined,
|
|
281
313
|
);
|
|
@@ -343,6 +375,9 @@ describe('Android platform instance', () => {
|
|
|
343
375
|
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
344
376
|
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
345
377
|
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
378
|
+
vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue(
|
|
379
|
+
'01-01 00:00:00.000',
|
|
380
|
+
);
|
|
346
381
|
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
|
|
347
382
|
undefined,
|
|
348
383
|
);
|
|
@@ -466,7 +501,8 @@ describe('Android platform instance', () => {
|
|
|
466
501
|
).rejects.toBeInstanceOf(HarnessEmulatorConfigError);
|
|
467
502
|
});
|
|
468
503
|
|
|
469
|
-
it('
|
|
504
|
+
it('creates a real emulator app session when native crash detection is disabled', async () => {
|
|
505
|
+
vi.useFakeTimers();
|
|
470
506
|
vi.spyOn(
|
|
471
507
|
await import('../environment.js'),
|
|
472
508
|
'ensureAndroidEmulatorEnvironment',
|
|
@@ -478,9 +514,18 @@ describe('Android platform instance', () => {
|
|
|
478
514
|
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
479
515
|
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
480
516
|
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
517
|
+
vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue(
|
|
518
|
+
'01-01 00:00:00.000',
|
|
519
|
+
);
|
|
481
520
|
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
|
|
482
521
|
undefined,
|
|
483
522
|
);
|
|
523
|
+
const stopApp = vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
|
|
524
|
+
const startApp = vi.spyOn(adb, 'startApp').mockResolvedValue(undefined);
|
|
525
|
+
const getAppPid = vi.spyOn(adb, 'getAppPid').mockResolvedValue(4321);
|
|
526
|
+
const startLogcat = vi
|
|
527
|
+
.spyOn(adb, 'startLogcat')
|
|
528
|
+
.mockReturnValue(createLogcatProcess());
|
|
484
529
|
|
|
485
530
|
const instance = await getAndroidEmulatorPlatformInstance(
|
|
486
531
|
{
|
|
@@ -503,16 +548,107 @@ describe('Android platform instance', () => {
|
|
|
503
548
|
);
|
|
504
549
|
|
|
505
550
|
const listener = vi.fn();
|
|
506
|
-
const
|
|
551
|
+
const appSession = await instance.createAppSession();
|
|
552
|
+
appSession.addListener(listener);
|
|
553
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
554
|
+
|
|
555
|
+
expect(startLogcat).toHaveBeenCalledWith('emulator-5554', [
|
|
556
|
+
'logcat',
|
|
557
|
+
'-v',
|
|
558
|
+
'threadtime',
|
|
559
|
+
'-b',
|
|
560
|
+
'crash',
|
|
561
|
+
'--uid=10234',
|
|
562
|
+
'-T',
|
|
563
|
+
'01-01 00:00:00.000',
|
|
564
|
+
]);
|
|
565
|
+
expect(startLogcat.mock.invocationCallOrder[0]).toBeLessThan(
|
|
566
|
+
startApp.mock.invocationCallOrder[0],
|
|
567
|
+
);
|
|
568
|
+
await expect(appSession.getState()).resolves.toEqual({
|
|
569
|
+
status: 'running',
|
|
570
|
+
pid: 4321,
|
|
571
|
+
});
|
|
572
|
+
expect(getAppPid).toHaveBeenCalled();
|
|
573
|
+
expect(listener).not.toHaveBeenCalled();
|
|
574
|
+
expect(appSession.removeListener(listener)).toBeUndefined();
|
|
575
|
+
await expect(appSession.dispose()).resolves.toBeUndefined();
|
|
576
|
+
expect(stopApp).toHaveBeenCalled();
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it('reports an early Android crash from logcat before a PID poll succeeds', async () => {
|
|
580
|
+
vi.useFakeTimers();
|
|
581
|
+
vi.spyOn(
|
|
582
|
+
await import('../environment.js'),
|
|
583
|
+
'ensureAndroidEmulatorEnvironment',
|
|
584
|
+
).mockResolvedValue('/tmp/android-sdk');
|
|
585
|
+
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
|
|
586
|
+
vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
|
|
587
|
+
vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
|
|
588
|
+
vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
|
|
589
|
+
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
590
|
+
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
591
|
+
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
592
|
+
vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue(
|
|
593
|
+
'01-01 00:00:00.000',
|
|
594
|
+
);
|
|
595
|
+
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
|
|
596
|
+
undefined,
|
|
597
|
+
);
|
|
598
|
+
vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
|
|
599
|
+
vi.spyOn(adb, 'startApp').mockResolvedValue(undefined);
|
|
600
|
+
vi.spyOn(adb, 'getAppPid').mockResolvedValue(null);
|
|
601
|
+
vi.spyOn(adb, 'startLogcat').mockReturnValue(
|
|
602
|
+
createLogcatProcess([
|
|
603
|
+
'--------- beginning of crash',
|
|
604
|
+
'Process: com.harnessplayground, PID: 7777',
|
|
605
|
+
'FATAL EXCEPTION: main',
|
|
606
|
+
]),
|
|
607
|
+
);
|
|
507
608
|
|
|
508
|
-
await
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
609
|
+
const instance = await getAndroidEmulatorPlatformInstance(
|
|
610
|
+
{
|
|
611
|
+
name: 'android',
|
|
612
|
+
device: {
|
|
613
|
+
type: 'emulator',
|
|
614
|
+
name: 'Pixel_8_API_35',
|
|
615
|
+
avd: {
|
|
616
|
+
apiLevel: 35,
|
|
617
|
+
profile: 'pixel_8',
|
|
618
|
+
diskSize: '1G',
|
|
619
|
+
heapSize: '1G',
|
|
620
|
+
},
|
|
621
|
+
},
|
|
622
|
+
bundleId: 'com.harnessplayground',
|
|
623
|
+
activityName: '.MainActivity',
|
|
624
|
+
},
|
|
625
|
+
harnessConfig,
|
|
626
|
+
init,
|
|
627
|
+
);
|
|
628
|
+
|
|
629
|
+
const listener = vi.fn();
|
|
630
|
+
const appSession = await instance.createAppSession();
|
|
631
|
+
appSession.addListener(listener);
|
|
632
|
+
|
|
633
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
634
|
+
|
|
635
|
+
expect(appSession.getLogs().map((entry) => entry.line)).toEqual([
|
|
636
|
+
'--------- beginning of crash',
|
|
637
|
+
'Process: com.harnessplayground, PID: 7777',
|
|
638
|
+
'FATAL EXCEPTION: main',
|
|
639
|
+
]);
|
|
640
|
+
await expect(appSession.getState()).resolves.toMatchObject({
|
|
641
|
+
status: 'exited',
|
|
642
|
+
pid: 7777,
|
|
643
|
+
reason: 'observed-exit',
|
|
644
|
+
});
|
|
645
|
+
expect(listener).toHaveBeenCalledWith({ type: 'app_exited' });
|
|
646
|
+
|
|
647
|
+
await appSession.dispose();
|
|
513
648
|
});
|
|
514
649
|
|
|
515
|
-
it('
|
|
650
|
+
it('creates a real physical-device app session when native crash detection is disabled', async () => {
|
|
651
|
+
vi.useFakeTimers();
|
|
516
652
|
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['012345']);
|
|
517
653
|
vi.spyOn(adb, 'getDeviceInfo').mockResolvedValue({
|
|
518
654
|
manufacturer: 'motorola',
|
|
@@ -522,25 +658,18 @@ describe('Android platform instance', () => {
|
|
|
522
658
|
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
523
659
|
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
524
660
|
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
661
|
+
vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue(
|
|
662
|
+
'01-01 00:00:00.000',
|
|
663
|
+
);
|
|
525
664
|
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
|
|
526
665
|
undefined,
|
|
527
666
|
);
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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();
|
|
667
|
+
vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
|
|
668
|
+
vi.spyOn(adb, 'startApp').mockResolvedValue(undefined);
|
|
669
|
+
vi.spyOn(adb, 'getAppPid').mockResolvedValue(8765);
|
|
670
|
+
const startLogcat = vi
|
|
671
|
+
.spyOn(adb, 'startLogcat')
|
|
672
|
+
.mockReturnValue(createLogcatProcess());
|
|
544
673
|
|
|
545
674
|
const instance = await getAndroidPhysicalDevicePlatformInstance(
|
|
546
675
|
{
|
|
@@ -557,13 +686,27 @@ describe('Android platform instance', () => {
|
|
|
557
686
|
);
|
|
558
687
|
|
|
559
688
|
const listener = vi.fn();
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
await
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
689
|
+
const appSession = await instance.createAppSession();
|
|
690
|
+
appSession.addListener(listener);
|
|
691
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
692
|
+
|
|
693
|
+
expect(startLogcat).toHaveBeenCalledWith('012345', [
|
|
694
|
+
'logcat',
|
|
695
|
+
'-v',
|
|
696
|
+
'threadtime',
|
|
697
|
+
'-b',
|
|
698
|
+
'crash',
|
|
699
|
+
'--uid=10234',
|
|
700
|
+
'-T',
|
|
701
|
+
'01-01 00:00:00.000',
|
|
702
|
+
]);
|
|
703
|
+
await expect(appSession.getState()).resolves.toEqual({
|
|
704
|
+
status: 'running',
|
|
705
|
+
pid: 8765,
|
|
706
|
+
});
|
|
707
|
+
expect(listener).not.toHaveBeenCalled();
|
|
708
|
+
expect(appSession.removeListener(listener)).toBeUndefined();
|
|
709
|
+
await expect(appSession.dispose()).resolves.toBeUndefined();
|
|
567
710
|
});
|
|
568
711
|
|
|
569
712
|
it('grants permissions when permissions are enabled for emulator', async () => {
|