@react-native-harness/jest 1.4.0-rc.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/__tests__/bridge.test.js +6 -2
  2. package/dist/__tests__/crash-monitor.test.js +64 -0
  3. package/dist/__tests__/environment-reset.test.d.ts +2 -0
  4. package/dist/__tests__/environment-reset.test.d.ts.map +1 -0
  5. package/dist/__tests__/environment-reset.test.js +238 -0
  6. package/dist/__tests__/execute-run.test.js +250 -4
  7. package/dist/__tests__/format-harness-error.test.d.ts +2 -0
  8. package/dist/__tests__/format-harness-error.test.d.ts.map +1 -0
  9. package/dist/__tests__/format-harness-error.test.js +27 -0
  10. package/dist/__tests__/harness-cache.test.js +62 -41
  11. package/dist/__tests__/harness-session-signal.test.d.ts +2 -0
  12. package/dist/__tests__/harness-session-signal.test.d.ts.map +1 -0
  13. package/dist/__tests__/harness-session-signal.test.js +298 -0
  14. package/dist/__tests__/harness-session.test.js +161 -2
  15. package/dist/__tests__/index.test.js +3 -1
  16. package/dist/__tests__/metro-port.test.js +2 -0
  17. package/dist/__tests__/run.test.d.ts +2 -0
  18. package/dist/__tests__/run.test.d.ts.map +1 -0
  19. package/dist/__tests__/run.test.js +48 -0
  20. package/dist/crash-monitor.d.ts +7 -0
  21. package/dist/crash-monitor.d.ts.map +1 -1
  22. package/dist/crash-monitor.js +19 -0
  23. package/dist/diagnostics/__tests__/derivers.test.d.ts +2 -0
  24. package/dist/diagnostics/__tests__/derivers.test.d.ts.map +1 -0
  25. package/dist/diagnostics/__tests__/derivers.test.js +248 -0
  26. package/dist/diagnostics/__tests__/report.test.d.ts +2 -0
  27. package/dist/diagnostics/__tests__/report.test.d.ts.map +1 -0
  28. package/dist/diagnostics/__tests__/report.test.js +97 -0
  29. package/dist/diagnostics/derivers.d.ts +24 -0
  30. package/dist/diagnostics/derivers.d.ts.map +1 -0
  31. package/dist/diagnostics/derivers.js +146 -0
  32. package/dist/diagnostics/index.d.ts +3 -0
  33. package/dist/diagnostics/index.d.ts.map +1 -0
  34. package/dist/diagnostics/index.js +2 -0
  35. package/dist/diagnostics/report.d.ts +7 -0
  36. package/dist/diagnostics/report.d.ts.map +1 -0
  37. package/dist/diagnostics/report.js +84 -0
  38. package/dist/environment-reset.d.ts +51 -0
  39. package/dist/environment-reset.d.ts.map +1 -0
  40. package/dist/environment-reset.js +83 -0
  41. package/dist/execute-run.d.ts.map +1 -1
  42. package/dist/execute-run.js +121 -69
  43. package/dist/format-harness-error.d.ts +5 -0
  44. package/dist/format-harness-error.d.ts.map +1 -0
  45. package/dist/format-harness-error.js +43 -0
  46. package/dist/harness-session.d.ts +26 -1
  47. package/dist/harness-session.d.ts.map +1 -1
  48. package/dist/harness-session.js +260 -87
  49. package/dist/run.d.ts.map +1 -1
  50. package/dist/run.js +21 -4
  51. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  52. package/package.json +8 -7
  53. package/src/__tests__/bridge.test.ts +8 -3
  54. package/src/__tests__/crash-monitor.test.ts +84 -0
  55. package/src/__tests__/environment-reset.test.ts +308 -0
  56. package/src/__tests__/execute-run.test.ts +327 -4
  57. package/src/__tests__/format-harness-error.test.ts +32 -0
  58. package/src/__tests__/harness-cache.test.ts +91 -0
  59. package/src/__tests__/harness-session.test.ts +211 -1
  60. package/src/__tests__/index.test.ts +3 -1
  61. package/src/__tests__/metro-port.test.ts +2 -0
  62. package/src/__tests__/run.test.ts +74 -0
  63. package/src/crash-monitor.ts +28 -0
  64. package/src/diagnostics/__tests__/derivers.test.ts +300 -0
  65. package/src/diagnostics/__tests__/report.test.ts +128 -0
  66. package/src/diagnostics/derivers.ts +170 -0
  67. package/src/diagnostics/index.ts +2 -0
  68. package/src/diagnostics/report.ts +124 -0
  69. package/src/environment-reset.ts +138 -0
  70. package/src/execute-run.ts +136 -77
  71. package/src/format-harness-error.ts +73 -0
  72. package/src/harness-session.ts +369 -131
  73. package/src/run.ts +23 -4
  74. package/tsconfig.json +3 -0
  75. package/tsconfig.lib.json +3 -0
@@ -0,0 +1,7 @@
1
+ import type { DiagnosticEntry } from '@react-native-harness/tools';
2
+ export declare const writeTraceFile: (entries: DiagnosticEntry[], options: {
3
+ projectRoot: string;
4
+ runId: string;
5
+ }) => Promise<string>;
6
+ export declare const printSummary: (entries: DiagnosticEntry[], log: (line: string) => void) => void;
7
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/diagnostics/report.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAyCnE,eAAO,MAAM,cAAc,GACzB,SAAS,eAAe,EAAE,EAC1B,SAAS;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,KAC9C,OAAO,CAAC,MAAM,CAUhB,CAAC;AAkBF,eAAO,MAAM,YAAY,GACvB,SAAS,eAAe,EAAE,EAC1B,KAAK,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,KAC1B,IA8CF,CAAC"}
@@ -0,0 +1,84 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { getHarnessRootPath } from '@react-native-harness/tools';
4
+ const TRACE_PID = 1;
5
+ const TRACE_TID = 1;
6
+ const toTraceEvent = (entry) => {
7
+ if (entry.kind === 'mark') {
8
+ return {
9
+ name: entry.label,
10
+ ph: 'i',
11
+ s: 'g',
12
+ ts: entry.startTime * 1000,
13
+ pid: TRACE_PID,
14
+ tid: TRACE_TID,
15
+ args: entry.attrs,
16
+ };
17
+ }
18
+ return {
19
+ name: entry.label,
20
+ ph: 'X',
21
+ ts: entry.startTime * 1000,
22
+ dur: entry.duration * 1000,
23
+ pid: TRACE_PID,
24
+ tid: TRACE_TID,
25
+ args: entry.attrs,
26
+ };
27
+ };
28
+ export const writeTraceFile = async (entries, options) => {
29
+ const dir = path.join(getHarnessRootPath(options.projectRoot), 'diagnostics');
30
+ await fs.mkdir(dir, { recursive: true });
31
+ const filePath = path.join(dir, `${options.runId}.json`);
32
+ const trace = { traceEvents: entries.map(toTraceEvent) };
33
+ await fs.writeFile(filePath, JSON.stringify(trace), 'utf-8');
34
+ return filePath;
35
+ };
36
+ const formatDuration = (ms) => {
37
+ if (ms >= 1000) {
38
+ return `${(ms / 1000).toFixed(1)}s`;
39
+ }
40
+ return `${Math.round(ms)}ms`;
41
+ };
42
+ const getNamespace = (label) => label.split('.')[0] ?? label;
43
+ export const printSummary = (entries, log) => {
44
+ if (entries.length === 0) {
45
+ return;
46
+ }
47
+ const statsByLabel = new Map();
48
+ for (const entry of entries) {
49
+ const existing = statsByLabel.get(entry.label);
50
+ if (existing) {
51
+ existing.count += 1;
52
+ existing.totalMs += entry.duration;
53
+ existing.maxMs = Math.max(existing.maxMs, entry.duration);
54
+ }
55
+ else {
56
+ statsByLabel.set(entry.label, {
57
+ label: entry.label,
58
+ count: 1,
59
+ totalMs: entry.duration,
60
+ maxMs: entry.duration,
61
+ });
62
+ }
63
+ }
64
+ const byNamespace = new Map();
65
+ for (const stats of statsByLabel.values()) {
66
+ const namespace = getNamespace(stats.label);
67
+ const group = byNamespace.get(namespace);
68
+ if (group) {
69
+ group.push(stats);
70
+ }
71
+ else {
72
+ byNamespace.set(namespace, [stats]);
73
+ }
74
+ }
75
+ log('Diagnostics summary:');
76
+ const sortedNamespaces = [...byNamespace.entries()].sort(([a], [b]) => a.localeCompare(b));
77
+ for (const [namespace, group] of sortedNamespaces) {
78
+ log(` ${namespace}`);
79
+ group.sort((a, b) => b.totalMs - a.totalMs);
80
+ for (const stats of group) {
81
+ log(` ${stats.label}: count=${stats.count} total=${formatDuration(stats.totalMs)} max=${formatDuration(stats.maxMs)}`);
82
+ }
83
+ }
84
+ };
@@ -0,0 +1,51 @@
1
+ import type { Config as HarnessConfig } from '@react-native-harness/config';
2
+ export type ResetStrategyKind = 'process' | 'runtime';
3
+ export type EnvironmentResetStrategy = {
4
+ readonly kind: ResetStrategyKind;
5
+ reset(ctx: {
6
+ testFilePath: string;
7
+ }): Promise<void>;
8
+ };
9
+ /**
10
+ * Normalizes the `resetEnvironmentBetweenTestFiles` config value into a
11
+ * strategy kind, or `null` when no reset should happen between test files
12
+ * (i.e. the value is `false`).
13
+ */
14
+ export declare const resolveResetStrategyKind: (value: HarnessConfig["resetEnvironmentBetweenTestFiles"]) => ResetStrategyKind | null;
15
+ export type ProcessResetStrategyDeps = {
16
+ restart: (testFilePath: string) => Promise<void>;
17
+ };
18
+ /**
19
+ * The 'process' strategy: kill and cold-restart the app process. This is
20
+ * the same implementation `restartApp` uses for crash/timeout recovery.
21
+ */
22
+ export declare const createProcessResetStrategy: (deps: ProcessResetStrategyDeps) => EnvironmentResetStrategy;
23
+ export type RuntimeResetConnection = {
24
+ resetEnvironment: () => Promise<void>;
25
+ };
26
+ export type RuntimeResetStrategyDeps = {
27
+ getConnection: () => RuntimeResetConnection | null;
28
+ expectDisconnect: () => () => void;
29
+ waitForReconnect: (signal: AbortSignal) => Promise<void>;
30
+ isDisconnectError: (error: unknown) => boolean;
31
+ timeoutMs: number;
32
+ };
33
+ /**
34
+ * The 'runtime' strategy: reload the JS runtime in place (DevSettings.reload()
35
+ * on native, window.location.reload() on web) via the resetEnvironment RPC,
36
+ * and wait for the bridge to reconnect. The app process stays alive
37
+ * throughout, so the crash monitor's expected-disconnect window is used to
38
+ * avoid misclassifying the intentional disconnect as a runtime failure.
39
+ */
40
+ export declare const createRuntimeResetStrategy: (deps: RuntimeResetStrategyDeps) => EnvironmentResetStrategy;
41
+ export type EscalationOptions = {
42
+ onEscalate?: (error: unknown) => void;
43
+ };
44
+ /**
45
+ * Wraps a primary strategy so that any failure (no active connection, RPC
46
+ * error, reconnect timeout, ...) falls back to running `fallback` instead of
47
+ * propagating the error. The composite reports the primary's `kind`, since
48
+ * that's the strategy that was requested.
49
+ */
50
+ export declare const withEscalation: (primary: EnvironmentResetStrategy, fallback: EnvironmentResetStrategy, options?: EscalationOptions) => EnvironmentResetStrategy;
51
+ //# sourceMappingURL=environment-reset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environment-reset.d.ts","sourceRoot":"","sources":["../src/environment-reset.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAI5E,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,SAAS,CAAC;AAEtD,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IAEjC,KAAK,CAAC,GAAG,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GACnC,OAAO,aAAa,CAAC,kCAAkC,CAAC,KACvD,iBAAiB,GAAG,IAQtB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IAErC,OAAO,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAClD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,0BAA0B,GACrC,MAAM,wBAAwB,KAC7B,wBAGD,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG;IACnC,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IAErC,aAAa,EAAE,MAAM,sBAAsB,GAAG,IAAI,CAAC;IAGnD,gBAAgB,EAAE,MAAM,MAAM,IAAI,CAAC;IAGnC,gBAAgB,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAIzD,iBAAiB,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IAE/C,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,GACrC,MAAM,wBAAwB,KAC7B,wBAkCD,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG;IAE9B,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACvC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GACzB,SAAS,wBAAwB,EACjC,UAAU,wBAAwB,EAClC,UAAS,iBAAsB,KAC9B,wBAUD,CAAC"}
@@ -0,0 +1,83 @@
1
+ import { getTimeoutSignal } from '@react-native-harness/tools';
2
+ const ignorePromiseRejection = () => undefined;
3
+ /**
4
+ * Normalizes the `resetEnvironmentBetweenTestFiles` config value into a
5
+ * strategy kind, or `null` when no reset should happen between test files
6
+ * (i.e. the value is `false`).
7
+ */
8
+ export const resolveResetStrategyKind = (value) => {
9
+ if (value === false) {
10
+ return null;
11
+ }
12
+ if (value === true) {
13
+ return 'process';
14
+ }
15
+ return value;
16
+ };
17
+ /**
18
+ * The 'process' strategy: kill and cold-restart the app process. This is
19
+ * the same implementation `restartApp` uses for crash/timeout recovery.
20
+ */
21
+ export const createProcessResetStrategy = (deps) => ({
22
+ kind: 'process',
23
+ reset: ({ testFilePath }) => deps.restart(testFilePath),
24
+ });
25
+ /**
26
+ * The 'runtime' strategy: reload the JS runtime in place (DevSettings.reload()
27
+ * on native, window.location.reload() on web) via the resetEnvironment RPC,
28
+ * and wait for the bridge to reconnect. The app process stays alive
29
+ * throughout, so the crash monitor's expected-disconnect window is used to
30
+ * avoid misclassifying the intentional disconnect as a runtime failure.
31
+ */
32
+ export const createRuntimeResetStrategy = (deps) => ({
33
+ kind: 'runtime',
34
+ reset: async () => {
35
+ const connection = deps.getConnection();
36
+ if (!connection) {
37
+ throw new Error('No active app connection to reset the runtime on');
38
+ }
39
+ const closeWindow = deps.expectDisconnect();
40
+ try {
41
+ // Start listening for the reconnect before firing the RPC, so a fast
42
+ // reload can't reconnect before we're watching for it.
43
+ const reconnected = deps.waitForReconnect(getTimeoutSignal(deps.timeoutMs));
44
+ // Observe the rejection even when the RPC throws first, so the timeout
45
+ // abort firing later can't become an unhandled rejection. (.catch()
46
+ // returns a new promise; the original is what gets awaited below.)
47
+ reconnected.catch(ignorePromiseRejection);
48
+ try {
49
+ await connection.resetEnvironment();
50
+ }
51
+ catch (error) {
52
+ // The reload can tear the runtime down before the RPC ack flushes.
53
+ // A disconnect-flavored rejection means the reload is in flight, so
54
+ // keep waiting for the reconnect; anything else is a real failure.
55
+ if (!deps.isDisconnectError(error)) {
56
+ throw error;
57
+ }
58
+ }
59
+ await reconnected;
60
+ }
61
+ finally {
62
+ closeWindow();
63
+ }
64
+ },
65
+ });
66
+ /**
67
+ * Wraps a primary strategy so that any failure (no active connection, RPC
68
+ * error, reconnect timeout, ...) falls back to running `fallback` instead of
69
+ * propagating the error. The composite reports the primary's `kind`, since
70
+ * that's the strategy that was requested.
71
+ */
72
+ export const withEscalation = (primary, fallback, options = {}) => ({
73
+ kind: primary.kind,
74
+ reset: async (ctx) => {
75
+ try {
76
+ await primary.reset(ctx);
77
+ }
78
+ catch (error) {
79
+ options.onEscalate?.(error);
80
+ await fallback.reset(ctx);
81
+ }
82
+ },
83
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"execute-run.d.ts","sourceRoot":"","sources":["../src/execute-run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,IAAI,EACJ,WAAW,EACX,MAAM,EACN,UAAU,EACX,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,KAAK,cAAc,EAAwB,MAAM,sBAAsB,CAAC;AAsBjF,KAAK,aAAa,GAAG,CAAC,IAAI,SAAS,MAAM,UAAU,EACjD,SAAS,EAAE,IAAI,EACf,GAAG,SAAS,EAAE,UAAU,CAAC,IAAI,CAAC,KAC3B,OAAO,CAAC,IAAI,CAAC,CAAC;AAmFnB,eAAO,MAAM,UAAU,GACrB,SAAS,cAAc,EACvB,OAAO,KAAK,CAAC,IAAI,CAAC,EAClB,SAAS,WAAW,EACpB,WAAW,aAAa,EACxB,cAAc,MAAM,CAAC,YAAY,KAChC,OAAO,CAAC,IAAI,CA8Ld,CAAC"}
1
+ {"version":3,"file":"execute-run.d.ts","sourceRoot":"","sources":["../src/execute-run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,IAAI,EACJ,WAAW,EACX,MAAM,EACN,UAAU,EACX,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,KAAK,cAAc,EAAwB,MAAM,sBAAsB,CAAC;AA6BjF,KAAK,aAAa,GAAG,CAAC,IAAI,SAAS,MAAM,UAAU,EACjD,SAAS,EAAE,IAAI,EACf,GAAG,SAAS,EAAE,UAAU,CAAC,IAAI,CAAC,KAC3B,OAAO,CAAC,IAAI,CAAC,CAAC;AA+FnB,eAAO,MAAM,UAAU,GACrB,SAAS,cAAc,EACvB,OAAO,KAAK,CAAC,IAAI,CAAC,EAClB,SAAS,WAAW,EACpB,WAAW,aAAa,EACxB,cAAc,MAAM,CAAC,YAAY,KAChC,OAAO,CAAC,IAAI,CAsOd,CAAC"}
@@ -1,9 +1,14 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import path from 'node:path';
3
3
  import { runHarnessTestFile } from './run.js';
4
+ import { resolveResetStrategyKind } from './environment-reset.js';
4
5
  import { NativeCrashError, RuntimeDisconnectError, StartupStallError, } from './errors.js';
5
6
  import { AppBridgeDisconnectedError, DeviceNotRespondingError, } from '@react-native-harness/bridge/server';
6
7
  import { createPlatformSkippedTestResult, shouldRunHarnessTestFile, } from './test-file-platform-filter.js';
8
+ import { formatHarnessErrorMessage } from './format-harness-error.js';
9
+ import { logger } from '@react-native-harness/tools';
10
+ import { printSummary, writeTraceFile } from './diagnostics/index.js';
11
+ const diagnosticsLogger = logger.child('diagnostics');
7
12
  const createRunSummary = () => ({ passed: 0, failed: 0, skipped: 0, todo: 0 });
8
13
  const applyJestResultToSummary = (summary, result) => {
9
14
  summary.passed += result.numPassingTests;
@@ -38,8 +43,10 @@ const emitHarnessTestStarted = async (emitEvent, event) => {
38
43
  });
39
44
  };
40
45
  const emitHarnessTestFinished = async (emitEvent, event) => {
41
- const failureMessage = event.error?.message;
42
46
  const codeFrame = event.error?.codeFrame;
47
+ const failureMessage = formatHarnessErrorMessage(event.error, {
48
+ testStartedAt: event.startedAt,
49
+ });
43
50
  const location = codeFrame?.location
44
51
  ? { column: codeFrame.location.column, line: codeFrame.location.row }
45
52
  : null;
@@ -47,9 +54,7 @@ const emitHarnessTestFinished = async (emitEvent, event) => {
47
54
  ancestorTitles: event.ancestorTitles,
48
55
  duration: event.duration,
49
56
  failureDetails: [],
50
- failureMessages: failureMessage
51
- ? [`${failureMessage}${codeFrame ? `\n\n${codeFrame.content}` : ''}`]
52
- : [],
57
+ failureMessages: failureMessage ? [failureMessage] : [],
53
58
  fullName: event.fullName,
54
59
  location,
55
60
  numPassingAsserts: event.status === 'passed' ? 1 : 0,
@@ -60,8 +65,17 @@ const emitHarnessTestFinished = async (emitEvent, event) => {
60
65
  await emitEvent('test-case-result', event.file, testCaseResult);
61
66
  };
62
67
  const isHarnessCaseEvent = (event) => event.type === 'test-started' || event.type === 'test-finished';
68
+ const TIMEOUT_ERROR_NAMES = new Set([
69
+ 'SuiteHookTimeoutError',
70
+ 'TestCaseTimeoutError',
71
+ ]);
72
+ const hasRuntimeTimeout = (result) => (result.error ? TIMEOUT_ERROR_NAMES.has(result.error.name) : false) ||
73
+ result.tests.some((test) => test.error ? TIMEOUT_ERROR_NAMES.has(test.error.name) : false) ||
74
+ result.suites.some(hasRuntimeTimeout);
63
75
  export const executeRun = async (session, tests, watcher, emitEvent, globalConfig) => {
64
76
  const runId = randomUUID();
77
+ const diagnostics = session.diagnostics.child({ runId });
78
+ const runSpan = diagnostics.start('run.total', { runId });
65
79
  const startTime = Date.now();
66
80
  const watchMode = globalConfig.watch || globalConfig.watchAll;
67
81
  const rootDir = globalConfig.rootDir ?? process.cwd();
@@ -82,6 +96,7 @@ export const executeRun = async (session, tests, watcher, emitEvent, globalConfi
82
96
  testFiles,
83
97
  watchMode,
84
98
  coverageEnabled: globalConfig.collectCoverage,
99
+ completed: false,
85
100
  summary,
86
101
  status: summary.failed > 0 ? 'failed' : 'passed',
87
102
  ...overrides,
@@ -102,10 +117,11 @@ export const executeRun = async (session, tests, watcher, emitEvent, globalConfi
102
117
  port: session.config.metroPort,
103
118
  host: session.config.host?.trim() || undefined,
104
119
  });
105
- const shouldResetEnv = session.config.resetEnvironmentBetweenTestFiles;
120
+ const resetStrategyKind = resolveResetStrategyKind(session.config.resetEnvironmentBetweenTestFiles);
106
121
  const platformId = session.context.platform.platformId;
107
122
  const knownPlatformIds = new Set(session.config.runners.map((runner) => runner.platformId));
108
123
  let isFirstTest = true;
124
+ let shouldRestartAfterTimeout = false;
109
125
  let runError;
110
126
  try {
111
127
  for (const test of tests) {
@@ -125,20 +141,82 @@ export const executeRun = async (session, tests, watcher, emitEvent, globalConfi
125
141
  });
126
142
  };
127
143
  await session.callHook('test-file:started', { runId, file: relativeTestPath });
128
- if (!shouldRunHarnessTestFile(test.path, platformId, knownPlatformIds)) {
144
+ const fileSpan = diagnostics.start('run.file', { file: relativeTestPath });
145
+ let fileStatus = 'failed';
146
+ try {
147
+ if (!shouldRunHarnessTestFile(test.path, platformId, knownPlatformIds)) {
148
+ try {
149
+ await emitEvent('test-file-start', test);
150
+ const skippedResult = createPlatformSkippedTestResult(test.path);
151
+ applyJestResultToSummary(summary, skippedResult);
152
+ updateRunState();
153
+ fileStatus = 'skipped';
154
+ await emitTestFileFinished({
155
+ status: 'skipped',
156
+ duration: Date.now() - fileStartedAt,
157
+ result: null,
158
+ });
159
+ await emitEvent('test-file-success', test, skippedResult);
160
+ }
161
+ catch (err) {
162
+ fileStatus = 'failed';
163
+ if (!emittedTestFileFinished) {
164
+ await emitTestFileFinished({
165
+ status: 'failed',
166
+ duration: Date.now() - fileStartedAt,
167
+ result: null,
168
+ });
169
+ }
170
+ updateRunState({ error: err });
171
+ await emitEvent('test-file-failure', test, buildTestFailure(err));
172
+ }
173
+ continue;
174
+ }
129
175
  try {
176
+ if (shouldRestartAfterTimeout) {
177
+ // Recovery from a runtime timeout always uses the (stronger)
178
+ // process restart, never the configured reset strategy.
179
+ await session.restartApp(test.path);
180
+ shouldRestartAfterTimeout = false;
181
+ }
182
+ else if (resetStrategyKind !== null && !isFirstTest) {
183
+ await session.resetEnvironment(test.path);
184
+ }
185
+ isFirstTest = false;
186
+ session.flushClientLogs();
130
187
  await emitEvent('test-file-start', test);
131
- const skippedResult = createPlatformSkippedTestResult(test.path);
132
- applyJestResultToSummary(summary, skippedResult);
188
+ await session.ensureAppReady(test.path);
189
+ // Crash detection is handled inside session.runTestFile; NativeCrashError
190
+ // propagates here if a crash wins the race.
191
+ const result = await runHarnessTestFile({
192
+ testPath: test.path,
193
+ session,
194
+ globalConfig,
195
+ projectConfig: test.context.config,
196
+ });
197
+ applyJestResultToSummary(summary, result.jestResult);
198
+ const clientLogs = session.flushClientLogs();
199
+ if (clientLogs.length > 0) {
200
+ result.jestResult.console = clientLogs;
201
+ }
133
202
  updateRunState();
203
+ fileStatus = result.harnessResult.status;
134
204
  await emitTestFileFinished({
135
- status: 'skipped',
136
- duration: Date.now() - fileStartedAt,
137
- result: null,
205
+ status: result.harnessResult.status,
206
+ duration: result.duration,
207
+ result: result.harnessResult,
138
208
  });
139
- await emitEvent('test-file-success', test, skippedResult);
209
+ const didRuntimeTimeout = hasRuntimeTimeout(result.harnessResult);
210
+ shouldRestartAfterTimeout = didRuntimeTimeout;
211
+ await caseEventChain;
212
+ await emitEvent('test-file-success', test, result.jestResult);
213
+ if (didRuntimeTimeout) {
214
+ await session.restartApp(test.path);
215
+ shouldRestartAfterTimeout = false;
216
+ }
140
217
  }
141
218
  catch (err) {
219
+ fileStatus = 'failed';
142
220
  if (!emittedTestFileFinished) {
143
221
  await emitTestFileFinished({
144
222
  status: 'failed',
@@ -146,64 +224,25 @@ export const executeRun = async (session, tests, watcher, emitEvent, globalConfi
146
224
  result: null,
147
225
  });
148
226
  }
149
- updateRunState({ error: err });
227
+ const isRuntimeFailure = err instanceof NativeCrashError ||
228
+ err instanceof RuntimeDisconnectError ||
229
+ err instanceof StartupStallError ||
230
+ err instanceof AppBridgeDisconnectedError ||
231
+ err instanceof DeviceNotRespondingError;
232
+ if (isRuntimeFailure) {
233
+ summary.failed += 1;
234
+ updateRunState();
235
+ }
236
+ if (err instanceof NativeCrashError || err instanceof RuntimeDisconnectError) {
237
+ session.resetCrashState();
238
+ }
239
+ updateRunState({ error: isRuntimeFailure ? undefined : err });
240
+ await caseEventChain;
150
241
  await emitEvent('test-file-failure', test, buildTestFailure(err));
151
242
  }
152
- continue;
153
243
  }
154
- try {
155
- if (shouldResetEnv && !isFirstTest) {
156
- await session.restartApp(test.path);
157
- }
158
- isFirstTest = false;
159
- session.flushClientLogs();
160
- await emitEvent('test-file-start', test);
161
- await session.ensureAppReady(test.path);
162
- // Crash detection is handled inside session.runTestFile; NativeCrashError
163
- // propagates here if a crash wins the race.
164
- const result = await runHarnessTestFile({
165
- testPath: test.path,
166
- session,
167
- globalConfig,
168
- projectConfig: test.context.config,
169
- });
170
- applyJestResultToSummary(summary, result.jestResult);
171
- const clientLogs = session.flushClientLogs();
172
- if (clientLogs.length > 0) {
173
- result.jestResult.console = clientLogs;
174
- }
175
- updateRunState();
176
- await emitTestFileFinished({
177
- status: result.harnessResult.status,
178
- duration: result.duration,
179
- result: result.harnessResult,
180
- });
181
- await caseEventChain;
182
- await emitEvent('test-file-success', test, result.jestResult);
183
- }
184
- catch (err) {
185
- if (!emittedTestFileFinished) {
186
- await emitTestFileFinished({
187
- status: 'failed',
188
- duration: Date.now() - fileStartedAt,
189
- result: null,
190
- });
191
- }
192
- const isRuntimeFailure = err instanceof NativeCrashError ||
193
- err instanceof RuntimeDisconnectError ||
194
- err instanceof StartupStallError ||
195
- err instanceof AppBridgeDisconnectedError ||
196
- err instanceof DeviceNotRespondingError;
197
- if (isRuntimeFailure) {
198
- summary.failed += 1;
199
- updateRunState();
200
- }
201
- if (err instanceof NativeCrashError || err instanceof RuntimeDisconnectError) {
202
- session.resetCrashState();
203
- }
204
- updateRunState({ error: isRuntimeFailure ? undefined : err });
205
- await caseEventChain;
206
- await emitEvent('test-file-failure', test, buildTestFailure(err));
244
+ finally {
245
+ fileSpan.end({ status: fileStatus });
207
246
  }
208
247
  }
209
248
  }
@@ -213,17 +252,30 @@ export const executeRun = async (session, tests, watcher, emitEvent, globalConfi
213
252
  throw err;
214
253
  }
215
254
  finally {
216
- const runState = updateRunState(runError != null ? { error: runError, status: 'failed' } : {});
255
+ const runState = updateRunState(runError != null ? { completed: true, error: runError, status: 'failed' } : { completed: true });
256
+ const finalStatus = runState.status ?? (runError != null ? 'failed' : 'passed');
217
257
  await session.callHook('run:finished', {
218
258
  runId,
219
259
  startTime,
220
260
  duration: Date.now() - startTime,
221
261
  testFiles,
222
262
  summary,
223
- status: runState.status ?? (runError != null ? 'failed' : 'passed'),
263
+ status: finalStatus,
224
264
  ...(runError != null ? { error: runError } : {}),
225
265
  });
226
266
  await caseEventChain;
227
267
  unsubscribe();
268
+ runSpan.end({ runId, status: finalStatus });
269
+ if (session.diagnostics.enabled) {
270
+ const entries = session.diagnostics.flush();
271
+ try {
272
+ printSummary(entries, (line) => diagnosticsLogger.info(line));
273
+ const tracePath = await writeTraceFile(entries, { projectRoot: rootDir, runId });
274
+ diagnosticsLogger.info(`wrote diagnostics trace to ${tracePath}`);
275
+ }
276
+ catch (reportError) {
277
+ diagnosticsLogger.warn('failed to write diagnostics report: %s', reportError);
278
+ }
279
+ }
228
280
  }
229
281
  };
@@ -0,0 +1,5 @@
1
+ import type { SerializedError } from '@react-native-harness/bridge';
2
+ export declare const formatHarnessErrorMessage: (error: SerializedError | undefined, options?: {
3
+ testStartedAt?: number;
4
+ }) => string | undefined;
5
+ //# sourceMappingURL=format-harness-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-harness-error.d.ts","sourceRoot":"","sources":["../src/format-harness-error.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AA6CpE,eAAO,MAAM,yBAAyB,GACpC,OAAO,eAAe,GAAG,SAAS,EAClC,UAAS;IACP,aAAa,CAAC,EAAE,MAAM,CAAC;CACnB,KACL,MAAM,GAAG,SAsBX,CAAC"}
@@ -0,0 +1,43 @@
1
+ const formatPendingPromiseStack = (stack) => stack
2
+ .split('\n')
3
+ .map((line) => ` ${line}`)
4
+ .join('\n');
5
+ const formatPendingPromises = (pendingPromises, testStartedAt) => {
6
+ if (pendingPromises.total === 0) {
7
+ return null;
8
+ }
9
+ const lines = [`Pending promises at timeout: ${pendingPromises.total}`];
10
+ if (pendingPromises.items.length < pendingPromises.total) {
11
+ lines.push(`Showing ${pendingPromises.items.length} of ${pendingPromises.total} pending promises.`);
12
+ }
13
+ for (const promise of pendingPromises.items) {
14
+ const age = testStartedAt !== undefined
15
+ ? `, created ${Math.max(0, promise.createdAt - testStartedAt)}ms after test start`
16
+ : '';
17
+ lines.push('');
18
+ lines.push(`Promise #${promise.id}${age}:`);
19
+ if (promise.stack) {
20
+ lines.push(formatPendingPromiseStack(promise.stack));
21
+ }
22
+ else {
23
+ lines.push(' <stack unavailable>');
24
+ }
25
+ }
26
+ return lines.join('\n');
27
+ };
28
+ export const formatHarnessErrorMessage = (error, options = {}) => {
29
+ if (!error) {
30
+ return undefined;
31
+ }
32
+ const parts = [error.message];
33
+ const pendingPromiseDetails = error.diagnostics?.pendingPromises
34
+ ? formatPendingPromises(error.diagnostics.pendingPromises, options.testStartedAt)
35
+ : null;
36
+ if (pendingPromiseDetails) {
37
+ parts.push(pendingPromiseDetails);
38
+ }
39
+ if (error.codeFrame) {
40
+ parts.push(error.codeFrame.content);
41
+ }
42
+ return parts.join('\n\n');
43
+ };
@@ -1,7 +1,8 @@
1
- import { type AppConnection } from '@react-native-harness/bridge/server';
1
+ import { type HarnessBridge, type AppConnection } from '@react-native-harness/bridge/server';
2
2
  import { type HarnessContext, type TestRunnerEvents, type TestExecutionOptions, type TestSuiteResult } from '@react-native-harness/bridge';
3
3
  import { type HarnessPlatform } from '@react-native-harness/platforms';
4
4
  import { type HarnessPluginManager, type HarnessRunStatus, type HarnessRunSummary } from '@react-native-harness/plugins';
5
+ import { type Diagnostics } from '@react-native-harness/tools';
5
6
  import { type Config as HarnessConfig } from '@react-native-harness/config';
6
7
  import type { Config as JestConfig } from 'jest-runner';
7
8
  import { type CrashMonitor } from './crash-monitor.js';
@@ -17,12 +18,17 @@ export declare const waitForBridgeDisconnectOrTimeout: ({ bridge, connection, ti
17
18
  connection: AppConnection;
18
19
  timeoutMs: number;
19
20
  }) => Promise<boolean>;
21
+ export declare const waitForNextConnected: ({ bridge, signal, }: {
22
+ bridge: Pick<HarnessBridge, "on" | "off">;
23
+ signal: AbortSignal;
24
+ }) => Promise<void>;
20
25
  export type HarnessRunState = {
21
26
  readonly runId: string;
22
27
  readonly startTime: number;
23
28
  readonly testFiles: string[];
24
29
  readonly watchMode: boolean;
25
30
  readonly coverageEnabled: boolean;
31
+ readonly completed?: boolean;
26
32
  readonly summary?: HarnessRunSummary;
27
33
  readonly status?: HarnessRunStatus;
28
34
  readonly error?: unknown;
@@ -31,22 +37,41 @@ export type HarnessRunTestsOptions = Exclude<TestExecutionOptions, 'platform'>;
31
37
  export type HarnessSession = {
32
38
  readonly config: HarnessConfig;
33
39
  readonly context: HarnessContext;
40
+ readonly diagnostics: Diagnostics;
34
41
  onTestRunnerEvent: (listener: (event: TestRunnerEvents) => void) => () => void;
35
42
  runTestFile: (path: string, options: HarnessRunTestsOptions) => Promise<TestSuiteResult>;
36
43
  ensureAppReady: (testFilePath: string) => Promise<void>;
37
44
  restartApp: (testFilePath?: string) => Promise<void>;
45
+ resetEnvironment: (testFilePath: string) => Promise<void>;
38
46
  resetCrashState: () => void;
39
47
  flushClientLogs: () => ClientLogBuffer;
40
48
  callHook: HarnessPluginManager<HarnessConfig, HarnessPlatform>['callHook'];
41
49
  setRunState: (state: HarnessRunState | null) => void;
42
50
  dispose: (reason?: 'normal' | 'abort' | 'error') => Promise<void>;
43
51
  };
52
+ export declare const getSignalExitCodeForRunState: (state: HarnessRunState | null) => number;
53
+ export declare const withPlatformReadyTimeout: <T>(options: {
54
+ timeout: number;
55
+ signal: AbortSignal;
56
+ work: (signal: AbortSignal) => Promise<T>;
57
+ }) => Promise<T>;
44
58
  export declare const waitForStartupCrash: ({ crashMonitor, detectNativeCrashes, testFilePath, signal, }: {
45
59
  crashMonitor: CrashMonitor;
46
60
  detectNativeCrashes: boolean;
47
61
  testFilePath: string;
48
62
  signal: AbortSignal;
49
63
  }) => Promise<never>;
64
+ /**
65
+ * Logs the "Reusing Metro cache" message when persistent Metro caching is
66
+ * enabled and the cache is warm. `projectRoot` must be the config-resolved
67
+ * project root so the warmth check looks at the same directory Metro caches
68
+ * into.
69
+ */
70
+ export declare const maybeLogMetroCacheReused: (options: {
71
+ config: Pick<HarnessConfig, "cache" | "unstable__enableMetroCache">;
72
+ projectRoot: string;
73
+ platform: HarnessPlatform;
74
+ }) => void;
50
75
  export declare const createHarnessSession: (globalConfig: JestConfig.GlobalConfig, { lockManager }?: {
51
76
  lockManager?: ResourceLockManager;
52
77
  }) => Promise<HarnessSession>;
@@ -1 +1 @@
1
- {"version":3,"file":"harness-session.d.ts","sourceRoot":"","sources":["../src/harness-session.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"harness-session.d.ts","sourceRoot":"","sources":["../src/harness-session.ts"],"names":[],"mappings":"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"}