@rstest/browser 0.8.2 → 0.8.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/css/index.5a71c757.css +1 -0
- package/dist/browser-container/container-static/js/{837.e631233e.js → 392.28f9a733.js} +14259 -9646
- package/dist/browser-container/container-static/js/392.28f9a733.js.LICENSE.txt +1 -0
- package/dist/browser-container/container-static/js/{container.93bdade7.js → index.129eaf9c.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.6976de44.js +411 -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 +227 -39
- package/dist/protocol.d.ts +6 -0
- package/dist/viewportPresets.d.ts +16 -0
- package/package.json +4 -4
- package/src/configValidation.ts +66 -0
- package/src/hostController.ts +156 -46
- 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
|
@@ -1623,6 +1623,8 @@ function nanoid(size = 21) {
|
|
|
1623
1623
|
const picomatch = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.3/node_modules/picomatch/index.js");
|
|
1624
1624
|
const { createRsbuild: createRsbuild, rspack: rspack } = rsbuild;
|
|
1625
1625
|
const hostController_dirname = dirname(fileURLToPath(import.meta.url));
|
|
1626
|
+
const OPTIONS_PLACEHOLDER = '__RSTEST_OPTIONS_PLACEHOLDER__';
|
|
1627
|
+
const serializeForInlineScript = (value)=>JSON.stringify(value).replace(/</g, '\\u003c').replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
|
|
1626
1628
|
class ContainerRpcManager {
|
|
1627
1629
|
wss;
|
|
1628
1630
|
ws = null;
|
|
@@ -1917,6 +1919,21 @@ const htmlTemplate = `<!DOCTYPE html>
|
|
|
1917
1919
|
</body>
|
|
1918
1920
|
</html>
|
|
1919
1921
|
`;
|
|
1922
|
+
const fallbackSchedulerHtmlTemplate = `<!DOCTYPE html>
|
|
1923
|
+
<html lang="en">
|
|
1924
|
+
<head>
|
|
1925
|
+
<meta charset="UTF-8" />
|
|
1926
|
+
<title>Rstest Browser Scheduler</title>
|
|
1927
|
+
<script>
|
|
1928
|
+
window.__RSTEST_BROWSER_OPTIONS__ = ${OPTIONS_PLACEHOLDER};
|
|
1929
|
+
</script>
|
|
1930
|
+
</head>
|
|
1931
|
+
<body>
|
|
1932
|
+
<script type="module" src="/container-static/js/scheduler.js"></script>
|
|
1933
|
+
</body>
|
|
1934
|
+
</html>
|
|
1935
|
+
`;
|
|
1936
|
+
const VIRTUAL_MANIFEST_FILENAME = 'virtual-manifest.ts';
|
|
1920
1937
|
const destroyBrowserRuntime = async (runtime)=>{
|
|
1921
1938
|
try {
|
|
1922
1939
|
await runtime.browser?.close?.();
|
|
@@ -1954,13 +1971,15 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
1954
1971
|
const virtualManifestPlugin = new rspack.experiments.VirtualModulesPlugin({
|
|
1955
1972
|
[manifestPath]: manifestSource
|
|
1956
1973
|
});
|
|
1957
|
-
const
|
|
1958
|
-
const
|
|
1974
|
+
const containerHtmlTemplate = containerDistPath ? await promises.readFile(join(containerDistPath, 'index.html'), 'utf-8') : null;
|
|
1975
|
+
const schedulerHtmlTemplate = containerDistPath ? await promises.readFile(join(containerDistPath, 'scheduler.html'), 'utf-8').catch(()=>null) : null;
|
|
1959
1976
|
let injectedContainerHtml = null;
|
|
1977
|
+
let injectedSchedulerHtml = null;
|
|
1960
1978
|
let serializedOptions = 'null';
|
|
1961
1979
|
const setContainerOptions = (options)=>{
|
|
1962
|
-
serializedOptions =
|
|
1963
|
-
if (containerHtmlTemplate) injectedContainerHtml = containerHtmlTemplate.replace(
|
|
1980
|
+
serializedOptions = serializeForInlineScript(options);
|
|
1981
|
+
if (containerHtmlTemplate) injectedContainerHtml = containerHtmlTemplate.replace(OPTIONS_PLACEHOLDER, serializedOptions);
|
|
1982
|
+
injectedSchedulerHtml = (schedulerHtmlTemplate || fallbackSchedulerHtmlTemplate).replace(OPTIONS_PLACEHOLDER, serializedOptions);
|
|
1964
1983
|
};
|
|
1965
1984
|
const browserProjects = getBrowserProjects(context);
|
|
1966
1985
|
const firstProject = browserProjects[0];
|
|
@@ -2080,7 +2099,7 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
2080
2099
|
});
|
|
2081
2100
|
const serveContainer = containerDistPath ? sirv(containerDistPath, {
|
|
2082
2101
|
dev: false,
|
|
2083
|
-
single: '
|
|
2102
|
+
single: 'index.html'
|
|
2084
2103
|
}) : null;
|
|
2085
2104
|
const containerDevBase = containerDevServer ? new URL(containerDevServer) : null;
|
|
2086
2105
|
const respondWithDevServerHtml = async (url, res)=>{
|
|
@@ -2090,7 +2109,7 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
2090
2109
|
const response = await fetch(target);
|
|
2091
2110
|
if (!response.ok) return false;
|
|
2092
2111
|
let html = await response.text();
|
|
2093
|
-
html = html.replace(
|
|
2112
|
+
html = html.replace(OPTIONS_PLACEHOLDER, serializedOptions);
|
|
2094
2113
|
res.statusCode = response.status;
|
|
2095
2114
|
response.headers.forEach((value, key)=>{
|
|
2096
2115
|
if ('content-length' === key.toLowerCase()) return;
|
|
@@ -2148,9 +2167,14 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
2148
2167
|
}
|
|
2149
2168
|
return;
|
|
2150
2169
|
}
|
|
2151
|
-
if ('/' === url.pathname) {
|
|
2170
|
+
if ('/' === url.pathname || '/scheduler.html' === url.pathname) {
|
|
2152
2171
|
if (await respondWithDevServerHtml(url, res)) return;
|
|
2153
|
-
|
|
2172
|
+
if ('/scheduler.html' === url.pathname) {
|
|
2173
|
+
res.setHeader('Content-Type', 'text/html');
|
|
2174
|
+
res.end(injectedSchedulerHtml || (schedulerHtmlTemplate || fallbackSchedulerHtmlTemplate).replace(OPTIONS_PLACEHOLDER, 'null'));
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
const html = injectedContainerHtml || containerHtmlTemplate?.replace(OPTIONS_PLACEHOLDER, 'null');
|
|
2154
2178
|
if (html) {
|
|
2155
2179
|
res.setHeader('Content-Type', 'text/html');
|
|
2156
2180
|
res.end(html);
|
|
@@ -2244,6 +2268,39 @@ async function resolveProjectEntries(context, shardedEntries) {
|
|
|
2244
2268
|
const runBrowserController = async (context, options)=>{
|
|
2245
2269
|
const { skipOnTestRunEnd = false } = options ?? {};
|
|
2246
2270
|
const buildStart = Date.now();
|
|
2271
|
+
const browserProjects = getBrowserProjects(context);
|
|
2272
|
+
const useSchedulerPage = browserProjects.every((project)=>project.normalizedConfig.browser.headless);
|
|
2273
|
+
const buildErrorResult = async (error)=>{
|
|
2274
|
+
const elapsed = Math.max(0, Date.now() - buildStart);
|
|
2275
|
+
const errorResult = {
|
|
2276
|
+
results: [],
|
|
2277
|
+
testResults: [],
|
|
2278
|
+
duration: {
|
|
2279
|
+
totalTime: elapsed,
|
|
2280
|
+
buildTime: elapsed,
|
|
2281
|
+
testTime: 0
|
|
2282
|
+
},
|
|
2283
|
+
hasFailure: true,
|
|
2284
|
+
unhandledErrors: [
|
|
2285
|
+
error
|
|
2286
|
+
]
|
|
2287
|
+
};
|
|
2288
|
+
if (!skipOnTestRunEnd) for (const reporter of context.reporters)await reporter.onTestRunEnd?.({
|
|
2289
|
+
results: [],
|
|
2290
|
+
testResults: [],
|
|
2291
|
+
duration: errorResult.duration,
|
|
2292
|
+
snapshotSummary: context.snapshotManager.summary,
|
|
2293
|
+
getSourcemap: async ()=>null,
|
|
2294
|
+
unhandledErrors: errorResult.unhandledErrors
|
|
2295
|
+
});
|
|
2296
|
+
return errorResult;
|
|
2297
|
+
};
|
|
2298
|
+
const toError = (error)=>error instanceof Error ? error : new Error(String(error));
|
|
2299
|
+
const failWithError = async (error, cleanup)=>{
|
|
2300
|
+
ensureProcessExitCode(1);
|
|
2301
|
+
await cleanup?.();
|
|
2302
|
+
return buildErrorResult(toError(error));
|
|
2303
|
+
};
|
|
2247
2304
|
const containerDevServerEnv = process.env.RSTEST_CONTAINER_DEV_SERVER;
|
|
2248
2305
|
let containerDevServer;
|
|
2249
2306
|
let containerDistPath;
|
|
@@ -2251,16 +2308,14 @@ const runBrowserController = async (context, options)=>{
|
|
|
2251
2308
|
containerDevServer = new URL(containerDevServerEnv).toString();
|
|
2252
2309
|
logger.debug(`[Browser UI] Using dev server for container: ${containerDevServer}`);
|
|
2253
2310
|
} catch (error) {
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
return;
|
|
2311
|
+
const originalError = toError(error);
|
|
2312
|
+
originalError.message = `Invalid RSTEST_CONTAINER_DEV_SERVER value: ${originalError.message}`;
|
|
2313
|
+
return failWithError(originalError);
|
|
2257
2314
|
}
|
|
2258
2315
|
if (!containerDevServer) try {
|
|
2259
2316
|
containerDistPath = resolveContainerDist();
|
|
2260
2317
|
} catch (error) {
|
|
2261
|
-
|
|
2262
|
-
ensureProcessExitCode(1);
|
|
2263
|
-
return;
|
|
2318
|
+
return failWithError(error);
|
|
2264
2319
|
}
|
|
2265
2320
|
const projectEntries = await resolveProjectEntries(context, options?.shardedEntries);
|
|
2266
2321
|
const totalTests = projectEntries.reduce((total, item)=>total + item.testFiles.length, 0);
|
|
@@ -2276,7 +2331,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
2276
2331
|
}
|
|
2277
2332
|
const isWatchMode = 'watch' === context.command;
|
|
2278
2333
|
const tempDir = isWatchMode && watchContext.runtime ? watchContext.runtime.tempDir : isWatchMode ? join(context.rootPath, TEMP_RSTEST_OUTPUT_DIR, 'browser', 'watch') : join(context.rootPath, TEMP_RSTEST_OUTPUT_DIR, 'browser', Date.now().toString());
|
|
2279
|
-
const manifestPath = join(tempDir,
|
|
2334
|
+
const manifestPath = join(tempDir, VIRTUAL_MANIFEST_FILENAME);
|
|
2280
2335
|
const manifestSource = generateManifestModule({
|
|
2281
2336
|
manifestPath,
|
|
2282
2337
|
entries: projectEntries
|
|
@@ -2302,13 +2357,12 @@ const runBrowserController = async (context, options)=>{
|
|
|
2302
2357
|
containerDevServer
|
|
2303
2358
|
});
|
|
2304
2359
|
} catch (error) {
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
})
|
|
2311
|
-
return;
|
|
2360
|
+
return failWithError(error, async ()=>{
|
|
2361
|
+
await promises.rm(tempDir, {
|
|
2362
|
+
recursive: true,
|
|
2363
|
+
force: true
|
|
2364
|
+
}).catch(()=>{});
|
|
2365
|
+
});
|
|
2312
2366
|
}
|
|
2313
2367
|
if (isWatchMode) {
|
|
2314
2368
|
watchContext.runtime = runtime;
|
|
@@ -2321,14 +2375,14 @@ const runBrowserController = async (context, options)=>{
|
|
|
2321
2375
|
testPath: normalize(testPath),
|
|
2322
2376
|
projectName: entry.project.name
|
|
2323
2377
|
})));
|
|
2324
|
-
const
|
|
2325
|
-
const projectRuntimeConfigs = browserProjectsForRuntime.map((project)=>({
|
|
2378
|
+
const projectRuntimeConfigs = browserProjects.map((project)=>({
|
|
2326
2379
|
name: project.name,
|
|
2327
2380
|
environmentName: project.environmentName,
|
|
2328
2381
|
projectRoot: normalize(project.rootPath),
|
|
2329
|
-
runtimeConfig: serializableConfig(getRuntimeConfigFromProject(project))
|
|
2382
|
+
runtimeConfig: serializableConfig(getRuntimeConfigFromProject(project)),
|
|
2383
|
+
viewport: project.normalizedConfig.browser.viewport
|
|
2330
2384
|
}));
|
|
2331
|
-
const maxTestTimeoutForRpc = Math.max(...
|
|
2385
|
+
const maxTestTimeoutForRpc = Math.max(...browserProjects.map((p)=>p.normalizedConfig.testTimeout ?? 5000));
|
|
2332
2386
|
const hostOptions = {
|
|
2333
2387
|
rootPath: normalize(context.rootPath),
|
|
2334
2388
|
projects: projectRuntimeConfigs,
|
|
@@ -2374,7 +2428,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
2374
2428
|
}
|
|
2375
2429
|
containerPage.on('console', (msg)=>{
|
|
2376
2430
|
const text = msg.text();
|
|
2377
|
-
if (text.
|
|
2431
|
+
if (text.startsWith('[Container]') || text.startsWith('[Runner]') || text.startsWith('[Scheduler]')) logger.log(color.gray(`[Browser Console] ${text}`));
|
|
2378
2432
|
});
|
|
2379
2433
|
}
|
|
2380
2434
|
const createRpcMethods = ()=>({
|
|
@@ -2457,12 +2511,17 @@ const runBrowserController = async (context, options)=>{
|
|
|
2457
2511
|
if (isWatchMode) runtime.rpcManager = rpcManager;
|
|
2458
2512
|
}
|
|
2459
2513
|
if (isNewPage) {
|
|
2460
|
-
|
|
2514
|
+
const pagePath = useSchedulerPage ? '/scheduler.html' : '/';
|
|
2515
|
+
if (useSchedulerPage) {
|
|
2516
|
+
const serializedOptions = serializeForInlineScript(hostOptions);
|
|
2517
|
+
await containerPage.addInitScript(`window.__RSTEST_BROWSER_OPTIONS__ = ${serializedOptions};`);
|
|
2518
|
+
}
|
|
2519
|
+
await containerPage.goto(`http://localhost:${port}${pagePath}`, {
|
|
2461
2520
|
waitUntil: 'load'
|
|
2462
2521
|
});
|
|
2463
|
-
logger.log(color.cyan(`\nBrowser mode opened at http://localhost:${port}
|
|
2522
|
+
logger.log(color.cyan(`\nBrowser mode opened at http://localhost:${port}${pagePath}\n`));
|
|
2464
2523
|
}
|
|
2465
|
-
const maxTestTimeout = Math.max(...
|
|
2524
|
+
const maxTestTimeout = Math.max(...browserProjects.map((p)=>p.normalizedConfig.testTimeout ?? 5000));
|
|
2466
2525
|
const totalTimeoutMs = maxTestTimeout * allTestFiles.length + 30000;
|
|
2467
2526
|
let timeoutId;
|
|
2468
2527
|
const testTimeout = new Promise((resolve)=>{
|
|
@@ -2497,12 +2556,16 @@ const runBrowserController = async (context, options)=>{
|
|
|
2497
2556
|
for (const testFile of affectedFiles)await rpcManager.reloadTestFile(testFile);
|
|
2498
2557
|
} else if (!filesChanged) logger.log(color.cyan('Tests will be re-executed automatically\n'));
|
|
2499
2558
|
};
|
|
2500
|
-
if (!isWatchMode)
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2559
|
+
if (!isWatchMode) {
|
|
2560
|
+
try {
|
|
2561
|
+
await containerPage.close();
|
|
2562
|
+
} catch {}
|
|
2563
|
+
try {
|
|
2564
|
+
await containerContext.close();
|
|
2565
|
+
} catch {}
|
|
2566
|
+
await destroyBrowserRuntime(runtime);
|
|
2505
2567
|
}
|
|
2568
|
+
if (fatalError) return failWithError(fatalError);
|
|
2506
2569
|
const duration = {
|
|
2507
2570
|
totalTime: buildTime + testTime,
|
|
2508
2571
|
buildTime,
|
|
@@ -2538,7 +2601,7 @@ const listBrowserTests = async (context, options)=>{
|
|
|
2538
2601
|
close: async ()=>{}
|
|
2539
2602
|
};
|
|
2540
2603
|
const tempDir = join(context.rootPath, TEMP_RSTEST_OUTPUT_DIR, 'browser', `list-${Date.now()}`);
|
|
2541
|
-
const manifestPath = join(tempDir,
|
|
2604
|
+
const manifestPath = join(tempDir, VIRTUAL_MANIFEST_FILENAME);
|
|
2542
2605
|
const manifestSource = generateManifestModule({
|
|
2543
2606
|
manifestPath,
|
|
2544
2607
|
entries: projectEntries
|
|
@@ -2565,7 +2628,8 @@ const listBrowserTests = async (context, options)=>{
|
|
|
2565
2628
|
name: project.name,
|
|
2566
2629
|
environmentName: project.environmentName,
|
|
2567
2630
|
projectRoot: normalize(project.rootPath),
|
|
2568
|
-
runtimeConfig: serializableConfig(getRuntimeConfigFromProject(project))
|
|
2631
|
+
runtimeConfig: serializableConfig(getRuntimeConfigFromProject(project)),
|
|
2632
|
+
viewport: project.normalizedConfig.browser.viewport
|
|
2569
2633
|
}));
|
|
2570
2634
|
const maxTestTimeoutForRpc = Math.max(...browserProjects.map((p)=>p.normalizedConfig.testTimeout ?? 5000));
|
|
2571
2635
|
const hostOptions = {
|
|
@@ -2621,7 +2685,7 @@ const listBrowserTests = async (context, options)=>{
|
|
|
2621
2685
|
logger.debug(`[List] Unexpected message: ${message.type}`);
|
|
2622
2686
|
}
|
|
2623
2687
|
});
|
|
2624
|
-
const serializedOptions =
|
|
2688
|
+
const serializedOptions = serializeForInlineScript(hostOptions);
|
|
2625
2689
|
await page.addInitScript(`window.__RSTEST_BROWSER_OPTIONS__ = ${serializedOptions};`);
|
|
2626
2690
|
await page.goto(`http://localhost:${port}/runner.html`, {
|
|
2627
2691
|
waitUntil: 'load'
|
|
@@ -2672,10 +2736,134 @@ const listBrowserTests = async (context, options)=>{
|
|
|
2672
2736
|
close: cleanup
|
|
2673
2737
|
};
|
|
2674
2738
|
};
|
|
2739
|
+
const BROWSER_VIEWPORT_PRESET_IDS = [
|
|
2740
|
+
'iPhoneSE',
|
|
2741
|
+
'iPhoneXR',
|
|
2742
|
+
'iPhone12Pro',
|
|
2743
|
+
'iPhone14ProMax',
|
|
2744
|
+
'Pixel7',
|
|
2745
|
+
'SamsungGalaxyS8Plus',
|
|
2746
|
+
'SamsungGalaxyS20Ultra',
|
|
2747
|
+
'iPadMini',
|
|
2748
|
+
'iPadAir',
|
|
2749
|
+
'iPadPro',
|
|
2750
|
+
'SurfacePro7',
|
|
2751
|
+
'SurfaceDuo',
|
|
2752
|
+
'GalaxyZFold5',
|
|
2753
|
+
'AsusZenbookFold',
|
|
2754
|
+
'SamsungGalaxyA51A71',
|
|
2755
|
+
'NestHub',
|
|
2756
|
+
'NestHubMax'
|
|
2757
|
+
];
|
|
2758
|
+
const BROWSER_VIEWPORT_PRESET_DIMENSIONS = {
|
|
2759
|
+
iPhoneSE: {
|
|
2760
|
+
width: 375,
|
|
2761
|
+
height: 667
|
|
2762
|
+
},
|
|
2763
|
+
iPhoneXR: {
|
|
2764
|
+
width: 414,
|
|
2765
|
+
height: 896
|
|
2766
|
+
},
|
|
2767
|
+
iPhone12Pro: {
|
|
2768
|
+
width: 390,
|
|
2769
|
+
height: 844
|
|
2770
|
+
},
|
|
2771
|
+
iPhone14ProMax: {
|
|
2772
|
+
width: 430,
|
|
2773
|
+
height: 932
|
|
2774
|
+
},
|
|
2775
|
+
Pixel7: {
|
|
2776
|
+
width: 412,
|
|
2777
|
+
height: 915
|
|
2778
|
+
},
|
|
2779
|
+
SamsungGalaxyS8Plus: {
|
|
2780
|
+
width: 360,
|
|
2781
|
+
height: 740
|
|
2782
|
+
},
|
|
2783
|
+
SamsungGalaxyS20Ultra: {
|
|
2784
|
+
width: 412,
|
|
2785
|
+
height: 915
|
|
2786
|
+
},
|
|
2787
|
+
iPadMini: {
|
|
2788
|
+
width: 768,
|
|
2789
|
+
height: 1024
|
|
2790
|
+
},
|
|
2791
|
+
iPadAir: {
|
|
2792
|
+
width: 820,
|
|
2793
|
+
height: 1180
|
|
2794
|
+
},
|
|
2795
|
+
iPadPro: {
|
|
2796
|
+
width: 1024,
|
|
2797
|
+
height: 1366
|
|
2798
|
+
},
|
|
2799
|
+
SurfacePro7: {
|
|
2800
|
+
width: 912,
|
|
2801
|
+
height: 1368
|
|
2802
|
+
},
|
|
2803
|
+
SurfaceDuo: {
|
|
2804
|
+
width: 540,
|
|
2805
|
+
height: 720
|
|
2806
|
+
},
|
|
2807
|
+
GalaxyZFold5: {
|
|
2808
|
+
width: 344,
|
|
2809
|
+
height: 882
|
|
2810
|
+
},
|
|
2811
|
+
AsusZenbookFold: {
|
|
2812
|
+
width: 853,
|
|
2813
|
+
height: 1280
|
|
2814
|
+
},
|
|
2815
|
+
SamsungGalaxyA51A71: {
|
|
2816
|
+
width: 412,
|
|
2817
|
+
height: 914
|
|
2818
|
+
},
|
|
2819
|
+
NestHub: {
|
|
2820
|
+
width: 1024,
|
|
2821
|
+
height: 600
|
|
2822
|
+
},
|
|
2823
|
+
NestHubMax: {
|
|
2824
|
+
width: 1280,
|
|
2825
|
+
height: 800
|
|
2826
|
+
}
|
|
2827
|
+
};
|
|
2828
|
+
const resolveBrowserViewportPreset = (presetId)=>{
|
|
2829
|
+
const size = BROWSER_VIEWPORT_PRESET_DIMENSIONS[presetId];
|
|
2830
|
+
return size ?? null;
|
|
2831
|
+
};
|
|
2832
|
+
const SUPPORTED_PROVIDERS = [
|
|
2833
|
+
'playwright'
|
|
2834
|
+
];
|
|
2835
|
+
const isPlainObject = (value)=>'[object Object]' === Object.prototype.toString.call(value);
|
|
2836
|
+
const validateViewport = (viewport)=>{
|
|
2837
|
+
if (null == viewport) return;
|
|
2838
|
+
if ('string' == typeof viewport) {
|
|
2839
|
+
const presetId = viewport.trim();
|
|
2840
|
+
if (!presetId) throw new Error('browser.viewport must be a non-empty preset id.');
|
|
2841
|
+
if (!resolveBrowserViewportPreset(presetId)) throw new Error(`browser.viewport must be a valid preset id. Received: ${viewport}`);
|
|
2842
|
+
return;
|
|
2843
|
+
}
|
|
2844
|
+
if (isPlainObject(viewport)) {
|
|
2845
|
+
const width = viewport.width;
|
|
2846
|
+
const height = viewport.height;
|
|
2847
|
+
if (!Number.isFinite(width) || width <= 0) throw new Error('browser.viewport.width must be a positive number.');
|
|
2848
|
+
if (!Number.isFinite(height) || height <= 0) throw new Error('browser.viewport.height must be a positive number.');
|
|
2849
|
+
return;
|
|
2850
|
+
}
|
|
2851
|
+
throw new Error('browser.viewport must be either a preset id or { width, height }.');
|
|
2852
|
+
};
|
|
2853
|
+
const validateBrowserConfig = (context)=>{
|
|
2854
|
+
for (const project of context.projects){
|
|
2855
|
+
const browser = project.normalizedConfig.browser;
|
|
2856
|
+
if (browser.enabled) {
|
|
2857
|
+
if (!browser.provider) throw new Error('browser.provider is required when browser.enabled is true.');
|
|
2858
|
+
if (!SUPPORTED_PROVIDERS.includes(browser.provider)) throw new Error(`browser.provider must be one of: ${SUPPORTED_PROVIDERS.join(', ')}.`);
|
|
2859
|
+
validateViewport(browser.viewport);
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
};
|
|
2675
2863
|
async function runBrowserTests(context, options) {
|
|
2676
2864
|
return runBrowserController(context, options);
|
|
2677
2865
|
}
|
|
2678
2866
|
async function src_listBrowserTests(context) {
|
|
2679
2867
|
return listBrowserTests(context);
|
|
2680
2868
|
}
|
|
2681
|
-
export { runBrowserTests, src_listBrowserTests as listBrowserTests };
|
|
2869
|
+
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.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,13 +52,13 @@
|
|
|
52
52
|
"picocolors": "^1.1.1",
|
|
53
53
|
"picomatch": "^4.0.3",
|
|
54
54
|
"playwright": "^1.49.1",
|
|
55
|
-
"@rstest/
|
|
55
|
+
"@rstest/browser-ui": "0.0.0",
|
|
56
56
|
"@rstest/tsconfig": "0.0.1",
|
|
57
|
-
"@rstest/
|
|
57
|
+
"@rstest/core": "0.8.4"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"playwright": "^1.49.1",
|
|
61
|
-
"@rstest/core": "^0.8.
|
|
61
|
+
"@rstest/core": "^0.8.4"
|
|
62
62
|
},
|
|
63
63
|
"peerDependenciesMeta": {
|
|
64
64
|
"playwright": {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Rstest } from '@rstest/core/browser';
|
|
2
|
+
import { resolveBrowserViewportPreset } from './viewportPresets';
|
|
3
|
+
|
|
4
|
+
const SUPPORTED_PROVIDERS = ['playwright'] as const;
|
|
5
|
+
|
|
6
|
+
const isPlainObject = (value: unknown): value is Record<string, unknown> => {
|
|
7
|
+
return Object.prototype.toString.call(value) === '[object Object]';
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const validateViewport = (viewport: unknown): void => {
|
|
11
|
+
if (viewport == null) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (typeof viewport === 'string') {
|
|
16
|
+
const presetId = viewport.trim();
|
|
17
|
+
if (!presetId) {
|
|
18
|
+
throw new Error('browser.viewport must be a non-empty preset id.');
|
|
19
|
+
}
|
|
20
|
+
if (!resolveBrowserViewportPreset(presetId)) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`browser.viewport must be a valid preset id. Received: ${viewport}`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (isPlainObject(viewport)) {
|
|
29
|
+
const width = (viewport as any).width;
|
|
30
|
+
const height = (viewport as any).height;
|
|
31
|
+
if (!Number.isFinite(width) || width <= 0) {
|
|
32
|
+
throw new Error('browser.viewport.width must be a positive number.');
|
|
33
|
+
}
|
|
34
|
+
if (!Number.isFinite(height) || height <= 0) {
|
|
35
|
+
throw new Error('browser.viewport.height must be a positive number.');
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
throw new Error(
|
|
41
|
+
'browser.viewport must be either a preset id or { width, height }.',
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const validateBrowserConfig = (context: Rstest): void => {
|
|
46
|
+
for (const project of context.projects) {
|
|
47
|
+
const browser = project.normalizedConfig.browser;
|
|
48
|
+
if (!browser.enabled) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!browser.provider) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
'browser.provider is required when browser.enabled is true.',
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!SUPPORTED_PROVIDERS.includes(browser.provider)) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`browser.provider must be one of: ${SUPPORTED_PROVIDERS.join(', ')}.`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
validateViewport(browser.viewport);
|
|
65
|
+
}
|
|
66
|
+
};
|