@rstest/browser 0.8.3 → 0.8.5
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/css/index.5a71c757.css +1 -0
- package/dist/browser-container/container-static/js/{837.e631233e.js → 565.226c9ef5.js} +18995 -14386
- package/dist/browser-container/container-static/js/565.226c9ef5.js.LICENSE.txt +1 -0
- package/dist/browser-container/container-static/js/{container.93bdade7.js → index.c1d17467.js} +854 -321
- package/dist/browser-container/container-static/js/{lib-react.48335539.js → lib-react.97ee79b0.js} +22 -22
- package/dist/browser-container/container-static/js/lib-react.97ee79b0.js.LICENSE.txt +1 -0
- package/dist/browser-container/container-static/js/scheduler.5accca0c.js +407 -0
- package/dist/browser-container/{container.html → index.html} +1 -1
- package/dist/browser-container/scheduler.html +19 -0
- package/dist/configValidation.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +257 -80
- package/dist/protocol.d.ts +6 -0
- package/dist/viewportPresets.d.ts +16 -0
- package/package.json +5 -5
- package/src/client/entry.ts +13 -6
- package/src/configValidation.ts +66 -0
- package/src/hostController.ts +149 -44
- package/src/index.ts +8 -0
- package/src/protocol.ts +9 -0
- package/src/viewportPresets.ts +64 -0
- package/dist/browser-container/container-static/css/container.425b2695.css +0 -1
- package/dist/browser-container/container-static/js/837.e631233e.js.LICENSE.txt +0 -1
- package/dist/browser-container/container-static/js/lib-react.48335539.js.LICENSE.txt +0 -1
package/dist/index.js
CHANGED
|
@@ -1417,15 +1417,32 @@ __webpack_require__.add({
|
|
|
1417
1417
|
});
|
|
1418
1418
|
const TYPE_REQUEST = "q";
|
|
1419
1419
|
const TYPE_RESPONSE = "s";
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1420
|
+
function createPromiseWithResolvers() {
|
|
1421
|
+
let resolve;
|
|
1422
|
+
let reject;
|
|
1423
|
+
return {
|
|
1424
|
+
promise: new Promise((res, rej)=>{
|
|
1425
|
+
resolve = res;
|
|
1426
|
+
reject = rej;
|
|
1427
|
+
}),
|
|
1428
|
+
resolve,
|
|
1429
|
+
reject
|
|
1430
|
+
};
|
|
1423
1431
|
}
|
|
1432
|
+
const random = Math.random.bind(Math);
|
|
1433
|
+
const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
|
|
1434
|
+
function nanoid(size = 21) {
|
|
1435
|
+
let id = "";
|
|
1436
|
+
let i = size;
|
|
1437
|
+
while(i--)id += urlAlphabet[64 * random() | 0];
|
|
1438
|
+
return id;
|
|
1439
|
+
}
|
|
1440
|
+
const DEFAULT_TIMEOUT = 6e4;
|
|
1441
|
+
const defaultSerialize = (i)=>i;
|
|
1424
1442
|
const defaultDeserialize = defaultSerialize;
|
|
1425
1443
|
const { clearTimeout: dist_clearTimeout, setTimeout: dist_setTimeout } = globalThis;
|
|
1426
|
-
const random = Math.random.bind(Math);
|
|
1427
1444
|
function createBirpc($functions, options) {
|
|
1428
|
-
const { post, on, off = ()=>{}, eventNames = [], serialize = defaultSerialize, deserialize = defaultDeserialize, resolver, bind = "rpc", timeout = DEFAULT_TIMEOUT } = options;
|
|
1445
|
+
const { post, on, off = ()=>{}, eventNames = [], serialize = defaultSerialize, deserialize = defaultDeserialize, resolver, bind = "rpc", timeout = DEFAULT_TIMEOUT, proxify = true } = options;
|
|
1429
1446
|
let $closed = false;
|
|
1430
1447
|
const _rpcPromiseMap = /* @__PURE__ */ new Map();
|
|
1431
1448
|
let _promiseInit;
|
|
@@ -1453,8 +1470,7 @@ function createBirpc($functions, options) {
|
|
|
1453
1470
|
if (timeout >= 0) {
|
|
1454
1471
|
timeoutId = dist_setTimeout(()=>{
|
|
1455
1472
|
try {
|
|
1456
|
-
|
|
1457
|
-
if (true !== handleResult) throw new Error(`[birpc] timeout on calling "${method}"`);
|
|
1473
|
+
if (options.onTimeoutError?.call(rpc, method, args) !== true) throw new Error(`[birpc] timeout on calling "${method}"`);
|
|
1458
1474
|
} catch (e) {
|
|
1459
1475
|
reject(e);
|
|
1460
1476
|
}
|
|
@@ -1483,15 +1499,11 @@ function createBirpc($functions, options) {
|
|
|
1483
1499
|
}
|
|
1484
1500
|
return promise;
|
|
1485
1501
|
}
|
|
1486
|
-
const $call = (method, ...args)=>_call(method, args, false);
|
|
1487
|
-
const $callOptional = (method, ...args)=>_call(method, args, false, true);
|
|
1488
|
-
const $callEvent = (method, ...args)=>_call(method, args, true);
|
|
1489
|
-
const $callRaw = (options2)=>_call(options2.method, options2.args, options2.event, options2.optional);
|
|
1490
1502
|
const builtinMethods = {
|
|
1491
|
-
$call,
|
|
1492
|
-
$callOptional,
|
|
1493
|
-
$callEvent,
|
|
1494
|
-
$callRaw,
|
|
1503
|
+
$call: (method, ...args)=>_call(method, args, false),
|
|
1504
|
+
$callOptional: (method, ...args)=>_call(method, args, false, true),
|
|
1505
|
+
$callEvent: (method, ...args)=>_call(method, args, true),
|
|
1506
|
+
$callRaw: (options$1)=>_call(options$1.method, options$1.args, options$1.event, options$1.optional),
|
|
1495
1507
|
$rejectPendingCalls,
|
|
1496
1508
|
get $closed () {
|
|
1497
1509
|
return $closed;
|
|
@@ -1502,7 +1514,7 @@ function createBirpc($functions, options) {
|
|
|
1502
1514
|
$close,
|
|
1503
1515
|
$functions
|
|
1504
1516
|
};
|
|
1505
|
-
rpc = new Proxy({}, {
|
|
1517
|
+
rpc = proxify ? new Proxy({}, {
|
|
1506
1518
|
get (_, method) {
|
|
1507
1519
|
if (Object.prototype.hasOwnProperty.call(builtinMethods, method)) return builtinMethods[method];
|
|
1508
1520
|
if ("then" === method && !eventNames.includes("then") && !("then" in $functions)) return;
|
|
@@ -1515,11 +1527,11 @@ function createBirpc($functions, options) {
|
|
|
1515
1527
|
sendCall.asEvent = sendEvent;
|
|
1516
1528
|
return sendCall;
|
|
1517
1529
|
}
|
|
1518
|
-
});
|
|
1530
|
+
}) : builtinMethods;
|
|
1519
1531
|
function $close(customError) {
|
|
1520
1532
|
$closed = true;
|
|
1521
1533
|
_rpcPromiseMap.forEach(({ reject, method })=>{
|
|
1522
|
-
const error = new Error(`[birpc] rpc is closed, cannot call "${method}"`);
|
|
1534
|
+
const error = /* @__PURE__ */ new Error(`[birpc] rpc is closed, cannot call "${method}"`);
|
|
1523
1535
|
if (customError) {
|
|
1524
1536
|
customError.cause ??= error;
|
|
1525
1537
|
return reject(customError);
|
|
@@ -1530,9 +1542,8 @@ function createBirpc($functions, options) {
|
|
|
1530
1542
|
off(onMessage);
|
|
1531
1543
|
}
|
|
1532
1544
|
function $rejectPendingCalls(handler) {
|
|
1533
|
-
const
|
|
1534
|
-
|
|
1535
|
-
if (!handler) return reject(new Error(`[birpc]: rejected pending call "${method}".`));
|
|
1545
|
+
const handlerResults = Array.from(_rpcPromiseMap.values()).map(({ method, reject })=>{
|
|
1546
|
+
if (!handler) return reject(/* @__PURE__ */ new Error(`[birpc]: rejected pending call "${method}".`));
|
|
1536
1547
|
return handler({
|
|
1537
1548
|
method,
|
|
1538
1549
|
reject
|
|
@@ -1559,9 +1570,8 @@ function createBirpc($functions, options) {
|
|
|
1559
1570
|
} catch (e) {
|
|
1560
1571
|
error = e;
|
|
1561
1572
|
}
|
|
1562
|
-
else error = new Error(`[birpc] function "${method}" not found`);
|
|
1573
|
+
else error = /* @__PURE__ */ new Error(`[birpc] function "${method}" not found`);
|
|
1563
1574
|
if (msg.i) {
|
|
1564
|
-
if (error && options.onError) options.onError.call(rpc, error, method, args);
|
|
1565
1575
|
if (error && options.onFunctionError) {
|
|
1566
1576
|
if (true === options.onFunctionError.call(rpc, error, method, args)) return;
|
|
1567
1577
|
}
|
|
@@ -1600,29 +1610,11 @@ function createBirpc($functions, options) {
|
|
|
1600
1610
|
_promiseInit = on(onMessage);
|
|
1601
1611
|
return rpc;
|
|
1602
1612
|
}
|
|
1603
|
-
function createPromiseWithResolvers() {
|
|
1604
|
-
let resolve;
|
|
1605
|
-
let reject;
|
|
1606
|
-
const promise = new Promise((res, rej)=>{
|
|
1607
|
-
resolve = res;
|
|
1608
|
-
reject = rej;
|
|
1609
|
-
});
|
|
1610
|
-
return {
|
|
1611
|
-
promise,
|
|
1612
|
-
resolve,
|
|
1613
|
-
reject
|
|
1614
|
-
};
|
|
1615
|
-
}
|
|
1616
|
-
const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
|
|
1617
|
-
function nanoid(size = 21) {
|
|
1618
|
-
let id = "";
|
|
1619
|
-
let i = size;
|
|
1620
|
-
while(i--)id += urlAlphabet[64 * random() | 0];
|
|
1621
|
-
return id;
|
|
1622
|
-
}
|
|
1623
1613
|
const picomatch = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.3/node_modules/picomatch/index.js");
|
|
1624
1614
|
const { createRsbuild: createRsbuild, rspack: rspack } = rsbuild;
|
|
1625
1615
|
const hostController_dirname = dirname(fileURLToPath(import.meta.url));
|
|
1616
|
+
const OPTIONS_PLACEHOLDER = '__RSTEST_OPTIONS_PLACEHOLDER__';
|
|
1617
|
+
const serializeForInlineScript = (value)=>JSON.stringify(value).replace(/</g, '\\u003c').replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
|
|
1626
1618
|
class ContainerRpcManager {
|
|
1627
1619
|
wss;
|
|
1628
1620
|
ws = null;
|
|
@@ -1917,6 +1909,20 @@ const htmlTemplate = `<!DOCTYPE html>
|
|
|
1917
1909
|
</body>
|
|
1918
1910
|
</html>
|
|
1919
1911
|
`;
|
|
1912
|
+
const fallbackSchedulerHtmlTemplate = `<!DOCTYPE html>
|
|
1913
|
+
<html lang="en">
|
|
1914
|
+
<head>
|
|
1915
|
+
<meta charset="UTF-8" />
|
|
1916
|
+
<title>Rstest Browser Scheduler</title>
|
|
1917
|
+
<script>
|
|
1918
|
+
window.__RSTEST_BROWSER_OPTIONS__ = ${OPTIONS_PLACEHOLDER};
|
|
1919
|
+
</script>
|
|
1920
|
+
</head>
|
|
1921
|
+
<body>
|
|
1922
|
+
<script type="module" src="/container-static/js/scheduler.js"></script>
|
|
1923
|
+
</body>
|
|
1924
|
+
</html>
|
|
1925
|
+
`;
|
|
1920
1926
|
const VIRTUAL_MANIFEST_FILENAME = 'virtual-manifest.ts';
|
|
1921
1927
|
const destroyBrowserRuntime = async (runtime)=>{
|
|
1922
1928
|
try {
|
|
@@ -1955,13 +1961,15 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
1955
1961
|
const virtualManifestPlugin = new rspack.experiments.VirtualModulesPlugin({
|
|
1956
1962
|
[manifestPath]: manifestSource
|
|
1957
1963
|
});
|
|
1958
|
-
const
|
|
1959
|
-
const
|
|
1964
|
+
const containerHtmlTemplate = containerDistPath ? await promises.readFile(join(containerDistPath, 'index.html'), 'utf-8') : null;
|
|
1965
|
+
const schedulerHtmlTemplate = containerDistPath ? await promises.readFile(join(containerDistPath, 'scheduler.html'), 'utf-8').catch(()=>null) : null;
|
|
1960
1966
|
let injectedContainerHtml = null;
|
|
1967
|
+
let injectedSchedulerHtml = null;
|
|
1961
1968
|
let serializedOptions = 'null';
|
|
1962
1969
|
const setContainerOptions = (options)=>{
|
|
1963
|
-
serializedOptions =
|
|
1964
|
-
if (containerHtmlTemplate) injectedContainerHtml = containerHtmlTemplate.replace(
|
|
1970
|
+
serializedOptions = serializeForInlineScript(options);
|
|
1971
|
+
if (containerHtmlTemplate) injectedContainerHtml = containerHtmlTemplate.replace(OPTIONS_PLACEHOLDER, serializedOptions);
|
|
1972
|
+
injectedSchedulerHtml = (schedulerHtmlTemplate || fallbackSchedulerHtmlTemplate).replace(OPTIONS_PLACEHOLDER, serializedOptions);
|
|
1965
1973
|
};
|
|
1966
1974
|
const browserProjects = getBrowserProjects(context);
|
|
1967
1975
|
const firstProject = browserProjects[0];
|
|
@@ -2081,7 +2089,7 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
2081
2089
|
});
|
|
2082
2090
|
const serveContainer = containerDistPath ? sirv(containerDistPath, {
|
|
2083
2091
|
dev: false,
|
|
2084
|
-
single: '
|
|
2092
|
+
single: 'index.html'
|
|
2085
2093
|
}) : null;
|
|
2086
2094
|
const containerDevBase = containerDevServer ? new URL(containerDevServer) : null;
|
|
2087
2095
|
const respondWithDevServerHtml = async (url, res)=>{
|
|
@@ -2091,7 +2099,7 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
2091
2099
|
const response = await fetch(target);
|
|
2092
2100
|
if (!response.ok) return false;
|
|
2093
2101
|
let html = await response.text();
|
|
2094
|
-
html = html.replace(
|
|
2102
|
+
html = html.replace(OPTIONS_PLACEHOLDER, serializedOptions);
|
|
2095
2103
|
res.statusCode = response.status;
|
|
2096
2104
|
response.headers.forEach((value, key)=>{
|
|
2097
2105
|
if ('content-length' === key.toLowerCase()) return;
|
|
@@ -2149,9 +2157,14 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
2149
2157
|
}
|
|
2150
2158
|
return;
|
|
2151
2159
|
}
|
|
2152
|
-
if ('/' === url.pathname) {
|
|
2160
|
+
if ('/' === url.pathname || '/scheduler.html' === url.pathname) {
|
|
2153
2161
|
if (await respondWithDevServerHtml(url, res)) return;
|
|
2154
|
-
|
|
2162
|
+
if ('/scheduler.html' === url.pathname) {
|
|
2163
|
+
res.setHeader('Content-Type', 'text/html');
|
|
2164
|
+
res.end(injectedSchedulerHtml || (schedulerHtmlTemplate || fallbackSchedulerHtmlTemplate).replace(OPTIONS_PLACEHOLDER, 'null'));
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
2167
|
+
const html = injectedContainerHtml || containerHtmlTemplate?.replace(OPTIONS_PLACEHOLDER, 'null');
|
|
2155
2168
|
if (html) {
|
|
2156
2169
|
res.setHeader('Content-Type', 'text/html');
|
|
2157
2170
|
res.end(html);
|
|
@@ -2245,6 +2258,39 @@ async function resolveProjectEntries(context, shardedEntries) {
|
|
|
2245
2258
|
const runBrowserController = async (context, options)=>{
|
|
2246
2259
|
const { skipOnTestRunEnd = false } = options ?? {};
|
|
2247
2260
|
const buildStart = Date.now();
|
|
2261
|
+
const browserProjects = getBrowserProjects(context);
|
|
2262
|
+
const useSchedulerPage = browserProjects.every((project)=>project.normalizedConfig.browser.headless);
|
|
2263
|
+
const buildErrorResult = async (error)=>{
|
|
2264
|
+
const elapsed = Math.max(0, Date.now() - buildStart);
|
|
2265
|
+
const errorResult = {
|
|
2266
|
+
results: [],
|
|
2267
|
+
testResults: [],
|
|
2268
|
+
duration: {
|
|
2269
|
+
totalTime: elapsed,
|
|
2270
|
+
buildTime: elapsed,
|
|
2271
|
+
testTime: 0
|
|
2272
|
+
},
|
|
2273
|
+
hasFailure: true,
|
|
2274
|
+
unhandledErrors: [
|
|
2275
|
+
error
|
|
2276
|
+
]
|
|
2277
|
+
};
|
|
2278
|
+
if (!skipOnTestRunEnd) for (const reporter of context.reporters)await reporter.onTestRunEnd?.({
|
|
2279
|
+
results: [],
|
|
2280
|
+
testResults: [],
|
|
2281
|
+
duration: errorResult.duration,
|
|
2282
|
+
snapshotSummary: context.snapshotManager.summary,
|
|
2283
|
+
getSourcemap: async ()=>null,
|
|
2284
|
+
unhandledErrors: errorResult.unhandledErrors
|
|
2285
|
+
});
|
|
2286
|
+
return errorResult;
|
|
2287
|
+
};
|
|
2288
|
+
const toError = (error)=>error instanceof Error ? error : new Error(String(error));
|
|
2289
|
+
const failWithError = async (error, cleanup)=>{
|
|
2290
|
+
ensureProcessExitCode(1);
|
|
2291
|
+
await cleanup?.();
|
|
2292
|
+
return buildErrorResult(toError(error));
|
|
2293
|
+
};
|
|
2248
2294
|
const containerDevServerEnv = process.env.RSTEST_CONTAINER_DEV_SERVER;
|
|
2249
2295
|
let containerDevServer;
|
|
2250
2296
|
let containerDistPath;
|
|
@@ -2252,16 +2298,14 @@ const runBrowserController = async (context, options)=>{
|
|
|
2252
2298
|
containerDevServer = new URL(containerDevServerEnv).toString();
|
|
2253
2299
|
logger.debug(`[Browser UI] Using dev server for container: ${containerDevServer}`);
|
|
2254
2300
|
} catch (error) {
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
return;
|
|
2301
|
+
const originalError = toError(error);
|
|
2302
|
+
originalError.message = `Invalid RSTEST_CONTAINER_DEV_SERVER value: ${originalError.message}`;
|
|
2303
|
+
return failWithError(originalError);
|
|
2258
2304
|
}
|
|
2259
2305
|
if (!containerDevServer) try {
|
|
2260
2306
|
containerDistPath = resolveContainerDist();
|
|
2261
2307
|
} catch (error) {
|
|
2262
|
-
|
|
2263
|
-
ensureProcessExitCode(1);
|
|
2264
|
-
return;
|
|
2308
|
+
return failWithError(error);
|
|
2265
2309
|
}
|
|
2266
2310
|
const projectEntries = await resolveProjectEntries(context, options?.shardedEntries);
|
|
2267
2311
|
const totalTests = projectEntries.reduce((total, item)=>total + item.testFiles.length, 0);
|
|
@@ -2303,13 +2347,12 @@ const runBrowserController = async (context, options)=>{
|
|
|
2303
2347
|
containerDevServer
|
|
2304
2348
|
});
|
|
2305
2349
|
} catch (error) {
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
})
|
|
2312
|
-
return;
|
|
2350
|
+
return failWithError(error, async ()=>{
|
|
2351
|
+
await promises.rm(tempDir, {
|
|
2352
|
+
recursive: true,
|
|
2353
|
+
force: true
|
|
2354
|
+
}).catch(()=>{});
|
|
2355
|
+
});
|
|
2313
2356
|
}
|
|
2314
2357
|
if (isWatchMode) {
|
|
2315
2358
|
watchContext.runtime = runtime;
|
|
@@ -2322,14 +2365,14 @@ const runBrowserController = async (context, options)=>{
|
|
|
2322
2365
|
testPath: normalize(testPath),
|
|
2323
2366
|
projectName: entry.project.name
|
|
2324
2367
|
})));
|
|
2325
|
-
const
|
|
2326
|
-
const projectRuntimeConfigs = browserProjectsForRuntime.map((project)=>({
|
|
2368
|
+
const projectRuntimeConfigs = browserProjects.map((project)=>({
|
|
2327
2369
|
name: project.name,
|
|
2328
2370
|
environmentName: project.environmentName,
|
|
2329
2371
|
projectRoot: normalize(project.rootPath),
|
|
2330
|
-
runtimeConfig: serializableConfig(getRuntimeConfigFromProject(project))
|
|
2372
|
+
runtimeConfig: serializableConfig(getRuntimeConfigFromProject(project)),
|
|
2373
|
+
viewport: project.normalizedConfig.browser.viewport
|
|
2331
2374
|
}));
|
|
2332
|
-
const maxTestTimeoutForRpc = Math.max(...
|
|
2375
|
+
const maxTestTimeoutForRpc = Math.max(...browserProjects.map((p)=>p.normalizedConfig.testTimeout ?? 5000));
|
|
2333
2376
|
const hostOptions = {
|
|
2334
2377
|
rootPath: normalize(context.rootPath),
|
|
2335
2378
|
projects: projectRuntimeConfigs,
|
|
@@ -2375,7 +2418,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
2375
2418
|
}
|
|
2376
2419
|
containerPage.on('console', (msg)=>{
|
|
2377
2420
|
const text = msg.text();
|
|
2378
|
-
if (text.
|
|
2421
|
+
if (text.startsWith('[Container]') || text.startsWith('[Runner]') || text.startsWith('[Scheduler]')) logger.log(color.gray(`[Browser Console] ${text}`));
|
|
2379
2422
|
});
|
|
2380
2423
|
}
|
|
2381
2424
|
const createRpcMethods = ()=>({
|
|
@@ -2458,12 +2501,17 @@ const runBrowserController = async (context, options)=>{
|
|
|
2458
2501
|
if (isWatchMode) runtime.rpcManager = rpcManager;
|
|
2459
2502
|
}
|
|
2460
2503
|
if (isNewPage) {
|
|
2461
|
-
|
|
2504
|
+
const pagePath = useSchedulerPage ? '/scheduler.html' : '/';
|
|
2505
|
+
if (useSchedulerPage) {
|
|
2506
|
+
const serializedOptions = serializeForInlineScript(hostOptions);
|
|
2507
|
+
await containerPage.addInitScript(`window.__RSTEST_BROWSER_OPTIONS__ = ${serializedOptions};`);
|
|
2508
|
+
}
|
|
2509
|
+
await containerPage.goto(`http://localhost:${port}${pagePath}`, {
|
|
2462
2510
|
waitUntil: 'load'
|
|
2463
2511
|
});
|
|
2464
|
-
logger.log(color.cyan(`\nBrowser mode opened at http://localhost:${port}
|
|
2512
|
+
logger.log(color.cyan(`\nBrowser mode opened at http://localhost:${port}${pagePath}\n`));
|
|
2465
2513
|
}
|
|
2466
|
-
const maxTestTimeout = Math.max(...
|
|
2514
|
+
const maxTestTimeout = Math.max(...browserProjects.map((p)=>p.normalizedConfig.testTimeout ?? 5000));
|
|
2467
2515
|
const totalTimeoutMs = maxTestTimeout * allTestFiles.length + 30000;
|
|
2468
2516
|
let timeoutId;
|
|
2469
2517
|
const testTimeout = new Promise((resolve)=>{
|
|
@@ -2498,12 +2546,16 @@ const runBrowserController = async (context, options)=>{
|
|
|
2498
2546
|
for (const testFile of affectedFiles)await rpcManager.reloadTestFile(testFile);
|
|
2499
2547
|
} else if (!filesChanged) logger.log(color.cyan('Tests will be re-executed automatically\n'));
|
|
2500
2548
|
};
|
|
2501
|
-
if (!isWatchMode)
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2549
|
+
if (!isWatchMode) {
|
|
2550
|
+
try {
|
|
2551
|
+
await containerPage.close();
|
|
2552
|
+
} catch {}
|
|
2553
|
+
try {
|
|
2554
|
+
await containerContext.close();
|
|
2555
|
+
} catch {}
|
|
2556
|
+
await destroyBrowserRuntime(runtime);
|
|
2506
2557
|
}
|
|
2558
|
+
if (fatalError) return failWithError(fatalError);
|
|
2507
2559
|
const duration = {
|
|
2508
2560
|
totalTime: buildTime + testTime,
|
|
2509
2561
|
buildTime,
|
|
@@ -2566,7 +2618,8 @@ const listBrowserTests = async (context, options)=>{
|
|
|
2566
2618
|
name: project.name,
|
|
2567
2619
|
environmentName: project.environmentName,
|
|
2568
2620
|
projectRoot: normalize(project.rootPath),
|
|
2569
|
-
runtimeConfig: serializableConfig(getRuntimeConfigFromProject(project))
|
|
2621
|
+
runtimeConfig: serializableConfig(getRuntimeConfigFromProject(project)),
|
|
2622
|
+
viewport: project.normalizedConfig.browser.viewport
|
|
2570
2623
|
}));
|
|
2571
2624
|
const maxTestTimeoutForRpc = Math.max(...browserProjects.map((p)=>p.normalizedConfig.testTimeout ?? 5000));
|
|
2572
2625
|
const hostOptions = {
|
|
@@ -2622,7 +2675,7 @@ const listBrowserTests = async (context, options)=>{
|
|
|
2622
2675
|
logger.debug(`[List] Unexpected message: ${message.type}`);
|
|
2623
2676
|
}
|
|
2624
2677
|
});
|
|
2625
|
-
const serializedOptions =
|
|
2678
|
+
const serializedOptions = serializeForInlineScript(hostOptions);
|
|
2626
2679
|
await page.addInitScript(`window.__RSTEST_BROWSER_OPTIONS__ = ${serializedOptions};`);
|
|
2627
2680
|
await page.goto(`http://localhost:${port}/runner.html`, {
|
|
2628
2681
|
waitUntil: 'load'
|
|
@@ -2673,10 +2726,134 @@ const listBrowserTests = async (context, options)=>{
|
|
|
2673
2726
|
close: cleanup
|
|
2674
2727
|
};
|
|
2675
2728
|
};
|
|
2729
|
+
const BROWSER_VIEWPORT_PRESET_IDS = [
|
|
2730
|
+
'iPhoneSE',
|
|
2731
|
+
'iPhoneXR',
|
|
2732
|
+
'iPhone12Pro',
|
|
2733
|
+
'iPhone14ProMax',
|
|
2734
|
+
'Pixel7',
|
|
2735
|
+
'SamsungGalaxyS8Plus',
|
|
2736
|
+
'SamsungGalaxyS20Ultra',
|
|
2737
|
+
'iPadMini',
|
|
2738
|
+
'iPadAir',
|
|
2739
|
+
'iPadPro',
|
|
2740
|
+
'SurfacePro7',
|
|
2741
|
+
'SurfaceDuo',
|
|
2742
|
+
'GalaxyZFold5',
|
|
2743
|
+
'AsusZenbookFold',
|
|
2744
|
+
'SamsungGalaxyA51A71',
|
|
2745
|
+
'NestHub',
|
|
2746
|
+
'NestHubMax'
|
|
2747
|
+
];
|
|
2748
|
+
const BROWSER_VIEWPORT_PRESET_DIMENSIONS = {
|
|
2749
|
+
iPhoneSE: {
|
|
2750
|
+
width: 375,
|
|
2751
|
+
height: 667
|
|
2752
|
+
},
|
|
2753
|
+
iPhoneXR: {
|
|
2754
|
+
width: 414,
|
|
2755
|
+
height: 896
|
|
2756
|
+
},
|
|
2757
|
+
iPhone12Pro: {
|
|
2758
|
+
width: 390,
|
|
2759
|
+
height: 844
|
|
2760
|
+
},
|
|
2761
|
+
iPhone14ProMax: {
|
|
2762
|
+
width: 430,
|
|
2763
|
+
height: 932
|
|
2764
|
+
},
|
|
2765
|
+
Pixel7: {
|
|
2766
|
+
width: 412,
|
|
2767
|
+
height: 915
|
|
2768
|
+
},
|
|
2769
|
+
SamsungGalaxyS8Plus: {
|
|
2770
|
+
width: 360,
|
|
2771
|
+
height: 740
|
|
2772
|
+
},
|
|
2773
|
+
SamsungGalaxyS20Ultra: {
|
|
2774
|
+
width: 412,
|
|
2775
|
+
height: 915
|
|
2776
|
+
},
|
|
2777
|
+
iPadMini: {
|
|
2778
|
+
width: 768,
|
|
2779
|
+
height: 1024
|
|
2780
|
+
},
|
|
2781
|
+
iPadAir: {
|
|
2782
|
+
width: 820,
|
|
2783
|
+
height: 1180
|
|
2784
|
+
},
|
|
2785
|
+
iPadPro: {
|
|
2786
|
+
width: 1024,
|
|
2787
|
+
height: 1366
|
|
2788
|
+
},
|
|
2789
|
+
SurfacePro7: {
|
|
2790
|
+
width: 912,
|
|
2791
|
+
height: 1368
|
|
2792
|
+
},
|
|
2793
|
+
SurfaceDuo: {
|
|
2794
|
+
width: 540,
|
|
2795
|
+
height: 720
|
|
2796
|
+
},
|
|
2797
|
+
GalaxyZFold5: {
|
|
2798
|
+
width: 344,
|
|
2799
|
+
height: 882
|
|
2800
|
+
},
|
|
2801
|
+
AsusZenbookFold: {
|
|
2802
|
+
width: 853,
|
|
2803
|
+
height: 1280
|
|
2804
|
+
},
|
|
2805
|
+
SamsungGalaxyA51A71: {
|
|
2806
|
+
width: 412,
|
|
2807
|
+
height: 914
|
|
2808
|
+
},
|
|
2809
|
+
NestHub: {
|
|
2810
|
+
width: 1024,
|
|
2811
|
+
height: 600
|
|
2812
|
+
},
|
|
2813
|
+
NestHubMax: {
|
|
2814
|
+
width: 1280,
|
|
2815
|
+
height: 800
|
|
2816
|
+
}
|
|
2817
|
+
};
|
|
2818
|
+
const resolveBrowserViewportPreset = (presetId)=>{
|
|
2819
|
+
const size = BROWSER_VIEWPORT_PRESET_DIMENSIONS[presetId];
|
|
2820
|
+
return size ?? null;
|
|
2821
|
+
};
|
|
2822
|
+
const SUPPORTED_PROVIDERS = [
|
|
2823
|
+
'playwright'
|
|
2824
|
+
];
|
|
2825
|
+
const isPlainObject = (value)=>'[object Object]' === Object.prototype.toString.call(value);
|
|
2826
|
+
const validateViewport = (viewport)=>{
|
|
2827
|
+
if (null == viewport) return;
|
|
2828
|
+
if ('string' == typeof viewport) {
|
|
2829
|
+
const presetId = viewport.trim();
|
|
2830
|
+
if (!presetId) throw new Error('browser.viewport must be a non-empty preset id.');
|
|
2831
|
+
if (!resolveBrowserViewportPreset(presetId)) throw new Error(`browser.viewport must be a valid preset id. Received: ${viewport}`);
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
2834
|
+
if (isPlainObject(viewport)) {
|
|
2835
|
+
const width = viewport.width;
|
|
2836
|
+
const height = viewport.height;
|
|
2837
|
+
if (!Number.isFinite(width) || width <= 0) throw new Error('browser.viewport.width must be a positive number.');
|
|
2838
|
+
if (!Number.isFinite(height) || height <= 0) throw new Error('browser.viewport.height must be a positive number.');
|
|
2839
|
+
return;
|
|
2840
|
+
}
|
|
2841
|
+
throw new Error('browser.viewport must be either a preset id or { width, height }.');
|
|
2842
|
+
};
|
|
2843
|
+
const validateBrowserConfig = (context)=>{
|
|
2844
|
+
for (const project of context.projects){
|
|
2845
|
+
const browser = project.normalizedConfig.browser;
|
|
2846
|
+
if (browser.enabled) {
|
|
2847
|
+
if (!browser.provider) throw new Error('browser.provider is required when browser.enabled is true.');
|
|
2848
|
+
if (!SUPPORTED_PROVIDERS.includes(browser.provider)) throw new Error(`browser.provider must be one of: ${SUPPORTED_PROVIDERS.join(', ')}.`);
|
|
2849
|
+
validateViewport(browser.viewport);
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
};
|
|
2676
2853
|
async function runBrowserTests(context, options) {
|
|
2677
2854
|
return runBrowserController(context, options);
|
|
2678
2855
|
}
|
|
2679
2856
|
async function src_listBrowserTests(context) {
|
|
2680
2857
|
return listBrowserTests(context);
|
|
2681
2858
|
}
|
|
2682
|
-
export { runBrowserTests, src_listBrowserTests as listBrowserTests };
|
|
2859
|
+
export { BROWSER_VIEWPORT_PRESET_DIMENSIONS, BROWSER_VIEWPORT_PRESET_IDS, resolveBrowserViewportPreset, runBrowserTests, src_listBrowserTests as listBrowserTests, validateBrowserConfig };
|
package/dist/protocol.d.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
+
import type { DevicePreset } from '@rstest/core/browser';
|
|
1
2
|
import type { RuntimeConfig, Test, TestFileResult, TestResult } from '@rstest/core/browser-runtime';
|
|
2
3
|
import type { SnapshotUpdateState } from '@vitest/snapshot';
|
|
3
4
|
export type SerializedRuntimeConfig = RuntimeConfig;
|
|
5
|
+
export type BrowserViewport = {
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
} | DevicePreset;
|
|
4
9
|
export type BrowserProjectRuntime = {
|
|
5
10
|
name: string;
|
|
6
11
|
environmentName: string;
|
|
7
12
|
projectRoot: string;
|
|
8
13
|
runtimeConfig: SerializedRuntimeConfig;
|
|
14
|
+
viewport?: BrowserViewport;
|
|
9
15
|
};
|
|
10
16
|
/**
|
|
11
17
|
* Test file info with associated project name.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime source of truth for browser viewport presets.
|
|
3
|
+
*
|
|
4
|
+
* IMPORTANT: Keep this list/map in sync with `DevicePreset` typing in
|
|
5
|
+
* `@rstest/core` (`packages/core/src/types/config.ts`) so `defineConfig`
|
|
6
|
+
* autocomplete and runtime validation stay consistent.
|
|
7
|
+
*/
|
|
8
|
+
export declare const BROWSER_VIEWPORT_PRESET_IDS: readonly ["iPhoneSE", "iPhoneXR", "iPhone12Pro", "iPhone14ProMax", "Pixel7", "SamsungGalaxyS8Plus", "SamsungGalaxyS20Ultra", "iPadMini", "iPadAir", "iPadPro", "SurfacePro7", "SurfaceDuo", "GalaxyZFold5", "AsusZenbookFold", "SamsungGalaxyA51A71", "NestHub", "NestHubMax"];
|
|
9
|
+
type BrowserViewportPresetId = (typeof BROWSER_VIEWPORT_PRESET_IDS)[number];
|
|
10
|
+
type BrowserViewportSize = {
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
};
|
|
14
|
+
export declare const BROWSER_VIEWPORT_PRESET_DIMENSIONS: Record<BrowserViewportPresetId, BrowserViewportSize>;
|
|
15
|
+
export declare const resolveBrowserViewportPreset: (presetId: string) => BrowserViewportSize | null;
|
|
16
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rstest/browser",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.5",
|
|
4
4
|
"description": "Browser mode support for Rstest testing framework.",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/web-infra-dev/rstest/issues"
|
|
@@ -48,17 +48,17 @@
|
|
|
48
48
|
"@types/picomatch": "^4.0.2",
|
|
49
49
|
"@types/ws": "^8.18.1",
|
|
50
50
|
"@vitest/snapshot": "^3.2.4",
|
|
51
|
-
"birpc": "
|
|
51
|
+
"birpc": "^4.0.0",
|
|
52
52
|
"picocolors": "^1.1.1",
|
|
53
53
|
"picomatch": "^4.0.3",
|
|
54
54
|
"playwright": "^1.49.1",
|
|
55
55
|
"@rstest/browser-ui": "0.0.0",
|
|
56
|
-
"@rstest/
|
|
57
|
-
"@rstest/
|
|
56
|
+
"@rstest/tsconfig": "0.0.1",
|
|
57
|
+
"@rstest/core": "0.8.5"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"playwright": "^1.49.1",
|
|
61
|
-
"@rstest/core": "^0.8.
|
|
61
|
+
"@rstest/core": "^0.8.5"
|
|
62
62
|
},
|
|
63
63
|
"peerDependenciesMeta": {
|
|
64
64
|
"playwright": {
|
package/src/client/entry.ts
CHANGED
|
@@ -426,12 +426,13 @@ const run = async () => {
|
|
|
426
426
|
return;
|
|
427
427
|
}
|
|
428
428
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
429
|
+
const loadSetupFiles = async (): Promise<void> => {
|
|
430
|
+
for (const loadSetup of currentSetupLoaders) {
|
|
431
|
+
await loadSetup();
|
|
432
|
+
}
|
|
433
|
+
};
|
|
433
434
|
|
|
434
|
-
//
|
|
435
|
+
// 1. Determine which test files to run
|
|
435
436
|
let testKeysToRun: string[];
|
|
436
437
|
|
|
437
438
|
if (targetTestFile) {
|
|
@@ -478,6 +479,9 @@ const run = async () => {
|
|
|
478
479
|
}
|
|
479
480
|
|
|
480
481
|
try {
|
|
482
|
+
// Load setup files for this project after runtime is ready.
|
|
483
|
+
await loadSetupFiles();
|
|
484
|
+
|
|
481
485
|
// Load the test file dynamically (registers tests without running)
|
|
482
486
|
await currentTestContext.loadTest(key);
|
|
483
487
|
|
|
@@ -512,7 +516,7 @@ const run = async () => {
|
|
|
512
516
|
return;
|
|
513
517
|
}
|
|
514
518
|
|
|
515
|
-
//
|
|
519
|
+
// 2. Run tests for each file
|
|
516
520
|
for (const key of testKeysToRun) {
|
|
517
521
|
const testPath = toAbsolutePath(key, currentProject.projectRoot);
|
|
518
522
|
|
|
@@ -573,6 +577,9 @@ const run = async () => {
|
|
|
573
577
|
});
|
|
574
578
|
|
|
575
579
|
try {
|
|
580
|
+
// Load setup files for this project after runtime is ready.
|
|
581
|
+
await loadSetupFiles();
|
|
582
|
+
|
|
576
583
|
// Record script URLs before loading the test file
|
|
577
584
|
const beforeScripts = getScriptUrls();
|
|
578
585
|
|