@rstest/browser 0.8.3 → 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 +224 -37
- 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 +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
|
@@ -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,20 @@ 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
|
+
`;
|
|
1920
1936
|
const VIRTUAL_MANIFEST_FILENAME = 'virtual-manifest.ts';
|
|
1921
1937
|
const destroyBrowserRuntime = async (runtime)=>{
|
|
1922
1938
|
try {
|
|
@@ -1955,13 +1971,15 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
1955
1971
|
const virtualManifestPlugin = new rspack.experiments.VirtualModulesPlugin({
|
|
1956
1972
|
[manifestPath]: manifestSource
|
|
1957
1973
|
});
|
|
1958
|
-
const
|
|
1959
|
-
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;
|
|
1960
1976
|
let injectedContainerHtml = null;
|
|
1977
|
+
let injectedSchedulerHtml = null;
|
|
1961
1978
|
let serializedOptions = 'null';
|
|
1962
1979
|
const setContainerOptions = (options)=>{
|
|
1963
|
-
serializedOptions =
|
|
1964
|
-
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);
|
|
1965
1983
|
};
|
|
1966
1984
|
const browserProjects = getBrowserProjects(context);
|
|
1967
1985
|
const firstProject = browserProjects[0];
|
|
@@ -2081,7 +2099,7 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
2081
2099
|
});
|
|
2082
2100
|
const serveContainer = containerDistPath ? sirv(containerDistPath, {
|
|
2083
2101
|
dev: false,
|
|
2084
|
-
single: '
|
|
2102
|
+
single: 'index.html'
|
|
2085
2103
|
}) : null;
|
|
2086
2104
|
const containerDevBase = containerDevServer ? new URL(containerDevServer) : null;
|
|
2087
2105
|
const respondWithDevServerHtml = async (url, res)=>{
|
|
@@ -2091,7 +2109,7 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
2091
2109
|
const response = await fetch(target);
|
|
2092
2110
|
if (!response.ok) return false;
|
|
2093
2111
|
let html = await response.text();
|
|
2094
|
-
html = html.replace(
|
|
2112
|
+
html = html.replace(OPTIONS_PLACEHOLDER, serializedOptions);
|
|
2095
2113
|
res.statusCode = response.status;
|
|
2096
2114
|
response.headers.forEach((value, key)=>{
|
|
2097
2115
|
if ('content-length' === key.toLowerCase()) return;
|
|
@@ -2149,9 +2167,14 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
2149
2167
|
}
|
|
2150
2168
|
return;
|
|
2151
2169
|
}
|
|
2152
|
-
if ('/' === url.pathname) {
|
|
2170
|
+
if ('/' === url.pathname || '/scheduler.html' === url.pathname) {
|
|
2153
2171
|
if (await respondWithDevServerHtml(url, res)) return;
|
|
2154
|
-
|
|
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');
|
|
2155
2178
|
if (html) {
|
|
2156
2179
|
res.setHeader('Content-Type', 'text/html');
|
|
2157
2180
|
res.end(html);
|
|
@@ -2245,6 +2268,39 @@ async function resolveProjectEntries(context, shardedEntries) {
|
|
|
2245
2268
|
const runBrowserController = async (context, options)=>{
|
|
2246
2269
|
const { skipOnTestRunEnd = false } = options ?? {};
|
|
2247
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
|
+
};
|
|
2248
2304
|
const containerDevServerEnv = process.env.RSTEST_CONTAINER_DEV_SERVER;
|
|
2249
2305
|
let containerDevServer;
|
|
2250
2306
|
let containerDistPath;
|
|
@@ -2252,16 +2308,14 @@ const runBrowserController = async (context, options)=>{
|
|
|
2252
2308
|
containerDevServer = new URL(containerDevServerEnv).toString();
|
|
2253
2309
|
logger.debug(`[Browser UI] Using dev server for container: ${containerDevServer}`);
|
|
2254
2310
|
} catch (error) {
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
return;
|
|
2311
|
+
const originalError = toError(error);
|
|
2312
|
+
originalError.message = `Invalid RSTEST_CONTAINER_DEV_SERVER value: ${originalError.message}`;
|
|
2313
|
+
return failWithError(originalError);
|
|
2258
2314
|
}
|
|
2259
2315
|
if (!containerDevServer) try {
|
|
2260
2316
|
containerDistPath = resolveContainerDist();
|
|
2261
2317
|
} catch (error) {
|
|
2262
|
-
|
|
2263
|
-
ensureProcessExitCode(1);
|
|
2264
|
-
return;
|
|
2318
|
+
return failWithError(error);
|
|
2265
2319
|
}
|
|
2266
2320
|
const projectEntries = await resolveProjectEntries(context, options?.shardedEntries);
|
|
2267
2321
|
const totalTests = projectEntries.reduce((total, item)=>total + item.testFiles.length, 0);
|
|
@@ -2303,13 +2357,12 @@ const runBrowserController = async (context, options)=>{
|
|
|
2303
2357
|
containerDevServer
|
|
2304
2358
|
});
|
|
2305
2359
|
} catch (error) {
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
})
|
|
2312
|
-
return;
|
|
2360
|
+
return failWithError(error, async ()=>{
|
|
2361
|
+
await promises.rm(tempDir, {
|
|
2362
|
+
recursive: true,
|
|
2363
|
+
force: true
|
|
2364
|
+
}).catch(()=>{});
|
|
2365
|
+
});
|
|
2313
2366
|
}
|
|
2314
2367
|
if (isWatchMode) {
|
|
2315
2368
|
watchContext.runtime = runtime;
|
|
@@ -2322,14 +2375,14 @@ const runBrowserController = async (context, options)=>{
|
|
|
2322
2375
|
testPath: normalize(testPath),
|
|
2323
2376
|
projectName: entry.project.name
|
|
2324
2377
|
})));
|
|
2325
|
-
const
|
|
2326
|
-
const projectRuntimeConfigs = browserProjectsForRuntime.map((project)=>({
|
|
2378
|
+
const projectRuntimeConfigs = browserProjects.map((project)=>({
|
|
2327
2379
|
name: project.name,
|
|
2328
2380
|
environmentName: project.environmentName,
|
|
2329
2381
|
projectRoot: normalize(project.rootPath),
|
|
2330
|
-
runtimeConfig: serializableConfig(getRuntimeConfigFromProject(project))
|
|
2382
|
+
runtimeConfig: serializableConfig(getRuntimeConfigFromProject(project)),
|
|
2383
|
+
viewport: project.normalizedConfig.browser.viewport
|
|
2331
2384
|
}));
|
|
2332
|
-
const maxTestTimeoutForRpc = Math.max(...
|
|
2385
|
+
const maxTestTimeoutForRpc = Math.max(...browserProjects.map((p)=>p.normalizedConfig.testTimeout ?? 5000));
|
|
2333
2386
|
const hostOptions = {
|
|
2334
2387
|
rootPath: normalize(context.rootPath),
|
|
2335
2388
|
projects: projectRuntimeConfigs,
|
|
@@ -2375,7 +2428,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
2375
2428
|
}
|
|
2376
2429
|
containerPage.on('console', (msg)=>{
|
|
2377
2430
|
const text = msg.text();
|
|
2378
|
-
if (text.
|
|
2431
|
+
if (text.startsWith('[Container]') || text.startsWith('[Runner]') || text.startsWith('[Scheduler]')) logger.log(color.gray(`[Browser Console] ${text}`));
|
|
2379
2432
|
});
|
|
2380
2433
|
}
|
|
2381
2434
|
const createRpcMethods = ()=>({
|
|
@@ -2458,12 +2511,17 @@ const runBrowserController = async (context, options)=>{
|
|
|
2458
2511
|
if (isWatchMode) runtime.rpcManager = rpcManager;
|
|
2459
2512
|
}
|
|
2460
2513
|
if (isNewPage) {
|
|
2461
|
-
|
|
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}`, {
|
|
2462
2520
|
waitUntil: 'load'
|
|
2463
2521
|
});
|
|
2464
|
-
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`));
|
|
2465
2523
|
}
|
|
2466
|
-
const maxTestTimeout = Math.max(...
|
|
2524
|
+
const maxTestTimeout = Math.max(...browserProjects.map((p)=>p.normalizedConfig.testTimeout ?? 5000));
|
|
2467
2525
|
const totalTimeoutMs = maxTestTimeout * allTestFiles.length + 30000;
|
|
2468
2526
|
let timeoutId;
|
|
2469
2527
|
const testTimeout = new Promise((resolve)=>{
|
|
@@ -2498,12 +2556,16 @@ const runBrowserController = async (context, options)=>{
|
|
|
2498
2556
|
for (const testFile of affectedFiles)await rpcManager.reloadTestFile(testFile);
|
|
2499
2557
|
} else if (!filesChanged) logger.log(color.cyan('Tests will be re-executed automatically\n'));
|
|
2500
2558
|
};
|
|
2501
|
-
if (!isWatchMode)
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2559
|
+
if (!isWatchMode) {
|
|
2560
|
+
try {
|
|
2561
|
+
await containerPage.close();
|
|
2562
|
+
} catch {}
|
|
2563
|
+
try {
|
|
2564
|
+
await containerContext.close();
|
|
2565
|
+
} catch {}
|
|
2566
|
+
await destroyBrowserRuntime(runtime);
|
|
2506
2567
|
}
|
|
2568
|
+
if (fatalError) return failWithError(fatalError);
|
|
2507
2569
|
const duration = {
|
|
2508
2570
|
totalTime: buildTime + testTime,
|
|
2509
2571
|
buildTime,
|
|
@@ -2566,7 +2628,8 @@ const listBrowserTests = async (context, options)=>{
|
|
|
2566
2628
|
name: project.name,
|
|
2567
2629
|
environmentName: project.environmentName,
|
|
2568
2630
|
projectRoot: normalize(project.rootPath),
|
|
2569
|
-
runtimeConfig: serializableConfig(getRuntimeConfigFromProject(project))
|
|
2631
|
+
runtimeConfig: serializableConfig(getRuntimeConfigFromProject(project)),
|
|
2632
|
+
viewport: project.normalizedConfig.browser.viewport
|
|
2570
2633
|
}));
|
|
2571
2634
|
const maxTestTimeoutForRpc = Math.max(...browserProjects.map((p)=>p.normalizedConfig.testTimeout ?? 5000));
|
|
2572
2635
|
const hostOptions = {
|
|
@@ -2622,7 +2685,7 @@ const listBrowserTests = async (context, options)=>{
|
|
|
2622
2685
|
logger.debug(`[List] Unexpected message: ${message.type}`);
|
|
2623
2686
|
}
|
|
2624
2687
|
});
|
|
2625
|
-
const serializedOptions =
|
|
2688
|
+
const serializedOptions = serializeForInlineScript(hostOptions);
|
|
2626
2689
|
await page.addInitScript(`window.__RSTEST_BROWSER_OPTIONS__ = ${serializedOptions};`);
|
|
2627
2690
|
await page.goto(`http://localhost:${port}/runner.html`, {
|
|
2628
2691
|
waitUntil: 'load'
|
|
@@ -2673,10 +2736,134 @@ const listBrowserTests = async (context, options)=>{
|
|
|
2673
2736
|
close: cleanup
|
|
2674
2737
|
};
|
|
2675
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
|
+
};
|
|
2676
2863
|
async function runBrowserTests(context, options) {
|
|
2677
2864
|
return runBrowserController(context, options);
|
|
2678
2865
|
}
|
|
2679
2866
|
async function src_listBrowserTests(context) {
|
|
2680
2867
|
return listBrowserTests(context);
|
|
2681
2868
|
}
|
|
2682
|
-
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"
|
|
@@ -53,12 +53,12 @@
|
|
|
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.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
|
+
};
|