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

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 (80) hide show
  1. package/dist/__tests__/crash-supervisor.test.d.ts +2 -0
  2. package/dist/__tests__/crash-supervisor.test.d.ts.map +1 -0
  3. package/dist/__tests__/crash-supervisor.test.js +151 -0
  4. package/dist/__tests__/errors.test.d.ts +2 -0
  5. package/dist/__tests__/errors.test.d.ts.map +1 -0
  6. package/dist/__tests__/errors.test.js +54 -0
  7. package/dist/__tests__/harness-cache.test.d.ts +2 -0
  8. package/dist/__tests__/harness-cache.test.d.ts.map +1 -0
  9. package/dist/__tests__/harness-cache.test.js +51 -0
  10. package/dist/__tests__/harness.test.d.ts +2 -0
  11. package/dist/__tests__/harness.test.d.ts.map +1 -0
  12. package/dist/__tests__/harness.test.js +399 -0
  13. package/dist/__tests__/index.test.d.ts +2 -0
  14. package/dist/__tests__/index.test.d.ts.map +1 -0
  15. package/dist/__tests__/index.test.js +42 -0
  16. package/dist/cli-args.d.ts +1 -0
  17. package/dist/cli-args.d.ts.map +1 -1
  18. package/dist/cli-args.js +5 -0
  19. package/dist/crash-supervisor.d.ts +21 -0
  20. package/dist/crash-supervisor.d.ts.map +1 -0
  21. package/dist/crash-supervisor.js +210 -0
  22. package/dist/errors.d.ts +9 -4
  23. package/dist/errors.d.ts.map +1 -1
  24. package/dist/errors.js +43 -8
  25. package/dist/harness.d.ts +30 -8
  26. package/dist/harness.d.ts.map +1 -1
  27. package/dist/harness.js +440 -98
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +205 -57
  30. package/dist/logs.d.ts +1 -0
  31. package/dist/logs.d.ts.map +1 -1
  32. package/dist/logs.js +3 -0
  33. package/dist/run.d.ts +7 -1
  34. package/dist/run.d.ts.map +1 -1
  35. package/dist/run.js +13 -8
  36. package/dist/setup.d.ts.map +1 -1
  37. package/dist/setup.js +18 -6
  38. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  39. package/package.json +7 -6
  40. package/src/__tests__/crash-supervisor.test.ts +200 -0
  41. package/src/__tests__/errors.test.ts +70 -0
  42. package/src/__tests__/harness-cache.test.ts +74 -0
  43. package/src/__tests__/harness.test.ts +547 -0
  44. package/src/__tests__/index.test.ts +61 -0
  45. package/src/cli-args.ts +6 -0
  46. package/src/crash-supervisor.ts +321 -0
  47. package/src/env.d.ts +1 -1
  48. package/src/errors.ts +68 -8
  49. package/src/harness.ts +566 -120
  50. package/src/index.ts +276 -66
  51. package/src/logs.ts +4 -0
  52. package/src/run.ts +20 -10
  53. package/src/setup.ts +22 -6
  54. package/tsconfig.json +3 -3
  55. package/tsconfig.lib.json +6 -6
  56. package/dist/cli-args.test.d.ts +0 -2
  57. package/dist/cli-args.test.d.ts.map +0 -1
  58. package/dist/cli-args.test.js +0 -1
  59. package/dist/crash-monitor.d.ts +0 -16
  60. package/dist/crash-monitor.d.ts.map +0 -1
  61. package/dist/crash-monitor.js +0 -75
  62. package/dist/event-mapper.d.ts +0 -16
  63. package/dist/event-mapper.d.ts.map +0 -1
  64. package/dist/event-mapper.js +0 -120
  65. package/dist/global-setup.d.ts +0 -3
  66. package/dist/global-setup.d.ts.map +0 -1
  67. package/dist/global-setup.js +0 -60
  68. package/dist/global-teardown.d.ts +0 -3
  69. package/dist/global-teardown.d.ts.map +0 -1
  70. package/dist/global-teardown.js +0 -11
  71. package/dist/harness-reporter.d.ts +0 -8
  72. package/dist/harness-reporter.d.ts.map +0 -1
  73. package/dist/harness-reporter.js +0 -12
  74. package/dist/jest-options.d.ts +0 -2
  75. package/dist/jest-options.d.ts.map +0 -1
  76. package/dist/jest-options.js +0 -1
  77. package/dist/types.d.ts +0 -17
  78. package/dist/types.d.ts.map +0 -1
  79. package/dist/types.js +0 -1
  80. package/src/crash-monitor.ts +0 -109
package/dist/harness.js CHANGED
@@ -1,159 +1,501 @@
1
1
  import { getBridgeServer, } from '@react-native-harness/bridge/server';
2
- import { getMetroInstance, prewarmMetroBundle, } from '@react-native-harness/bundler-metro';
3
- import { InitializationTimeoutError, MaxAppRestartsError } from './errors.js';
4
- import { createCrashMonitor } from './crash-monitor.js';
2
+ import { getMetroInstance, isMetroCacheReusable, waitForMetroBackedAppReady, } from '@react-native-harness/bundler-metro';
3
+ import { createHarnessPluginManager, } from '@react-native-harness/plugins';
4
+ import { logger, createCrashArtifactWriter } from '@react-native-harness/tools';
5
+ import { InitializationTimeoutError } from './errors.js';
6
+ import { createCrashSupervisor, } from './crash-supervisor.js';
5
7
  import { createClientLogListener } from './client-log-handler.js';
6
- import { logMetroPrewarmCompleted } from './logs.js';
8
+ import path from 'node:path';
9
+ import { logMetroCacheReused } from './logs.js';
10
+ const harnessLogger = logger.child('runtime');
11
+ export const maybeLogMetroCacheReuse = (config, platform, projectRoot) => {
12
+ if (config.unstable__enableMetroCache &&
13
+ isMetroCacheReusable(projectRoot)) {
14
+ logMetroCacheReused(platform);
15
+ }
16
+ };
17
+ const createAbortError = () => new DOMException('The operation was aborted', 'AbortError');
18
+ const waitForAbort = (signal) => {
19
+ if (signal.aborted) {
20
+ return Promise.reject(signal.reason ?? createAbortError());
21
+ }
22
+ return new Promise((_, reject) => {
23
+ signal.addEventListener('abort', () => {
24
+ reject(signal.reason ?? createAbortError());
25
+ }, { once: true });
26
+ });
27
+ };
7
28
  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
- }
29
+ const { metroInstance, serverBridge, platformInstance, platformId, bundleStartTimeout, readyTimeout, maxAppRestarts, testFilePath, crashSupervisor, appLaunchOptions, launchApp = () => platformInstance.restartApp(appLaunchOptions), } = options;
30
+ const signal = options.signal ?? new AbortController().signal;
31
+ const logWait = (message, ...args) => {
32
+ harnessLogger.debug(`waitForAppReady: ${message}`, ...args);
17
33
  };
18
- 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);
39
- };
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
34
+ return await waitForMetroBackedAppReady({
35
+ metro: metroInstance,
36
+ platformId,
37
+ bundleStartTimeout,
38
+ readyTimeout,
39
+ maxAppRestarts,
40
+ signal,
41
+ startAttempt: async () => {
42
+ logWait('launching app for %s', testFilePath);
43
+ await launchApp();
44
+ logWait('launch request completed, waiting for bridge ready');
45
+ },
46
+ waitForReady: async (signal) => {
47
+ logWait('waiting for runtime ready');
48
+ return await Promise.race([
49
+ new Promise((resolve) => {
50
+ const onReady = () => {
51
+ cleanup();
52
+ crashSupervisor.markReady();
53
+ logWait('runtime ready received');
54
+ resolve();
55
+ };
56
+ const onAbort = () => {
57
+ cleanup();
58
+ };
59
+ const cleanup = () => {
60
+ serverBridge.off('ready', onReady);
61
+ signal.removeEventListener('abort', onAbort);
62
+ };
63
+ serverBridge.on('ready', onReady);
64
+ signal.addEventListener('abort', onAbort, { once: true });
65
+ }),
66
+ waitForAbort(signal),
67
+ ]);
68
+ },
69
+ waitForCrash: async (signal) => {
70
+ try {
71
+ logWait('waiting for crash or runtime ready');
72
+ return await Promise.race([
73
+ crashSupervisor.waitForCrash(testFilePath),
74
+ waitForAbort(signal),
75
+ ]);
45
76
  }
46
- else if (event.type === 'bundle_build_done' ||
47
- event.type === 'bundle_build_failed') {
48
- isBundling = false;
49
- startBundleTimeout(); // Reset timer after bundle completes
77
+ finally {
78
+ crashSupervisor.cancelCrashWaiters();
50
79
  }
51
- };
52
- // Bridge ready listener
53
- const onReady = () => {
54
- cleanup();
55
- resolve();
56
- };
57
- const cleanup = () => {
58
- clearBundleTimeout();
59
- metroEvents.removeListener(onMetroEvent);
60
- serverBridge.off('ready', onReady);
61
- };
62
- // Setup listeners
63
- metroEvents.addListener(onMetroEvent);
64
- serverBridge.once('ready', onReady);
65
- // Start the app and timeout
66
- platformInstance.restartApp().catch(reject);
67
- startBundleTimeout();
80
+ },
81
+ onAttemptStart: () => {
82
+ logWait('beginning launch attempt for %s', testFilePath);
83
+ crashSupervisor.beginLaunch(testFilePath);
84
+ },
85
+ onAttemptReset: () => {
86
+ logWait('resetting launch attempt state');
87
+ crashSupervisor.cancelCrashWaiters();
88
+ },
68
89
  });
69
90
  };
70
91
  const getHarnessInternal = async (config, platform, projectRoot, signal) => {
71
92
  const context = {
72
93
  platform,
73
94
  };
95
+ harnessLogger.debug('creating Harness internals for runner=%s platform=%s', platform.name, platform.platformId);
96
+ maybeLogMetroCacheReuse(config, platform, projectRoot);
97
+ const pluginAbortController = new AbortController();
98
+ const pluginManager = createHarnessPluginManager({
99
+ plugins: (config.plugins ?? []),
100
+ projectRoot,
101
+ config,
102
+ runner: platform,
103
+ abortSignal: pluginAbortController.signal,
104
+ });
105
+ let currentRun = null;
106
+ let activeTestFilePath;
107
+ const pendingHookPromises = new Set();
108
+ let pendingHookError;
109
+ const getCurrentRunId = () => currentRun?.runId;
110
+ const toRelativeTestFilePath = (testFilePath) => testFilePath == null ? undefined : path.relative(projectRoot, testFilePath);
111
+ const setActiveTestFilePath = (testFilePath) => {
112
+ activeTestFilePath = toRelativeTestFilePath(testFilePath);
113
+ };
114
+ const flushPendingHooks = async () => {
115
+ if (pendingHookPromises.size > 0) {
116
+ await Promise.allSettled([...pendingHookPromises]);
117
+ }
118
+ if (pendingHookError !== undefined) {
119
+ const error = pendingHookError;
120
+ pendingHookError = undefined;
121
+ throw error;
122
+ }
123
+ };
124
+ const trackHook = (promise) => {
125
+ const trackedPromise = promise
126
+ .catch((error) => {
127
+ pendingHookError ??= error;
128
+ })
129
+ .finally(() => {
130
+ pendingHookPromises.delete(trackedPromise);
131
+ });
132
+ pendingHookPromises.add(trackedPromise);
133
+ };
134
+ const scheduleHook = (name, payload) => {
135
+ trackHook(pluginManager.callHook(name, payload));
136
+ };
137
+ harnessLogger.debug('starting Metro, platform runner, and bridge initialization');
74
138
  const [metroInstance, platformInstance, serverBridge] = await Promise.all([
75
- getMetroInstance({ projectRoot, harnessConfig: config }, signal),
76
- import(platform.runner).then((module) => module.default(platform.config, config)),
139
+ getMetroInstance({ projectRoot, harnessConfig: config }, signal).then((instance) => {
140
+ harnessLogger.debug('Metro initialized');
141
+ return instance;
142
+ }),
143
+ import(platform.runner)
144
+ .then((module) => module.default(platform.config, config))
145
+ .then((instance) => {
146
+ harnessLogger.debug('platform runner initialized');
147
+ return instance;
148
+ }),
77
149
  getBridgeServer({
78
150
  port: config.webSocketPort,
79
151
  timeout: config.bridgeTimeout,
80
152
  context,
153
+ }).then((bridge) => {
154
+ harnessLogger.debug('bridge server initialized on port %d', config.webSocketPort);
155
+ return bridge;
81
156
  }),
82
157
  ]);
83
- // Forward client logs to console if enabled
158
+ const crashArtifactWriter = createCrashArtifactWriter({
159
+ runnerName: platform.name,
160
+ platformId: platform.platformId,
161
+ });
162
+ const appMonitor = platformInstance.createAppMonitor({
163
+ crashArtifactWriter,
164
+ });
165
+ const appLaunchOptions = platform.config.appLaunchOptions;
84
166
  const clientLogListener = createClientLogListener();
167
+ const bridgeEventListener = (event) => {
168
+ const runId = getCurrentRunId();
169
+ if (!runId) {
170
+ return;
171
+ }
172
+ switch (event.type) {
173
+ case 'collection-started':
174
+ scheduleHook('collection:started', {
175
+ runId,
176
+ file: event.file,
177
+ });
178
+ break;
179
+ case 'collection-finished':
180
+ scheduleHook('collection:finished', {
181
+ runId,
182
+ file: event.file,
183
+ duration: event.duration,
184
+ totalTests: event.totalTests,
185
+ });
186
+ break;
187
+ case 'suite-started':
188
+ scheduleHook('suite:started', {
189
+ runId,
190
+ file: event.file,
191
+ name: event.name,
192
+ });
193
+ break;
194
+ case 'suite-finished':
195
+ scheduleHook('suite:finished', {
196
+ runId,
197
+ file: event.file,
198
+ name: event.name,
199
+ duration: event.duration,
200
+ status: event.status,
201
+ error: event.error,
202
+ });
203
+ break;
204
+ case 'test-started':
205
+ scheduleHook('test:started', {
206
+ runId,
207
+ file: event.file,
208
+ suite: event.suite,
209
+ name: event.name,
210
+ });
211
+ break;
212
+ case 'test-finished':
213
+ scheduleHook('test:finished', {
214
+ runId,
215
+ file: event.file,
216
+ suite: event.suite,
217
+ name: event.name,
218
+ duration: event.duration,
219
+ status: event.status,
220
+ error: event.error,
221
+ });
222
+ break;
223
+ case 'module-bundling-started':
224
+ scheduleHook('metro:bundle-started', {
225
+ runId,
226
+ target: 'module',
227
+ file: event.file,
228
+ });
229
+ break;
230
+ case 'module-bundling-finished':
231
+ scheduleHook('metro:bundle-finished', {
232
+ runId,
233
+ target: 'module',
234
+ file: event.file,
235
+ duration: event.duration,
236
+ });
237
+ break;
238
+ case 'module-bundling-failed':
239
+ scheduleHook('metro:bundle-failed', {
240
+ runId,
241
+ target: 'module',
242
+ file: event.file,
243
+ duration: event.duration,
244
+ error: event.error,
245
+ });
246
+ break;
247
+ case 'setup-file-bundling-started':
248
+ scheduleHook('metro:bundle-started', {
249
+ runId,
250
+ target: 'setupFile',
251
+ file: event.file,
252
+ setupType: event.setupType,
253
+ });
254
+ break;
255
+ case 'setup-file-bundling-finished':
256
+ scheduleHook('metro:bundle-finished', {
257
+ runId,
258
+ target: 'setupFile',
259
+ file: event.file,
260
+ setupType: event.setupType,
261
+ duration: event.duration,
262
+ });
263
+ break;
264
+ case 'setup-file-bundling-failed':
265
+ scheduleHook('metro:bundle-failed', {
266
+ runId,
267
+ target: 'setupFile',
268
+ file: event.file,
269
+ setupType: event.setupType,
270
+ duration: event.duration,
271
+ error: event.error,
272
+ });
273
+ break;
274
+ }
275
+ };
276
+ const onMetroEvent = (event) => {
277
+ const runId = getCurrentRunId();
278
+ if (runId && event.type === 'client_log') {
279
+ scheduleHook('metro:client-log', {
280
+ runId,
281
+ level: event.level,
282
+ data: event.data,
283
+ });
284
+ }
285
+ };
286
+ const crashSupervisor = createCrashSupervisor({
287
+ appMonitor,
288
+ platformRunner: platformInstance,
289
+ });
290
+ const onReady = (device) => {
291
+ crashSupervisor.markReady();
292
+ const runId = getCurrentRunId();
293
+ if (!runId) {
294
+ return;
295
+ }
296
+ scheduleHook('runtime:ready', {
297
+ runId,
298
+ device,
299
+ });
300
+ };
301
+ const onDisconnect = () => {
302
+ const runId = getCurrentRunId();
303
+ if (!runId) {
304
+ return;
305
+ }
306
+ scheduleHook('runtime:disconnected', {
307
+ runId,
308
+ reason: 'bridge-disconnected',
309
+ });
310
+ };
311
+ const onAppMonitorEvent = (event) => {
312
+ const runId = getCurrentRunId();
313
+ if (!runId) {
314
+ return;
315
+ }
316
+ if (event.type === 'app_started') {
317
+ scheduleHook('app:started', {
318
+ runId,
319
+ testFile: activeTestFilePath,
320
+ pid: event.pid,
321
+ source: event.source,
322
+ line: event.line,
323
+ });
324
+ return;
325
+ }
326
+ if (event.type === 'app_exited') {
327
+ scheduleHook('app:exited', {
328
+ runId,
329
+ testFile: activeTestFilePath,
330
+ pid: event.pid,
331
+ source: event.source,
332
+ line: event.line,
333
+ isConfirmed: event.isConfirmed,
334
+ crashDetails: event.crashDetails,
335
+ });
336
+ return;
337
+ }
338
+ if (event.type === 'possible_crash') {
339
+ scheduleHook('app:possible-crash', {
340
+ runId,
341
+ testFile: activeTestFilePath,
342
+ pid: event.pid,
343
+ source: event.source,
344
+ line: event.line,
345
+ isConfirmed: event.isConfirmed,
346
+ crashDetails: event.crashDetails,
347
+ });
348
+ }
349
+ };
350
+ serverBridge.on('ready', onReady);
351
+ serverBridge.on('disconnect', onDisconnect);
352
+ serverBridge.on('event', bridgeEventListener);
353
+ metroInstance.events.addListener(onMetroEvent);
354
+ appMonitor.addListener(onAppMonitorEvent);
355
+ harnessLogger.debug('registered runtime, bridge, and Metro listeners');
85
356
  if (config.forwardClientLogs) {
86
357
  metroInstance.events.addListener(clientLogListener);
358
+ harnessLogger.debug('client log forwarding enabled');
87
359
  }
88
- const dispose = async () => {
360
+ const dispose = async (reason = 'normal') => {
361
+ harnessLogger.debug('disposing Harness (reason=%s)', reason);
362
+ let hookError;
363
+ try {
364
+ await flushPendingHooks();
365
+ await pluginManager.callHook('harness:before-dispose', {
366
+ runId: currentRun?.runId,
367
+ reason,
368
+ summary: currentRun?.summary,
369
+ status: currentRun?.status,
370
+ error: currentRun?.error,
371
+ });
372
+ await flushPendingHooks();
373
+ }
374
+ catch (error) {
375
+ hookError = error;
376
+ }
89
377
  if (config.forwardClientLogs) {
90
378
  metroInstance.events.removeListener(clientLogListener);
91
379
  }
380
+ metroInstance.events.removeListener(onMetroEvent);
381
+ appMonitor.removeListener(onAppMonitorEvent);
382
+ serverBridge.off('ready', onReady);
383
+ serverBridge.off('disconnect', onDisconnect);
384
+ serverBridge.off('event', bridgeEventListener);
92
385
  await Promise.all([
386
+ crashSupervisor.dispose(),
93
387
  serverBridge.dispose(),
94
388
  platformInstance.dispose(),
95
389
  metroInstance.dispose(),
96
390
  ]);
391
+ pluginAbortController.abort();
392
+ harnessLogger.debug('Harness resources disposed');
393
+ if (hookError) {
394
+ throw hookError;
395
+ }
97
396
  };
98
- const crashMonitor = createCrashMonitor({
99
- interval: config.crashDetectionInterval,
100
- platformRunner: platformInstance,
101
- bridgeServer: serverBridge,
102
- });
103
397
  if (signal.aborted) {
104
- await dispose();
398
+ await dispose('abort');
105
399
  throw new DOMException('The operation was aborted', 'AbortError');
106
400
  }
107
401
  try {
108
- await prewarmMetroBundle({
109
- projectRoot,
110
- entryPoint: config.entryPoint,
111
- platform: platform.platformId,
112
- dev: true,
113
- minify: false,
114
- signal,
115
- });
116
- logMetroPrewarmCompleted(platform);
117
- await waitForAppReady({
118
- metroEvents: metroInstance.events,
119
- serverBridge,
120
- platformInstance: platformInstance,
121
- bundleStartTimeout: config.bundleStartTimeout,
122
- maxRestarts: config.maxAppRestarts,
123
- signal,
402
+ await pluginManager.callHook('harness:before-creation', {
403
+ appLaunchOptions,
124
404
  });
405
+ await appMonitor.start();
406
+ harnessLogger.debug('app monitor started');
125
407
  }
126
408
  catch (error) {
127
- await dispose();
409
+ const runState = currentRun;
410
+ if (runState) {
411
+ runState.error = error;
412
+ currentRun = runState;
413
+ }
414
+ await dispose(error instanceof DOMException && error.name === 'AbortError' ? 'abort' : 'error');
128
415
  throw error;
129
416
  }
130
- const restart = () => new Promise((resolve, reject) => {
131
- crashMonitor.markIntentionalRestart();
132
- serverBridge.once('ready', () => {
133
- crashMonitor.clearIntentionalRestart();
134
- resolve();
417
+ const ensureAppReady = async (testFilePath) => {
418
+ await flushPendingHooks();
419
+ setActiveTestFilePath(testFilePath);
420
+ crashSupervisor.setActiveTestFile(testFilePath);
421
+ harnessLogger.debug('ensuring app is ready for %s', testFilePath);
422
+ if (crashSupervisor.isReady() && (await platformInstance.isAppRunning())) {
423
+ harnessLogger.debug('reusing existing ready app for %s', testFilePath);
424
+ return;
425
+ }
426
+ crashSupervisor.reset();
427
+ harnessLogger.debug('app not ready, waiting for launch and runtime readiness');
428
+ await waitForAppReady({
429
+ metroInstance,
430
+ serverBridge,
431
+ platformInstance: platformInstance,
432
+ platformId: platform.platformId,
433
+ bundleStartTimeout: config.bundleStartTimeout ?? 60000,
434
+ readyTimeout: config.bridgeTimeout,
435
+ maxAppRestarts: config.maxAppRestarts ?? 2,
436
+ testFilePath,
437
+ crashSupervisor,
438
+ appLaunchOptions,
135
439
  });
136
- platformInstance.restartApp().catch(reject);
137
- });
440
+ await flushPendingHooks();
441
+ harnessLogger.debug('app is ready for %s', testFilePath);
442
+ };
443
+ const restart = async (testFilePath) => {
444
+ await flushPendingHooks();
445
+ await crashSupervisor.stop();
446
+ setActiveTestFilePath(testFilePath);
447
+ harnessLogger.debug('restarting app (testFile=%s mode=%s)', testFilePath ?? 'n/a', testFilePath ? 'stop-and-ensure-ready' : 'direct-restart');
448
+ if (testFilePath) {
449
+ harnessLogger.debug('stopping app before restart');
450
+ await platformInstance.stopApp();
451
+ }
452
+ else {
453
+ harnessLogger.debug('requesting direct app restart');
454
+ await platformInstance.restartApp(appLaunchOptions);
455
+ }
456
+ crashSupervisor.reset();
457
+ await crashSupervisor.start();
458
+ if (testFilePath) {
459
+ await ensureAppReady(testFilePath);
460
+ }
461
+ await flushPendingHooks();
462
+ harnessLogger.debug('restart completed');
463
+ };
138
464
  return {
139
465
  context,
140
466
  runTests: async (path, options) => {
467
+ await flushPendingHooks();
468
+ activeTestFilePath = path;
141
469
  const client = serverBridge.rpc.clients.at(-1);
142
470
  if (!client) {
143
471
  throw new Error('No client found');
144
472
  }
145
- return await client.runTests(path, {
473
+ harnessLogger.debug('running test file on client: %s', path);
474
+ const result = await client.runTests(path, {
146
475
  ...options,
147
476
  runner: platform.runner,
148
477
  });
478
+ await flushPendingHooks();
479
+ return result;
149
480
  },
481
+ ensureAppReady,
150
482
  restart,
151
- dispose,
152
- crashMonitor,
483
+ dispose: () => dispose('normal'),
484
+ crashSupervisor,
485
+ callHook: async (name, payload) => {
486
+ await flushPendingHooks();
487
+ await pluginManager.callHook(name, payload);
488
+ await flushPendingHooks();
489
+ },
490
+ setRunState: (runState) => {
491
+ currentRun = runState;
492
+ },
493
+ getRunState: () => currentRun,
153
494
  };
154
495
  };
155
496
  export const getHarness = async (config, platform, projectRoot) => {
156
497
  const abortSignal = AbortSignal.timeout(config.bridgeTimeout);
498
+ harnessLogger.debug('creating Harness with bridge timeout %dms', config.bridgeTimeout);
157
499
  try {
158
500
  const harness = await getHarnessInternal(config, platform, projectRoot, abortSignal);
159
501
  return harness;
@@ -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,EAAwB,MAAM,cAAc,CAAC;AA0ClE,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;CAyQjB"}