@react-native-harness/jest 1.2.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__/bridge.test.js +23 -2
- package/dist/__tests__/crash-monitor.test.js +146 -192
- package/dist/__tests__/errors.test.js +39 -0
- package/dist/__tests__/execute-run.test.js +190 -26
- package/dist/__tests__/fixtures/mock-platform-runner.d.ts +7 -0
- package/dist/__tests__/fixtures/mock-platform-runner.d.ts.map +1 -0
- package/dist/__tests__/fixtures/mock-platform-runner.js +6 -0
- package/dist/__tests__/harness-session.test.d.ts +2 -0
- package/dist/__tests__/harness-session.test.d.ts.map +1 -0
- package/dist/__tests__/harness-session.test.js +74 -0
- package/dist/__tests__/index.test.js +59 -10
- package/dist/__tests__/test-file-platform-filter.test.d.ts +2 -0
- package/dist/__tests__/test-file-platform-filter.test.d.ts.map +1 -0
- package/dist/__tests__/test-file-platform-filter.test.js +47 -0
- package/dist/crash-monitor.d.ts +5 -4
- package/dist/crash-monitor.d.ts.map +1 -1
- package/dist/crash-monitor.js +116 -102
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +41 -2
- package/dist/execute-run.d.ts +4 -2
- package/dist/execute-run.d.ts.map +1 -1
- package/dist/execute-run.js +85 -7
- package/dist/harness-session.d.ts +21 -1
- package/dist/harness-session.d.ts.map +1 -1
- package/dist/harness-session.js +134 -39
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -2
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +1 -0
- package/dist/test-file-platform-filter.d.ts +5 -0
- package/dist/test-file-platform-filter.d.ts.map +1 -0
- package/dist/test-file-platform-filter.js +40 -0
- package/dist/toTestResult.d.ts +1 -0
- package/dist/toTestResult.d.ts.map +1 -1
- package/dist/toTestResult.js +2 -2
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/__tests__/bridge.test.ts +37 -2
- package/src/__tests__/crash-monitor.test.ts +182 -231
- package/src/__tests__/errors.test.ts +75 -0
- package/src/__tests__/execute-run.test.ts +235 -34
- package/src/__tests__/harness-session.test.ts +93 -0
- package/src/__tests__/index.test.ts +89 -11
- package/src/__tests__/test-file-platform-filter.test.ts +83 -0
- package/src/crash-monitor.ts +192 -131
- package/src/errors.ts +62 -1
- package/src/execute-run.ts +125 -11
- package/src/harness-session.ts +177 -42
- package/src/index.ts +40 -11
- package/src/run.ts +1 -0
- package/src/test-file-platform-filter.ts +57 -0
- package/src/toTestResult.ts +3 -2
package/src/errors.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HarnessError } from '@react-native-harness/tools';
|
|
1
|
+
import { formatPath, HarnessError } from '@react-native-harness/tools';
|
|
2
2
|
import type { AppCrashDetails } from '@react-native-harness/platforms';
|
|
3
3
|
export {
|
|
4
4
|
StartupStallError,
|
|
@@ -55,6 +55,10 @@ export type NativeCrashDetails = AppCrashDetails & {
|
|
|
55
55
|
phase: NativeCrashPhase;
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
+
export type RuntimeDisconnectDetails = AppCrashDetails & {
|
|
59
|
+
phase: NativeCrashPhase;
|
|
60
|
+
};
|
|
61
|
+
|
|
58
62
|
const buildNativeCrashMessage = ({
|
|
59
63
|
phase,
|
|
60
64
|
summary,
|
|
@@ -64,12 +68,28 @@ const buildNativeCrashMessage = ({
|
|
|
64
68
|
pid,
|
|
65
69
|
stackTrace,
|
|
66
70
|
artifactType,
|
|
71
|
+
artifactPath,
|
|
72
|
+
enrichmentArtifacts,
|
|
67
73
|
}: NativeCrashDetails) => {
|
|
68
74
|
const lines = [
|
|
69
75
|
phase === 'startup'
|
|
70
76
|
? 'The native app crashed while preparing to run this test file.'
|
|
71
77
|
: 'The native app crashed during test execution.',
|
|
72
78
|
];
|
|
79
|
+
|
|
80
|
+
lines.push(
|
|
81
|
+
artifactPath
|
|
82
|
+
? `Harness extracted the crash log: ${formatPath(artifactPath)}`
|
|
83
|
+
: "Harness couldn't extract the crash log."
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
if (enrichmentArtifacts && enrichmentArtifacts.length > 0) {
|
|
87
|
+
lines.push('Additional crash artifacts:');
|
|
88
|
+
for (const artifact of enrichmentArtifacts) {
|
|
89
|
+
lines.push(` - ${formatPath(artifact.artifactPath)}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
73
93
|
const hasCrashBlock = summary?.includes('\n') ?? false;
|
|
74
94
|
const shouldRenderSummary =
|
|
75
95
|
Boolean(summary) &&
|
|
@@ -119,3 +139,44 @@ export class NativeCrashError extends HarnessError {
|
|
|
119
139
|
return this.details.phase;
|
|
120
140
|
}
|
|
121
141
|
}
|
|
142
|
+
|
|
143
|
+
const buildRuntimeDisconnectMessage = ({
|
|
144
|
+
phase,
|
|
145
|
+
summary,
|
|
146
|
+
rawLines,
|
|
147
|
+
}: RuntimeDisconnectDetails) => {
|
|
148
|
+
const lines = [
|
|
149
|
+
phase === 'startup'
|
|
150
|
+
? 'The native runtime disconnected while preparing to run this test file.'
|
|
151
|
+
: 'The native runtime disconnected during test execution.',
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
if (summary) {
|
|
155
|
+
lines.push('');
|
|
156
|
+
lines.push(summary);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (rawLines && rawLines.length > 0 && summary !== rawLines.join('\n')) {
|
|
160
|
+
lines.push('');
|
|
161
|
+
lines.push(...rawLines);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return lines.join('\n');
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export class RuntimeDisconnectError extends HarnessError {
|
|
168
|
+
constructor(
|
|
169
|
+
public readonly testFilePath: string,
|
|
170
|
+
public readonly details: RuntimeDisconnectDetails
|
|
171
|
+
) {
|
|
172
|
+
super(buildRuntimeDisconnectMessage(details));
|
|
173
|
+
this.name = 'RuntimeDisconnectError';
|
|
174
|
+
this.stack = `${this.name}: ${this.message.split('\n')[0]}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
get phase() {
|
|
178
|
+
return this.details.phase;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export type HarnessRuntimeFailure = NativeCrashError | RuntimeDisconnectError;
|
package/src/execute-run.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
OnTestFailure,
|
|
3
|
-
OnTestStart,
|
|
4
|
-
OnTestSuccess,
|
|
5
2
|
Test,
|
|
6
3
|
TestWatcher,
|
|
7
4
|
Config,
|
|
5
|
+
TestEvents,
|
|
8
6
|
} from 'jest-runner';
|
|
9
7
|
import { randomUUID } from 'node:crypto';
|
|
10
8
|
import path from 'node:path';
|
|
@@ -12,9 +10,28 @@ import { type HarnessSession, type HarnessRunState } from './harness-session.js'
|
|
|
12
10
|
import { runHarnessTestFile } from './run.js';
|
|
13
11
|
import {
|
|
14
12
|
NativeCrashError,
|
|
13
|
+
RuntimeDisconnectError,
|
|
15
14
|
StartupStallError,
|
|
16
15
|
} from './errors.js';
|
|
17
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
AppBridgeDisconnectedError,
|
|
18
|
+
DeviceNotRespondingError,
|
|
19
|
+
} from '@react-native-harness/bridge/server';
|
|
20
|
+
import type { TestCaseResult } from '@jest/test-result';
|
|
21
|
+
import type {
|
|
22
|
+
TestRunnerEvents,
|
|
23
|
+
TestRunnerTestFinishedEvent,
|
|
24
|
+
TestRunnerTestStartedEvent,
|
|
25
|
+
} from '@react-native-harness/bridge';
|
|
26
|
+
import {
|
|
27
|
+
createPlatformSkippedTestResult,
|
|
28
|
+
shouldRunHarnessTestFile,
|
|
29
|
+
} from './test-file-platform-filter.js';
|
|
30
|
+
|
|
31
|
+
type EmitTestEvent = <Name extends keyof TestEvents>(
|
|
32
|
+
eventName: Name,
|
|
33
|
+
...eventData: TestEvents[Name]
|
|
34
|
+
) => Promise<void>;
|
|
18
35
|
|
|
19
36
|
const createRunSummary = () => ({ passed: 0, failed: 0, skipped: 0, todo: 0 });
|
|
20
37
|
|
|
@@ -38,7 +55,9 @@ class CancelRun extends Error {
|
|
|
38
55
|
const buildTestFailure = (err: unknown): { message: string; stack: string } => {
|
|
39
56
|
if (
|
|
40
57
|
err instanceof NativeCrashError ||
|
|
58
|
+
err instanceof RuntimeDisconnectError ||
|
|
41
59
|
err instanceof StartupStallError ||
|
|
60
|
+
err instanceof AppBridgeDisconnectedError ||
|
|
42
61
|
err instanceof DeviceNotRespondingError
|
|
43
62
|
) {
|
|
44
63
|
return { message: (err as Error).message, stack: '' };
|
|
@@ -46,13 +65,60 @@ const buildTestFailure = (err: unknown): { message: string; stack: string } => {
|
|
|
46
65
|
return err as { message: string; stack: string };
|
|
47
66
|
};
|
|
48
67
|
|
|
68
|
+
const toJestMode = (
|
|
69
|
+
declarationMode?: 'only' | 'skip' | 'todo',
|
|
70
|
+
): 'only' | 'skip' | 'todo' | undefined => declarationMode;
|
|
71
|
+
|
|
72
|
+
const emitHarnessTestStarted = async (
|
|
73
|
+
emitEvent: EmitTestEvent,
|
|
74
|
+
event: TestRunnerTestStartedEvent,
|
|
75
|
+
): Promise<void> => {
|
|
76
|
+
await emitEvent('test-case-start', event.file, {
|
|
77
|
+
ancestorTitles: event.ancestorTitles,
|
|
78
|
+
fullName: event.fullName,
|
|
79
|
+
mode: toJestMode(event.declarationMode),
|
|
80
|
+
title: event.name,
|
|
81
|
+
startedAt: event.startedAt,
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const emitHarnessTestFinished = async (
|
|
86
|
+
emitEvent: EmitTestEvent,
|
|
87
|
+
event: TestRunnerTestFinishedEvent,
|
|
88
|
+
): Promise<void> => {
|
|
89
|
+
const failureMessage = event.error?.message;
|
|
90
|
+
const codeFrame = event.error?.codeFrame;
|
|
91
|
+
const location = codeFrame?.location
|
|
92
|
+
? { column: codeFrame.location.column, line: codeFrame.location.row }
|
|
93
|
+
: null;
|
|
94
|
+
const testCaseResult: TestCaseResult = {
|
|
95
|
+
ancestorTitles: event.ancestorTitles,
|
|
96
|
+
duration: event.duration,
|
|
97
|
+
failureDetails: [],
|
|
98
|
+
failureMessages: failureMessage
|
|
99
|
+
? [`${failureMessage}${codeFrame ? `\n\n${codeFrame.content}` : ''}`]
|
|
100
|
+
: [],
|
|
101
|
+
fullName: event.fullName,
|
|
102
|
+
location,
|
|
103
|
+
numPassingAsserts: event.status === 'passed' ? 1 : 0,
|
|
104
|
+
startedAt: event.startedAt,
|
|
105
|
+
status: event.status,
|
|
106
|
+
title: event.name,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
await emitEvent('test-case-result', event.file, testCaseResult);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const isHarnessCaseEvent = (
|
|
113
|
+
event: TestRunnerEvents,
|
|
114
|
+
): event is TestRunnerTestStartedEvent | TestRunnerTestFinishedEvent =>
|
|
115
|
+
event.type === 'test-started' || event.type === 'test-finished';
|
|
116
|
+
|
|
49
117
|
export const executeRun = async (
|
|
50
118
|
session: HarnessSession,
|
|
51
119
|
tests: Array<Test>,
|
|
52
120
|
watcher: TestWatcher,
|
|
53
|
-
|
|
54
|
-
onResult: OnTestSuccess,
|
|
55
|
-
onFailure: OnTestFailure,
|
|
121
|
+
emitEvent: EmitTestEvent,
|
|
56
122
|
globalConfig: Config.GlobalConfig,
|
|
57
123
|
): Promise<void> => {
|
|
58
124
|
const runId = randomUUID();
|
|
@@ -61,6 +127,16 @@ export const executeRun = async (
|
|
|
61
127
|
const rootDir = globalConfig.rootDir ?? process.cwd();
|
|
62
128
|
const testFiles = tests.map((t) => path.relative(rootDir, t.path));
|
|
63
129
|
const summary = createRunSummary();
|
|
130
|
+
let caseEventChain = Promise.resolve();
|
|
131
|
+
const unsubscribe = session.onTestRunnerEvent((event) => {
|
|
132
|
+
if (isHarnessCaseEvent(event)) {
|
|
133
|
+
caseEventChain = caseEventChain.then(() =>
|
|
134
|
+
event.type === 'test-started'
|
|
135
|
+
? emitHarnessTestStarted(emitEvent, event)
|
|
136
|
+
: emitHarnessTestFinished(emitEvent, event),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
64
140
|
|
|
65
141
|
const updateRunState = (overrides: Partial<HarnessRunState> = {}) => {
|
|
66
142
|
const state: HarnessRunState = {
|
|
@@ -93,6 +169,10 @@ export const executeRun = async (
|
|
|
93
169
|
});
|
|
94
170
|
|
|
95
171
|
const shouldResetEnv = session.config.resetEnvironmentBetweenTestFiles;
|
|
172
|
+
const platformId = session.context.platform.platformId;
|
|
173
|
+
const knownPlatformIds = new Set(
|
|
174
|
+
session.config.runners.map((runner) => runner.platformId),
|
|
175
|
+
);
|
|
96
176
|
let isFirstTest = true;
|
|
97
177
|
let runError: unknown;
|
|
98
178
|
|
|
@@ -121,6 +201,34 @@ export const executeRun = async (
|
|
|
121
201
|
|
|
122
202
|
await session.callHook('test-file:started', { runId, file: relativeTestPath });
|
|
123
203
|
|
|
204
|
+
if (
|
|
205
|
+
!shouldRunHarnessTestFile(test.path, platformId, knownPlatformIds)
|
|
206
|
+
) {
|
|
207
|
+
try {
|
|
208
|
+
await emitEvent('test-file-start', test);
|
|
209
|
+
const skippedResult = createPlatformSkippedTestResult(test.path);
|
|
210
|
+
applyJestResultToSummary(summary, skippedResult);
|
|
211
|
+
updateRunState();
|
|
212
|
+
await emitTestFileFinished({
|
|
213
|
+
status: 'skipped',
|
|
214
|
+
duration: Date.now() - fileStartedAt,
|
|
215
|
+
result: null,
|
|
216
|
+
});
|
|
217
|
+
await emitEvent('test-file-success', test, skippedResult);
|
|
218
|
+
} catch (err) {
|
|
219
|
+
if (!emittedTestFileFinished) {
|
|
220
|
+
await emitTestFileFinished({
|
|
221
|
+
status: 'failed',
|
|
222
|
+
duration: Date.now() - fileStartedAt,
|
|
223
|
+
result: null,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
updateRunState({ error: err });
|
|
227
|
+
await emitEvent('test-file-failure', test, buildTestFailure(err));
|
|
228
|
+
}
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
|
|
124
232
|
try {
|
|
125
233
|
if (shouldResetEnv && !isFirstTest) {
|
|
126
234
|
await session.restartApp(test.path);
|
|
@@ -128,7 +236,7 @@ export const executeRun = async (
|
|
|
128
236
|
isFirstTest = false;
|
|
129
237
|
|
|
130
238
|
session.flushClientLogs();
|
|
131
|
-
await
|
|
239
|
+
await emitEvent('test-file-start', test);
|
|
132
240
|
await session.ensureAppReady(test.path);
|
|
133
241
|
|
|
134
242
|
// Crash detection is handled inside session.runTestFile; NativeCrashError
|
|
@@ -151,7 +259,8 @@ export const executeRun = async (
|
|
|
151
259
|
duration: result.duration,
|
|
152
260
|
result: result.harnessResult,
|
|
153
261
|
});
|
|
154
|
-
await
|
|
262
|
+
await caseEventChain;
|
|
263
|
+
await emitEvent('test-file-success', test, result.jestResult);
|
|
155
264
|
} catch (err) {
|
|
156
265
|
if (!emittedTestFileFinished) {
|
|
157
266
|
await emitTestFileFinished({
|
|
@@ -163,7 +272,9 @@ export const executeRun = async (
|
|
|
163
272
|
|
|
164
273
|
const isRuntimeFailure =
|
|
165
274
|
err instanceof NativeCrashError ||
|
|
275
|
+
err instanceof RuntimeDisconnectError ||
|
|
166
276
|
err instanceof StartupStallError ||
|
|
277
|
+
err instanceof AppBridgeDisconnectedError ||
|
|
167
278
|
err instanceof DeviceNotRespondingError;
|
|
168
279
|
|
|
169
280
|
if (isRuntimeFailure) {
|
|
@@ -171,12 +282,13 @@ export const executeRun = async (
|
|
|
171
282
|
updateRunState();
|
|
172
283
|
}
|
|
173
284
|
|
|
174
|
-
if (err instanceof NativeCrashError) {
|
|
285
|
+
if (err instanceof NativeCrashError || err instanceof RuntimeDisconnectError) {
|
|
175
286
|
session.resetCrashState();
|
|
176
287
|
}
|
|
177
288
|
|
|
178
289
|
updateRunState({ error: isRuntimeFailure ? undefined : err });
|
|
179
|
-
|
|
290
|
+
await caseEventChain;
|
|
291
|
+
await emitEvent('test-file-failure', test, buildTestFailure(err));
|
|
180
292
|
}
|
|
181
293
|
}
|
|
182
294
|
} catch (err) {
|
|
@@ -195,5 +307,7 @@ export const executeRun = async (
|
|
|
195
307
|
status: runState.status ?? (runError != null ? 'failed' : 'passed'),
|
|
196
308
|
...(runError != null ? { error: runError } : {}),
|
|
197
309
|
});
|
|
310
|
+
await caseEventChain;
|
|
311
|
+
unsubscribe();
|
|
198
312
|
}
|
|
199
313
|
};
|