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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/__tests__/bridge.test.js +23 -2
  2. package/dist/__tests__/crash-monitor.test.js +146 -192
  3. package/dist/__tests__/errors.test.js +39 -0
  4. package/dist/__tests__/execute-run.test.js +190 -26
  5. package/dist/__tests__/harness-session.test.js +63 -239
  6. package/dist/__tests__/index.test.js +59 -10
  7. package/dist/__tests__/test-file-platform-filter.test.d.ts +2 -0
  8. package/dist/__tests__/test-file-platform-filter.test.d.ts.map +1 -0
  9. package/dist/__tests__/test-file-platform-filter.test.js +47 -0
  10. package/dist/crash-monitor.d.ts +5 -4
  11. package/dist/crash-monitor.d.ts.map +1 -1
  12. package/dist/crash-monitor.js +116 -102
  13. package/dist/errors.d.ts +10 -0
  14. package/dist/errors.d.ts.map +1 -1
  15. package/dist/errors.js +41 -2
  16. package/dist/execute-run.d.ts +4 -2
  17. package/dist/execute-run.d.ts.map +1 -1
  18. package/dist/execute-run.js +85 -7
  19. package/dist/harness-session.d.ts +21 -1
  20. package/dist/harness-session.d.ts.map +1 -1
  21. package/dist/harness-session.js +134 -39
  22. package/dist/index.d.ts +7 -3
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +24 -2
  25. package/dist/run.d.ts.map +1 -1
  26. package/dist/run.js +1 -0
  27. package/dist/test-file-platform-filter.d.ts +5 -0
  28. package/dist/test-file-platform-filter.d.ts.map +1 -0
  29. package/dist/test-file-platform-filter.js +40 -0
  30. package/dist/toTestResult.d.ts +1 -0
  31. package/dist/toTestResult.d.ts.map +1 -1
  32. package/dist/toTestResult.js +2 -2
  33. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  34. package/package.json +7 -7
  35. package/src/__tests__/bridge.test.ts +37 -2
  36. package/src/__tests__/crash-monitor.test.ts +182 -231
  37. package/src/__tests__/errors.test.ts +75 -0
  38. package/src/__tests__/execute-run.test.ts +235 -34
  39. package/src/__tests__/harness-session.test.ts +93 -0
  40. package/src/__tests__/index.test.ts +89 -11
  41. package/src/__tests__/test-file-platform-filter.test.ts +83 -0
  42. package/src/crash-monitor.ts +192 -131
  43. package/src/errors.ts +62 -1
  44. package/src/execute-run.ts +125 -11
  45. package/src/harness-session.ts +177 -42
  46. package/src/index.ts +40 -11
  47. package/src/run.ts +1 -0
  48. package/src/test-file-platform-filter.ts +57 -0
  49. package/src/toTestResult.ts +3 -2
@@ -1,8 +1,9 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import path from 'node:path';
3
3
  import { runHarnessTestFile } from './run.js';
4
- import { NativeCrashError, StartupStallError, } from './errors.js';
5
- import { DeviceNotRespondingError } from '@react-native-harness/bridge/server';
4
+ import { NativeCrashError, RuntimeDisconnectError, StartupStallError, } from './errors.js';
5
+ import { AppBridgeDisconnectedError, DeviceNotRespondingError, } from '@react-native-harness/bridge/server';
6
+ import { createPlatformSkippedTestResult, shouldRunHarnessTestFile, } from './test-file-platform-filter.js';
6
7
  const createRunSummary = () => ({ passed: 0, failed: 0, skipped: 0, todo: 0 });
7
8
  const applyJestResultToSummary = (summary, result) => {
8
9
  summary.passed += result.numPassingTests;
@@ -18,19 +19,62 @@ class CancelRun extends Error {
18
19
  }
19
20
  const buildTestFailure = (err) => {
20
21
  if (err instanceof NativeCrashError ||
22
+ err instanceof RuntimeDisconnectError ||
21
23
  err instanceof StartupStallError ||
24
+ err instanceof AppBridgeDisconnectedError ||
22
25
  err instanceof DeviceNotRespondingError) {
23
26
  return { message: err.message, stack: '' };
24
27
  }
25
28
  return err;
26
29
  };
27
- export const executeRun = async (session, tests, watcher, onStart, onResult, onFailure, globalConfig) => {
30
+ const toJestMode = (declarationMode) => declarationMode;
31
+ const emitHarnessTestStarted = async (emitEvent, event) => {
32
+ await emitEvent('test-case-start', event.file, {
33
+ ancestorTitles: event.ancestorTitles,
34
+ fullName: event.fullName,
35
+ mode: toJestMode(event.declarationMode),
36
+ title: event.name,
37
+ startedAt: event.startedAt,
38
+ });
39
+ };
40
+ const emitHarnessTestFinished = async (emitEvent, event) => {
41
+ const failureMessage = event.error?.message;
42
+ const codeFrame = event.error?.codeFrame;
43
+ const location = codeFrame?.location
44
+ ? { column: codeFrame.location.column, line: codeFrame.location.row }
45
+ : null;
46
+ const testCaseResult = {
47
+ ancestorTitles: event.ancestorTitles,
48
+ duration: event.duration,
49
+ failureDetails: [],
50
+ failureMessages: failureMessage
51
+ ? [`${failureMessage}${codeFrame ? `\n\n${codeFrame.content}` : ''}`]
52
+ : [],
53
+ fullName: event.fullName,
54
+ location,
55
+ numPassingAsserts: event.status === 'passed' ? 1 : 0,
56
+ startedAt: event.startedAt,
57
+ status: event.status,
58
+ title: event.name,
59
+ };
60
+ await emitEvent('test-case-result', event.file, testCaseResult);
61
+ };
62
+ const isHarnessCaseEvent = (event) => event.type === 'test-started' || event.type === 'test-finished';
63
+ export const executeRun = async (session, tests, watcher, emitEvent, globalConfig) => {
28
64
  const runId = randomUUID();
29
65
  const startTime = Date.now();
30
66
  const watchMode = globalConfig.watch || globalConfig.watchAll;
31
67
  const rootDir = globalConfig.rootDir ?? process.cwd();
32
68
  const testFiles = tests.map((t) => path.relative(rootDir, t.path));
33
69
  const summary = createRunSummary();
70
+ let caseEventChain = Promise.resolve();
71
+ const unsubscribe = session.onTestRunnerEvent((event) => {
72
+ if (isHarnessCaseEvent(event)) {
73
+ caseEventChain = caseEventChain.then(() => event.type === 'test-started'
74
+ ? emitHarnessTestStarted(emitEvent, event)
75
+ : emitHarnessTestFinished(emitEvent, event));
76
+ }
77
+ });
34
78
  const updateRunState = (overrides = {}) => {
35
79
  const state = {
36
80
  runId,
@@ -59,6 +103,8 @@ export const executeRun = async (session, tests, watcher, onStart, onResult, onF
59
103
  host: session.config.host?.trim() || undefined,
60
104
  });
61
105
  const shouldResetEnv = session.config.resetEnvironmentBetweenTestFiles;
106
+ const platformId = session.context.platform.platformId;
107
+ const knownPlatformIds = new Set(session.config.runners.map((runner) => runner.platformId));
62
108
  let isFirstTest = true;
63
109
  let runError;
64
110
  try {
@@ -79,13 +125,39 @@ export const executeRun = async (session, tests, watcher, onStart, onResult, onF
79
125
  });
80
126
  };
81
127
  await session.callHook('test-file:started', { runId, file: relativeTestPath });
128
+ if (!shouldRunHarnessTestFile(test.path, platformId, knownPlatformIds)) {
129
+ try {
130
+ await emitEvent('test-file-start', test);
131
+ const skippedResult = createPlatformSkippedTestResult(test.path);
132
+ applyJestResultToSummary(summary, skippedResult);
133
+ updateRunState();
134
+ await emitTestFileFinished({
135
+ status: 'skipped',
136
+ duration: Date.now() - fileStartedAt,
137
+ result: null,
138
+ });
139
+ await emitEvent('test-file-success', test, skippedResult);
140
+ }
141
+ catch (err) {
142
+ if (!emittedTestFileFinished) {
143
+ await emitTestFileFinished({
144
+ status: 'failed',
145
+ duration: Date.now() - fileStartedAt,
146
+ result: null,
147
+ });
148
+ }
149
+ updateRunState({ error: err });
150
+ await emitEvent('test-file-failure', test, buildTestFailure(err));
151
+ }
152
+ continue;
153
+ }
82
154
  try {
83
155
  if (shouldResetEnv && !isFirstTest) {
84
156
  await session.restartApp(test.path);
85
157
  }
86
158
  isFirstTest = false;
87
159
  session.flushClientLogs();
88
- await onStart(test);
160
+ await emitEvent('test-file-start', test);
89
161
  await session.ensureAppReady(test.path);
90
162
  // Crash detection is handled inside session.runTestFile; NativeCrashError
91
163
  // propagates here if a crash wins the race.
@@ -106,7 +178,8 @@ export const executeRun = async (session, tests, watcher, onStart, onResult, onF
106
178
  duration: result.duration,
107
179
  result: result.harnessResult,
108
180
  });
109
- await onResult(test, result.jestResult);
181
+ await caseEventChain;
182
+ await emitEvent('test-file-success', test, result.jestResult);
110
183
  }
111
184
  catch (err) {
112
185
  if (!emittedTestFileFinished) {
@@ -117,17 +190,20 @@ export const executeRun = async (session, tests, watcher, onStart, onResult, onF
117
190
  });
118
191
  }
119
192
  const isRuntimeFailure = err instanceof NativeCrashError ||
193
+ err instanceof RuntimeDisconnectError ||
120
194
  err instanceof StartupStallError ||
195
+ err instanceof AppBridgeDisconnectedError ||
121
196
  err instanceof DeviceNotRespondingError;
122
197
  if (isRuntimeFailure) {
123
198
  summary.failed += 1;
124
199
  updateRunState();
125
200
  }
126
- if (err instanceof NativeCrashError) {
201
+ if (err instanceof NativeCrashError || err instanceof RuntimeDisconnectError) {
127
202
  session.resetCrashState();
128
203
  }
129
204
  updateRunState({ error: isRuntimeFailure ? undefined : err });
130
- onFailure(test, buildTestFailure(err));
205
+ await caseEventChain;
206
+ await emitEvent('test-file-failure', test, buildTestFailure(err));
131
207
  }
132
208
  }
133
209
  }
@@ -147,5 +223,7 @@ export const executeRun = async (session, tests, watcher, onStart, onResult, onF
147
223
  status: runState.status ?? (runError != null ? 'failed' : 'passed'),
148
224
  ...(runError != null ? { error: runError } : {}),
149
225
  });
226
+ await caseEventChain;
227
+ unsubscribe();
150
228
  }
151
229
  };
@@ -1,10 +1,22 @@
1
- import { type HarnessContext, type TestExecutionOptions, type TestSuiteResult } from '@react-native-harness/bridge';
1
+ import { type AppConnection } from '@react-native-harness/bridge/server';
2
+ import { type HarnessContext, type TestRunnerEvents, type TestExecutionOptions, type TestSuiteResult } from '@react-native-harness/bridge';
2
3
  import { type HarnessPlatform } from '@react-native-harness/platforms';
3
4
  import { type HarnessPluginManager, type HarnessRunStatus, type HarnessRunSummary } from '@react-native-harness/plugins';
4
5
  import { type Config as HarnessConfig } from '@react-native-harness/config';
5
6
  import type { Config as JestConfig } from 'jest-runner';
7
+ import { type CrashMonitor } from './crash-monitor.js';
6
8
  import { type ClientLogBuffer } from './client-log-handler.js';
7
9
  import { type ResourceLockManager } from './resource-lock.js';
10
+ type DisconnectObservableBridge = {
11
+ readonly connection: AppConnection | null;
12
+ on: (event: 'disconnected', listener: () => void) => void;
13
+ off: (event: 'disconnected', listener: () => void) => void;
14
+ };
15
+ export declare const waitForBridgeDisconnectOrTimeout: ({ bridge, connection, timeoutMs, }: {
16
+ bridge: DisconnectObservableBridge;
17
+ connection: AppConnection;
18
+ timeoutMs: number;
19
+ }) => Promise<boolean>;
8
20
  export type HarnessRunState = {
9
21
  readonly runId: string;
10
22
  readonly startTime: number;
@@ -19,6 +31,7 @@ export type HarnessRunTestsOptions = Exclude<TestExecutionOptions, 'platform'>;
19
31
  export type HarnessSession = {
20
32
  readonly config: HarnessConfig;
21
33
  readonly context: HarnessContext;
34
+ onTestRunnerEvent: (listener: (event: TestRunnerEvents) => void) => () => void;
22
35
  runTestFile: (path: string, options: HarnessRunTestsOptions) => Promise<TestSuiteResult>;
23
36
  ensureAppReady: (testFilePath: string) => Promise<void>;
24
37
  restartApp: (testFilePath?: string) => Promise<void>;
@@ -28,7 +41,14 @@ export type HarnessSession = {
28
41
  setRunState: (state: HarnessRunState | null) => void;
29
42
  dispose: (reason?: 'normal' | 'abort' | 'error') => Promise<void>;
30
43
  };
44
+ export declare const waitForStartupCrash: ({ crashMonitor, detectNativeCrashes, testFilePath, signal, }: {
45
+ crashMonitor: CrashMonitor;
46
+ detectNativeCrashes: boolean;
47
+ testFilePath: string;
48
+ signal: AbortSignal;
49
+ }) => Promise<never>;
31
50
  export declare const createHarnessSession: (globalConfig: JestConfig.GlobalConfig, { lockManager }?: {
32
51
  lockManager?: ResourceLockManager;
33
52
  }) => Promise<HarnessSession>;
53
+ export {};
34
54
  //# sourceMappingURL=harness-session.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"harness-session.d.ts","sourceRoot":"","sources":["../src/harness-session.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,KAAK,cAAc,EAEnB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACrB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAEL,KAAK,eAAe,EAGrB,MAAM,iCAAiC,CAAC;AASzC,OAAO,EAIL,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,+BAA+B,CAAC;AAOvC,OAAO,EAEL,KAAK,MAAM,IAAI,aAAa,EAE7B,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,aAAa,CAAC;AAMxD,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAEL,KAAK,mBAAmB,EAEzB,MAAM,oBAAoB,CAAC;AAkB5B,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IACnC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,OAAO,CAAC,oBAAoB,EAAE,UAAU,CAAC,CAAC;AAE/E,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;IACzF,cAAc,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,UAAU,EAAE,CAAC,YAAY,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,eAAe,EAAE,MAAM,eAAe,CAAC;IACvC,QAAQ,EAAE,oBAAoB,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC,UAAU,CAAC,CAAC;IAC3E,WAAW,EAAE,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,KAAK,IAAI,CAAC;IACrD,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE,CAAC;AAuOF,eAAO,MAAM,oBAAoB,GAC/B,cAAc,UAAU,CAAC,YAAY,EACrC,kBAA8C;IAAE,WAAW,CAAC,EAAE,mBAAmB,CAAA;CAAO,KACvF,OAAO,CAAC,cAAc,CA6YxB,CAAC"}
1
+ {"version":3,"file":"harness-session.d.ts","sourceRoot":"","sources":["../src/harness-session.ts"],"names":[],"mappings":"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"}
@@ -2,9 +2,10 @@ import { createHarnessBridge, } from '@react-native-harness/bridge/server';
2
2
  import { HARNESS_BRIDGE_PATH, } from '@react-native-harness/bridge';
3
3
  import { getMetroInstance, isMetroCacheReusable, waitForMetroBackedAppReady, } from '@react-native-harness/bundler-metro';
4
4
  import { createHarnessPluginManager, } from '@react-native-harness/plugins';
5
- import { logger, createCrashArtifactWriter, getTimeoutSignal, raceAbortSignals, } from '@react-native-harness/tools';
5
+ import { createCrashArtifactWriter, logger, getTimeoutSignal, raceAbortSignals, } from '@react-native-harness/tools';
6
6
  import { getConfig, ConfigSchema, } from '@react-native-harness/config';
7
7
  import { preRunMessage } from 'jest-util';
8
+ import path from 'node:path';
8
9
  import { PlatformReadyTimeoutError } from './errors.js';
9
10
  import { NoRunnerSpecifiedError, RunnerNotFoundError } from './errors.js';
10
11
  import { createCrashMonitor } from './crash-monitor.js';
@@ -18,6 +19,28 @@ import { logMetroCacheReused, logMetroPortFallback, logNativeCoverageCollected,
18
19
  const sessionLogger = logger.child('runtime');
19
20
  const defaultResourceLockManager = createResourceLockManager();
20
21
  const ignorePromiseRejection = () => undefined;
22
+ const isBridgeDisconnectError = (error) => error instanceof Error && error.message === 'App bridge disconnected';
23
+ const TEST_RUN_BRIDGE_STABILITY_WAIT_MS = 500;
24
+ export const waitForBridgeDisconnectOrTimeout = async ({ bridge, connection, timeoutMs, }) => {
25
+ if (bridge.connection !== connection) {
26
+ return true;
27
+ }
28
+ return await new Promise((resolve) => {
29
+ const onDisconnected = () => {
30
+ cleanup();
31
+ resolve(true);
32
+ };
33
+ const cleanup = () => {
34
+ clearTimeout(timer);
35
+ bridge.off('disconnected', onDisconnected);
36
+ };
37
+ const timer = setTimeout(() => {
38
+ cleanup();
39
+ resolve(bridge.connection !== connection);
40
+ }, timeoutMs);
41
+ bridge.on('disconnected', onDisconnected);
42
+ });
43
+ };
21
44
  // ---------------------------------------------------------------------------
22
45
  // Internal helpers
23
46
  // ---------------------------------------------------------------------------
@@ -46,8 +69,21 @@ const withPlatformReadyTimeout = async (options) => {
46
69
  throw error;
47
70
  }
48
71
  };
72
+ export const waitForStartupCrash = async ({ crashMonitor, detectNativeCrashes, testFilePath, signal, }) => {
73
+ if (!detectNativeCrashes) {
74
+ return await waitForAbort(signal);
75
+ }
76
+ const watch = crashMonitor.watch(testFilePath, 'startup');
77
+ watch.promise.catch(ignorePromiseRejection); // suppress unhandled-rejection when abort wins race
78
+ try {
79
+ return await Promise.race([watch.promise, waitForAbort(signal)]);
80
+ }
81
+ finally {
82
+ watch.cancel();
83
+ }
84
+ };
49
85
  const waitForAppReady = async (base, testFilePath) => {
50
- const { metroInstance, bridge, platformInstance, platformId, bundleStartTimeout, readyTimeout, maxAppRestarts, crashMonitor, appLaunchOptions, } = base;
86
+ const { metroInstance, bridge, platformId, bundleStartTimeout, readyTimeout, maxAppRestarts, crashMonitor, detectNativeCrashes, restartAppSession, } = base;
51
87
  const logWait = (message, ...args) => sessionLogger.debug(`waitForAppReady: ${message}`, ...args);
52
88
  return await waitForMetroBackedAppReady({
53
89
  metro: metroInstance,
@@ -58,7 +94,7 @@ const waitForAppReady = async (base, testFilePath) => {
58
94
  signal: new AbortController().signal,
59
95
  startAttempt: async () => {
60
96
  logWait('launching app for %s', testFilePath);
61
- await platformInstance.restartApp(appLaunchOptions);
97
+ await restartAppSession();
62
98
  logWait('launch request completed, waiting for bridge ready');
63
99
  },
64
100
  waitForReady: async (signal) => {
@@ -69,7 +105,7 @@ const waitForAppReady = async (base, testFilePath) => {
69
105
  // connection from a previous run would resolve the promise before startAttempt
70
106
  // even restarts the app — leaving bridge.connection null after the restart.
71
107
  await new Promise((resolve, reject) => {
72
- const onConnected = (_conn) => { cleanup(); resolve(); };
108
+ const onConnected = () => { cleanup(); resolve(); };
73
109
  const onAbort = () => { cleanup(); reject(signal.reason ?? new DOMException('Aborted', 'AbortError')); };
74
110
  const cleanup = () => {
75
111
  bridge.off('connected', onConnected);
@@ -85,15 +121,13 @@ const waitForAppReady = async (base, testFilePath) => {
85
121
  logWait('runtime ready received');
86
122
  },
87
123
  waitForCrash: async (signal) => {
88
- const watch = crashMonitor.watch(testFilePath, 'startup');
89
- watch.promise.catch(ignorePromiseRejection); // suppress unhandled-rejection when abort wins race
90
- try {
91
- logWait('waiting for crash or runtime ready');
92
- return await Promise.race([watch.promise, waitForAbort(signal)]);
93
- }
94
- finally {
95
- watch.cancel();
96
- }
124
+ logWait('waiting for crash or runtime ready');
125
+ return await waitForStartupCrash({
126
+ crashMonitor,
127
+ detectNativeCrashes,
128
+ testFilePath,
129
+ signal,
130
+ });
97
131
  },
98
132
  onAttemptStart: () => {
99
133
  logWait('beginning launch attempt for %s', testFilePath);
@@ -242,6 +276,11 @@ export const createHarnessSession = async (globalConfig, { lockManager = default
242
276
  const getCurrentRunId = () => currentRun?.runId;
243
277
  const clientLogCollector = createClientLogCollector();
244
278
  const context = { platform };
279
+ const crashArtifactWriter = createCrashArtifactWriter({
280
+ runnerName: platform.name,
281
+ platformId: platform.platformId,
282
+ rootDir: path.join(projectRoot, '.harness', 'crash-reports'),
283
+ });
245
284
  const bridge = await createHarnessBridge({
246
285
  noServer: true,
247
286
  timeout: runtimeConfig.bridgeTimeout,
@@ -268,6 +307,7 @@ export const createHarnessSession = async (globalConfig, { lockManager = default
268
307
  work: async (signal) => {
269
308
  return await import(platform.runner).then((module) => module.default(platform.config, runtimeConfig, {
270
309
  signal,
310
+ crashArtifactWriter,
271
311
  })).then((instance) => {
272
312
  sessionLogger.debug('platform runner initialized');
273
313
  return instance;
@@ -281,25 +321,38 @@ export const createHarnessSession = async (globalConfig, { lockManager = default
281
321
  await bridge.dispose();
282
322
  throw error;
283
323
  }
284
- const crashArtifactWriter = createCrashArtifactWriter({
285
- runnerName: platform.name,
286
- platformId: platform.platformId,
287
- });
288
- const appMonitor = platformInstance.createAppMonitor({ crashArtifactWriter });
289
324
  const appLaunchOptions = platform.config.appLaunchOptions;
290
- const crashMonitor = createCrashMonitor({ appMonitor, platformRunner: platformInstance });
325
+ let currentAppSession = null;
326
+ const crashMonitor = createCrashMonitor();
327
+ const disposeCurrentAppSession = async () => {
328
+ const session = currentAppSession;
329
+ currentAppSession = null;
330
+ crashMonitor.setAppSession(null);
331
+ if (session) {
332
+ await session.dispose();
333
+ }
334
+ };
335
+ const restartAppSession = async () => {
336
+ await crashMonitor.stop();
337
+ await disposeCurrentAppSession();
338
+ const session = await platformInstance.createAppSession(appLaunchOptions);
339
+ currentAppSession = session;
340
+ crashMonitor.setAppSession(session);
341
+ await crashMonitor.start();
342
+ return session;
343
+ };
291
344
  // Pre-build the options that are constant across all app-ready calls;
292
345
  // only testFilePath varies per call.
293
346
  const appReadyBaseOptions = {
294
347
  metroInstance,
295
348
  bridge,
296
- platformInstance,
297
349
  platformId: platform.platformId,
298
350
  bundleStartTimeout: runtimeConfig.bundleStartTimeout ?? 60000,
299
351
  readyTimeout: runtimeConfig.bridgeTimeout,
300
352
  maxAppRestarts: runtimeConfig.maxAppRestarts ?? 2,
301
353
  crashMonitor,
302
- appLaunchOptions,
354
+ detectNativeCrashes: runtimeConfig.detectNativeCrashes !== false,
355
+ restartAppSession,
303
356
  };
304
357
  // --- Event listeners ---
305
358
  const bridgeEventListener = buildBridgeHookScheduler(hooks, pluginManager, getCurrentRunId);
@@ -311,6 +364,17 @@ export const createHarnessSession = async (globalConfig, { lockManager = default
311
364
  };
312
365
  const flushClientLogs = () => clientLogCollector.flush();
313
366
  const clientLogListener = clientLogCollector.handleEvent;
367
+ const testRunnerEventListeners = new Set();
368
+ const onTestRunnerEvent = (event) => {
369
+ if (event.type === 'test-started' ||
370
+ event.type === 'test-finished' ||
371
+ event.type === 'suite-started' ||
372
+ event.type === 'suite-finished' ||
373
+ event.type === 'file-started' ||
374
+ event.type === 'file-finished') {
375
+ testRunnerEventListeners.forEach((listener) => listener(event));
376
+ }
377
+ };
314
378
  const onConnected = (conn) => {
315
379
  const runId = getCurrentRunId();
316
380
  if (!runId)
@@ -322,10 +386,14 @@ export const createHarnessSession = async (globalConfig, { lockManager = default
322
386
  if (!runId)
323
387
  return;
324
388
  hooks.schedule(() => pluginManager.callHook('runtime:disconnected', { runId, reason: 'bridge-disconnected' }));
389
+ if (runtimeConfig.detectNativeCrashes !== false) {
390
+ crashMonitor.handleBridgeDisconnect();
391
+ }
325
392
  };
326
393
  bridge.on('connected', onConnected);
327
394
  bridge.on('disconnected', onDisconnected);
328
395
  bridge.on('event', bridgeEventListener);
396
+ bridge.on('event', onTestRunnerEvent);
329
397
  metroInstance.events.addListener(onMetroEvent);
330
398
  if (runtimeConfig.forwardClientLogs) {
331
399
  metroInstance.events.addListener(clientLogListener);
@@ -365,10 +433,12 @@ export const createHarnessSession = async (globalConfig, { lockManager = default
365
433
  bridge.off('connected', onConnected);
366
434
  bridge.off('disconnected', onDisconnected);
367
435
  bridge.off('event', bridgeEventListener);
436
+ bridge.off('event', onTestRunnerEvent);
368
437
  const nativeCoverageConfig = runtimeConfig.coverage?.native?.ios;
369
438
  if (nativeCoverageConfig?.pods?.length && platformInstance.collectNativeCoverage) {
370
439
  try {
371
- await platformInstance.stopApp();
440
+ await crashMonitor.stop();
441
+ await disposeCurrentAppSession();
372
442
  const lcovPath = await platformInstance.collectNativeCoverage({
373
443
  pods: nativeCoverageConfig.pods,
374
444
  outputDir: projectRoot,
@@ -385,6 +455,7 @@ export const createHarnessSession = async (globalConfig, { lockManager = default
385
455
  try {
386
456
  await Promise.all([
387
457
  crashMonitor.dispose(),
458
+ disposeCurrentAppSession(),
388
459
  bridge.dispose(),
389
460
  platformInstance.dispose(),
390
461
  metroInstance.dispose(),
@@ -419,8 +490,6 @@ export const createHarnessSession = async (globalConfig, { lockManager = default
419
490
  try {
420
491
  await pluginManager.callHook('harness:before-creation', { appLaunchOptions });
421
492
  await hooks.drain();
422
- await appMonitor.start();
423
- sessionLogger.debug('app monitor started');
424
493
  await pluginManager.callHook('harness:before-run', { appLaunchOptions });
425
494
  await hooks.drain();
426
495
  }
@@ -436,9 +505,12 @@ export const createHarnessSession = async (globalConfig, { lockManager = default
436
505
  const ensureAppReady = async (testFilePath) => {
437
506
  await hooks.drain();
438
507
  sessionLogger.debug('ensuring app is ready for %s', testFilePath);
439
- if (crashMonitor.isAlive() && bridge.connection !== null && await platformInstance.isAppRunning()) {
440
- sessionLogger.debug('reusing existing ready app for %s', testFilePath);
441
- return;
508
+ if (crashMonitor.isAlive() && bridge.connection !== null && currentAppSession) {
509
+ const state = await currentAppSession.getState();
510
+ if (state.status === 'running') {
511
+ sessionLogger.debug('reusing existing ready app for %s', testFilePath);
512
+ return;
513
+ }
442
514
  }
443
515
  crashMonitor.reset();
444
516
  sessionLogger.debug('app not ready, waiting for launch and runtime readiness');
@@ -450,16 +522,16 @@ export const createHarnessSession = async (globalConfig, { lockManager = default
450
522
  await hooks.drain();
451
523
  await crashMonitor.stop();
452
524
  sessionLogger.debug('restarting app (testFile=%s mode=%s)', testFilePath ?? 'n/a', testFilePath ? 'stop-and-ensure-ready' : 'direct-restart');
525
+ await disposeCurrentAppSession();
453
526
  if (testFilePath) {
454
- await platformInstance.stopApp();
527
+ await ensureAppReady(testFilePath);
455
528
  }
456
529
  else {
457
- await platformInstance.restartApp(appLaunchOptions);
458
- }
459
- crashMonitor.reset();
460
- await crashMonitor.start();
461
- if (testFilePath) {
462
- await ensureAppReady(testFilePath);
530
+ const session = await platformInstance.createAppSession(appLaunchOptions);
531
+ currentAppSession = session;
532
+ crashMonitor.setAppSession(session);
533
+ crashMonitor.reset();
534
+ await crashMonitor.start();
463
535
  }
464
536
  await hooks.drain();
465
537
  sessionLogger.debug('restart completed');
@@ -470,7 +542,7 @@ export const createHarnessSession = async (globalConfig, { lockManager = default
470
542
  if (!conn)
471
543
  throw new Error('No active app connection');
472
544
  sessionLogger.debug('running test file on client: %s', testPath);
473
- if (!runtimeConfig.detectNativeCrashes) {
545
+ if (runtimeConfig.detectNativeCrashes === false) {
474
546
  const result = await conn.runTests(testPath, { ...options, runner: platform.runner });
475
547
  await hooks.drain();
476
548
  return result;
@@ -480,13 +552,30 @@ export const createHarnessSession = async (globalConfig, { lockManager = default
480
552
  // crash wins the race or cancel() is called after the test run wins.
481
553
  crashWatch.promise.catch(ignorePromiseRejection);
482
554
  try {
483
- const result = await Promise.race([
484
- conn.runTests(testPath, { ...options, runner: platform.runner }),
485
- crashWatch.promise,
486
- ]);
555
+ const testRunPromise = conn.runTests(testPath, {
556
+ ...options,
557
+ runner: platform.runner,
558
+ });
559
+ const result = await Promise.race([testRunPromise, crashWatch.promise]);
560
+ const bridgeDisconnected = await waitForBridgeDisconnectOrTimeout({
561
+ bridge,
562
+ connection: conn,
563
+ timeoutMs: TEST_RUN_BRIDGE_STABILITY_WAIT_MS,
564
+ });
565
+ if (bridgeDisconnected) {
566
+ return await crashWatch.promise;
567
+ }
487
568
  await hooks.drain();
488
569
  return result;
489
570
  }
571
+ catch (error) {
572
+ if (isBridgeDisconnectError(error)) {
573
+ const result = await crashWatch.promise;
574
+ await hooks.drain();
575
+ return result;
576
+ }
577
+ throw error;
578
+ }
490
579
  finally {
491
580
  crashWatch.cancel();
492
581
  }
@@ -494,6 +583,12 @@ export const createHarnessSession = async (globalConfig, { lockManager = default
494
583
  return {
495
584
  config: runtimeConfig,
496
585
  context,
586
+ onTestRunnerEvent: (listener) => {
587
+ testRunnerEventListeners.add(listener);
588
+ return () => {
589
+ testRunnerEventListeners.delete(listener);
590
+ };
591
+ },
497
592
  runTestFile,
498
593
  ensureAppReady,
499
594
  restartApp,
package/dist/index.d.ts CHANGED
@@ -1,8 +1,12 @@
1
- import type { CallbackTestRunnerInterface, Config, OnTestFailure, OnTestStart, OnTestSuccess, Test, TestRunnerOptions, TestWatcher } from 'jest-runner';
2
- export default class JestHarness implements CallbackTestRunnerInterface {
1
+ import type { Config, EmittingTestRunnerInterface, Test, TestEvents, TestRunnerOptions, TestWatcher } from 'jest-runner';
2
+ type TestEventListener<Name extends keyof TestEvents> = (eventData: TestEvents[Name]) => void | Promise<void>;
3
+ export default class JestHarness implements EmittingTestRunnerInterface {
3
4
  #private;
4
5
  readonly isSerial = true;
6
+ readonly supportsEventEmitters: true;
5
7
  constructor(globalConfig: Config.GlobalConfig);
6
- runTests(tests: Array<Test>, watcher: TestWatcher, onStart: OnTestStart, onResult: OnTestSuccess, onFailure: OnTestFailure, options: TestRunnerOptions): Promise<void>;
8
+ on<Name extends keyof TestEvents>(eventName: Name, listener: TestEventListener<Name>): () => void;
9
+ runTests(tests: Array<Test>, watcher: TestWatcher, options: TestRunnerOptions): Promise<void>;
7
10
  }
11
+ export {};
8
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,2BAA2B,EAC3B,MAAM,EACN,aAAa,EACb,WAAW,EACX,aAAa,EACb,IAAI,EACJ,iBAAiB,EACjB,WAAW,EACZ,MAAM,aAAa,CAAC;AAMrB,MAAM,CAAC,OAAO,OAAO,WAAY,YAAW,2BAA2B;;IACrE,QAAQ,CAAC,QAAQ,QAAQ;gBAKb,YAAY,EAAE,MAAM,CAAC,YAAY;IAIvC,QAAQ,CACZ,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAClB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,aAAa,EACvB,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC;CAiCjB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EACN,2BAA2B,EAC3B,IAAI,EACJ,UAAU,EACV,iBAAiB,EACjB,WAAW,EACZ,MAAM,aAAa,CAAC;AAMrB,KAAK,iBAAiB,CAAC,IAAI,SAAS,MAAM,UAAU,IAAI,CACtD,SAAS,EAAE,UAAU,CAAC,IAAI,CAAC,KACxB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B,MAAM,CAAC,OAAO,OAAO,WAAY,YAAW,2BAA2B;;IACrE,QAAQ,CAAC,QAAQ,QAAQ;IACzB,QAAQ,CAAC,qBAAqB,EAAG,IAAI,CAAU;gBAMnC,YAAY,EAAE,MAAM,CAAC,YAAY;IAI7C,EAAE,CAAC,IAAI,SAAS,MAAM,UAAU,EAC9B,SAAS,EAAE,IAAI,EACf,QAAQ,EAAE,iBAAiB,CAAC,IAAI,CAAC,GAChC,MAAM,IAAI;IAaP,QAAQ,CACZ,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAClB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC;CA6CjB"}
package/dist/index.js CHANGED
@@ -4,12 +4,25 @@ import { HarnessError } from '@react-native-harness/tools';
4
4
  import { getErrorMessage } from './logs.js';
5
5
  export default class JestHarness {
6
6
  isSerial = true;
7
+ supportsEventEmitters = true;
7
8
  #globalConfig;
8
9
  #session = null;
10
+ #listeners = new Map();
9
11
  constructor(globalConfig) {
10
12
  this.#globalConfig = globalConfig;
11
13
  }
12
- async runTests(tests, watcher, onStart, onResult, onFailure, options) {
14
+ on(eventName, listener) {
15
+ const listeners = this.#listeners.get(eventName) ?? new Set();
16
+ listeners.add(listener);
17
+ this.#listeners.set(eventName, listeners);
18
+ return () => {
19
+ listeners.delete(listener);
20
+ if (listeners.size === 0) {
21
+ this.#listeners.delete(eventName);
22
+ }
23
+ };
24
+ }
25
+ async runTests(tests, watcher, options) {
13
26
  if (!options.serial) {
14
27
  throw new Error('Parallel test running is not supported');
15
28
  }
@@ -18,7 +31,7 @@ export default class JestHarness {
18
31
  if (!this.#session) {
19
32
  this.#session = await createHarnessSession(this.#globalConfig);
20
33
  }
21
- await executeRun(this.#session, tests, watcher, onStart, onResult, onFailure, this.#globalConfig);
34
+ await executeRun(this.#session, tests, watcher, this.#emit, this.#globalConfig);
22
35
  }
23
36
  catch (error) {
24
37
  if (error instanceof HarnessError) {
@@ -33,4 +46,13 @@ export default class JestHarness {
33
46
  }
34
47
  }
35
48
  }
49
+ #emit = async (eventName, ...eventData) => {
50
+ const listeners = this.#listeners.get(eventName);
51
+ if (!listeners) {
52
+ return;
53
+ }
54
+ for (const listener of listeners) {
55
+ await listener(eventData);
56
+ }
57
+ };
36
58
  }
package/dist/run.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,UAAU,IAAI,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,KAAK,EACV,eAAe,IAAI,sBAAsB,EAE1C,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAuD3D,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,cAAc,CAAC;IACxB,aAAa,EAAE,UAAU,CAAC,aAAa,CAAC;IACxC,YAAY,EAAE,UAAU,CAAC,YAAY,CAAC;CACvC,KAAK,OAAO,CAAC;IACZ,UAAU,EAAE,cAAc,CAAC;IAC3B,aAAa,EAAE,sBAAsB,CAAC;IACtC,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CAAC;AAEH,eAAO,MAAM,kBAAkB,EAAE,kBAkFhC,CAAC"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,UAAU,IAAI,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,KAAK,EACV,eAAe,IAAI,sBAAsB,EAE1C,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAuD3D,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,cAAc,CAAC;IACxB,aAAa,EAAE,UAAU,CAAC,aAAa,CAAC;IACxC,YAAY,EAAE,UAAU,CAAC,YAAY,CAAC;CACvC,KAAK,OAAO,CAAC;IACZ,UAAU,EAAE,cAAc,CAAC;IAC3B,aAAa,EAAE,sBAAsB,CAAC;IACtC,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CAAC;AAEH,eAAO,MAAM,kBAAkB,EAAE,kBAmFhC,CAAC"}
package/dist/run.js CHANGED
@@ -67,6 +67,7 @@ export const runHarnessTestFile = async ({ testPath, globalConfig, projectConfig
67
67
  ? `${errorMessage}${codeFrame ? `\n\n${codeFrame.content}` : ''}`
68
68
  : undefined,
69
69
  title: test.name,
70
+ fullName: test.fullName,
70
71
  status: test.status,
71
72
  location: codeFrame?.location
72
73
  ? { column: codeFrame.location.column, line: codeFrame.location.row }
@@ -0,0 +1,5 @@
1
+ import type { TestResult as JestTestResult } from '@jest/test-result';
2
+ export declare const getHarnessTestFilePlatform: (testPath: string, knownPlatformIds: ReadonlySet<string>) => string | null;
3
+ export declare const shouldRunHarnessTestFile: (testPath: string, platformId: string, knownPlatformIds: ReadonlySet<string>) => boolean;
4
+ export declare const createPlatformSkippedTestResult: (testPath: string) => JestTestResult;
5
+ //# sourceMappingURL=test-file-platform-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-file-platform-filter.d.ts","sourceRoot":"","sources":["../src/test-file-platform-filter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,IAAI,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAMtE,eAAO,MAAM,0BAA0B,GACrC,UAAU,MAAM,EAChB,kBAAkB,WAAW,CAAC,MAAM,CAAC,KACpC,MAAM,GAAG,IAQX,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,UAAU,MAAM,EAChB,YAAY,MAAM,EAClB,kBAAkB,WAAW,CAAC,MAAM,CAAC,KACpC,OAGF,CAAC;AAEF,eAAO,MAAM,+BAA+B,GAC1C,UAAU,MAAM,KACf,cAyBF,CAAC"}