Skip to content

Commit 529396f

Browse files
committed
fix(migrate): detect legacy browser providers
1 parent 5cb22f0 commit 529396f

4 files changed

Lines changed: 132 additions & 1 deletion

File tree

docs/guide/migrate-rules.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,21 @@ Vite+ and should be removed as direct dependencies. The Playwright and
117117
WebdriverIO providers remain opt-in: keep or add the provider at the bundled
118118
Vitest version and ensure its `playwright` or `webdriverio` peer is installed.
119119

120+
Migration detects providers before rewriting imports. This includes legacy
121+
projects that aliased `vitest` to `@voidzero-dev/vite-plus-test` and import from
122+
`vitest/browser-<provider>`, `vitest/browser/providers/<provider>`, or
123+
`vitest/plugins/browser-<provider>`. These imports still cause the corresponding
124+
`@vitest/browser-playwright` or `@vitest/browser-webdriverio` dependency and its
125+
framework peer to be installed.
126+
120127
Object-valued nested npm and Bun overrides are preserved because they are
121128
user-defined scopes rather than scalar version pins.
122129

123130
## Source Rewrite Rules
124131

125132
- Rewrite ordinary `vitest` and `vitest/*` imports to `vite-plus/test*`.
133+
- Detect legacy Playwright and WebdriverIO provider imports before applying that
134+
rewrite so their optional provider dependencies are not lost.
126135
- Rewrite scoped browser imports to the corresponding
127136
`vite-plus/test/browser*` exports and provision opt-in providers when needed.
128137
- Leave existing `vite-plus/test*` imports unchanged.

packages/cli/src/migration/__tests__/migrator.spec.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3888,6 +3888,104 @@ describe('rewriteStandaloneProject pnpm workspace yaml', () => {
38883888
expect(devDeps.playwright).toBe('*');
38893889
});
38903890

3891+
it.each([
3892+
['playwright', 'browser-playwright'],
3893+
['playwright', 'browser/providers/playwright'],
3894+
['playwright', 'plugins/browser-playwright'],
3895+
['webdriverio', 'browser-webdriverio'],
3896+
['webdriverio', 'browser/providers/webdriverio'],
3897+
['webdriverio', 'plugins/browser-webdriverio'],
3898+
] as const)(
3899+
'injects the %s provider before rewriting the legacy vitest/%s import',
3900+
(provider, subpath) => {
3901+
const legacySpecifier = `vitest/${subpath}`;
3902+
fs.writeFileSync(
3903+
path.join(tmpDir, 'package.json'),
3904+
JSON.stringify({
3905+
name: 'test',
3906+
devDependencies: {
3907+
vite: '^7.0.0',
3908+
vitest: 'npm:@voidzero-dev/vite-plus-test@0.1.24',
3909+
},
3910+
}),
3911+
);
3912+
fs.writeFileSync(
3913+
path.join(tmpDir, 'vite.config.ts'),
3914+
[
3915+
`import { ${provider} } from '${legacySpecifier}';`,
3916+
"import { defineConfig } from 'vite-plus';",
3917+
'export default defineConfig({',
3918+
` test: { browser: { enabled: true, provider: ${provider}() } },`,
3919+
'});',
3920+
'',
3921+
].join('\n'),
3922+
);
3923+
3924+
rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true);
3925+
3926+
const devDeps = readJson(path.join(tmpDir, 'package.json')).devDependencies as Record<
3927+
string,
3928+
string
3929+
>;
3930+
expect(devDeps[`@vitest/browser-${provider}`]).toBe(VITEST_VERSION);
3931+
expect(devDeps[provider]).toBe('*');
3932+
expect(devDeps.vitest).toBe('catalog:');
3933+
expect(fs.readFileSync(path.join(tmpDir, 'vite.config.ts'), 'utf8')).toContain(
3934+
`from 'vite-plus/test/${subpath}'`,
3935+
);
3936+
},
3937+
);
3938+
3939+
it('injects the provider before rewriting a legacy provider import at a monorepo root', () => {
3940+
// Regression for vue-core: the root manifest is rewritten before imports,
3941+
// so the legacy vite-plus-test alias path must be recognized during the
3942+
// initial source scan.
3943+
fs.writeFileSync(
3944+
path.join(tmpDir, 'package.json'),
3945+
JSON.stringify({
3946+
name: 'root',
3947+
devDependencies: {
3948+
playwright: '^1.56.1',
3949+
vite: 'catalog:',
3950+
vitest: 'npm:@voidzero-dev/vite-plus-test@0.1.24',
3951+
},
3952+
}),
3953+
);
3954+
fs.writeFileSync(path.join(tmpDir, 'pnpm-workspace.yaml'), 'packages:\n - packages/*\n');
3955+
fs.writeFileSync(
3956+
path.join(tmpDir, 'vite.config.ts'),
3957+
[
3958+
"import { playwright } from 'vitest/browser-playwright';",
3959+
"import { defineConfig } from 'vite-plus';",
3960+
'export default defineConfig({',
3961+
' test: { browser: { enabled: true, provider: playwright() } },',
3962+
'});',
3963+
'',
3964+
].join('\n'),
3965+
);
3966+
3967+
rewriteMonorepo(
3968+
{
3969+
...makeWorkspaceInfo(tmpDir, PackageManager.pnpm),
3970+
isMonorepo: true,
3971+
workspacePatterns: ['packages/*'],
3972+
},
3973+
true,
3974+
true,
3975+
);
3976+
3977+
const devDeps = readJson(path.join(tmpDir, 'package.json')).devDependencies as Record<
3978+
string,
3979+
string
3980+
>;
3981+
expect(devDeps['@vitest/browser-playwright']).toBe(VITEST_VERSION);
3982+
expect(devDeps.playwright).toBe('^1.56.1');
3983+
expect(devDeps.vitest).toBe('catalog:');
3984+
expect(fs.readFileSync(path.join(tmpDir, 'vite.config.ts'), 'utf8')).toContain(
3985+
"from 'vite-plus/test/browser-playwright'",
3986+
);
3987+
});
3988+
38913989
it('injects the playwright provider on a re-run from the migrated provider-subpath import', () => {
38923990
// Re-running migration on an ALREADY-migrated project: the import rewriter
38933991
// maps `@vitest/browser-playwright/provider` to

packages/cli/src/migration/migrator.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4812,6 +4812,10 @@ export function ensureVitePlusBootstrap(
48124812
// against every browser-surface `./test/*` export in package.json (those that
48134813
// re-export `@vitest/browser*` or `vitest/internal/browser`).
48144814
const VITEST_BROWSER_SPECIFIER_HINTS = [
4815+
// Before v0.2, projects commonly aliased `vitest` to
4816+
// `@voidzero-dev/vite-plus-test`, whose browser exports used these paths.
4817+
'vitest/browser',
4818+
'vitest/plugins/browser',
48154819
'@vitest/browser',
48164820
'vite-plus/test/browser',
48174821
'vite-plus/test/plugins/browser',
@@ -4826,6 +4830,9 @@ const VITEST_BROWSER_SPECIFIER_HINTS = [
48264830
// Specifier fragments that signal the WEBDRIVERIO provider specifically. Each
48274831
// is a prefix, matched as a substring, so subpath imports (`/context`,
48284832
// `/provider`, …) are covered too:
4833+
// - `vitest/browser-webdriverio`, `vitest/browser/providers/webdriverio`, and
4834+
// `vitest/plugins/browser-webdriverio` are legacy
4835+
// `@voidzero-dev/vite-plus-test` exports reached through the `vitest` alias
48294836
// - `@vitest/browser-webdriverio` pre-migration (incl. `/provider`,
48304837
// `/context` subpaths)
48314838
// - `vite-plus/test/browser-webdriverio` migrated (re-run); covers
@@ -4846,6 +4853,9 @@ const VITEST_BROWSER_SPECIFIER_HINTS = [
48464853
// it pulls in the (now opt-in)
48474854
// provider, so it signals usage too.
48484855
const WEBDRIVERIO_PROVIDER_SPECIFIER_HINTS = [
4856+
'vitest/browser-webdriverio',
4857+
'vitest/browser/providers/webdriverio',
4858+
'vitest/plugins/browser-webdriverio',
48494859
'@vitest/browser-webdriverio',
48504860
'vite-plus/test/browser-webdriverio',
48514861
'vite-plus/test/browser/providers/webdriverio',
@@ -4860,6 +4870,11 @@ const WEBDRIVERIO_PROVIDER_SPECIFIER_HINTS = [
48604870
// provider via a `vite-plus/test/browser-playwright` shim with no declared dep)
48614871
// must still have the provider kept/injected for the rewritten import to resolve.
48624872
const PLAYWRIGHT_PROVIDER_SPECIFIER_HINTS = [
4873+
// Legacy `@voidzero-dev/vite-plus-test` exports reached through the `vitest`
4874+
// alias. These must be detected before rewriteAllImports changes the prefix.
4875+
'vitest/browser-playwright',
4876+
'vitest/browser/providers/playwright',
4877+
'vitest/plugins/browser-playwright',
48634878
'@vitest/browser-playwright',
48644879
'vite-plus/test/browser-playwright',
48654880
'vite-plus/test/browser/providers/playwright',

rfcs/migrate-existing-projects.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,15 @@ When PnP is active, interactive migration prints the incompatibility and asks wh
4545

4646
Force-override/CI mode (`VP_OVERRIDE_PACKAGES`) is respected: when `vitest` is not a managed key there, the project's own `vitest` is never stripped and its `@vitest/*` ecosystem dependencies are not realigned. Object-valued nested npm/Bun overrides are user-owned scopes rather than managed version pins and are preserved.
4747

48+
Legacy browser-provider usage must be detected before source imports are
49+
rewritten. Projects that aliased `vitest` to the removed
50+
`@voidzero-dev/vite-plus-test` package can import Playwright or WebdriverIO from
51+
`vitest/browser-<provider>`, `vitest/browser/providers/<provider>`, or
52+
`vitest/plugins/browser-<provider>`. Migration treats all three forms as opt-in
53+
provider usage, installs the matching `@vitest/browser-<provider>` package and
54+
framework peer, and then rewrites the import to the equivalent
55+
`vite-plus/test*` surface.
56+
4857
## `@nuxt/test-utils` compatibility
4958

5059
`@nuxt/test-utils`'s transform detects an existing `vi` import only when its module specifier is exactly `vitest`. When a test uses `mockNuxtImport` or `mockComponent`, changing that import to `vite-plus/test` makes the transform inject a second `vi` import and can fail compilation with a duplicate identifier. Requiring users to know which individual files exercise that transform is brittle, so the migration uses one package-level rule instead.
@@ -96,7 +105,7 @@ How each package the `vitest` ecosystem rule covers is handled, verified against
96105
| `packages/cli/src/migration/{migrator,npm-reinstall,bin}.ts` | Yarn PnP preflight and `node-modules` conversion; usage-aware managed override set; per-package dependency reconciliation; `vitest` removal across every sink; full `@vitest/*` alignment; browser-provider restoration; behind `vite-plus`/`vite` re-pin; empty/unrelated-`pnpm` routing fix; stale npm Vite install cleanup; package-level Nuxt dependency detection and retained Vitest provisioning. |
97106
| Oxlint `prefer-vite-plus-imports` rule | Apply the same Nuxt package-level `vitest` / `vitest/*` exception so diagnostics and autofix preserve the migration's compatible result. |
98107

99-
Covered by unit tests in `migrator.spec.ts` (vitest removal, required-peer provisioning, ecosystem alignment, browser-provider restoration, workspace localization, behind re-pin, empty-`pnpm` reconciliation), `npm-reinstall.spec.ts` (stale npm install and lock cleanup), and a routing test in `vite_global_cli`.
108+
Covered by unit tests in `migrator.spec.ts` (vitest removal, required-peer provisioning, ecosystem alignment, browser-provider restoration including legacy wrapper import paths, workspace localization, behind re-pin, empty-`pnpm` reconciliation), `npm-reinstall.spec.ts` (stale npm install and lock cleanup), and a routing test in `vite_global_cli`.
100109

101110
## Snapshot coverage
102111

0 commit comments

Comments
 (0)