@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.
- package/dist/__tests__/crash-supervisor.test.d.ts +2 -0
- package/dist/__tests__/crash-supervisor.test.d.ts.map +1 -0
- package/dist/__tests__/crash-supervisor.test.js +151 -0
- package/dist/__tests__/errors.test.d.ts +2 -0
- package/dist/__tests__/errors.test.d.ts.map +1 -0
- package/dist/__tests__/errors.test.js +54 -0
- package/dist/__tests__/harness-cache.test.d.ts +2 -0
- package/dist/__tests__/harness-cache.test.d.ts.map +1 -0
- package/dist/__tests__/harness-cache.test.js +51 -0
- package/dist/__tests__/harness.test.d.ts +2 -0
- package/dist/__tests__/harness.test.d.ts.map +1 -0
- package/dist/__tests__/harness.test.js +399 -0
- package/dist/__tests__/index.test.d.ts +2 -0
- package/dist/__tests__/index.test.d.ts.map +1 -0
- package/dist/__tests__/index.test.js +42 -0
- package/dist/cli-args.d.ts +1 -0
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +5 -0
- package/dist/crash-supervisor.d.ts +21 -0
- package/dist/crash-supervisor.d.ts.map +1 -0
- package/dist/crash-supervisor.js +210 -0
- package/dist/errors.d.ts +9 -4
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +43 -8
- package/dist/harness.d.ts +30 -8
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js +440 -98
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +205 -57
- package/dist/logs.d.ts +1 -0
- package/dist/logs.d.ts.map +1 -1
- package/dist/logs.js +3 -0
- package/dist/run.d.ts +7 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +13 -8
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +18 -6
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +7 -6
- package/src/__tests__/crash-supervisor.test.ts +200 -0
- package/src/__tests__/errors.test.ts +70 -0
- package/src/__tests__/harness-cache.test.ts +74 -0
- package/src/__tests__/harness.test.ts +547 -0
- package/src/__tests__/index.test.ts +61 -0
- package/src/cli-args.ts +6 -0
- package/src/crash-supervisor.ts +321 -0
- package/src/env.d.ts +1 -1
- package/src/errors.ts +68 -8
- package/src/harness.ts +566 -120
- package/src/index.ts +276 -66
- package/src/logs.ts +4 -0
- package/src/run.ts +20 -10
- package/src/setup.ts +22 -6
- package/tsconfig.json +3 -3
- package/tsconfig.lib.json +6 -6
- package/dist/cli-args.test.d.ts +0 -2
- package/dist/cli-args.test.d.ts.map +0 -1
- package/dist/cli-args.test.js +0 -1
- package/dist/crash-monitor.d.ts +0 -16
- package/dist/crash-monitor.d.ts.map +0 -1
- package/dist/crash-monitor.js +0 -75
- package/dist/event-mapper.d.ts +0 -16
- package/dist/event-mapper.d.ts.map +0 -1
- package/dist/event-mapper.js +0 -120
- package/dist/global-setup.d.ts +0 -3
- package/dist/global-setup.d.ts.map +0 -1
- package/dist/global-setup.js +0 -60
- package/dist/global-teardown.d.ts +0 -3
- package/dist/global-teardown.d.ts.map +0 -1
- package/dist/global-teardown.js +0 -11
- package/dist/harness-reporter.d.ts +0 -8
- package/dist/harness-reporter.d.ts.map +0 -1
- package/dist/harness-reporter.js +0 -12
- package/dist/jest-options.d.ts +0 -2
- package/dist/jest-options.d.ts.map +0 -1
- package/dist/jest-options.js +0 -1
- package/dist/types.d.ts +0 -17
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -1
- 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,
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
|
|
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 {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
isBundling = false;
|
|
49
|
-
startBundleTimeout(); // Reset timer after bundle completes
|
|
77
|
+
finally {
|
|
78
|
+
crashSupervisor.cancelCrashWaiters();
|
|
50
79
|
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
109
|
-
|
|
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
|
-
|
|
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
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|