diff --git a/packages/cli/package.json b/packages/cli/package.json index 0dce464de8..2eefc177c7 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -168,6 +168,14 @@ "types": "./dist/test/browser-playwright.d.ts", "default": "./dist/test/browser-playwright.js" }, + "./test/browser-webdriverio": { + "types": "./dist/test/browser-webdriverio.d.ts", + "default": "./dist/test/browser-webdriverio.js" + }, + "./test/browser-preview": { + "types": "./dist/test/browser-preview.d.ts", + "default": "./dist/test/browser-preview.js" + }, "./test/plugins/runner": { "default": "./dist/test/plugins/runner.js" }, @@ -264,8 +272,11 @@ "./test/plugins/browser-playwright": { "default": "./dist/test/plugins/browser-playwright.js" }, - "./test/plugins/browser-playwright-context": { - "default": "./dist/test/plugins/browser-playwright-context.js" + "./test/plugins/browser-webdriverio": { + "default": "./dist/test/plugins/browser-webdriverio.js" + }, + "./test/plugins/browser-preview": { + "default": "./dist/test/plugins/browser-preview.js" } } } diff --git a/packages/test/BUNDLING.md b/packages/test/BUNDLING.md index 1667fc931e..c6f27e98ba 100644 --- a/packages/test/BUNDLING.md +++ b/packages/test/BUNDLING.md @@ -16,19 +16,21 @@ This approach avoids the critical issue of Rolldown creating shared chunks that ### Copied Packages (`dist/@vitest/`) -These 9 `@vitest/*` packages are **copied** (not bundled) to preserve their original file structure: - -| Package | Purpose | -| ---------------------------- | ---------------------------------------------------- | -| `@vitest/runner` | Test runner core | -| `@vitest/utils` | Utilities (source-map, error, display, timers, etc.) | -| `@vitest/spy` | Spy/mock implementation | -| `@vitest/expect` | Assertion library | -| `@vitest/snapshot` | Snapshot testing | -| `@vitest/mocker` | Module mocking (node, browser, automock) | -| `@vitest/pretty-format` | Output formatting | -| `@vitest/browser` | Browser testing support | -| `@vitest/browser-playwright` | Playwright integration | +These 11 `@vitest/*` packages are **copied** (not bundled) to preserve their original file structure: + +| Package | Purpose | +| ----------------------------- | ---------------------------------------------------- | +| `@vitest/runner` | Test runner core | +| `@vitest/utils` | Utilities (source-map, error, display, timers, etc.) | +| `@vitest/spy` | Spy/mock implementation | +| `@vitest/expect` | Assertion library | +| `@vitest/snapshot` | Snapshot testing | +| `@vitest/mocker` | Module mocking (node, browser, automock) | +| `@vitest/pretty-format` | Output formatting | +| `@vitest/browser` | Browser testing support | +| `@vitest/browser-playwright` | Playwright integration | +| `@vitest/browser-webdriverio` | WebdriverIO integration | +| `@vitest/browser-preview` | Preview (testing-library) integration | **Why copy instead of bundle?** Bundling would create shared chunks that mix browser-safe and Node.js-only code. Copying preserves the original separation. @@ -73,6 +75,7 @@ These packages are explicitly kept external in `EXTERNAL_BLOCKLIST` during the R | Package | Reason | | ------------------ | ----------------------------------------- | | `playwright` | Native bindings, user must install | +| `webdriverio` | Native bindings, user must install | | `debug` | Environment detection breaks when bundled | | `happy-dom` | Optional peer dependency | | `jsdom` | Optional peer dependency | @@ -102,13 +105,21 @@ For maintainers developing the vitest/vite migration feature, here are the trans ### Import Rewrites -| Original Import | Rewritten Import | -| ----------------------------------- | -------------------------------------------------------- | -| `from "@vitest/browser-playwright"` | `from "@voidzero-dev/vite-plus-test/browser-playwright"` | -| `from "vite"` | `from "@voidzero-dev/vite-plus-core"` | -| `from "vite/module-runner"` | `from "@voidzero-dev/vite-plus-core/module-runner"` | +| Original Import | Rewritten Import | +| ------------------------------------ | --------------------------------------------------------- | +| `from "@vitest/browser-playwright"` | `from "@voidzero-dev/vite-plus-test/browser-playwright"` | +| `from "@vitest/browser-webdriverio"` | `from "@voidzero-dev/vite-plus-test/browser-webdriverio"` | +| `from "@vitest/browser-preview"` | `from "@voidzero-dev/vite-plus-test/browser-preview"` | +| `from "vite"` | `from "@voidzero-dev/vite-plus-core"` | +| `from "vite/module-runner"` | `from "@voidzero-dev/vite-plus-core/module-runner"` | -**Note:** pnpm overrides don't affect Node.js module resolution at config load time, so config files must use the `@voidzero-dev/vite-plus-test/browser-playwright` import path. +**Note:** When using pnpm overrides, you have three options for browser provider imports: + +- `vitest/browser-playwright` (or `vitest/browser-webdriverio`, `vitest/browser-preview`) - works when `vitest` is overridden to our package (Recommended) +- `@voidzero-dev/vite-plus-test/browser-playwright` - direct import from test package +- `@voidzero-dev/vite-plus/test/plugins/browser-playwright` - direct import from CLI package + +Importing from `@vitest/browser-*` packages directly requires additional overrides for those specific packages. ### package.json Changes @@ -118,7 +129,9 @@ For maintainers developing the vitest/vite migration feature, here are the trans { "devDependencies": { "@vitest/browser": "...", // Remove - "@vitest/browser-playwright": "...", // Remove + "@vitest/browser-playwright": "...", // Remove (if using playwright) + "@vitest/browser-webdriverio": "...", // Remove (if using webdriverio) + "@vitest/browser-preview": "...", // Remove (if using testing-library) "@vitest/ui": "..." // Remove (peer dep, not bundled but optional) } } @@ -133,6 +146,8 @@ overrides: vitest: 'file:path/to/vite-plus-test.tgz' '@vitest/browser': 'file:path/to/vite-plus-test.tgz' '@vitest/browser-playwright': 'file:path/to/vite-plus-test.tgz' + '@vitest/browser-webdriverio': 'file:path/to/vite-plus-test.tgz' + '@vitest/browser-preview': 'file:path/to/vite-plus-test.tgz' ``` Or using npm package names: @@ -143,16 +158,36 @@ overrides: vitest: 'npm:@voidzero-dev/vite-plus-test' '@vitest/browser': 'npm:@voidzero-dev/vite-plus-test' '@vitest/browser-playwright': 'npm:@voidzero-dev/vite-plus-test' + '@vitest/browser-webdriverio': 'npm:@voidzero-dev/vite-plus-test' + '@vitest/browser-preview': 'npm:@voidzero-dev/vite-plus-test' ``` ### Config File Updates ```typescript -// Before +// Before (playwright) import { playwright } from '@vitest/browser-playwright'; -// After +// After - Option 1 (Recommended): Via vitest subpath (works when vitest is overridden) +import { playwright } from 'vitest/browser-playwright'; + +// After - Option 2: Direct import from test package import { playwright } from '@voidzero-dev/vite-plus-test/browser-playwright'; + +// After - Option 3: Direct import from CLI package +import { playwright } from '@voidzero-dev/vite-plus/test/plugins/browser-playwright'; +``` + +Similarly for WebdriverIO: + +```typescript +import { webdriverio } from 'vitest/browser-webdriverio'; +``` + +And for Preview (testing-library): + +```typescript +import { preview } from 'vitest/browser-preview'; ``` ### Plugin Exports for pnpm Overrides @@ -160,16 +195,18 @@ import { playwright } from '@voidzero-dev/vite-plus-test/browser-playwright'; The package provides `./plugins/*` exports to enable pnpm overrides for all `@vitest/*` packages: ``` -@vitest/runner -> @voidzero-dev/vite-plus-test/plugins/runner -@vitest/utils -> @voidzero-dev/vite-plus-test/plugins/utils -@vitest/utils/error -> @voidzero-dev/vite-plus-test/plugins/utils-error -@vitest/spy -> @voidzero-dev/vite-plus-test/plugins/spy -@vitest/expect -> @voidzero-dev/vite-plus-test/plugins/expect -@vitest/snapshot -> @voidzero-dev/vite-plus-test/plugins/snapshot -@vitest/mocker -> @voidzero-dev/vite-plus-test/plugins/mocker -@vitest/pretty-format -> @voidzero-dev/vite-plus-test/plugins/pretty-format -@vitest/browser -> @voidzero-dev/vite-plus-test/plugins/browser -@vitest/browser-playwright -> @voidzero-dev/vite-plus-test/plugins/browser-playwright +@vitest/runner -> @voidzero-dev/vite-plus-test/plugins/runner +@vitest/utils -> @voidzero-dev/vite-plus-test/plugins/utils +@vitest/utils/error -> @voidzero-dev/vite-plus-test/plugins/utils-error +@vitest/spy -> @voidzero-dev/vite-plus-test/plugins/spy +@vitest/expect -> @voidzero-dev/vite-plus-test/plugins/expect +@vitest/snapshot -> @voidzero-dev/vite-plus-test/plugins/snapshot +@vitest/mocker -> @voidzero-dev/vite-plus-test/plugins/mocker +@vitest/pretty-format -> @voidzero-dev/vite-plus-test/plugins/pretty-format +@vitest/browser -> @voidzero-dev/vite-plus-test/plugins/browser +@vitest/browser-playwright -> @voidzero-dev/vite-plus-test/plugins/browser-playwright +@vitest/browser-webdriverio -> @voidzero-dev/vite-plus-test/plugins/browser-webdriverio +@vitest/browser-preview -> @voidzero-dev/vite-plus-test/plugins/browser-preview ``` --- @@ -247,21 +284,22 @@ When upgrading the vitest version: ### Build Flow ``` -1. bundleVitest() Copy vitest-dev dist/ -> dist/ -2. copyVitestPackages() Copy @vitest/* -> dist/@vitest/ -3. convertTabsToSpaces() Normalize formatting for patches -4. collectLeafDependencies() Parse imports with oxc-parser -5. bundleLeafDeps() Bundle chai, pathe, etc -> dist/vendor/ -6. rewriteVitestImports() Rewrite @vitest/*, vitest/*, vite -7. patchVitestPkgRootPaths() Fix distRoot for relocated files +1. bundleVitest() Copy vitest-dev dist/ -> dist/ +2. copyVitestPackages() Copy @vitest/* -> dist/@vitest/ +3. convertTabsToSpaces() Normalize formatting for patches +4. collectLeafDependencies() Parse imports with oxc-parser +5. bundleLeafDeps() Bundle chai, pathe, etc -> dist/vendor/ +6. rewriteVitestImports() Rewrite @vitest/*, vitest/*, vite +7. patchVitestPkgRootPaths() Fix distRoot for relocated files 8. patchVitestBrowserPackage() Inject vendor-aliases plugin -9. patchPlaywrightLocators() Fix browser-safe imports +9. patchBrowserProviderLocators() Fix browser-safe imports 10. Post-processing: - patchVendorPaths() - createBrowserCompatShim() - createModuleRunnerStub() Browser-safe stub - createNodeEntry() index-node.js with browser-provider - copyBrowserClientFiles() + - createBrowserEntryFiles() browser/ entry files at package root - createPluginExports() dist/plugins/* for pnpm overrides - mergePackageJson() - validateExternalDeps() @@ -270,6 +308,9 @@ When upgrading the vitest version: ### Output Structure ``` +browser/ # Entry files for ./browser export +├── context.js # Runtime guard (throws if not in browser) +└── context.d.ts # Re-exports from dist/@vitest/browser/context.d.ts dist/ ├── @vitest/ # Copied packages (browser/Node.js safe) │ ├── runner/ @@ -315,7 +356,13 @@ This is achieved through: 1. Conditional exports in package.json (`"node": "./dist/index-node.js"`) 2. Browser-safe stubs for `module-runner` 3. Import rewriting to prevent Node.js code from being pulled into browser bundles -4. `vendor-aliases` plugin injection to resolve imports at runtime +4. `vendor-aliases` plugin injection to resolve imports at runtime: + - Handles `@vitest/*` imports → resolves to copied `dist/@vitest/` files + - Handles `vitest/*` subpaths → resolves to dist files (enables `vitest/browser-playwright` usage) + - Handles `vitest/browser-playwright`, `vitest/browser-webdriverio`, `vitest/browser-preview` → resolves to bundled browser providers + - Handles `@voidzero-dev/vite-plus-test/*` subpaths → maps to equivalent vitest paths + - Handles `@voidzero-dev/vite-plus/test/*` subpaths → maps to equivalent vitest paths (CLI package) + - Intercepts `vitest/browser`, `@voidzero-dev/vite-plus-test/browser`, `@voidzero-dev/vite-plus/test/browser` → returns virtual module ID for BrowserContext plugin ### Key Constants @@ -331,6 +378,8 @@ const VITEST_PACKAGES_TO_COPY = [ '@vitest/pretty-format', '@vitest/browser', '@vitest/browser-playwright', + '@vitest/browser-webdriverio', + '@vitest/browser-preview', ]; // Packages that must NOT be bundled (from build.ts lines 131-158) @@ -350,6 +399,7 @@ const EXTERNAL_BLOCKLIST = new Set([ // Optional dependencies with bundling issues or native bindings 'debug', // environment detection broken when bundled 'playwright', // native bindings + 'webdriverio', // native bindings // Runtime deps (in package.json dependencies) - not bundled, resolved at install time 'sirv', diff --git a/packages/test/build.ts b/packages/test/build.ts index da59f432c5..f17577835d 100644 --- a/packages/test/build.ts +++ b/packages/test/build.ts @@ -12,7 +12,7 @@ // │ 5. rewriteVitestImports() Rewrite @vitest/*, vitest/*, vite │ // │ 6. patchVitestPkgRootPaths() Fix distRoot for relocated files │ // │ 7. patchVitestBrowserPackage() Inject vendor-aliases plugin │ -// │ 8. patchPlaywrightLocators() Fix browser-safe imports │ +// │ 8. patchBrowserProviderLocators() Fix browser-safe imports │ // │ 9. Post-processing: │ // │ - patchVendorPaths() │ // │ - createBrowserCompatShim() │ @@ -75,6 +75,8 @@ const VITEST_PACKAGES_TO_COPY = [ '@vitest/pretty-format', '@vitest/browser', '@vitest/browser-playwright', + '@vitest/browser-webdriverio', + '@vitest/browser-preview', ] as const; // Mapping from @vitest/* package specifiers to their paths within dist/@vitest/ @@ -121,7 +123,10 @@ const VITEST_PACKAGE_TO_PATH: Record = { '@vitest/browser/locators': '@vitest/browser/locators.js', // @vitest/browser-playwright '@vitest/browser-playwright': '@vitest/browser-playwright/index.js', - '@vitest/browser-playwright/context': '@vitest/browser-playwright/context.d.ts', + // @vitest/browser-webdriverio + '@vitest/browser-webdriverio': '@vitest/browser-webdriverio/index.js', + // @vitest/browser-preview + '@vitest/browser-preview': '@vitest/browser-preview/index.js', }; // Packages that should NOT be bundled into dist/vendor/ (remain external at runtime) @@ -144,6 +149,7 @@ const EXTERNAL_BLOCKLIST = new Set([ // Optional dependencies with bundling issues or native bindings 'debug', // environment detection broken when bundled 'playwright', // native bindings + 'webdriverio', // native bindings // Runtime deps (in package.json dependencies) - not bundled, resolved at install time 'sirv', @@ -194,8 +200,8 @@ await patchVitestPkgRootPaths(); // Step 7: Patch @vitest/browser package (vendor-aliases plugin, exclude list) await patchVitestBrowserPackage(); -// Step 8: Patch @vitest/browser-playwright/locators.js for browser-safe imports -await patchPlaywrightLocators(); +// Step 8: Patch browser provider locators.js files for browser-safe imports +await patchBrowserProviderLocators(); // Step 9: Post-processing await patchVendorPaths(); @@ -203,6 +209,7 @@ await createBrowserCompatShim(); await createModuleRunnerStub(); await createNodeEntry(); await copyBrowserClientFiles(); +await createBrowserEntryFiles(); const pluginExports = await createPluginExports(); await mergePackageJson(pluginExports); await validateExternalDeps(); @@ -233,8 +240,12 @@ async function mergePackageJson(pluginExports: Array<{ exportPath: string; shimF } // Remove bundled @vitest/* packages from peerDependencies - // @vitest/browser-playwright is now bundled, so users don't need to install it - const bundledPeerDeps = ['@vitest/browser-playwright']; + // These browser provider packages are now bundled, so users don't need to install them + const bundledPeerDeps = [ + '@vitest/browser-playwright', + '@vitest/browser-webdriverio', + '@vitest/browser-preview', + ]; if (destPkg.peerDependencies) { for (const dep of bundledPeerDeps) { delete destPkg.peerDependencies[dep]; @@ -293,6 +304,20 @@ async function mergePackageJson(pluginExports: Array<{ exportPath: string; shimF default: './dist/@vitest/browser-playwright/index.js', }; + // Add @vitest/browser-webdriverio compatible export + // Users can import { webdriverio } from 'vitest/browser-webdriverio' + destPkg.exports['./browser-webdriverio'] = { + types: './dist/@vitest/browser-webdriverio/index.d.ts', + default: './dist/@vitest/browser-webdriverio/index.js', + }; + + // Add @vitest/browser-preview compatible export + // Users can import { preview } from 'vitest/browser-preview' + destPkg.exports['./browser-preview'] = { + types: './dist/@vitest/browser-preview/index.d.ts', + default: './dist/@vitest/browser-preview/index.js', + }; + // Add plugin exports for all bundled @vitest/* packages // This allows pnpm overrides to redirect: @vitest/runner -> vitest/plugins/runner for (const { exportPath, shimFile } of pluginExports) { @@ -398,6 +423,21 @@ async function copyVitestPackages() { const copied = await copyDirRecursive(srcDir, destPkgDir); totalCopied += copied; console.log(` -> ${copied} files`); + + // Copy root type definition files if they exist + // These include context.d.ts (browser providers), matchers.d.ts (expect.element), jest-dom.d.ts (matchers) + const rootDtsFiles = ['context.d.ts', 'matchers.d.ts', 'jest-dom.d.ts']; + for (const dtsFile of rootDtsFiles) { + const rootDts = resolve(projectDir, `node_modules/${pkg}/${dtsFile}`); + try { + await stat(rootDts); + await copyFile(rootDts, join(destPkgDir, dtsFile)); + console.log(` + copied ${dtsFile}`); + totalCopied++; + } catch { + // File doesn't exist, skip + } + } } console.log(`\nCopied ${totalCopied} files to dist/@vitest/`); @@ -627,9 +667,12 @@ async function rewriteVitestImports(leafDepToVendorPath: Map) { let rewrittenCount = 0; // Scan both @vitest/* packages AND vitest core dist files + // Include .d.ts files so TypeScript type imports also get rewritten const jsFiles = fsGlob([ join(vitestDir, '**/*.js'), + join(vitestDir, '**/*.d.ts'), join(distDir, '*.js'), + join(distDir, '*.d.ts'), join(distDir, 'chunks/*.js'), ]); @@ -1148,6 +1191,53 @@ async function patchVitestBrowserPackage() { if (id === '${CORE_PACKAGE_NAME}' || id === 'vite') { return { id, external: true }; } + // Handle vitest/browser and package aliases + // Return virtual module ID so BrowserContext plugin can load it + // Supports: vitest/browser, @voidzero-dev/vite-plus-test/browser, @voidzero-dev/vite-plus/test/browser + if (id === 'vitest/browser' || id === '@voidzero-dev/vite-plus-test/browser' || id === '@voidzero-dev/vite-plus/test/browser') { + return '\\0vitest/browser'; + } + // Handle vitest/* subpaths (resolve to our dist files) + // Also handle @voidzero-dev package aliases that resolve to the same files + const vitestSubpathMap = { + 'vitest': resolve(packageRoot, 'index.js'), + '@voidzero-dev/vite-plus-test': resolve(packageRoot, 'index.js'), + '@voidzero-dev/vite-plus/test': resolve(packageRoot, 'index.js'), + 'vitest/node': resolve(packageRoot, 'node.js'), + 'vitest/config': resolve(packageRoot, 'config.js'), + 'vitest/internal/browser': resolve(packageRoot, 'browser.js'), + 'vitest/runners': resolve(packageRoot, 'runners.js'), + 'vitest/suite': resolve(packageRoot, 'suite.js'), + 'vitest/environments': resolve(packageRoot, 'environments.js'), + 'vitest/coverage': resolve(packageRoot, 'coverage.js'), + 'vitest/reporters': resolve(packageRoot, 'reporters.js'), + 'vitest/snapshot': resolve(packageRoot, 'snapshot.js'), + 'vitest/mocker': resolve(packageRoot, 'mocker.js'), + // Browser providers - resolve to our bundled @vitest/browser-* packages + 'vitest/browser-playwright': resolve(packageRoot, '@vitest/browser-playwright/index.js'), + 'vitest/browser-webdriverio': resolve(packageRoot, '@vitest/browser-webdriverio/index.js'), + 'vitest/browser-preview': resolve(packageRoot, '@vitest/browser-preview/index.js'), + }; + if (vitestSubpathMap[id]) { + return vitestSubpathMap[id]; + } + // Handle @voidzero-dev/vite-plus-test/* subpaths (same as vitest/*) + if (id.startsWith('@voidzero-dev/vite-plus-test/')) { + const subpath = id.slice('@voidzero-dev/vite-plus-test/'.length); + const vitestEquiv = 'vitest/' + subpath; + if (vitestSubpathMap[vitestEquiv]) { + return vitestSubpathMap[vitestEquiv]; + } + } + // Handle @voidzero-dev/vite-plus/test/* subpaths (CLI package paths, same as vitest/*) + if (id.startsWith('@voidzero-dev/vite-plus/test/')) { + const subpath = id.slice('@voidzero-dev/vite-plus/test/'.length); + const vitestEquiv = 'vitest/' + subpath; + if (vitestSubpathMap[vitestEquiv]) { + return vitestSubpathMap[vitestEquiv]; + } + } + // Handle @vitest/* packages (resolve to our copied files) const vendorMap = { ${mappingEntries} }; @@ -1163,7 +1253,10 @@ async function patchVitestBrowserPackage() { content = content.replace(pluginArrayPattern, `$1\n ${vendorAliasesPlugin},$2`); console.log(' Injected vitest:vendor-aliases plugin'); } else { - console.log(' Warning: Could not find browser plugin array to inject vendor-aliases'); + throw new Error( + 'Failed to inject vendor-aliases plugin in @vitest/browser/index.js: pattern not found. ' + + 'This likely means vitest code has changed and the patch needs to be updated.', + ); } // 2. Patch exclude list to add native deps @@ -1185,7 +1278,10 @@ async function patchVitestBrowserPackage() { content = content.replace(excludePattern, excludeReplacement); console.log(' Patched exclude list with native deps'); } else { - console.log(' Warning: Could not find exclude array to patch'); + throw new Error( + 'Failed to patch exclude list in @vitest/browser/index.js: pattern not found. ' + + 'This likely means vitest code has changed and the patch needs to be updated.', + ); } // 3. Remove include patterns that reference bundled deps @@ -1204,56 +1300,115 @@ async function patchVitestBrowserPackage() { } console.log(' Removed bundled deps from include list'); + // 4. Patch BrowserContext to also handle our package aliases as fallback + // This allows direct imports from our package without requiring vitest override + // Supports: vitest/browser, @voidzero-dev/vite-plus-test/browser, @voidzero-dev/vite-plus/test/browser + const browserContextPattern = /if \(id === ID_CONTEXT\) \{/; + if (browserContextPattern.test(content)) { + content = content.replace( + browserContextPattern, + `if (id === ID_CONTEXT || id === "@voidzero-dev/vite-plus-test/browser" || id === "@voidzero-dev/vite-plus/test/browser") {`, + ); + console.log(' Patched BrowserContext to handle package aliases'); + } else { + throw new Error( + 'Failed to patch BrowserContext in @vitest/browser/index.js: pattern not found. ' + + 'This likely means vitest code has changed and the patch needs to be updated.', + ); + } + await writeFile(browserIndexPath, content, 'utf-8'); console.log(' Successfully patched @vitest/browser/index.js'); } /** - * Patch @vitest/browser-playwright/locators.js to use browser-safe imports. + * Patch browser provider locators.js files to use browser-safe imports. * - * The original file imports from '../browser/index.js' which is Node.js server code. - * We need to change it to import from browser-safe files instead. + * The original files import from '../browser/index.js' which includes Node.js server code. + * We need to change them to import from browser-safe files instead. + * + * Providers handled: + * - @vitest/browser-playwright: import { page, server } from '../browser/index.js'; + * - @vitest/browser-webdriverio: import { page, server, utils } from '../browser/index.js'; + * - @vitest/browser-preview: import { page, server, utils, userEvent } from '../browser/index.js'; */ -async function patchPlaywrightLocators() { - console.log('\nPatching @vitest/browser-playwright/locators.js...'); +async function patchBrowserProviderLocators() { + console.log('\nPatching browser provider locators.js files...'); - const locatorsPath = join(distDir, '@vitest/browser-playwright/locators.js'); + const providers = [ + { name: 'browser-playwright', extraImports: [] as string[] }, + { name: 'browser-webdriverio', extraImports: ['utils'] }, + { name: 'browser-preview', extraImports: ['utils', 'userEvent'] }, + ]; - try { - await stat(locatorsPath); - } catch { - console.log(' Warning: locators.js not found, skipping'); - return; - } + for (const provider of providers) { + const locatorsPath = join(distDir, `@vitest/${provider.name}/locators.js`); - let content = await readFile(locatorsPath, 'utf-8'); + try { + await stat(locatorsPath); + } catch { + console.log(` Warning: @vitest/${provider.name}/locators.js not found, skipping`); + continue; + } - // 1. Change import of `page, server` from '../browser/index.js' to just `page` from '../browser/context.js' - // The server import is only used for server.config.browser.locators.testIdAttribute - // We'll inline the access via window.__vitest_worker__.config - const serverImportPattern = /import \{ page, server \} from ['"]\.\.\/browser\/index\.js['"];?/; - if (serverImportPattern.test(content)) { - content = content.replace(serverImportPattern, `import { page } from '../browser/context.js';`); - console.log(' Changed server import to browser-safe context import'); - } else { - console.log(' Warning: Could not find server import to patch'); - } + let content = await readFile(locatorsPath, 'utf-8'); + let patched = false; + + // 1. Patch the vitest/browser import to separate page (from context.js) and other imports + // After rewriteVitestImports(), the import is: import { page, server, ... } from '../browser/index.js'; + // We need: + // - page from '../browser/context.js' (browser-safe) + // - server removed (we'll use window.__vitest_worker__.config instead) + // - other imports (utils, userEvent) still from '../browser/index.js' + + if (provider.extraImports.length === 0) { + // playwright: just import page from context.js + const serverImportPattern = + /import \{ page, server \} from ['"]\.\.\/browser\/index\.js['"];?/; + if (serverImportPattern.test(content)) { + content = content.replace( + serverImportPattern, + `import { page } from '../browser/context.js';`, + ); + console.log(` [${provider.name}] Changed server import to browser-safe context import`); + patched = true; + } + } else { + // webdriverio/preview: import page from context.js, keep other imports from index.js + const extraImportsStr = provider.extraImports.join(', '); + const importPattern = new RegExp( + `import \\{ page, server, ${extraImportsStr} \\} from ['"]\\.\\.\/browser\/index\\.js['"];?`, + ); + if (importPattern.test(content)) { + const replacement = `import { page } from '../browser/context.js';\nimport { ${extraImportsStr} } from '../browser/index.js';`; + content = content.replace(importPattern, replacement); + console.log( + ` [${provider.name}] Split imports: page from context.js, {${extraImportsStr}} from index.js`, + ); + patched = true; + } + } - // 2. Replace server.config.browser.locators.testIdAttribute with browser-accessible version - // The browser has access to config via window.__vitest_worker__ - const testIdAttrPattern = /server\.config\.browser\.locators\.testIdAttribute/g; - if (testIdAttrPattern.test(content)) { - content = content.replace( - testIdAttrPattern, - `window.__vitest_worker__.config.browser.locators.testIdAttribute`, - ); - console.log(' Replaced server.config access with browser-safe window access'); - } else { - console.log(' Warning: Could not find testIdAttribute pattern to patch'); - } + if (!patched) { + console.log(` Warning: [${provider.name}] Could not find server import to patch`); + } - await writeFile(locatorsPath, content, 'utf-8'); - console.log(' Successfully patched @vitest/browser-playwright/locators.js'); + // 2. Replace all server.config references with browser-accessible window.__vitest_worker__.config + // This handles both: + // - server.config.browser.locators.testIdAttribute + // - server.config.browser.ui + const serverConfigPattern = /server\.config\./g; + const matchCount = (content.match(serverConfigPattern) || []).length; + if (matchCount > 0) { + content = content.replace(serverConfigPattern, `window.__vitest_worker__.config.`); + console.log( + ` [${provider.name}] Replaced ${matchCount} server.config references with window.__vitest_worker__.config`, + ); + } + + await writeFile(locatorsPath, content, 'utf-8'); + console.log(` Successfully patched @vitest/${provider.name}/locators.js`); + } } /** @@ -1566,6 +1721,38 @@ export * from '../@vitest/runner/index.js'; console.log(` Created ${browserVendorStubs.length} vendor stubs`); } +/** + * Create browser/ directory at package root with context files. + * The package exports "./browser" pointing to these files: + * - browser/context.js: Runtime guard (throws if used outside browser mode) + * - browser/context.d.ts: Re-exports types from dist/@vitest/browser/context.d.ts + * + * These files are NOT tracked in git (.gitignore excludes browser/) + * but ARE included in the package (package.json files: ["browser/**"]) + */ +async function createBrowserEntryFiles() { + console.log('\nCreating browser/ entry files...'); + + const browserDir = resolve(projectDir, 'browser'); + await mkdir(browserDir, { recursive: true }); + + // 1. Copy context.js from @vitest/browser (runtime guard) + const srcContextJs = resolve(projectDir, 'node_modules/@vitest/browser/context.js'); + const destContextJs = join(browserDir, 'context.js'); + await copyFile(srcContextJs, destContextJs); + console.log(' Created browser/context.js'); + + // 2. Create context.d.ts that re-exports from our bundled types + const contextDtsContent = `// Re-export browser context types from bundled @vitest/browser package +// This provides: page, userEvent, server, commands, utils, locators, cdp, Locator, etc. +// The bundled context.d.ts has imports rewritten to point to our dist files +export * from '../dist/@vitest/browser/context.d.ts' +`; + const destContextDts = join(browserDir, 'context.d.ts'); + await writeFile(destContextDts, contextDtsContent, 'utf-8'); + console.log(' Created browser/context.d.ts'); +} + /** * Create /plugins/* exports for all copied @vitest/* packages. * This allows pnpm overrides to redirect @vitest/* imports to our copied versions. diff --git a/packages/test/package.json b/packages/test/package.json index 0d366a5ec7..eaa988e9de 100644 --- a/packages/test/package.json +++ b/packages/test/package.json @@ -121,6 +121,14 @@ "types": "./dist/@vitest/browser-playwright/index.d.ts", "default": "./dist/@vitest/browser-playwright/index.js" }, + "./browser-webdriverio": { + "types": "./dist/@vitest/browser-webdriverio/index.d.ts", + "default": "./dist/@vitest/browser-webdriverio/index.js" + }, + "./browser-preview": { + "types": "./dist/@vitest/browser-preview/index.d.ts", + "default": "./dist/@vitest/browser-preview/index.js" + }, "./plugins/runner": { "default": "./dist/plugins/runner.mjs" }, @@ -217,8 +225,11 @@ "./plugins/browser-playwright": { "default": "./dist/plugins/browser-playwright.mjs" }, - "./plugins/browser-playwright-context": { - "default": "./dist/plugins/browser-playwright-context.mjs" + "./plugins/browser-webdriverio": { + "default": "./dist/plugins/browser-webdriverio.mjs" + }, + "./plugins/browser-preview": { + "default": "./dist/plugins/browser-preview.mjs" } }, "main": "./dist/index.js", @@ -236,9 +247,7 @@ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "happy-dom": "*", "jsdom": "*", - "@vitest/browser-preview": "4.0.15", - "@vitest/ui": "4.0.15", - "@vitest/browser-webdriverio": "4.0.15" + "@vitest/ui": "4.0.15" }, "peerDependenciesMeta": { "@edge-runtime/vm": { @@ -250,12 +259,6 @@ "@types/node": { "optional": true }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, "@vitest/ui": { "optional": true }, @@ -286,6 +289,8 @@ "@oxc-node/core": "catalog:", "@vitest/browser": "4.0.15", "@vitest/browser-playwright": "4.0.15", + "@vitest/browser-preview": "4.0.15", + "@vitest/browser-webdriverio": "4.0.15", "@vitest/expect": "4.0.15", "@vitest/mocker": "4.0.15", "@vitest/pretty-format": "4.0.15", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5f75d9c51f..319d1fc825 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -533,12 +533,6 @@ importers: '@types/node': specifier: ^20.0.0 || ^22.0.0 || >=24.0.0 version: 22.19.1 - '@vitest/browser-preview': - specifier: 4.0.15 - version: 4.0.15(vite@packages+core)(vitest@4.0.15) - '@vitest/browser-webdriverio': - specifier: 4.0.15 - version: 4.0.15(vite@packages+core)(vitest@4.0.15)(webdriverio@9.20.1) '@vitest/ui': specifier: 4.0.15 version: 4.0.15(vitest@4.0.15) @@ -600,6 +594,12 @@ importers: '@vitest/browser-playwright': specifier: 4.0.15 version: 4.0.15(playwright@1.57.0)(vite@packages+core)(vitest@4.0.15) + '@vitest/browser-preview': + specifier: 4.0.15 + version: 4.0.15(vite@packages+core)(vitest@4.0.15) + '@vitest/browser-webdriverio': + specifier: 4.0.15 + version: 4.0.15(vite@packages+core)(vitest@4.0.15)(webdriverio@9.20.1) '@vitest/expect': specifier: 4.0.15 version: 4.0.15