@react-native-harness/jest 1.1.0-rc.1 → 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__/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.js +340 -47
- 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.map +1 -1
- package/dist/crash-supervisor.js +7 -6
- package/dist/errors.d.ts +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -0
- package/dist/harness.d.ts +23 -1
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js +396 -50
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +205 -58
- 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 -2
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +7 -6
- package/src/__tests__/harness-cache.test.ts +74 -0
- package/src/__tests__/harness.test.ts +469 -51
- package/src/__tests__/index.test.ts +61 -0
- package/src/cli-args.ts +6 -0
- package/src/crash-supervisor.ts +13 -6
- package/src/env.d.ts +1 -1
- package/src/errors.ts +5 -0
- package/src/harness.ts +504 -58
- package/src/index.ts +276 -64
- package/src/logs.ts +4 -0
- package/src/run.ts +20 -10
- package/src/setup.ts +22 -1
- 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/dist/harness.d.ts
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import { BridgeServer } from '@react-native-harness/bridge/server';
|
|
2
2
|
import { HarnessContext, TestExecutionOptions, TestSuiteResult } from '@react-native-harness/bridge';
|
|
3
3
|
import { type AppLaunchOptions, HarnessPlatform, HarnessPlatformRunner } from '@react-native-harness/platforms';
|
|
4
|
+
import { type MetroInstance } from '@react-native-harness/bundler-metro';
|
|
5
|
+
import { type HarnessPluginManager, type HarnessRunStatus, type HarnessRunSummary } from '@react-native-harness/plugins';
|
|
4
6
|
import { Config as HarnessConfig } from '@react-native-harness/config';
|
|
5
7
|
import { type CrashSupervisor } from './crash-supervisor.js';
|
|
6
8
|
export type HarnessRunTestsOptions = Exclude<TestExecutionOptions, 'platform'>;
|
|
9
|
+
export type HarnessRunState = {
|
|
10
|
+
runId: string;
|
|
11
|
+
startTime: number;
|
|
12
|
+
testFiles: string[];
|
|
13
|
+
watchMode: boolean;
|
|
14
|
+
coverageEnabled: boolean;
|
|
15
|
+
summary?: HarnessRunSummary;
|
|
16
|
+
status?: HarnessRunStatus;
|
|
17
|
+
error?: unknown;
|
|
18
|
+
};
|
|
7
19
|
export type Harness = {
|
|
8
20
|
context: HarnessContext;
|
|
9
21
|
runTests: (path: string, options: HarnessRunTestsOptions) => Promise<TestSuiteResult>;
|
|
@@ -11,14 +23,24 @@ export type Harness = {
|
|
|
11
23
|
restart: (testFilePath?: string) => Promise<void>;
|
|
12
24
|
dispose: () => Promise<void>;
|
|
13
25
|
crashSupervisor: CrashSupervisor;
|
|
26
|
+
callHook: HarnessPluginManager<HarnessConfig, HarnessPlatform>['callHook'];
|
|
27
|
+
setRunState: (runState: HarnessRunState | null) => void;
|
|
28
|
+
getRunState: () => HarnessRunState | null;
|
|
14
29
|
};
|
|
30
|
+
export declare const maybeLogMetroCacheReuse: (config: HarnessConfig, platform: HarnessPlatform, projectRoot: string) => void;
|
|
15
31
|
export declare const waitForAppReady: (options: {
|
|
32
|
+
metroInstance: MetroInstance;
|
|
16
33
|
serverBridge: BridgeServer;
|
|
17
34
|
platformInstance: HarnessPlatformRunner;
|
|
18
|
-
|
|
35
|
+
platformId: string;
|
|
36
|
+
bundleStartTimeout: number;
|
|
37
|
+
readyTimeout: number;
|
|
38
|
+
maxAppRestarts: number;
|
|
19
39
|
testFilePath: string;
|
|
20
40
|
crashSupervisor: CrashSupervisor;
|
|
41
|
+
signal?: AbortSignal;
|
|
21
42
|
appLaunchOptions?: AppLaunchOptions;
|
|
43
|
+
launchApp?: () => Promise<void>;
|
|
22
44
|
}) => Promise<void>;
|
|
23
45
|
export declare const getHarness: (config: HarnessConfig, platform: HarnessPlatform, projectRoot: string) => Promise<Harness>;
|
|
24
46
|
//# sourceMappingURL=harness.d.ts.map
|
package/dist/harness.d.ts.map
CHANGED
|
@@ -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,
|
|
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,EAGd,oBAAoB,EACpB,eAAe,EAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAEL,KAAK,gBAAgB,EACrB,eAAe,EACf,qBAAqB,EACtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAIL,KAAK,aAAa,EAEnB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAIL,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,+BAA+B,CAAC;AAGvC,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AAO/B,MAAM,MAAM,sBAAsB,GAAG,OAAO,CAAC,oBAAoB,EAAE,UAAU,CAAC,CAAC;AAE/E,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,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;IACjC,QAAQ,EAAE,oBAAoB,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC,UAAU,CAAC,CAAC;IAC3E,WAAW,EAAE,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,KAAK,IAAI,CAAC;IACxD,WAAW,EAAE,MAAM,eAAe,GAAG,IAAI,CAAC;CAC3C,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,QAAQ,aAAa,EACrB,UAAU,eAAe,EACzB,aAAa,MAAM,KAClB,IAOF,CAAC;AAqBF,eAAO,MAAM,eAAe,GAAU,SAAS;IAC7C,aAAa,EAAE,aAAa,CAAC;IAC7B,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,qBAAqB,CAAC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,eAAe,CAAC;IACjC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC,KAAG,OAAO,CAAC,IAAI,CA2Ef,CAAC;AAkeF,eAAO,MAAM,UAAU,GACrB,QAAQ,aAAa,EACrB,UAAU,eAAe,EACzB,aAAa,MAAM,KAClB,OAAO,CAAC,OAAO,CAsBjB,CAAC"}
|
package/dist/harness.js
CHANGED
|
@@ -1,55 +1,158 @@
|
|
|
1
1
|
import { getBridgeServer, } from '@react-native-harness/bridge/server';
|
|
2
|
-
import { getMetroInstance,
|
|
3
|
-
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';
|
|
4
5
|
import { InitializationTimeoutError } from './errors.js';
|
|
5
6
|
import { createCrashSupervisor, } from './crash-supervisor.js';
|
|
6
7
|
import { createClientLogListener } from './client-log-handler.js';
|
|
7
|
-
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
|
+
};
|
|
8
28
|
export const waitForAppReady = async (options) => {
|
|
9
|
-
const { serverBridge, platformInstance,
|
|
10
|
-
const signal =
|
|
11
|
-
|
|
12
|
-
|
|
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);
|
|
33
|
+
};
|
|
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
|
+
]);
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
crashSupervisor.cancelCrashWaiters();
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
onAttemptStart: () => {
|
|
82
|
+
logWait('beginning launch attempt for %s', testFilePath);
|
|
13
83
|
crashSupervisor.beginLaunch(testFilePath);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
crashSupervisor.markReady();
|
|
18
|
-
cleanup();
|
|
19
|
-
resolve();
|
|
20
|
-
};
|
|
21
|
-
const onAbort = () => {
|
|
22
|
-
cleanup();
|
|
23
|
-
reject(new DOMException('The operation was aborted', 'AbortError'));
|
|
24
|
-
};
|
|
25
|
-
const cleanup = () => {
|
|
26
|
-
serverBridge.off('ready', onReady);
|
|
27
|
-
signal.removeEventListener('abort', onAbort);
|
|
84
|
+
},
|
|
85
|
+
onAttemptReset: () => {
|
|
86
|
+
logWait('resetting launch attempt state');
|
|
28
87
|
crashSupervisor.cancelCrashWaiters();
|
|
29
|
-
}
|
|
30
|
-
signal.addEventListener('abort', onAbort);
|
|
31
|
-
serverBridge.once('ready', onReady);
|
|
32
|
-
void crashSupervisor.waitForCrash(testFilePath).catch((error) => {
|
|
33
|
-
cleanup();
|
|
34
|
-
reject(error);
|
|
35
|
-
});
|
|
36
|
-
void launchApp().catch((error) => {
|
|
37
|
-
cleanup();
|
|
38
|
-
reject(error);
|
|
39
|
-
});
|
|
88
|
+
},
|
|
40
89
|
});
|
|
41
90
|
};
|
|
42
91
|
const getHarnessInternal = async (config, platform, projectRoot, signal) => {
|
|
43
92
|
const context = {
|
|
44
93
|
platform,
|
|
45
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');
|
|
46
138
|
const [metroInstance, platformInstance, serverBridge] = await Promise.all([
|
|
47
|
-
getMetroInstance({ projectRoot, harnessConfig: config }, signal)
|
|
48
|
-
|
|
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
|
+
}),
|
|
49
149
|
getBridgeServer({
|
|
50
150
|
port: config.webSocketPort,
|
|
51
151
|
timeout: config.bridgeTimeout,
|
|
52
152
|
context,
|
|
153
|
+
}).then((bridge) => {
|
|
154
|
+
harnessLogger.debug('bridge server initialized on port %d', config.webSocketPort);
|
|
155
|
+
return bridge;
|
|
53
156
|
}),
|
|
54
157
|
]);
|
|
55
158
|
const crashArtifactWriter = createCrashArtifactWriter({
|
|
@@ -61,67 +164,293 @@ const getHarnessInternal = async (config, platform, projectRoot, signal) => {
|
|
|
61
164
|
});
|
|
62
165
|
const appLaunchOptions = platform.config.appLaunchOptions;
|
|
63
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
|
+
};
|
|
64
286
|
const crashSupervisor = createCrashSupervisor({
|
|
65
287
|
appMonitor,
|
|
66
288
|
platformRunner: platformInstance,
|
|
67
289
|
});
|
|
68
|
-
|
|
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');
|
|
69
356
|
if (config.forwardClientLogs) {
|
|
70
357
|
metroInstance.events.addListener(clientLogListener);
|
|
358
|
+
harnessLogger.debug('client log forwarding enabled');
|
|
71
359
|
}
|
|
72
|
-
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
|
+
}
|
|
73
377
|
if (config.forwardClientLogs) {
|
|
74
378
|
metroInstance.events.removeListener(clientLogListener);
|
|
75
379
|
}
|
|
76
|
-
|
|
380
|
+
metroInstance.events.removeListener(onMetroEvent);
|
|
381
|
+
appMonitor.removeListener(onAppMonitorEvent);
|
|
382
|
+
serverBridge.off('ready', onReady);
|
|
383
|
+
serverBridge.off('disconnect', onDisconnect);
|
|
384
|
+
serverBridge.off('event', bridgeEventListener);
|
|
77
385
|
await Promise.all([
|
|
78
386
|
crashSupervisor.dispose(),
|
|
79
387
|
serverBridge.dispose(),
|
|
80
388
|
platformInstance.dispose(),
|
|
81
389
|
metroInstance.dispose(),
|
|
82
390
|
]);
|
|
391
|
+
pluginAbortController.abort();
|
|
392
|
+
harnessLogger.debug('Harness resources disposed');
|
|
393
|
+
if (hookError) {
|
|
394
|
+
throw hookError;
|
|
395
|
+
}
|
|
83
396
|
};
|
|
84
397
|
if (signal.aborted) {
|
|
85
|
-
await dispose();
|
|
398
|
+
await dispose('abort');
|
|
86
399
|
throw new DOMException('The operation was aborted', 'AbortError');
|
|
87
400
|
}
|
|
88
401
|
try {
|
|
89
|
-
await
|
|
90
|
-
|
|
91
|
-
entryPoint: config.entryPoint,
|
|
92
|
-
platform: platform.platformId,
|
|
93
|
-
dev: true,
|
|
94
|
-
minify: false,
|
|
95
|
-
signal,
|
|
402
|
+
await pluginManager.callHook('harness:before-creation', {
|
|
403
|
+
appLaunchOptions,
|
|
96
404
|
});
|
|
97
|
-
logMetroPrewarmCompleted(platform);
|
|
98
405
|
await appMonitor.start();
|
|
406
|
+
harnessLogger.debug('app monitor started');
|
|
99
407
|
}
|
|
100
408
|
catch (error) {
|
|
101
|
-
|
|
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');
|
|
102
415
|
throw error;
|
|
103
416
|
}
|
|
104
417
|
const ensureAppReady = async (testFilePath) => {
|
|
418
|
+
await flushPendingHooks();
|
|
419
|
+
setActiveTestFilePath(testFilePath);
|
|
105
420
|
crashSupervisor.setActiveTestFile(testFilePath);
|
|
421
|
+
harnessLogger.debug('ensuring app is ready for %s', testFilePath);
|
|
106
422
|
if (crashSupervisor.isReady() && (await platformInstance.isAppRunning())) {
|
|
423
|
+
harnessLogger.debug('reusing existing ready app for %s', testFilePath);
|
|
107
424
|
return;
|
|
108
425
|
}
|
|
109
426
|
crashSupervisor.reset();
|
|
427
|
+
harnessLogger.debug('app not ready, waiting for launch and runtime readiness');
|
|
110
428
|
await waitForAppReady({
|
|
429
|
+
metroInstance,
|
|
111
430
|
serverBridge,
|
|
112
431
|
platformInstance: platformInstance,
|
|
113
|
-
|
|
432
|
+
platformId: platform.platformId,
|
|
433
|
+
bundleStartTimeout: config.bundleStartTimeout ?? 60000,
|
|
434
|
+
readyTimeout: config.bridgeTimeout,
|
|
435
|
+
maxAppRestarts: config.maxAppRestarts ?? 2,
|
|
114
436
|
testFilePath,
|
|
115
437
|
crashSupervisor,
|
|
116
438
|
appLaunchOptions,
|
|
117
439
|
});
|
|
440
|
+
await flushPendingHooks();
|
|
441
|
+
harnessLogger.debug('app is ready for %s', testFilePath);
|
|
118
442
|
};
|
|
119
443
|
const restart = async (testFilePath) => {
|
|
444
|
+
await flushPendingHooks();
|
|
120
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');
|
|
121
448
|
if (testFilePath) {
|
|
449
|
+
harnessLogger.debug('stopping app before restart');
|
|
122
450
|
await platformInstance.stopApp();
|
|
123
451
|
}
|
|
124
452
|
else {
|
|
453
|
+
harnessLogger.debug('requesting direct app restart');
|
|
125
454
|
await platformInstance.restartApp(appLaunchOptions);
|
|
126
455
|
}
|
|
127
456
|
crashSupervisor.reset();
|
|
@@ -129,27 +458,44 @@ const getHarnessInternal = async (config, platform, projectRoot, signal) => {
|
|
|
129
458
|
if (testFilePath) {
|
|
130
459
|
await ensureAppReady(testFilePath);
|
|
131
460
|
}
|
|
461
|
+
await flushPendingHooks();
|
|
462
|
+
harnessLogger.debug('restart completed');
|
|
132
463
|
};
|
|
133
464
|
return {
|
|
134
465
|
context,
|
|
135
466
|
runTests: async (path, options) => {
|
|
467
|
+
await flushPendingHooks();
|
|
468
|
+
activeTestFilePath = path;
|
|
136
469
|
const client = serverBridge.rpc.clients.at(-1);
|
|
137
470
|
if (!client) {
|
|
138
471
|
throw new Error('No client found');
|
|
139
472
|
}
|
|
140
|
-
|
|
473
|
+
harnessLogger.debug('running test file on client: %s', path);
|
|
474
|
+
const result = await client.runTests(path, {
|
|
141
475
|
...options,
|
|
142
476
|
runner: platform.runner,
|
|
143
477
|
});
|
|
478
|
+
await flushPendingHooks();
|
|
479
|
+
return result;
|
|
144
480
|
},
|
|
145
481
|
ensureAppReady,
|
|
146
482
|
restart,
|
|
147
|
-
dispose,
|
|
483
|
+
dispose: () => dispose('normal'),
|
|
148
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,
|
|
149
494
|
};
|
|
150
495
|
};
|
|
151
496
|
export const getHarness = async (config, platform, projectRoot) => {
|
|
152
497
|
const abortSignal = AbortSignal.timeout(config.bridgeTimeout);
|
|
498
|
+
harnessLogger.debug('creating Harness with bridge timeout %dms', config.bridgeTimeout);
|
|
153
499
|
try {
|
|
154
500
|
const harness = await getHarnessInternal(config, platform, projectRoot, abortSignal);
|
|
155
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"}
|