@rstest/browser 0.9.2 → 0.9.4
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/browser-container/container-static/js/{101.82cdbbe145.js → 927.514b181bd2.js} +1710 -1645
- package/dist/browser-container/container-static/js/927.514b181bd2.js.LICENSE.txt +1 -0
- package/dist/browser-container/container-static/js/{index.602d6770fe.js → index.5acf502b10.js} +505 -434
- package/dist/browser-container/container-static/js/{lib-react.ce60b6aea5.js → lib-react.f905279759.js} +4 -4
- package/dist/browser-container/container-static/js/lib-react.f905279759.js.LICENSE.txt +1 -0
- package/dist/browser-container/index.html +1 -1
- package/dist/index.js +149 -36
- package/dist/providers/index.d.ts +10 -0
- package/dist/providers/playwright/runtime.d.ts +2 -1
- package/dist/rslib-runtime.js +20 -0
- package/package.json +4 -3
- package/src/AGENTS.md +1 -1
- package/src/client/AGENTS.md +8 -8
- package/src/configValidation.ts +4 -0
- package/src/hostController.ts +254 -52
- package/src/index.ts +0 -1
- package/src/providers/index.ts +10 -0
- package/src/providers/playwright/implementation.ts +2 -0
- package/src/providers/playwright/runtime.ts +37 -15
- package/dist/browser-container/container-static/js/101.82cdbbe145.js.LICENSE.txt +0 -1
- package/dist/browser-container/container-static/js/lib-react.ce60b6aea5.js.LICENSE.txt +0 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
/*! LICENSE: lib-react.
|
|
2
|
-
/*! LICENSE: lib-react.
|
|
1
|
+
/*! LICENSE: lib-react.f905279759.js.LICENSE.txt */
|
|
2
|
+
/*! LICENSE: lib-react.f905279759.js.LICENSE.txt */ "use strict";
|
|
3
3
|
(self.rspackChunk_rstest_browser_ui = self.rspackChunk_rstest_browser_ui || []).push([
|
|
4
4
|
[
|
|
5
5
|
"783"
|
|
6
6
|
],
|
|
7
7
|
{
|
|
8
8
|
7816 (e, t, n) {
|
|
9
|
-
var r, l = n(1099), a = n(162), o = n(
|
|
9
|
+
var r, l = n(1099), a = n(162), o = n(5466);
|
|
10
10
|
function i(e) {
|
|
11
11
|
var t = "https://react.dev/errors/" + e;
|
|
12
12
|
if (1 < arguments.length) {
|
|
@@ -7947,7 +7947,7 @@
|
|
|
7947
7947
|
}
|
|
7948
7948
|
})(), e.exports = n(7816);
|
|
7949
7949
|
},
|
|
7950
|
-
|
|
7950
|
+
5466 (e, t, n) {
|
|
7951
7951
|
(function e() {
|
|
7952
7952
|
if ("u" > typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" == typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE) try {
|
|
7953
7953
|
__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/*! LICENSE: lib-react.f905279759.js.LICENSE.txt */
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<script>
|
|
13
13
|
window.__RSTEST_BROWSER_OPTIONS__ = __RSTEST_OPTIONS_PLACEHOLDER__;
|
|
14
14
|
</script>
|
|
15
|
-
<script defer src="/container-static/js/lib-react.
|
|
15
|
+
<script defer src="/container-static/js/lib-react.f905279759.js"></script><script defer src="/container-static/js/927.514b181bd2.js"></script><script defer src="/container-static/js/index.5acf502b10.js"></script><link href="/container-static/css/index.5c72297783.css" rel="stylesheet"></head>
|
|
16
16
|
<body>
|
|
17
17
|
<div id="root"></div>
|
|
18
18
|
</body>
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { __webpack_require__ } from "./rslib-runtime.js";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import promises from "node:fs/promises";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { isDeepStrictEqual } from "node:util";
|
|
5
6
|
import { TEMP_RSTEST_OUTPUT_DIR, color, getSetupFiles, getTestEntries, isDebug, loadCoverageProvider, logger, rsbuild, serializableConfig } from "@rstest/core/browser";
|
|
6
7
|
import open_editor from "open-editor";
|
|
7
8
|
import { basename, dirname, join, normalize, relative, resolve as external_pathe_resolve } from "pathe";
|
|
@@ -2214,27 +2215,42 @@ async function dispatchPlaywrightBrowserRpc({ containerPage, runnerPage, request
|
|
|
2214
2215
|
}
|
|
2215
2216
|
throw new Error(`Unknown browser rpc kind: ${request.kind}`);
|
|
2216
2217
|
}
|
|
2217
|
-
async function launchPlaywrightBrowser({ browserName, headless }) {
|
|
2218
|
+
async function launchPlaywrightBrowser({ browserName, headless, providerOptions }) {
|
|
2218
2219
|
const playwright = await import("playwright");
|
|
2219
2220
|
const browserType = playwright[browserName];
|
|
2221
|
+
const launchOptions = providerOptions.launch;
|
|
2222
|
+
const launchArgs = Array.isArray(launchOptions?.args) ? launchOptions.args : 'chromium' === browserName ? [
|
|
2223
|
+
'--disable-popup-blocking',
|
|
2224
|
+
'--no-first-run',
|
|
2225
|
+
'--no-default-browser-check'
|
|
2226
|
+
] : void 0;
|
|
2220
2227
|
const browser = await browserType.launch({
|
|
2228
|
+
...launchOptions,
|
|
2221
2229
|
headless,
|
|
2222
|
-
args:
|
|
2223
|
-
'--disable-popup-blocking',
|
|
2224
|
-
'--no-first-run',
|
|
2225
|
-
'--no-default-browser-check'
|
|
2226
|
-
] : void 0
|
|
2230
|
+
args: launchArgs
|
|
2227
2231
|
});
|
|
2232
|
+
const wrappedBrowser = {
|
|
2233
|
+
close: async ()=>browser.close(),
|
|
2234
|
+
newContext: async ({ providerOptions: contextProviderOptions, viewport })=>{
|
|
2235
|
+
const contextOptions = contextProviderOptions?.context;
|
|
2236
|
+
const context = await browser.newContext({
|
|
2237
|
+
...contextOptions,
|
|
2238
|
+
viewport
|
|
2239
|
+
});
|
|
2240
|
+
return context;
|
|
2241
|
+
}
|
|
2242
|
+
};
|
|
2228
2243
|
return {
|
|
2229
|
-
browser:
|
|
2244
|
+
browser: wrappedBrowser
|
|
2230
2245
|
};
|
|
2231
2246
|
}
|
|
2232
2247
|
const playwrightProviderImplementation = {
|
|
2233
2248
|
name: 'playwright',
|
|
2234
|
-
async launchRuntime ({ browserName, headless }) {
|
|
2249
|
+
async launchRuntime ({ browserName, headless, providerOptions }) {
|
|
2235
2250
|
return launchPlaywrightBrowser({
|
|
2236
2251
|
browserName,
|
|
2237
|
-
headless
|
|
2252
|
+
headless,
|
|
2253
|
+
providerOptions
|
|
2238
2254
|
});
|
|
2239
2255
|
},
|
|
2240
2256
|
async dispatchRpc ({ containerPage, runnerPage, request, timeoutFallbackMs }) {
|
|
@@ -2567,22 +2583,44 @@ const planWatchRerun = ({ projectEntries, previousTestFiles, affectedTestFiles }
|
|
|
2567
2583
|
};
|
|
2568
2584
|
};
|
|
2569
2585
|
const picomatch = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.3/node_modules/picomatch/index.js");
|
|
2586
|
+
var picomatch_default = /*#__PURE__*/ __webpack_require__.n(picomatch);
|
|
2570
2587
|
const { createRsbuild: createRsbuild, rspack: rspack } = rsbuild;
|
|
2571
2588
|
const hostController_dirname = dirname(fileURLToPath(import.meta.url));
|
|
2572
2589
|
const OPTIONS_PLACEHOLDER = '__RSTEST_OPTIONS_PLACEHOLDER__';
|
|
2573
2590
|
const serializeForInlineScript = (value)=>JSON.stringify(value).replace(/</g, '\\u003c').replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
|
|
2591
|
+
const getBrowserProviderOptions = (project)=>{
|
|
2592
|
+
const browserConfig = project.normalizedConfig.browser;
|
|
2593
|
+
return browserConfig.providerOptions ?? {};
|
|
2594
|
+
};
|
|
2595
|
+
const createDeferredPromise = ()=>{
|
|
2596
|
+
let resolve;
|
|
2597
|
+
let reject;
|
|
2598
|
+
const promise = new Promise((res, rej)=>{
|
|
2599
|
+
resolve = res;
|
|
2600
|
+
reject = rej;
|
|
2601
|
+
});
|
|
2602
|
+
return {
|
|
2603
|
+
promise,
|
|
2604
|
+
resolve,
|
|
2605
|
+
reject
|
|
2606
|
+
};
|
|
2607
|
+
};
|
|
2574
2608
|
class ContainerRpcManager {
|
|
2575
2609
|
wss;
|
|
2576
2610
|
ws = null;
|
|
2577
2611
|
rpc = null;
|
|
2578
2612
|
methods;
|
|
2579
|
-
|
|
2613
|
+
onDisconnect;
|
|
2614
|
+
detachActiveSocketListeners = null;
|
|
2615
|
+
constructor(wss, methods, onDisconnect){
|
|
2580
2616
|
this.wss = wss;
|
|
2581
2617
|
this.methods = methods;
|
|
2618
|
+
this.onDisconnect = onDisconnect;
|
|
2582
2619
|
this.setupConnectionHandler();
|
|
2583
2620
|
}
|
|
2584
|
-
updateMethods(methods) {
|
|
2621
|
+
updateMethods(methods, onDisconnect) {
|
|
2585
2622
|
this.methods = methods;
|
|
2623
|
+
this.onDisconnect = onDisconnect;
|
|
2586
2624
|
if (this.ws && this.ws.readyState === this.ws.OPEN) this.attachWebSocket(this.ws);
|
|
2587
2625
|
}
|
|
2588
2626
|
setupConnectionHandler() {
|
|
@@ -2593,26 +2631,47 @@ class ContainerRpcManager {
|
|
|
2593
2631
|
});
|
|
2594
2632
|
}
|
|
2595
2633
|
attachWebSocket(ws) {
|
|
2634
|
+
this.detachActiveSocketListeners?.();
|
|
2635
|
+
if (this.rpc && !this.rpc.$closed) this.rpc.$close(new Error('Container RPC transport reattached'));
|
|
2596
2636
|
this.ws = ws;
|
|
2637
|
+
const messageHandlers = new WeakMap();
|
|
2597
2638
|
this.rpc = createBirpc(this.methods, {
|
|
2639
|
+
timeout: -1,
|
|
2598
2640
|
post: (data)=>{
|
|
2599
2641
|
if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(data));
|
|
2600
2642
|
},
|
|
2601
2643
|
on: (fn)=>{
|
|
2602
|
-
|
|
2644
|
+
const handler = (message)=>{
|
|
2603
2645
|
try {
|
|
2604
2646
|
const data = JSON.parse(message.toString());
|
|
2605
2647
|
fn(data);
|
|
2606
2648
|
} catch {}
|
|
2607
|
-
}
|
|
2649
|
+
};
|
|
2650
|
+
messageHandlers.set(fn, handler);
|
|
2651
|
+
ws.on('message', handler);
|
|
2652
|
+
},
|
|
2653
|
+
off: (fn)=>{
|
|
2654
|
+
const handler = messageHandlers.get(fn);
|
|
2655
|
+
if (!handler) return;
|
|
2656
|
+
ws.off('message', handler);
|
|
2657
|
+
messageHandlers.delete(fn);
|
|
2608
2658
|
}
|
|
2609
2659
|
});
|
|
2610
|
-
|
|
2611
|
-
if (this.ws === ws)
|
|
2612
|
-
|
|
2613
|
-
|
|
2660
|
+
const handleClose = ()=>{
|
|
2661
|
+
if (this.ws === ws) this.ws = null;
|
|
2662
|
+
this.detachActiveSocketListeners?.();
|
|
2663
|
+
this.detachActiveSocketListeners = null;
|
|
2664
|
+
if (this.rpc && !this.rpc.$closed) {
|
|
2665
|
+
const disconnectError = new Error('Browser UI WebSocket disconnected before reload completed');
|
|
2666
|
+
this.rpc.$close(disconnectError);
|
|
2667
|
+
this.onDisconnect?.(disconnectError);
|
|
2614
2668
|
}
|
|
2615
|
-
|
|
2669
|
+
this.rpc = null;
|
|
2670
|
+
};
|
|
2671
|
+
ws.on('close', handleClose);
|
|
2672
|
+
this.detachActiveSocketListeners = ()=>{
|
|
2673
|
+
ws.off('close', handleClose);
|
|
2674
|
+
};
|
|
2616
2675
|
}
|
|
2617
2676
|
get isConnected() {
|
|
2618
2677
|
return null !== this.ws && this.ws.readyState === this.ws.OPEN;
|
|
@@ -2628,9 +2687,9 @@ class ContainerRpcManager {
|
|
|
2628
2687
|
}
|
|
2629
2688
|
async reloadTestFile(testFile, testNamePattern) {
|
|
2630
2689
|
logger.debug(`[Browser UI] reloadTestFile called, rpc: ${this.rpc ? 'exists' : 'null'}, ws: ${this.ws ? 'exists' : 'null'}`);
|
|
2631
|
-
if (!this.rpc)
|
|
2690
|
+
if (!this.rpc) throw new Error('Browser UI RPC not available for reloadTestFile');
|
|
2632
2691
|
logger.debug(`[Browser UI] Calling reloadTestFile: ${testFile}`);
|
|
2633
|
-
|
|
2692
|
+
return this.rpc.reloadTestFile(testFile, testNamePattern);
|
|
2634
2693
|
}
|
|
2635
2694
|
}
|
|
2636
2695
|
const watchContext = {
|
|
@@ -2701,7 +2760,7 @@ const createBrowserRsbuildDevConfig = (isWatchMode)=>({
|
|
|
2701
2760
|
}
|
|
2702
2761
|
});
|
|
2703
2762
|
const globToRegexp = (glob)=>{
|
|
2704
|
-
const regex =
|
|
2763
|
+
const regex = picomatch_default().makeRe(glob, {
|
|
2705
2764
|
fastpaths: false,
|
|
2706
2765
|
noglobstar: false,
|
|
2707
2766
|
bash: false
|
|
@@ -2813,7 +2872,8 @@ const getBrowserLaunchOptions = (project)=>({
|
|
|
2813
2872
|
browser: project.normalizedConfig.browser.browser,
|
|
2814
2873
|
headless: project.normalizedConfig.browser.headless,
|
|
2815
2874
|
port: project.normalizedConfig.browser.port,
|
|
2816
|
-
strictPort: project.normalizedConfig.browser.strictPort
|
|
2875
|
+
strictPort: project.normalizedConfig.browser.strictPort,
|
|
2876
|
+
providerOptions: getBrowserProviderOptions(project)
|
|
2817
2877
|
});
|
|
2818
2878
|
const ensureConsistentBrowserLaunchOptions = (projects)=>{
|
|
2819
2879
|
if (0 === projects.length) throw new Error('No browser-enabled projects found.');
|
|
@@ -2821,7 +2881,7 @@ const ensureConsistentBrowserLaunchOptions = (projects)=>{
|
|
|
2821
2881
|
const firstOptions = getBrowserLaunchOptions(firstProject);
|
|
2822
2882
|
for (const project of projects.slice(1)){
|
|
2823
2883
|
const options = getBrowserLaunchOptions(project);
|
|
2824
|
-
if (options.provider !== firstOptions.provider || options.browser !== firstOptions.browser || options.headless !== firstOptions.headless || options.port !== firstOptions.port || options.strictPort !== firstOptions.strictPort) throw new Error(`Browser launch config mismatch between projects "${firstProject.name}" and "${project.name}". All browser-enabled projects in one run must share provider/browser/headless/port/strictPort.`);
|
|
2884
|
+
if (options.provider !== firstOptions.provider || options.browser !== firstOptions.browser || options.headless !== firstOptions.headless || options.port !== firstOptions.port || options.strictPort !== firstOptions.strictPort || !isDeepStrictEqual(options.providerOptions, firstOptions.providerOptions)) throw new Error(`Browser launch config mismatch between projects "${firstProject.name}" and "${project.name}". All browser-enabled projects in one run must share provider/browser/headless/port/strictPort/providerOptions.`);
|
|
2825
2885
|
}
|
|
2826
2886
|
return firstOptions;
|
|
2827
2887
|
};
|
|
@@ -2834,9 +2894,8 @@ const resolveProviderForTestPath = ({ testPath, browserProjects })=>{
|
|
|
2834
2894
|
throw new Error(`Cannot resolve browser provider for test path: ${JSON.stringify(testPath)}. Known project roots: ${JSON.stringify(sortedProjects.map((p)=>p.rootPath))}`);
|
|
2835
2895
|
};
|
|
2836
2896
|
const collectProjectEntries = async (context)=>{
|
|
2837
|
-
const projectEntries = [];
|
|
2838
2897
|
const browserProjects = getBrowserProjects(context);
|
|
2839
|
-
|
|
2898
|
+
return Promise.all(browserProjects.map(async (project)=>{
|
|
2840
2899
|
const { normalizedConfig: { include, exclude, includeSource, setupFiles } } = project;
|
|
2841
2900
|
const tests = await getTestEntries({
|
|
2842
2901
|
include,
|
|
@@ -2847,13 +2906,12 @@ const collectProjectEntries = async (context)=>{
|
|
|
2847
2906
|
fileFilters: context.fileFilters || []
|
|
2848
2907
|
});
|
|
2849
2908
|
const setup = getSetupFiles(setupFiles, project.rootPath);
|
|
2850
|
-
|
|
2909
|
+
return {
|
|
2851
2910
|
project,
|
|
2852
2911
|
setupFiles: Object.values(setup),
|
|
2853
2912
|
testFiles: Object.values(tests)
|
|
2854
|
-
}
|
|
2855
|
-
}
|
|
2856
|
-
return projectEntries;
|
|
2913
|
+
};
|
|
2914
|
+
}));
|
|
2857
2915
|
};
|
|
2858
2916
|
const resolveBrowserFile = (relativePath)=>{
|
|
2859
2917
|
const candidates = [
|
|
@@ -3241,12 +3299,14 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
3241
3299
|
const providerImplementation = getBrowserProviderImplementation(browserLaunchOptions.provider);
|
|
3242
3300
|
const runtime = await providerImplementation.launchRuntime({
|
|
3243
3301
|
browserName,
|
|
3244
|
-
headless: forceHeadless ?? browserLaunchOptions.headless
|
|
3302
|
+
headless: forceHeadless ?? browserLaunchOptions.headless,
|
|
3303
|
+
providerOptions: browserLaunchOptions.providerOptions
|
|
3245
3304
|
});
|
|
3246
3305
|
return {
|
|
3247
3306
|
rsbuildInstance,
|
|
3248
3307
|
devServer,
|
|
3249
3308
|
browser: runtime.browser,
|
|
3309
|
+
browserLaunchOptions,
|
|
3250
3310
|
port,
|
|
3251
3311
|
wsPort,
|
|
3252
3312
|
manifestPath,
|
|
@@ -3445,7 +3505,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
3445
3505
|
});
|
|
3446
3506
|
}
|
|
3447
3507
|
}
|
|
3448
|
-
const { browser, port, wsPort, wss } = runtime;
|
|
3508
|
+
const { browser, browserLaunchOptions, port, wsPort, wss } = runtime;
|
|
3449
3509
|
const buildTime = Date.now() - buildStart;
|
|
3450
3510
|
const allTestFiles = projectEntries.flatMap((entry)=>entry.testFiles.map((testPath)=>({
|
|
3451
3511
|
testPath: normalize(testPath),
|
|
@@ -3665,6 +3725,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
3665
3725
|
if (run.cancelled || runLifecycle.isTokenStale(run.token)) return;
|
|
3666
3726
|
const viewport = viewportByProject.get(file.projectName);
|
|
3667
3727
|
const browserContext = await browser.newContext({
|
|
3728
|
+
providerOptions: browserLaunchOptions.providerOptions,
|
|
3668
3729
|
viewport: viewport ?? null
|
|
3669
3730
|
});
|
|
3670
3731
|
run.contexts.add(browserContext);
|
|
@@ -3970,6 +4031,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
3970
4031
|
} else {
|
|
3971
4032
|
isNewPage = true;
|
|
3972
4033
|
containerContext = await browser.newContext({
|
|
4034
|
+
providerOptions: browserLaunchOptions.providerOptions,
|
|
3973
4035
|
viewport: null
|
|
3974
4036
|
});
|
|
3975
4037
|
containerPage = await containerContext.newPage();
|
|
@@ -3991,21 +4053,61 @@ const runBrowserController = async (context, options)=>{
|
|
|
3991
4053
|
activeContainerPage = containerPage;
|
|
3992
4054
|
const dispatchRouter = createDispatchRouter();
|
|
3993
4055
|
const headedReloadQueue = createHeadedSerialTaskQueue();
|
|
4056
|
+
const pendingHeadedReloads = new Map();
|
|
3994
4057
|
let enqueueHeadedReload = async (_file, _testNamePattern)=>{
|
|
3995
4058
|
throw new Error('Headed reload queue is not initialized');
|
|
3996
4059
|
};
|
|
4060
|
+
const rejectPendingHeadedReload = (testPath, error, runId)=>{
|
|
4061
|
+
const pending = pendingHeadedReloads.get(testPath);
|
|
4062
|
+
if (!pending) return;
|
|
4063
|
+
if (runId && pending.runId !== runId) return;
|
|
4064
|
+
pendingHeadedReloads.delete(testPath);
|
|
4065
|
+
pending.deferred.reject(error);
|
|
4066
|
+
};
|
|
4067
|
+
const rejectAllPendingHeadedReloads = (error)=>{
|
|
4068
|
+
for (const [testPath, pending] of pendingHeadedReloads){
|
|
4069
|
+
pendingHeadedReloads.delete(testPath);
|
|
4070
|
+
pending.deferred.reject(error);
|
|
4071
|
+
}
|
|
4072
|
+
};
|
|
4073
|
+
const registerPendingHeadedReload = (testPath, runId)=>{
|
|
4074
|
+
const previousPending = pendingHeadedReloads.get(testPath);
|
|
4075
|
+
if (previousPending) {
|
|
4076
|
+
previousPending.deferred.reject(new Error(`Reload for "${testPath}" was superseded by a newer request.`));
|
|
4077
|
+
pendingHeadedReloads.delete(testPath);
|
|
4078
|
+
}
|
|
4079
|
+
const deferred = createDeferredPromise();
|
|
4080
|
+
pendingHeadedReloads.set(testPath, {
|
|
4081
|
+
runId,
|
|
4082
|
+
deferred
|
|
4083
|
+
});
|
|
4084
|
+
return deferred.promise;
|
|
4085
|
+
};
|
|
4086
|
+
const resolvePendingHeadedReload = (testPath, runId)=>{
|
|
4087
|
+
const pending = pendingHeadedReloads.get(testPath);
|
|
4088
|
+
if (!pending) return;
|
|
4089
|
+
if (runId && pending.runId !== runId) return void logger.debug(`[Browser UI] Ignoring stale file-complete for ${testPath}. current=${pending.runId}, incoming=${runId}`);
|
|
4090
|
+
pendingHeadedReloads.delete(testPath);
|
|
4091
|
+
pending.deferred.resolve();
|
|
4092
|
+
};
|
|
3997
4093
|
const reloadTestFileWithTimeout = async (file, testNamePattern)=>{
|
|
3998
4094
|
const timeoutMs = getHeadedPerFileTimeoutMs(file);
|
|
3999
4095
|
let timeoutId;
|
|
4096
|
+
let reloadAck;
|
|
4000
4097
|
try {
|
|
4098
|
+
reloadAck = await rpcManager.reloadTestFile(file.testPath, testNamePattern);
|
|
4099
|
+
const completionPromise = registerPendingHeadedReload(file.testPath, reloadAck.runId);
|
|
4001
4100
|
await Promise.race([
|
|
4002
|
-
|
|
4101
|
+
completionPromise,
|
|
4003
4102
|
new Promise((_, reject)=>{
|
|
4004
4103
|
timeoutId = setTimeout(()=>{
|
|
4005
4104
|
reject(new Error(`Headed test execution timeout after ${timeoutMs / 1000}s for ${file.testPath}.`));
|
|
4006
4105
|
}, timeoutMs);
|
|
4007
4106
|
})
|
|
4008
4107
|
]);
|
|
4108
|
+
} catch (error) {
|
|
4109
|
+
if (reloadAck?.runId) rejectPendingHeadedReload(file.testPath, toError(error), reloadAck.runId);
|
|
4110
|
+
throw error;
|
|
4009
4111
|
} finally{
|
|
4010
4112
|
if (timeoutId) clearTimeout(timeoutId);
|
|
4011
4113
|
}
|
|
@@ -4031,12 +4133,21 @@ const runBrowserController = async (context, options)=>{
|
|
|
4031
4133
|
await handleTestCaseResult(payload);
|
|
4032
4134
|
},
|
|
4033
4135
|
async onTestFileComplete (payload) {
|
|
4034
|
-
|
|
4136
|
+
try {
|
|
4137
|
+
await handleTestFileComplete(payload);
|
|
4138
|
+
resolvePendingHeadedReload(payload.testPath, payload.runId);
|
|
4139
|
+
} catch (error) {
|
|
4140
|
+
rejectPendingHeadedReload(payload.testPath, toError(error), payload.runId);
|
|
4141
|
+
throw error;
|
|
4142
|
+
}
|
|
4035
4143
|
},
|
|
4036
4144
|
async onLog (payload) {
|
|
4037
4145
|
await handleLog(payload);
|
|
4038
4146
|
},
|
|
4039
4147
|
async onFatal (payload) {
|
|
4148
|
+
const error = new Error(payload.message);
|
|
4149
|
+
error.stack = payload.stack;
|
|
4150
|
+
rejectAllPendingHeadedReloads(error);
|
|
4040
4151
|
await handleFatal(payload);
|
|
4041
4152
|
},
|
|
4042
4153
|
async dispatch (request) {
|
|
@@ -4046,11 +4157,11 @@ const runBrowserController = async (context, options)=>{
|
|
|
4046
4157
|
let rpcManager;
|
|
4047
4158
|
if (isWatchMode && runtime.rpcManager) {
|
|
4048
4159
|
rpcManager = runtime.rpcManager;
|
|
4049
|
-
rpcManager.updateMethods(createRpcMethods());
|
|
4160
|
+
rpcManager.updateMethods(createRpcMethods(), rejectAllPendingHeadedReloads);
|
|
4050
4161
|
const existingWs = rpcManager.currentWebSocket;
|
|
4051
4162
|
if (existingWs) rpcManager.reattach(existingWs);
|
|
4052
4163
|
} else {
|
|
4053
|
-
rpcManager = new ContainerRpcManager(wss, createRpcMethods());
|
|
4164
|
+
rpcManager = new ContainerRpcManager(wss, createRpcMethods(), rejectAllPendingHeadedReloads);
|
|
4054
4165
|
if (isWatchMode) runtime.rpcManager = rpcManager;
|
|
4055
4166
|
}
|
|
4056
4167
|
if (isNewPage) {
|
|
@@ -4200,7 +4311,7 @@ const listBrowserTests = async (context, options)=>{
|
|
|
4200
4311
|
logger.error(color.red(`Failed to initialize browser provider runtime (${providers.join(', ')}).`), error);
|
|
4201
4312
|
throw error;
|
|
4202
4313
|
}
|
|
4203
|
-
const { browser, port } = runtime;
|
|
4314
|
+
const { browser, browserLaunchOptions, port } = runtime;
|
|
4204
4315
|
const projectRuntimeConfigs = browserProjects.map((project)=>({
|
|
4205
4316
|
name: project.name,
|
|
4206
4317
|
environmentName: project.environmentName,
|
|
@@ -4228,6 +4339,7 @@ const listBrowserTests = async (context, options)=>{
|
|
|
4228
4339
|
resolveCollect = resolve;
|
|
4229
4340
|
});
|
|
4230
4341
|
const browserContext = await browser.newContext({
|
|
4342
|
+
providerOptions: browserLaunchOptions.providerOptions,
|
|
4231
4343
|
viewport: null
|
|
4232
4344
|
});
|
|
4233
4345
|
const page = await browserContext.newPage();
|
|
@@ -4341,6 +4453,7 @@ const validateBrowserConfig = (context)=>{
|
|
|
4341
4453
|
if (!browser.provider) throw new Error('browser.provider is required when browser.enabled is true.');
|
|
4342
4454
|
if (!SUPPORTED_PROVIDERS.includes(browser.provider)) throw new Error(`browser.provider must be one of: ${SUPPORTED_PROVIDERS.join(', ')}.`);
|
|
4343
4455
|
validateViewport(browser.viewport);
|
|
4456
|
+
if (!isPlainObject(browser.providerOptions)) throw new Error('browser.providerOptions must be a plain object.');
|
|
4344
4457
|
}
|
|
4345
4458
|
}
|
|
4346
4459
|
};
|
|
@@ -4,6 +4,14 @@ import type { BrowserRpcRequest } from '../rpcProtocol';
|
|
|
4
4
|
*
|
|
5
5
|
* When adding a new built-in provider, implement `BrowserProviderImplementation`
|
|
6
6
|
* and register it in `providerImplementations` below.
|
|
7
|
+
*
|
|
8
|
+
* Provider-agnostic policy:
|
|
9
|
+
* - keep shared contracts and behavior provider-neutral
|
|
10
|
+
* - `browser.providerOptions` stays opaque at the framework boundary
|
|
11
|
+
* - do not export provider-owned config types from `@rstest/browser`
|
|
12
|
+
* - do not reference optional peer provider types from public declarations
|
|
13
|
+
* - keep provider-specific behavior and config decoding inside provider implementations
|
|
14
|
+
* - prefer direct passthrough to provider APIs over provider-specific translation
|
|
7
15
|
*/
|
|
8
16
|
export type BrowserProvider = 'playwright';
|
|
9
17
|
/** Minimal console shape needed by host logging bridge. */
|
|
@@ -46,6 +54,7 @@ export type BrowserProviderBrowser = {
|
|
|
46
54
|
width: number;
|
|
47
55
|
height: number;
|
|
48
56
|
} | null;
|
|
57
|
+
providerOptions?: Record<string, unknown>;
|
|
49
58
|
}) => Promise<BrowserProviderContext>;
|
|
50
59
|
};
|
|
51
60
|
/** Provider launch result consumed by hostController. */
|
|
@@ -56,6 +65,7 @@ export type BrowserProviderRuntime = {
|
|
|
56
65
|
export type LaunchBrowserInput = {
|
|
57
66
|
browserName: 'chromium' | 'firefox' | 'webkit';
|
|
58
67
|
headless: boolean | undefined;
|
|
68
|
+
providerOptions: Record<string, unknown>;
|
|
59
69
|
};
|
|
60
70
|
/** Input contract for provider-side browser RPC dispatch. */
|
|
61
71
|
export type DispatchBrowserRpcInput = {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BrowserProviderRuntime } from '../index';
|
|
2
|
-
export declare function launchPlaywrightBrowser({ browserName, headless, }: {
|
|
2
|
+
export declare function launchPlaywrightBrowser({ browserName, headless, providerOptions, }: {
|
|
3
3
|
browserName: 'chromium' | 'firefox' | 'webkit';
|
|
4
4
|
headless: boolean | undefined;
|
|
5
|
+
providerOptions: Record<string, unknown>;
|
|
5
6
|
}): Promise<BrowserProviderRuntime>;
|
package/dist/rslib-runtime.js
CHANGED
|
@@ -10,9 +10,29 @@ function __webpack_require__(moduleId) {
|
|
|
10
10
|
return module.exports;
|
|
11
11
|
}
|
|
12
12
|
__webpack_require__.m = __webpack_modules__;
|
|
13
|
+
(()=>{
|
|
14
|
+
__webpack_require__.n = (module)=>{
|
|
15
|
+
var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
|
|
16
|
+
__webpack_require__.d(getter, {
|
|
17
|
+
a: getter
|
|
18
|
+
});
|
|
19
|
+
return getter;
|
|
20
|
+
};
|
|
21
|
+
})();
|
|
22
|
+
(()=>{
|
|
23
|
+
__webpack_require__.d = (exports, definition)=>{
|
|
24
|
+
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) Object.defineProperty(exports, key, {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
get: definition[key]
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
})();
|
|
13
30
|
(()=>{
|
|
14
31
|
__webpack_require__.add = function(modules) {
|
|
15
32
|
Object.assign(__webpack_require__.m, modules);
|
|
16
33
|
};
|
|
17
34
|
})();
|
|
35
|
+
(()=>{
|
|
36
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
37
|
+
})();
|
|
18
38
|
export { __webpack_require__ };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rstest/browser",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
4
|
"description": "Browser mode support for Rstest testing framework.",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/web-infra-dev/rstest/issues"
|
|
@@ -52,18 +52,19 @@
|
|
|
52
52
|
"@types/convert-source-map": "^2.0.3",
|
|
53
53
|
"@types/picomatch": "^4.0.2",
|
|
54
54
|
"@types/ws": "^8.18.1",
|
|
55
|
+
"@typescript/native-preview": "7.0.0-dev.20260317.1",
|
|
55
56
|
"@vitest/snapshot": "^3.2.4",
|
|
56
57
|
"birpc": "^4.0.0",
|
|
57
58
|
"picocolors": "^1.1.1",
|
|
58
59
|
"picomatch": "^4.0.3",
|
|
59
60
|
"playwright": "^1.58.2",
|
|
60
|
-
"@rstest/core": "0.9.2",
|
|
61
61
|
"@rstest/tsconfig": "0.0.1",
|
|
62
|
+
"@rstest/core": "0.9.4",
|
|
62
63
|
"@rstest/browser-ui": "0.0.0"
|
|
63
64
|
},
|
|
64
65
|
"peerDependencies": {
|
|
65
66
|
"playwright": "^1.49.1",
|
|
66
|
-
"@rstest/core": "^0.9.
|
|
67
|
+
"@rstest/core": "^0.9.4"
|
|
67
68
|
},
|
|
68
69
|
"peerDependenciesMeta": {
|
|
69
70
|
"playwright": {
|
package/src/AGENTS.md
CHANGED
|
@@ -42,7 +42,7 @@ flowchart LR
|
|
|
42
42
|
RPC --> CH
|
|
43
43
|
CH --> UR
|
|
44
44
|
|
|
45
|
-
HC -."headless bridge:\nexposeFunction(
|
|
45
|
+
HC -."headless bridge:\nexposeFunction(__rstest_dispatch__, __rstest_dispatch_rpc__)".-> Runner
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
## Headed transport path
|
package/src/client/AGENTS.md
CHANGED
|
@@ -6,7 +6,7 @@ This document is architecture-only and focuses on the browser runner runtime in
|
|
|
6
6
|
|
|
7
7
|
```mermaid
|
|
8
8
|
flowchart TD
|
|
9
|
-
A["waitForConfig()"] --> B["read
|
|
9
|
+
A["waitForConfig()"] --> B["read __RSTEST_BROWSER_OPTIONS__ + URL overrides"]
|
|
10
10
|
B --> R["send ready"]
|
|
11
11
|
R --> C["setRealTimers()"]
|
|
12
12
|
C --> D["preloadRunnerSourceMap()"]
|
|
@@ -23,7 +23,7 @@ flowchart TD
|
|
|
23
23
|
L --> N["send file-complete per file"]
|
|
24
24
|
N --> O["send complete after all files"]
|
|
25
25
|
|
|
26
|
-
H --> M["window
|
|
26
|
+
H --> M["window.__RSTEST_DONE__ = true"]
|
|
27
27
|
O --> M
|
|
28
28
|
```
|
|
29
29
|
|
|
@@ -32,19 +32,19 @@ flowchart TD
|
|
|
32
32
|
```mermaid
|
|
33
33
|
flowchart LR
|
|
34
34
|
subgraph IframePath["Iframe path (headed)"]
|
|
35
|
-
S1["send()"] --> P1["parent.postMessage(
|
|
35
|
+
S1["send()"] --> P1["parent.postMessage(__rstest_dispatch__)"]
|
|
36
36
|
R1["dispatchRunnerLifecycle()"] --> P2["postMessage dispatch-rpc-request"]
|
|
37
|
-
SN1["snapshot.sendRpcRequest()"] --> P3["postMessage dispatch-rpc-request + wait
|
|
37
|
+
SN1["snapshot.sendRpcRequest()"] --> P3["postMessage dispatch-rpc-request + wait __rstest_dispatch_response__"]
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
subgraph TopLevelRunPath["Top-level page path (headless run)"]
|
|
41
|
-
S2["send()"] --> D1["window
|
|
42
|
-
R2["dispatchRunnerLifecycle()"] --> D2["window
|
|
43
|
-
SN2["snapshot.sendRpcRequest()"] --> D3["window
|
|
41
|
+
S2["send()"] --> D1["window.__rstest_dispatch__"]
|
|
42
|
+
R2["dispatchRunnerLifecycle()"] --> D2["window.__rstest_dispatch_rpc__"]
|
|
43
|
+
SN2["snapshot.sendRpcRequest()"] --> D3["window.__rstest_dispatch_rpc__"]
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
subgraph TopLevelCollectPath["Top-level page path (list collect)"]
|
|
47
|
-
S3["send()"] --> C1["window
|
|
47
|
+
S3["send()"] --> C1["window.__rstest_dispatch__"]
|
|
48
48
|
end
|
|
49
49
|
```
|
|
50
50
|
|
package/src/configValidation.ts
CHANGED