@react-native-harness/jest 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.
- package/dist/__tests__/bridge.test.js +29 -4
- package/dist/__tests__/crash-monitor.test.js +194 -176
- package/dist/__tests__/environment-reset.test.d.ts +2 -0
- package/dist/__tests__/environment-reset.test.d.ts.map +1 -0
- package/dist/__tests__/environment-reset.test.js +238 -0
- package/dist/__tests__/errors.test.js +39 -0
- package/dist/__tests__/execute-run.test.js +439 -29
- package/dist/__tests__/format-harness-error.test.d.ts +2 -0
- package/dist/__tests__/format-harness-error.test.d.ts.map +1 -0
- package/dist/__tests__/format-harness-error.test.js +27 -0
- package/dist/__tests__/harness-cache.test.js +62 -41
- package/dist/__tests__/harness-session-signal.test.d.ts +2 -0
- package/dist/__tests__/harness-session-signal.test.d.ts.map +1 -0
- package/dist/__tests__/harness-session-signal.test.js +298 -0
- package/dist/__tests__/harness-session.test.js +217 -234
- package/dist/__tests__/index.test.js +62 -11
- package/dist/__tests__/metro-port.test.js +2 -0
- package/dist/__tests__/run.test.d.ts +2 -0
- package/dist/__tests__/run.test.d.ts.map +1 -0
- package/dist/__tests__/run.test.js +48 -0
- 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 +12 -4
- package/dist/crash-monitor.d.ts.map +1 -1
- package/dist/crash-monitor.js +135 -102
- package/dist/diagnostics/__tests__/derivers.test.d.ts +2 -0
- package/dist/diagnostics/__tests__/derivers.test.d.ts.map +1 -0
- package/dist/diagnostics/__tests__/derivers.test.js +248 -0
- package/dist/diagnostics/__tests__/report.test.d.ts +2 -0
- package/dist/diagnostics/__tests__/report.test.d.ts.map +1 -0
- package/dist/diagnostics/__tests__/report.test.js +97 -0
- package/dist/diagnostics/derivers.d.ts +24 -0
- package/dist/diagnostics/derivers.d.ts.map +1 -0
- package/dist/diagnostics/derivers.js +146 -0
- package/dist/diagnostics/index.d.ts +3 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/index.js +2 -0
- package/dist/diagnostics/report.d.ts +7 -0
- package/dist/diagnostics/report.d.ts.map +1 -0
- package/dist/diagnostics/report.js +84 -0
- package/dist/environment-reset.d.ts +51 -0
- package/dist/environment-reset.d.ts.map +1 -0
- package/dist/environment-reset.js +83 -0
- 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 +179 -49
- package/dist/format-harness-error.d.ts +5 -0
- package/dist/format-harness-error.d.ts.map +1 -0
- package/dist/format-harness-error.js +43 -0
- package/dist/harness-session.d.ts +46 -1
- package/dist/harness-session.d.ts.map +1 -1
- package/dist/harness-session.js +386 -118
- 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 +22 -4
- 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 +8 -7
- package/src/__tests__/bridge.test.ts +45 -5
- package/src/__tests__/crash-monitor.test.ts +234 -199
- package/src/__tests__/environment-reset.test.ts +308 -0
- package/src/__tests__/errors.test.ts +75 -0
- package/src/__tests__/execute-run.test.ts +561 -37
- package/src/__tests__/format-harness-error.test.ts +32 -0
- package/src/__tests__/harness-cache.test.ts +91 -0
- package/src/__tests__/harness-session.test.ts +303 -0
- package/src/__tests__/index.test.ts +92 -12
- package/src/__tests__/metro-port.test.ts +2 -0
- package/src/__tests__/run.test.ts +74 -0
- package/src/__tests__/test-file-platform-filter.test.ts +83 -0
- package/src/crash-monitor.ts +220 -131
- package/src/diagnostics/__tests__/derivers.test.ts +300 -0
- package/src/diagnostics/__tests__/report.test.ts +128 -0
- package/src/diagnostics/derivers.ts +170 -0
- package/src/diagnostics/index.ts +2 -0
- package/src/diagnostics/report.ts +124 -0
- package/src/environment-reset.ts +138 -0
- package/src/errors.ts +62 -1
- package/src/execute-run.ts +230 -57
- package/src/format-harness-error.ts +73 -0
- package/src/harness-session.ts +535 -162
- package/src/index.ts +40 -11
- package/src/run.ts +24 -4
- package/src/test-file-platform-filter.ts +57 -0
- package/src/toTestResult.ts +3 -2
- package/tsconfig.json +3 -0
- package/tsconfig.lib.json +3 -0
package/src/execute-run.ts
CHANGED
|
@@ -1,20 +1,44 @@
|
|
|
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';
|
|
11
9
|
import { type HarnessSession, type HarnessRunState } from './harness-session.js';
|
|
12
10
|
import { runHarnessTestFile } from './run.js';
|
|
11
|
+
import { resolveResetStrategyKind } from './environment-reset.js';
|
|
13
12
|
import {
|
|
14
13
|
NativeCrashError,
|
|
14
|
+
RuntimeDisconnectError,
|
|
15
15
|
StartupStallError,
|
|
16
16
|
} from './errors.js';
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
AppBridgeDisconnectedError,
|
|
19
|
+
DeviceNotRespondingError,
|
|
20
|
+
} from '@react-native-harness/bridge/server';
|
|
21
|
+
import type { TestCaseResult } from '@jest/test-result';
|
|
22
|
+
import type {
|
|
23
|
+
TestRunnerEvents,
|
|
24
|
+
TestRunnerTestFinishedEvent,
|
|
25
|
+
TestRunnerTestStartedEvent,
|
|
26
|
+
TestSuiteResult,
|
|
27
|
+
} from '@react-native-harness/bridge';
|
|
28
|
+
import {
|
|
29
|
+
createPlatformSkippedTestResult,
|
|
30
|
+
shouldRunHarnessTestFile,
|
|
31
|
+
} from './test-file-platform-filter.js';
|
|
32
|
+
import { formatHarnessErrorMessage } from './format-harness-error.js';
|
|
33
|
+
import { logger } from '@react-native-harness/tools';
|
|
34
|
+
import { printSummary, writeTraceFile } from './diagnostics/index.js';
|
|
35
|
+
|
|
36
|
+
const diagnosticsLogger = logger.child('diagnostics');
|
|
37
|
+
|
|
38
|
+
type EmitTestEvent = <Name extends keyof TestEvents>(
|
|
39
|
+
eventName: Name,
|
|
40
|
+
...eventData: TestEvents[Name]
|
|
41
|
+
) => Promise<void>;
|
|
18
42
|
|
|
19
43
|
const createRunSummary = () => ({ passed: 0, failed: 0, skipped: 0, todo: 0 });
|
|
20
44
|
|
|
@@ -38,7 +62,9 @@ class CancelRun extends Error {
|
|
|
38
62
|
const buildTestFailure = (err: unknown): { message: string; stack: string } => {
|
|
39
63
|
if (
|
|
40
64
|
err instanceof NativeCrashError ||
|
|
65
|
+
err instanceof RuntimeDisconnectError ||
|
|
41
66
|
err instanceof StartupStallError ||
|
|
67
|
+
err instanceof AppBridgeDisconnectedError ||
|
|
42
68
|
err instanceof DeviceNotRespondingError
|
|
43
69
|
) {
|
|
44
70
|
return { message: (err as Error).message, stack: '' };
|
|
@@ -46,21 +72,92 @@ const buildTestFailure = (err: unknown): { message: string; stack: string } => {
|
|
|
46
72
|
return err as { message: string; stack: string };
|
|
47
73
|
};
|
|
48
74
|
|
|
75
|
+
const toJestMode = (
|
|
76
|
+
declarationMode?: 'only' | 'skip' | 'todo',
|
|
77
|
+
): 'only' | 'skip' | 'todo' | undefined => declarationMode;
|
|
78
|
+
|
|
79
|
+
const emitHarnessTestStarted = async (
|
|
80
|
+
emitEvent: EmitTestEvent,
|
|
81
|
+
event: TestRunnerTestStartedEvent,
|
|
82
|
+
): Promise<void> => {
|
|
83
|
+
await emitEvent('test-case-start', event.file, {
|
|
84
|
+
ancestorTitles: event.ancestorTitles,
|
|
85
|
+
fullName: event.fullName,
|
|
86
|
+
mode: toJestMode(event.declarationMode),
|
|
87
|
+
title: event.name,
|
|
88
|
+
startedAt: event.startedAt,
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const emitHarnessTestFinished = async (
|
|
93
|
+
emitEvent: EmitTestEvent,
|
|
94
|
+
event: TestRunnerTestFinishedEvent,
|
|
95
|
+
): Promise<void> => {
|
|
96
|
+
const codeFrame = event.error?.codeFrame;
|
|
97
|
+
const failureMessage = formatHarnessErrorMessage(event.error, {
|
|
98
|
+
testStartedAt: event.startedAt,
|
|
99
|
+
});
|
|
100
|
+
const location = codeFrame?.location
|
|
101
|
+
? { column: codeFrame.location.column, line: codeFrame.location.row }
|
|
102
|
+
: null;
|
|
103
|
+
const testCaseResult: TestCaseResult = {
|
|
104
|
+
ancestorTitles: event.ancestorTitles,
|
|
105
|
+
duration: event.duration,
|
|
106
|
+
failureDetails: [],
|
|
107
|
+
failureMessages: failureMessage ? [failureMessage] : [],
|
|
108
|
+
fullName: event.fullName,
|
|
109
|
+
location,
|
|
110
|
+
numPassingAsserts: event.status === 'passed' ? 1 : 0,
|
|
111
|
+
startedAt: event.startedAt,
|
|
112
|
+
status: event.status,
|
|
113
|
+
title: event.name,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
await emitEvent('test-case-result', event.file, testCaseResult);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const isHarnessCaseEvent = (
|
|
120
|
+
event: TestRunnerEvents,
|
|
121
|
+
): event is TestRunnerTestStartedEvent | TestRunnerTestFinishedEvent =>
|
|
122
|
+
event.type === 'test-started' || event.type === 'test-finished';
|
|
123
|
+
|
|
124
|
+
const TIMEOUT_ERROR_NAMES = new Set([
|
|
125
|
+
'SuiteHookTimeoutError',
|
|
126
|
+
'TestCaseTimeoutError',
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
const hasRuntimeTimeout = (result: TestSuiteResult): boolean =>
|
|
130
|
+
(result.error ? TIMEOUT_ERROR_NAMES.has(result.error.name) : false) ||
|
|
131
|
+
result.tests.some((test) =>
|
|
132
|
+
test.error ? TIMEOUT_ERROR_NAMES.has(test.error.name) : false,
|
|
133
|
+
) ||
|
|
134
|
+
result.suites.some(hasRuntimeTimeout);
|
|
135
|
+
|
|
49
136
|
export const executeRun = async (
|
|
50
137
|
session: HarnessSession,
|
|
51
138
|
tests: Array<Test>,
|
|
52
139
|
watcher: TestWatcher,
|
|
53
|
-
|
|
54
|
-
onResult: OnTestSuccess,
|
|
55
|
-
onFailure: OnTestFailure,
|
|
140
|
+
emitEvent: EmitTestEvent,
|
|
56
141
|
globalConfig: Config.GlobalConfig,
|
|
57
142
|
): Promise<void> => {
|
|
58
143
|
const runId = randomUUID();
|
|
144
|
+
const diagnostics = session.diagnostics.child({ runId });
|
|
145
|
+
const runSpan = diagnostics.start('run.total', { runId });
|
|
59
146
|
const startTime = Date.now();
|
|
60
147
|
const watchMode = globalConfig.watch || globalConfig.watchAll;
|
|
61
148
|
const rootDir = globalConfig.rootDir ?? process.cwd();
|
|
62
149
|
const testFiles = tests.map((t) => path.relative(rootDir, t.path));
|
|
63
150
|
const summary = createRunSummary();
|
|
151
|
+
let caseEventChain = Promise.resolve();
|
|
152
|
+
const unsubscribe = session.onTestRunnerEvent((event) => {
|
|
153
|
+
if (isHarnessCaseEvent(event)) {
|
|
154
|
+
caseEventChain = caseEventChain.then(() =>
|
|
155
|
+
event.type === 'test-started'
|
|
156
|
+
? emitHarnessTestStarted(emitEvent, event)
|
|
157
|
+
: emitHarnessTestFinished(emitEvent, event),
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
64
161
|
|
|
65
162
|
const updateRunState = (overrides: Partial<HarnessRunState> = {}) => {
|
|
66
163
|
const state: HarnessRunState = {
|
|
@@ -69,6 +166,7 @@ export const executeRun = async (
|
|
|
69
166
|
testFiles,
|
|
70
167
|
watchMode,
|
|
71
168
|
coverageEnabled: globalConfig.collectCoverage,
|
|
169
|
+
completed: false,
|
|
72
170
|
summary,
|
|
73
171
|
status: summary.failed > 0 ? 'failed' : 'passed',
|
|
74
172
|
...overrides,
|
|
@@ -92,8 +190,15 @@ export const executeRun = async (
|
|
|
92
190
|
host: session.config.host?.trim() || undefined,
|
|
93
191
|
});
|
|
94
192
|
|
|
95
|
-
const
|
|
193
|
+
const resetStrategyKind = resolveResetStrategyKind(
|
|
194
|
+
session.config.resetEnvironmentBetweenTestFiles,
|
|
195
|
+
);
|
|
196
|
+
const platformId = session.context.platform.platformId;
|
|
197
|
+
const knownPlatformIds = new Set(
|
|
198
|
+
session.config.runners.map((runner) => runner.platformId),
|
|
199
|
+
);
|
|
96
200
|
let isFirstTest = true;
|
|
201
|
+
let shouldRestartAfterTimeout = false;
|
|
97
202
|
let runError: unknown;
|
|
98
203
|
|
|
99
204
|
try {
|
|
@@ -120,63 +225,116 @@ export const executeRun = async (
|
|
|
120
225
|
};
|
|
121
226
|
|
|
122
227
|
await session.callHook('test-file:started', { runId, file: relativeTestPath });
|
|
228
|
+
const fileSpan = diagnostics.start('run.file', { file: relativeTestPath });
|
|
229
|
+
let fileStatus: 'passed' | 'failed' | 'skipped' | 'todo' = 'failed';
|
|
123
230
|
|
|
124
231
|
try {
|
|
125
|
-
if (
|
|
126
|
-
|
|
232
|
+
if (
|
|
233
|
+
!shouldRunHarnessTestFile(test.path, platformId, knownPlatformIds)
|
|
234
|
+
) {
|
|
235
|
+
try {
|
|
236
|
+
await emitEvent('test-file-start', test);
|
|
237
|
+
const skippedResult = createPlatformSkippedTestResult(test.path);
|
|
238
|
+
applyJestResultToSummary(summary, skippedResult);
|
|
239
|
+
updateRunState();
|
|
240
|
+
fileStatus = 'skipped';
|
|
241
|
+
await emitTestFileFinished({
|
|
242
|
+
status: 'skipped',
|
|
243
|
+
duration: Date.now() - fileStartedAt,
|
|
244
|
+
result: null,
|
|
245
|
+
});
|
|
246
|
+
await emitEvent('test-file-success', test, skippedResult);
|
|
247
|
+
} catch (err) {
|
|
248
|
+
fileStatus = 'failed';
|
|
249
|
+
if (!emittedTestFileFinished) {
|
|
250
|
+
await emitTestFileFinished({
|
|
251
|
+
status: 'failed',
|
|
252
|
+
duration: Date.now() - fileStartedAt,
|
|
253
|
+
result: null,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
updateRunState({ error: err });
|
|
257
|
+
await emitEvent('test-file-failure', test, buildTestFailure(err));
|
|
258
|
+
}
|
|
259
|
+
continue;
|
|
127
260
|
}
|
|
128
|
-
isFirstTest = false;
|
|
129
|
-
|
|
130
|
-
session.flushClientLogs();
|
|
131
|
-
await onStart(test);
|
|
132
|
-
await session.ensureAppReady(test.path);
|
|
133
|
-
|
|
134
|
-
// Crash detection is handled inside session.runTestFile; NativeCrashError
|
|
135
|
-
// propagates here if a crash wins the race.
|
|
136
|
-
const result = await runHarnessTestFile({
|
|
137
|
-
testPath: test.path,
|
|
138
|
-
session,
|
|
139
|
-
globalConfig,
|
|
140
|
-
projectConfig: test.context.config,
|
|
141
|
-
});
|
|
142
261
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
262
|
+
try {
|
|
263
|
+
if (shouldRestartAfterTimeout) {
|
|
264
|
+
// Recovery from a runtime timeout always uses the (stronger)
|
|
265
|
+
// process restart, never the configured reset strategy.
|
|
266
|
+
await session.restartApp(test.path);
|
|
267
|
+
shouldRestartAfterTimeout = false;
|
|
268
|
+
} else if (resetStrategyKind !== null && !isFirstTest) {
|
|
269
|
+
await session.resetEnvironment(test.path);
|
|
270
|
+
}
|
|
271
|
+
isFirstTest = false;
|
|
272
|
+
|
|
273
|
+
session.flushClientLogs();
|
|
274
|
+
await emitEvent('test-file-start', test);
|
|
275
|
+
await session.ensureAppReady(test.path);
|
|
276
|
+
|
|
277
|
+
// Crash detection is handled inside session.runTestFile; NativeCrashError
|
|
278
|
+
// propagates here if a crash wins the race.
|
|
279
|
+
const result = await runHarnessTestFile({
|
|
280
|
+
testPath: test.path,
|
|
281
|
+
session,
|
|
282
|
+
globalConfig,
|
|
283
|
+
projectConfig: test.context.config,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
applyJestResultToSummary(summary, result.jestResult);
|
|
287
|
+
const clientLogs = session.flushClientLogs();
|
|
288
|
+
if (clientLogs.length > 0) {
|
|
289
|
+
result.jestResult.console = clientLogs;
|
|
290
|
+
}
|
|
291
|
+
updateRunState();
|
|
292
|
+
fileStatus = result.harnessResult.status;
|
|
157
293
|
await emitTestFileFinished({
|
|
158
|
-
status:
|
|
159
|
-
duration:
|
|
160
|
-
result:
|
|
294
|
+
status: result.harnessResult.status,
|
|
295
|
+
duration: result.duration,
|
|
296
|
+
result: result.harnessResult,
|
|
161
297
|
});
|
|
162
|
-
|
|
298
|
+
const didRuntimeTimeout = hasRuntimeTimeout(result.harnessResult);
|
|
299
|
+
shouldRestartAfterTimeout = didRuntimeTimeout;
|
|
300
|
+
await caseEventChain;
|
|
301
|
+
await emitEvent('test-file-success', test, result.jestResult);
|
|
302
|
+
if (didRuntimeTimeout) {
|
|
303
|
+
await session.restartApp(test.path);
|
|
304
|
+
shouldRestartAfterTimeout = false;
|
|
305
|
+
}
|
|
306
|
+
} catch (err) {
|
|
307
|
+
fileStatus = 'failed';
|
|
308
|
+
if (!emittedTestFileFinished) {
|
|
309
|
+
await emitTestFileFinished({
|
|
310
|
+
status: 'failed',
|
|
311
|
+
duration: Date.now() - fileStartedAt,
|
|
312
|
+
result: null,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
163
315
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
316
|
+
const isRuntimeFailure =
|
|
317
|
+
err instanceof NativeCrashError ||
|
|
318
|
+
err instanceof RuntimeDisconnectError ||
|
|
319
|
+
err instanceof StartupStallError ||
|
|
320
|
+
err instanceof AppBridgeDisconnectedError ||
|
|
321
|
+
err instanceof DeviceNotRespondingError;
|
|
168
322
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
323
|
+
if (isRuntimeFailure) {
|
|
324
|
+
summary.failed += 1;
|
|
325
|
+
updateRunState();
|
|
326
|
+
}
|
|
173
327
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
328
|
+
if (err instanceof NativeCrashError || err instanceof RuntimeDisconnectError) {
|
|
329
|
+
session.resetCrashState();
|
|
330
|
+
}
|
|
177
331
|
|
|
178
|
-
|
|
179
|
-
|
|
332
|
+
updateRunState({ error: isRuntimeFailure ? undefined : err });
|
|
333
|
+
await caseEventChain;
|
|
334
|
+
await emitEvent('test-file-failure', test, buildTestFailure(err));
|
|
335
|
+
}
|
|
336
|
+
} finally {
|
|
337
|
+
fileSpan.end({ status: fileStatus });
|
|
180
338
|
}
|
|
181
339
|
}
|
|
182
340
|
} catch (err) {
|
|
@@ -184,16 +342,31 @@ export const executeRun = async (
|
|
|
184
342
|
if (!(err instanceof CancelRun)) throw err;
|
|
185
343
|
} finally {
|
|
186
344
|
const runState = updateRunState(
|
|
187
|
-
runError != null ? { error: runError, status: 'failed' } : {},
|
|
345
|
+
runError != null ? { completed: true, error: runError, status: 'failed' } : { completed: true },
|
|
188
346
|
);
|
|
347
|
+
const finalStatus = runState.status ?? (runError != null ? 'failed' : 'passed');
|
|
189
348
|
await session.callHook('run:finished', {
|
|
190
349
|
runId,
|
|
191
350
|
startTime,
|
|
192
351
|
duration: Date.now() - startTime,
|
|
193
352
|
testFiles,
|
|
194
353
|
summary,
|
|
195
|
-
status:
|
|
354
|
+
status: finalStatus,
|
|
196
355
|
...(runError != null ? { error: runError } : {}),
|
|
197
356
|
});
|
|
357
|
+
await caseEventChain;
|
|
358
|
+
unsubscribe();
|
|
359
|
+
runSpan.end({ runId, status: finalStatus });
|
|
360
|
+
|
|
361
|
+
if (session.diagnostics.enabled) {
|
|
362
|
+
const entries = session.diagnostics.flush();
|
|
363
|
+
try {
|
|
364
|
+
printSummary(entries, (line) => diagnosticsLogger.info(line));
|
|
365
|
+
const tracePath = await writeTraceFile(entries, { projectRoot: rootDir, runId });
|
|
366
|
+
diagnosticsLogger.info(`wrote diagnostics trace to ${tracePath}`);
|
|
367
|
+
} catch (reportError) {
|
|
368
|
+
diagnosticsLogger.warn('failed to write diagnostics report: %s', reportError);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
198
371
|
}
|
|
199
372
|
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { SerializedError } from '@react-native-harness/bridge';
|
|
2
|
+
|
|
3
|
+
const formatPendingPromiseStack = (stack: string): string =>
|
|
4
|
+
stack
|
|
5
|
+
.split('\n')
|
|
6
|
+
.map((line) => ` ${line}`)
|
|
7
|
+
.join('\n');
|
|
8
|
+
|
|
9
|
+
const formatPendingPromises = (
|
|
10
|
+
pendingPromises: NonNullable<
|
|
11
|
+
NonNullable<SerializedError['diagnostics']>['pendingPromises']
|
|
12
|
+
>,
|
|
13
|
+
testStartedAt?: number,
|
|
14
|
+
): string | null => {
|
|
15
|
+
if (pendingPromises.total === 0) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const lines = [`Pending promises at timeout: ${pendingPromises.total}`];
|
|
20
|
+
|
|
21
|
+
if (pendingPromises.items.length < pendingPromises.total) {
|
|
22
|
+
lines.push(
|
|
23
|
+
`Showing ${pendingPromises.items.length} of ${pendingPromises.total} pending promises.`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
for (const promise of pendingPromises.items) {
|
|
28
|
+
const age =
|
|
29
|
+
testStartedAt !== undefined
|
|
30
|
+
? `, created ${Math.max(0, promise.createdAt - testStartedAt)}ms after test start`
|
|
31
|
+
: '';
|
|
32
|
+
|
|
33
|
+
lines.push('');
|
|
34
|
+
lines.push(`Promise #${promise.id}${age}:`);
|
|
35
|
+
|
|
36
|
+
if (promise.stack) {
|
|
37
|
+
lines.push(formatPendingPromiseStack(promise.stack));
|
|
38
|
+
} else {
|
|
39
|
+
lines.push(' <stack unavailable>');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return lines.join('\n');
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const formatHarnessErrorMessage = (
|
|
47
|
+
error: SerializedError | undefined,
|
|
48
|
+
options: {
|
|
49
|
+
testStartedAt?: number;
|
|
50
|
+
} = {},
|
|
51
|
+
): string | undefined => {
|
|
52
|
+
if (!error) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const parts = [error.message];
|
|
57
|
+
const pendingPromiseDetails = error.diagnostics?.pendingPromises
|
|
58
|
+
? formatPendingPromises(
|
|
59
|
+
error.diagnostics.pendingPromises,
|
|
60
|
+
options.testStartedAt,
|
|
61
|
+
)
|
|
62
|
+
: null;
|
|
63
|
+
|
|
64
|
+
if (pendingPromiseDetails) {
|
|
65
|
+
parts.push(pendingPromiseDetails);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (error.codeFrame) {
|
|
69
|
+
parts.push(error.codeFrame.content);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return parts.join('\n\n');
|
|
73
|
+
};
|