@react-native-harness/jest 1.0.0 → 1.1.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/harness.d.ts CHANGED
@@ -1,24 +1,24 @@
1
1
  import { BridgeServer } from '@react-native-harness/bridge/server';
2
2
  import { HarnessContext, TestExecutionOptions, TestSuiteResult } from '@react-native-harness/bridge';
3
- import { HarnessPlatform, HarnessPlatformRunner } from '@react-native-harness/platforms';
4
- import { Reporter } from '@react-native-harness/bundler-metro';
3
+ import { type AppLaunchOptions, HarnessPlatform, HarnessPlatformRunner } from '@react-native-harness/platforms';
5
4
  import { Config as HarnessConfig } from '@react-native-harness/config';
6
- import { CrashMonitor } from './crash-monitor.js';
5
+ import { type CrashSupervisor } from './crash-supervisor.js';
7
6
  export type HarnessRunTestsOptions = Exclude<TestExecutionOptions, 'platform'>;
8
7
  export type Harness = {
9
8
  context: HarnessContext;
10
9
  runTests: (path: string, options: HarnessRunTestsOptions) => Promise<TestSuiteResult>;
11
- restart: () => Promise<void>;
10
+ ensureAppReady: (testFilePath: string) => Promise<void>;
11
+ restart: (testFilePath?: string) => Promise<void>;
12
12
  dispose: () => Promise<void>;
13
- crashMonitor: CrashMonitor;
13
+ crashSupervisor: CrashSupervisor;
14
14
  };
15
15
  export declare const waitForAppReady: (options: {
16
- metroEvents: Reporter;
17
16
  serverBridge: BridgeServer;
18
17
  platformInstance: HarnessPlatformRunner;
19
- bundleStartTimeout: number;
20
- maxRestarts: number;
21
- signal: AbortSignal;
18
+ bridgeTimeout: number;
19
+ testFilePath: string;
20
+ crashSupervisor: CrashSupervisor;
21
+ appLaunchOptions?: AppLaunchOptions;
22
22
  }) => Promise<void>;
23
23
  export declare const getHarness: (config: HarnessConfig, platform: HarnessPlatform, projectRoot: string) => Promise<Harness>;
24
24
  //# sourceMappingURL=harness.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../src/harness.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,YAAY,EACb,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,eAAe,EAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,eAAe,EACf,qBAAqB,EACtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAGL,QAAQ,EAET,MAAM,qCAAqC,CAAC;AAE7C,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAsB,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAItE,MAAM,MAAM,sBAAsB,GAAG,OAAO,CAAC,oBAAoB,EAAE,UAAU,CAAC,CAAC;AAE/E,MAAM,MAAM,OAAO,GAAG;IACpB,OAAO,EAAE,cAAc,CAAC;IACxB,QAAQ,EAAE,CACR,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,sBAAsB,KAC5B,OAAO,CAAC,eAAe,CAAC,CAAC;IAC9B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,YAAY,EAAE,YAAY,CAAC;CAC5B,CAAC;AAEF,eAAO,MAAM,eAAe,GAAU,SAAS;IAC7C,WAAW,EAAE,QAAQ,CAAC;IACtB,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,qBAAqB,CAAC;IACxC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,WAAW,CAAC;CACrB,KAAG,OAAO,CAAC,IAAI,CAgFf,CAAC;AA4GF,eAAO,MAAM,UAAU,GACrB,QAAQ,aAAa,EACrB,UAAU,eAAe,EACzB,aAAa,MAAM,KAClB,OAAO,CAAC,OAAO,CAkBjB,CAAC"}
1
+ {"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../src/harness.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,YAAY,EACb,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,eAAe,EAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,KAAK,gBAAgB,EACrB,eAAe,EACf,qBAAqB,EACtB,MAAM,iCAAiC,CAAC;AAOzC,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AAI/B,MAAM,MAAM,sBAAsB,GAAG,OAAO,CAAC,oBAAoB,EAAE,UAAU,CAAC,CAAC;AAE/E,MAAM,MAAM,OAAO,GAAG;IACpB,OAAO,EAAE,cAAc,CAAC;IACxB,QAAQ,EAAE,CACR,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,sBAAsB,KAC5B,OAAO,CAAC,eAAe,CAAC,CAAC;IAC9B,cAAc,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,OAAO,EAAE,CAAC,YAAY,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,eAAe,EAAE,eAAe,CAAC;CAClC,CAAC;AAEF,eAAO,MAAM,eAAe,GAAU,SAAS;IAC7C,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,qBAAqB,CAAC;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,eAAe,CAAC;IACjC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC,KAAG,OAAO,CAAC,IAAI,CA+Cf,CAAC;AAyIF,eAAO,MAAM,UAAU,GACrB,QAAQ,aAAa,EACrB,UAAU,eAAe,EACzB,aAAa,MAAM,KAClB,OAAO,CAAC,OAAO,CAkBjB,CAAC"}
package/dist/harness.js CHANGED
@@ -1,70 +1,42 @@
1
1
  import { getBridgeServer, } from '@react-native-harness/bridge/server';
2
2
  import { getMetroInstance, prewarmMetroBundle, } from '@react-native-harness/bundler-metro';
3
- import { InitializationTimeoutError, MaxAppRestartsError } from './errors.js';
4
- import { createCrashMonitor } from './crash-monitor.js';
3
+ import { createCrashArtifactWriter } from '@react-native-harness/tools';
4
+ import { InitializationTimeoutError } from './errors.js';
5
+ import { createCrashSupervisor, } from './crash-supervisor.js';
5
6
  import { createClientLogListener } from './client-log-handler.js';
6
7
  import { logMetroPrewarmCompleted } from './logs.js';
7
8
  export const waitForAppReady = async (options) => {
8
- const { metroEvents, serverBridge, platformInstance, bundleStartTimeout, maxRestarts, signal, } = options;
9
- let restartCount = 0;
10
- let isBundling = false;
11
- let bundleTimeoutId = null;
12
- const clearBundleTimeout = () => {
13
- if (bundleTimeoutId) {
14
- clearTimeout(bundleTimeoutId);
15
- bundleTimeoutId = null;
16
- }
17
- };
9
+ const { serverBridge, platformInstance, bridgeTimeout, testFilePath, crashSupervisor, appLaunchOptions, } = options;
10
+ const signal = AbortSignal.timeout(bridgeTimeout);
18
11
  return new Promise((resolve, reject) => {
19
- // Handle abort signal
20
- signal.addEventListener('abort', () => {
21
- clearBundleTimeout();
22
- reject(new DOMException('The operation was aborted', 'AbortError'));
23
- });
24
- // Start/restart the bundle timeout
25
- const startBundleTimeout = () => {
26
- clearBundleTimeout();
27
- bundleTimeoutId = setTimeout(() => {
28
- if (isBundling)
29
- return; // Don't restart while bundling
30
- if (restartCount >= maxRestarts) {
31
- cleanup();
32
- reject(new MaxAppRestartsError(restartCount + 1));
33
- return;
34
- }
35
- restartCount++;
36
- platformInstance.restartApp().catch(reject);
37
- startBundleTimeout(); // Reset timer for next attempt
38
- }, bundleStartTimeout);
12
+ const launchApp = async () => {
13
+ crashSupervisor.beginLaunch(testFilePath);
14
+ await platformInstance.restartApp(appLaunchOptions);
39
15
  };
40
- // Metro event listener
41
- const onMetroEvent = (event) => {
42
- if (event.type === 'bundle_build_started') {
43
- isBundling = true;
44
- clearBundleTimeout(); // Cancel restart timer while bundling
45
- }
46
- else if (event.type === 'bundle_build_done' ||
47
- event.type === 'bundle_build_failed') {
48
- isBundling = false;
49
- startBundleTimeout(); // Reset timer after bundle completes
50
- }
51
- };
52
- // Bridge ready listener
53
16
  const onReady = () => {
17
+ crashSupervisor.markReady();
54
18
  cleanup();
55
19
  resolve();
56
20
  };
21
+ const onAbort = () => {
22
+ cleanup();
23
+ reject(new DOMException('The operation was aborted', 'AbortError'));
24
+ };
57
25
  const cleanup = () => {
58
- clearBundleTimeout();
59
- metroEvents.removeListener(onMetroEvent);
60
26
  serverBridge.off('ready', onReady);
27
+ signal.removeEventListener('abort', onAbort);
28
+ crashSupervisor.cancelCrashWaiters();
61
29
  };
62
- // Setup listeners
63
- metroEvents.addListener(onMetroEvent);
30
+ signal.addEventListener('abort', onAbort);
64
31
  serverBridge.once('ready', onReady);
65
- // Start the app and timeout
66
- platformInstance.restartApp().catch(reject);
67
- startBundleTimeout();
32
+ void crashSupervisor.waitForCrash(testFilePath).catch((error) => {
33
+ cleanup();
34
+ reject(error);
35
+ });
36
+ void launchApp().catch((error) => {
37
+ cleanup();
38
+ reject(error);
39
+ });
68
40
  });
69
41
  };
70
42
  const getHarnessInternal = async (config, platform, projectRoot, signal) => {
@@ -80,8 +52,20 @@ const getHarnessInternal = async (config, platform, projectRoot, signal) => {
80
52
  context,
81
53
  }),
82
54
  ]);
83
- // Forward client logs to console if enabled
55
+ const crashArtifactWriter = createCrashArtifactWriter({
56
+ runnerName: platform.name,
57
+ platformId: platform.platformId,
58
+ });
59
+ const appMonitor = platformInstance.createAppMonitor({
60
+ crashArtifactWriter,
61
+ });
62
+ const appLaunchOptions = platform.config.appLaunchOptions;
84
63
  const clientLogListener = createClientLogListener();
64
+ const crashSupervisor = createCrashSupervisor({
65
+ appMonitor,
66
+ platformRunner: platformInstance,
67
+ });
68
+ serverBridge.on('ready', crashSupervisor.markReady);
85
69
  if (config.forwardClientLogs) {
86
70
  metroInstance.events.addListener(clientLogListener);
87
71
  }
@@ -89,17 +73,14 @@ const getHarnessInternal = async (config, platform, projectRoot, signal) => {
89
73
  if (config.forwardClientLogs) {
90
74
  metroInstance.events.removeListener(clientLogListener);
91
75
  }
76
+ serverBridge.off('ready', crashSupervisor.markReady);
92
77
  await Promise.all([
78
+ crashSupervisor.dispose(),
93
79
  serverBridge.dispose(),
94
80
  platformInstance.dispose(),
95
81
  metroInstance.dispose(),
96
82
  ]);
97
83
  };
98
- const crashMonitor = createCrashMonitor({
99
- interval: config.crashDetectionInterval,
100
- platformRunner: platformInstance,
101
- bridgeServer: serverBridge,
102
- });
103
84
  if (signal.aborted) {
104
85
  await dispose();
105
86
  throw new DOMException('The operation was aborted', 'AbortError');
@@ -114,27 +95,41 @@ const getHarnessInternal = async (config, platform, projectRoot, signal) => {
114
95
  signal,
115
96
  });
116
97
  logMetroPrewarmCompleted(platform);
117
- await waitForAppReady({
118
- metroEvents: metroInstance.events,
119
- serverBridge,
120
- platformInstance: platformInstance,
121
- bundleStartTimeout: config.bundleStartTimeout,
122
- maxRestarts: config.maxAppRestarts,
123
- signal,
124
- });
98
+ await appMonitor.start();
125
99
  }
126
100
  catch (error) {
127
101
  await dispose();
128
102
  throw error;
129
103
  }
130
- const restart = () => new Promise((resolve, reject) => {
131
- crashMonitor.markIntentionalRestart();
132
- serverBridge.once('ready', () => {
133
- crashMonitor.clearIntentionalRestart();
134
- resolve();
104
+ const ensureAppReady = async (testFilePath) => {
105
+ crashSupervisor.setActiveTestFile(testFilePath);
106
+ if (crashSupervisor.isReady() && (await platformInstance.isAppRunning())) {
107
+ return;
108
+ }
109
+ crashSupervisor.reset();
110
+ await waitForAppReady({
111
+ serverBridge,
112
+ platformInstance: platformInstance,
113
+ bridgeTimeout: config.bridgeTimeout,
114
+ testFilePath,
115
+ crashSupervisor,
116
+ appLaunchOptions,
135
117
  });
136
- platformInstance.restartApp().catch(reject);
137
- });
118
+ };
119
+ const restart = async (testFilePath) => {
120
+ await crashSupervisor.stop();
121
+ if (testFilePath) {
122
+ await platformInstance.stopApp();
123
+ }
124
+ else {
125
+ await platformInstance.restartApp(appLaunchOptions);
126
+ }
127
+ crashSupervisor.reset();
128
+ await crashSupervisor.start();
129
+ if (testFilePath) {
130
+ await ensureAppReady(testFilePath);
131
+ }
132
+ };
138
133
  return {
139
134
  context,
140
135
  runTests: async (path, options) => {
@@ -147,9 +142,10 @@ const getHarnessInternal = async (config, platform, projectRoot, signal) => {
147
142
  runner: platform.runner,
148
143
  });
149
144
  },
145
+ ensureAppReady,
150
146
  restart,
151
147
  dispose,
152
- crashMonitor,
148
+ crashSupervisor,
153
149
  };
154
150
  };
155
151
  export const getHarness = async (config, platform, projectRoot) => {
@@ -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;AAGrB,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAe5C,MAAM,CAAC,OAAO,OAAO,WAAY,YAAW,2BAA2B;;IACrE,QAAQ,CAAC,QAAQ,QAAQ;gBAIb,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;IAkCV,oBAAoB,CACxB,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAClB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,aAAa,EAC5B,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,aAAa,EACvB,SAAS,EAAE,aAAa,GACvB,OAAO,CAAC,IAAI,CAAC;CAkFjB"}
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;AAGrB,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAe5C,MAAM,CAAC,OAAO,OAAO,WAAY,YAAW,2BAA2B;;IACrE,QAAQ,CAAC,QAAQ,QAAQ;gBAIb,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;IAkCV,oBAAoB,CACxB,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAClB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,aAAa,EAC5B,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,aAAa,EACvB,SAAS,EAAE,aAAa,GACvB,OAAO,CAAC,IAAI,CAAC;CAgFjB"}
package/dist/index.js CHANGED
@@ -51,11 +51,12 @@ export default class JestHarness {
51
51
  }
52
52
  if (harnessConfig.resetEnvironmentBetweenTestFiles &&
53
53
  !isFirstTest) {
54
- await harness.restart();
54
+ await harness.restart(test.path);
55
55
  }
56
56
  isFirstTest = false;
57
57
  return onStart(test).then(async () => {
58
58
  if (!harnessConfig.detectNativeCrashes) {
59
+ await harness.ensureAppReady(test.path);
59
60
  return runHarnessTestFile({
60
61
  testPath: test.path,
61
62
  harness,
@@ -63,8 +64,9 @@ export default class JestHarness {
63
64
  projectConfig: test.context.config,
64
65
  });
65
66
  }
66
- // Start crash monitoring
67
- const crashPromise = harness.crashMonitor.startMonitoring(test.path);
67
+ await harness.ensureAppReady(test.path);
68
+ harness.crashSupervisor.beginTestRun(test.path);
69
+ const crashPromise = harness.crashSupervisor.waitForCrash(test.path);
68
70
  try {
69
71
  const result = await Promise.race([
70
72
  runHarnessTestFile({
@@ -78,19 +80,18 @@ export default class JestHarness {
78
80
  return result;
79
81
  }
80
82
  finally {
81
- harness.crashMonitor.stopMonitoring();
83
+ harness.crashSupervisor.cancelCrashWaiters();
82
84
  }
83
85
  });
84
86
  })
85
87
  .then((result) => onResult(test, result))
86
88
  .catch(async (err) => {
87
89
  if (err instanceof NativeCrashError) {
90
+ harness.crashSupervisor.reset();
88
91
  onFailure(test, {
89
92
  message: err.message,
90
93
  stack: '',
91
94
  });
92
- // Restart the app for the next test file
93
- await harness.restart();
94
95
  return;
95
96
  }
96
97
  if (err instanceof DeviceNotRespondingError) {
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,aAAa,CAAC;AAqCxD,eAAO,MAAM,KAAK,GAAU,cAAc,UAAU,CAAC,YAAY,kBAiDhE,CAAC"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,aAAa,CAAC;AAqCxD,eAAO,MAAM,KAAK,GAAU,cAAc,UAAU,CAAC,YAAY,kBA4ChE,CAAC"}
package/dist/setup.js CHANGED
@@ -26,10 +26,6 @@ export const setup = async (globalConfig) => {
26
26
  if (global.HARNESS) {
27
27
  // Do not setup again if HARNESS is already initialized
28
28
  // This is useful when running tests in watch mode
29
- if (harnessConfig.resetEnvironmentBetweenTestFiles) {
30
- // In watch mode, we want to restart the environment before each test run
31
- await global.HARNESS.restart();
32
- }
33
29
  return;
34
30
  }
35
31
  // Gracefully dispose the Harness when the process exits.