@rstest/browser 0.8.1 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,4 +4,5 @@ declare global {
4
4
  __RSTEST_BROWSER_OPTIONS__?: BrowserHostConfig;
5
5
  __rstest_dispatch__?: (message: BrowserClientMessage) => void;
6
6
  }
7
+ var __coverage__: Record<string, unknown> | undefined;
7
8
  }
@@ -14,4 +14,8 @@ export type ListBrowserTestsResult = {
14
14
  * This function creates a headless browser runtime, loads test files,
15
15
  * and collects their test structure (describe/test declarations).
16
16
  */
17
- export declare const listBrowserTests: (context: Rstest) => Promise<ListBrowserTestsResult>;
17
+ export declare const listBrowserTests: (context: Rstest, options?: {
18
+ shardedEntries?: Map<string, {
19
+ entries: Record<string, string>;
20
+ }>;
21
+ }) => Promise<ListBrowserTestsResult>;
package/dist/index.js CHANGED
@@ -2,7 +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 { TEMP_RSTEST_OUTPUT_DIR, color, getSetupFiles, getTestEntries, isDebug, logger, rsbuild, serializableConfig } from "@rstest/core/browser";
5
+ import { TEMP_RSTEST_OUTPUT_DIR, color, getSetupFiles, getTestEntries, isDebug, loadCoverageProvider, logger, rsbuild, serializableConfig } from "@rstest/core/browser";
6
6
  import open_editor from "open-editor";
7
7
  import { basename, dirname, join, normalize, relative, resolve as external_pathe_resolve } from "pathe";
8
8
  import sirv from "sirv";
@@ -1966,6 +1966,7 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
1966
1966
  const firstProject = browserProjects[0];
1967
1967
  const userPlugins = firstProject?.normalizedConfig.plugins || [];
1968
1968
  const userRsbuildConfig = firstProject?.normalizedConfig ?? {};
1969
+ const browserConfig = firstProject?.normalizedConfig.browser ?? context.normalizedConfig.browser;
1969
1970
  const browserRuntimePath = fileURLToPath(import.meta.resolve('@rstest/core/browser-runtime'));
1970
1971
  const rstestInternalAliases = {
1971
1972
  '@rstest/browser-manifest': manifestPath,
@@ -1981,8 +1982,8 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
1981
1982
  plugins: userPlugins,
1982
1983
  server: {
1983
1984
  printUrls: false,
1984
- port: context.normalizedConfig.browser.port ?? 4000,
1985
- strictPort: context.normalizedConfig.browser.strictPort
1985
+ port: browserConfig.port ?? 4000,
1986
+ strictPort: browserConfig.strictPort
1986
1987
  },
1987
1988
  dev: {
1988
1989
  client: {
@@ -1990,7 +1991,7 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
1990
1991
  }
1991
1992
  },
1992
1993
  environments: {
1993
- web: {}
1994
+ [firstProject?.environmentName || 'web']: {}
1994
1995
  }
1995
1996
  }
1996
1997
  });
@@ -2067,6 +2068,13 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
2067
2068
  }
2068
2069
  }
2069
2070
  ]);
2071
+ const coverage = firstProject?.normalizedConfig.coverage;
2072
+ if (coverage?.enabled && 'list' !== context.command) {
2073
+ const { pluginCoverage } = await loadCoverageProvider(coverage, context.rootPath);
2074
+ rsbuildInstance.addPlugins([
2075
+ pluginCoverage(coverage)
2076
+ ]);
2077
+ }
2070
2078
  const devServer = await rsbuildInstance.createDevServer({
2071
2079
  getPortSilently: true
2072
2080
  });
@@ -2177,7 +2185,7 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
2177
2185
  const wsPort = wss.address().port;
2178
2186
  logger.debug(`[Browser UI] WebSocket server started on port ${wsPort}`);
2179
2187
  let browserLauncher;
2180
- const browserName = context.normalizedConfig.browser.browser;
2188
+ const browserName = browserConfig.browser;
2181
2189
  try {
2182
2190
  const playwright = await import("playwright");
2183
2191
  browserLauncher = playwright[browserName];
@@ -2189,7 +2197,7 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
2189
2197
  let browser;
2190
2198
  try {
2191
2199
  browser = await browserLauncher.launch({
2192
- headless: forceHeadless ?? context.normalizedConfig.browser.headless,
2200
+ headless: forceHeadless ?? browserConfig.headless,
2193
2201
  args: 'chromium' === browserName ? [
2194
2202
  '--disable-popup-blocking',
2195
2203
  '--no-first-run',
@@ -2214,6 +2222,25 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
2214
2222
  wss
2215
2223
  };
2216
2224
  };
2225
+ async function resolveProjectEntries(context, shardedEntries) {
2226
+ if (shardedEntries) {
2227
+ const browserProjects = getBrowserProjects(context);
2228
+ const projectEntries = [];
2229
+ for (const project of browserProjects){
2230
+ const entryInfo = shardedEntries.get(project.environmentName);
2231
+ if (entryInfo && Object.keys(entryInfo.entries).length > 0) {
2232
+ const setup = getSetupFiles(project.normalizedConfig.setupFiles, project.rootPath);
2233
+ projectEntries.push({
2234
+ project,
2235
+ setupFiles: Object.values(setup),
2236
+ testFiles: Object.values(entryInfo.entries)
2237
+ });
2238
+ }
2239
+ }
2240
+ return projectEntries;
2241
+ }
2242
+ return collectProjectEntries(context);
2243
+ }
2217
2244
  const runBrowserController = async (context, options)=>{
2218
2245
  const { skipOnTestRunEnd = false } = options ?? {};
2219
2246
  const buildStart = Date.now();
@@ -2235,11 +2262,15 @@ const runBrowserController = async (context, options)=>{
2235
2262
  ensureProcessExitCode(1);
2236
2263
  return;
2237
2264
  }
2238
- const projectEntries = await collectProjectEntries(context);
2265
+ const projectEntries = await resolveProjectEntries(context, options?.shardedEntries);
2239
2266
  const totalTests = projectEntries.reduce((total, item)=>total + item.testFiles.length, 0);
2240
2267
  if (0 === totalTests) {
2241
2268
  const code = context.normalizedConfig.passWithNoTests ? 0 : 1;
2242
- if (!skipOnTestRunEnd) logger.log(color[code ? 'red' : 'yellow'](`No test files found, exiting with code ${code}.`));
2269
+ if (!skipOnTestRunEnd) {
2270
+ const message = `No test files found, exiting with code ${code}.`;
2271
+ if (0 === code) logger.log(color.yellow(message));
2272
+ else logger.error(color.red(message));
2273
+ }
2243
2274
  if (0 !== code) ensureProcessExitCode(code);
2244
2275
  return;
2245
2276
  }
@@ -2499,8 +2530,8 @@ const runBrowserController = async (context, options)=>{
2499
2530
  }
2500
2531
  return result;
2501
2532
  };
2502
- const listBrowserTests = async (context)=>{
2503
- const projectEntries = await collectProjectEntries(context);
2533
+ const listBrowserTests = async (context, options)=>{
2534
+ const projectEntries = await resolveProjectEntries(context, options?.shardedEntries);
2504
2535
  const totalTests = projectEntries.reduce((total, item)=>total + item.testFiles.length, 0);
2505
2536
  if (0 === totalTests) return {
2506
2537
  list: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rstest/browser",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
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/browser-ui": "0.0.0",
56
- "@rstest/core": "0.8.1",
57
- "@rstest/tsconfig": "0.0.1"
55
+ "@rstest/core": "0.8.2",
56
+ "@rstest/tsconfig": "0.0.1",
57
+ "@rstest/browser-ui": "0.0.0"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "playwright": "^1.49.1",
61
- "@rstest/core": "^0.8.1"
61
+ "@rstest/core": "^0.8.2"
62
62
  },
63
63
  "peerDependenciesMeta": {
64
64
  "playwright": {
@@ -7,6 +7,7 @@ import {
7
7
  projectTestContexts,
8
8
  } from '@rstest/browser-manifest';
9
9
  import type {
10
+ CoverageMapData,
10
11
  RunnerHooks,
11
12
  RuntimeConfig,
12
13
  WorkerState,
@@ -35,6 +36,8 @@ declare global {
35
36
  __RSTEST_BROWSER_OPTIONS__?: BrowserHostConfig;
36
37
  __rstest_dispatch__?: (message: BrowserClientMessage) => void;
37
38
  }
39
+ // eslint-disable-next-line no-var
40
+ var __coverage__: Record<string, unknown> | undefined;
38
41
  }
39
42
 
40
43
  /**
@@ -589,6 +592,11 @@ const run = async () => {
589
592
  runtime.api,
590
593
  );
591
594
 
595
+ // Collect coverage data from global __coverage__ object
596
+ if (globalThis.__coverage__) {
597
+ result.coverage = globalThis.__coverage__ as CoverageMapData;
598
+ }
599
+
592
600
  send({
593
601
  type: 'file-complete',
594
602
  payload: result,
@@ -12,6 +12,7 @@ import {
12
12
  getTestEntries,
13
13
  isDebug,
14
14
  type ListCommandResult,
15
+ loadCoverageProvider,
15
16
  logger,
16
17
  type ProjectContext,
17
18
  type Reporter,
@@ -817,6 +818,8 @@ const createBrowserRuntime = async ({
817
818
  const firstProject = browserProjects[0];
818
819
  const userPlugins = firstProject?.normalizedConfig.plugins || [];
819
820
  const userRsbuildConfig = firstProject?.normalizedConfig ?? {};
821
+ const browserConfig =
822
+ firstProject?.normalizedConfig.browser ?? context.normalizedConfig.browser;
820
823
 
821
824
  // Rstest internal aliases that must not be overridden by user config
822
825
  const browserRuntimePath = fileURLToPath(
@@ -841,8 +844,8 @@ const createBrowserRuntime = async ({
841
844
  plugins: userPlugins,
842
845
  server: {
843
846
  printUrls: false,
844
- port: context.normalizedConfig.browser.port ?? 4000,
845
- strictPort: context.normalizedConfig.browser.strictPort,
847
+ port: browserConfig.port ?? 4000,
848
+ strictPort: browserConfig.strictPort,
846
849
  },
847
850
  dev: {
848
851
  client: {
@@ -850,7 +853,7 @@ const createBrowserRuntime = async ({
850
853
  },
851
854
  },
852
855
  environments: {
853
- web: {},
856
+ [firstProject?.environmentName || 'web']: {},
854
857
  },
855
858
  },
856
859
  });
@@ -970,6 +973,16 @@ const createBrowserRuntime = async ({
970
973
  ]);
971
974
  }
972
975
 
976
+ // Register coverage plugin for browser mode
977
+ const coverage = firstProject?.normalizedConfig.coverage;
978
+ if (coverage?.enabled && context.command !== 'list') {
979
+ const { pluginCoverage } = await loadCoverageProvider(
980
+ coverage,
981
+ context.rootPath,
982
+ );
983
+ rsbuildInstance.addPlugins([pluginCoverage(coverage)]);
984
+ }
985
+
973
986
  const devServer = await rsbuildInstance.createDevServer({
974
987
  getPortSilently: true,
975
988
  });
@@ -1134,7 +1147,7 @@ const createBrowserRuntime = async ({
1134
1147
  logger.debug(`[Browser UI] WebSocket server started on port ${wsPort}`);
1135
1148
 
1136
1149
  let browserLauncher: BrowserType;
1137
- const browserName = context.normalizedConfig.browser.browser;
1150
+ const browserName = browserConfig.browser;
1138
1151
  try {
1139
1152
  const playwright = await import('playwright');
1140
1153
  browserLauncher = playwright[browserName];
@@ -1147,7 +1160,7 @@ const createBrowserRuntime = async ({
1147
1160
  let browser: BrowserInstance;
1148
1161
  try {
1149
1162
  browser = await browserLauncher.launch({
1150
- headless: forceHeadless ?? context.normalizedConfig.browser.headless,
1163
+ headless: forceHeadless ?? browserConfig.headless,
1151
1164
  // Chromium-specific args (ignored by other browsers)
1152
1165
  args:
1153
1166
  browserName === 'chromium'
@@ -1178,6 +1191,32 @@ const createBrowserRuntime = async ({
1178
1191
  };
1179
1192
  };
1180
1193
 
1194
+ async function resolveProjectEntries(
1195
+ context: Rstest,
1196
+ shardedEntries?: Map<string, { entries: Record<string, string> }>,
1197
+ ): Promise<BrowserProjectEntries[]> {
1198
+ if (shardedEntries) {
1199
+ const browserProjects = getBrowserProjects(context);
1200
+ const projectEntries: BrowserProjectEntries[] = [];
1201
+ for (const project of browserProjects) {
1202
+ const entryInfo = shardedEntries.get(project.environmentName);
1203
+ if (entryInfo && Object.keys(entryInfo.entries).length > 0) {
1204
+ const setup = getSetupFiles(
1205
+ project.normalizedConfig.setupFiles,
1206
+ project.rootPath,
1207
+ );
1208
+ projectEntries.push({
1209
+ project,
1210
+ setupFiles: Object.values(setup),
1211
+ testFiles: Object.values(entryInfo.entries),
1212
+ });
1213
+ }
1214
+ }
1215
+ return projectEntries;
1216
+ }
1217
+ return collectProjectEntries(context);
1218
+ }
1219
+
1181
1220
  // ============================================================================
1182
1221
  // Main Entry Point
1183
1222
  // ============================================================================
@@ -1219,7 +1258,10 @@ export const runBrowserController = async (
1219
1258
  }
1220
1259
  }
1221
1260
 
1222
- const projectEntries = await collectProjectEntries(context);
1261
+ const projectEntries = await resolveProjectEntries(
1262
+ context,
1263
+ options?.shardedEntries,
1264
+ );
1223
1265
  const totalTests = projectEntries.reduce(
1224
1266
  (total, item) => total + item.testFiles.length,
1225
1267
  0,
@@ -1228,11 +1270,12 @@ export const runBrowserController = async (
1228
1270
  if (totalTests === 0) {
1229
1271
  const code = context.normalizedConfig.passWithNoTests ? 0 : 1;
1230
1272
  if (!skipOnTestRunEnd) {
1231
- logger.log(
1232
- color[code ? 'red' : 'yellow'](
1233
- `No test files found, exiting with code ${code}.`,
1234
- ),
1235
- );
1273
+ const message = `No test files found, exiting with code ${code}.`;
1274
+ if (code === 0) {
1275
+ logger.log(color.yellow(message));
1276
+ } else {
1277
+ logger.error(color.red(message));
1278
+ }
1236
1279
  }
1237
1280
 
1238
1281
  if (code !== 0) {
@@ -1700,8 +1743,14 @@ export type ListBrowserTestsResult = {
1700
1743
  */
1701
1744
  export const listBrowserTests = async (
1702
1745
  context: Rstest,
1746
+ options?: {
1747
+ shardedEntries?: Map<string, { entries: Record<string, string> }>;
1748
+ },
1703
1749
  ): Promise<ListBrowserTestsResult> => {
1704
- const projectEntries = await collectProjectEntries(context);
1750
+ const projectEntries = await resolveProjectEntries(
1751
+ context,
1752
+ options?.shardedEntries,
1753
+ );
1705
1754
  const totalTests = projectEntries.reduce(
1706
1755
  (total, item) => total + item.testFiles.length,
1707
1756
  0,