From 61ccbb461e7da43923249c0031212a10f4b93e5a Mon Sep 17 00:00:00 2001 From: yusuke <66258931+yusuke99@users.noreply.github.com> Date: Wed, 9 Sep 2026 15:11:58 +0900 Subject: [PATCH 1/5] fix(migrate): don't add playwright when @playwright/test exists --- .../cli/src/migration/migrator/package-json.ts | 5 ++--- packages/cli/src/migration/migrator/shared.ts | 17 ++++++++++++++++- .../migration/migrator/vite-plus-bootstrap.ts | 6 ++---- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/migration/migrator/package-json.ts b/packages/cli/src/migration/migrator/package-json.ts index 8b8234c283..4c78dd163e 100644 --- a/packages/cli/src/migration/migrator/package-json.ts +++ b/packages/cli/src/migration/migrator/package-json.ts @@ -24,7 +24,7 @@ import { } from '../migrator.ts'; import { BROWSER_PROVIDER_PEER_DEPS, - findDeclaredSpec, + hasProviderPeerDependency, resolveProviderPeerSpec, OPT_IN_BROWSER_PROVIDERS, OXLINT_PLUGINS_PACKAGE, @@ -280,8 +280,7 @@ export function rewritePackageJson( ); } const peer = BROWSER_PROVIDER_PEER_DEPS[provider]; // 'webdriverio' / 'playwright' - const peerPresent = findDeclaredSpec(pkg, peer); - if (peer && !peerPresent) { + if (!hasProviderPeerDependency(pkg, peer)) { pkg.devDependencies ??= {}; pkg.devDependencies[peer] = resolveProviderPeerSpec( pkg, diff --git a/packages/cli/src/migration/migrator/shared.ts b/packages/cli/src/migration/migrator/shared.ts index 7ab78a21c7..5e8361a567 100644 --- a/packages/cli/src/migration/migrator/shared.ts +++ b/packages/cli/src/migration/migrator/shared.ts @@ -82,10 +82,14 @@ export const BROWSER_PROVIDER_PEER_DEPS: Record = { '@vitest/browser-webdriverio': 'webdriverio', }; +// Packages that include runtime peer as a dependency. +const RUNTIME_PEER_INCLUDED_BY: Record = { + playwright: ['@playwright/test'], +}; + // Lockstep sibling packages whose declared version a browser provider's runtime // framework peer should reuse (they publish together). Keyed by the peer name. export const PROVIDER_PEER_VERSION_SIBLINGS: Record = { - playwright: ['@playwright/test'], webdriverio: ['@wdio/cli', '@wdio/globals'], }; @@ -99,6 +103,17 @@ export function findDeclaredSpec(pkg: DependencyBag, name: string): string | und ); } +// A provider's runtime peer is available when declared directly or included by +// another declared package. +export function hasProviderPeerDependency(pkg: DependencyBag, runtimePeer: string): boolean { + return ( + findDeclaredSpec(pkg, runtimePeer) !== undefined || + (RUNTIME_PEER_INCLUDED_BY[runtimePeer] ?? []).some( + (packageName) => findDeclaredSpec(pkg, packageName) !== undefined, + ) + ); +} + // A deterministic spec for a browser provider's framework peer instead of `*`: // reference the catalog when it already owns the peer, otherwise reuse a declared // lockstep sibling's version (concrete, or a catalog reference resolved to its diff --git a/packages/cli/src/migration/migrator/vite-plus-bootstrap.ts b/packages/cli/src/migration/migrator/vite-plus-bootstrap.ts index 4a327f91e9..9459496c95 100644 --- a/packages/cli/src/migration/migrator/vite-plus-bootstrap.ts +++ b/packages/cli/src/migration/migrator/vite-plus-bootstrap.ts @@ -55,6 +55,7 @@ import { import { type DependencyVersionChange, type MigrationReport } from '../report.ts'; import { BROWSER_PROVIDER_PEER_DEPS, + hasProviderPeerDependency, resolveProviderPeerSpec, OPT_IN_BROWSER_PROVIDERS, REMOVE_PACKAGES, @@ -405,10 +406,7 @@ function reconcileVitePlusBootstrapPackage( ); } const frameworkPeer = BROWSER_PROVIDER_PEER_DEPS[provider]; - const frameworkPresent = dependencyGroups.some( - (dependencies) => dependencies?.[frameworkPeer] !== undefined, - ); - if (frameworkPeer && !frameworkPresent) { + if (!hasProviderPeerDependency(pkg, frameworkPeer)) { pkg.devDependencies ??= {}; pkg.devDependencies[frameworkPeer] = resolveProviderPeerSpec( pkg, From d01e422572ae212645ef71ea94e8e73d4b4bb5c5 Mon Sep 17 00:00:00 2001 From: yusuke <66258931+yusuke99@users.noreply.github.com> Date: Wed, 9 Sep 2026 15:13:24 +0900 Subject: [PATCH 2/5] test(migrate): ensure playwright is not added when @playwright/test exists --- .../src/migration/__tests__/migrator.spec.ts | 59 +++++++++++++++++-- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/migration/__tests__/migrator.spec.ts b/packages/cli/src/migration/__tests__/migrator.spec.ts index c07158b82b..d3e92bdc6c 100644 --- a/packages/cli/src/migration/__tests__/migrator.spec.ts +++ b/packages/cli/src/migration/__tests__/migrator.spec.ts @@ -632,10 +632,9 @@ describe('rewritePackageJson', () => { expect(pkg.devDependencies).not.toHaveProperty('@vitest/browser'); }); - it('pins the provider framework peer to a lockstep sibling instead of * (npmx.dev #27)', () => { - // `playwright` and `@playwright/test` release in lockstep, so a newly-added - // `playwright` peer should reuse the pinned @playwright/test version rather - // than a non-deterministic `*`. + it('does not add playwright when @playwright/test already provides it', () => { + // `@playwright/test` has a dependency on `playwright`, + // so adding a second direct dependency is redundant. const pkg = { devDependencies: { '@vitest/browser-playwright': '^4.0.0', @@ -644,7 +643,7 @@ describe('rewritePackageJson', () => { }, }; rewritePackageJson(pkg, PackageManager.pnpm); - expect(pkg.devDependencies).toHaveProperty('playwright', '1.60.0'); + expect(pkg.devDependencies).not.toHaveProperty('playwright'); }); it('injects a direct vite devDependency for an npm project that uses an opt-in browser provider', async () => { @@ -3409,6 +3408,56 @@ describe('ensureVitePlusBootstrap', () => { expect(detectVitePlusBootstrapPending(tmpDir, PackageManager.pnpm)).toBe(false); }); + it('does not add playwright on upgrade when @playwright/test already provides it', () => { + fs.writeFileSync( + path.join(tmpDir, 'package.json'), + JSON.stringify({ + name: 'browser-app', + devDependencies: { + '@playwright/test': '1.60.0', + 'vite-plus': 'catalog:', + }, + devEngines: { + packageManager: { name: 'pnpm', version: '10.33.0', onFail: 'download' }, + }, + }), + ); + fs.writeFileSync( + path.join(tmpDir, 'vite.config.ts'), + [ + "import { defineConfig } from 'vite-plus';", + "import { playwright } from 'vite-plus/test/browser-playwright';", + 'export default defineConfig({ test: { browser: { enabled: true, provider: playwright() } } });', + ].join('\n'), + ); + fs.writeFileSync( + path.join(tmpDir, 'pnpm-workspace.yaml'), + [ + 'catalog:', + ' vite-plus: latest', + ' vite: npm:@voidzero-dev/vite-plus-core@latest', + 'overrides:', + " vite: 'catalog:'", + 'peerDependencyRules:', + ' allowAny: [vite]', + ' allowedVersions:', + " vite: '*'", + '', + ].join('\n'), + ); + + expect(detectVitePlusBootstrapPending(tmpDir, PackageManager.pnpm)).toBe(true); + ensureVitePlusBootstrap(makeWorkspaceInfo(tmpDir, PackageManager.pnpm)); + + const pkg = readJson(path.join(tmpDir, 'package.json')) as { + devDependencies: Record; + }; + expect(pkg.devDependencies).not.toHaveProperty('playwright'); + expect(pkg.devDependencies.vitest).toBe('catalog:'); + expect(pkg.devDependencies['@vitest/browser-playwright']).toBe('catalog:'); + expect(detectVitePlusBootstrapPending(tmpDir, PackageManager.pnpm)).toBe(false); + }); + it('references the catalog for an installed browser provider the injection adds to the catalog', () => { // #2005: a workspace package uses `@vitest/browser-playwright` only through a // Vite+ shim (no dep), so migration ADDS the provider to the default catalog. From ac1e52ca83828010b513d7004bacbb6e5f89bc20 Mon Sep 17 00:00:00 2001 From: yusuke <66258931+yusuke99@users.noreply.github.com> Date: Wed, 9 Sep 2026 15:13:51 +0900 Subject: [PATCH 3/5] chore(migrate): update snapshot --- .../cli_snapshots/fixtures/migration_npmx_dev/package.json | 2 +- .../cli_snapshots/fixtures/migration_npmx_dev/snapshots.toml | 2 +- .../migration_npmx_dev/snapshots/migration_npmx_dev.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/package.json index 032fa150c6..49e47aefb2 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/package.json +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/package.json @@ -2,9 +2,9 @@ "name": "npmx", "private": true, "devDependencies": { + "@playwright/test": "1.60.0", "@vitest/browser-playwright": "4.1.10", "@vitest/coverage-v8": "4.1.10", - "playwright": "1.60.0", "vite": "catalog:", "vite-plus": "catalog:", "vitest": "catalog:" diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/snapshots.toml index fad393a279..da02e39063 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/snapshots.toml @@ -3,6 +3,6 @@ name = "migration_npmx_dev" vp = "global" steps = [ { argv = ["vp", "migrate", "--no-interactive"], comment = "npmx.dev shape: existing Vite+ upgrade, concrete @vitest/* should move into the catalog", continue-on-failure = true }, - { argv = ["vpt", "print-file", "package.json"], comment = "@vitest/browser-playwright and @vitest/coverage-v8 should become catalog:", continue-on-failure = true }, + { argv = ["vpt", "print-file", "package.json"], comment = "@vitest/* should become catalog without adding redundant playwright alongside @playwright/test:", continue-on-failure = true }, { argv = ["vpt", "print-file", "pnpm-workspace.yaml"], comment = "the default catalog should own the aligned @vitest/* packages", continue-on-failure = true }, ] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/snapshots/migration_npmx_dev.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/snapshots/migration_npmx_dev.md index 47b7ab55d9..da609dc017 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/snapshots/migration_npmx_dev.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/snapshots/migration_npmx_dev.md @@ -20,16 +20,16 @@ VITE+ - The Unified Toolchain for the Web ## `vpt print-file package.json` -@vitest/browser-playwright and @vitest/coverage-v8 should become catalog: +@vitest/* should become catalog without adding redundant playwright alongside @playwright/test: ``` { "name": "npmx", "private": true, "devDependencies": { + "@playwright/test": "1.60.0", "@vitest/browser-playwright": "catalog:", "@vitest/coverage-v8": "catalog:", - "playwright": "1.60.0", "vite": "catalog:", "vite-plus": "catalog:", "vitest": "catalog:" From 03f54a8f3221dbd76211ae4953a4900fb317cd25 Mon Sep 17 00:00:00 2001 From: yusuke <66258931+yusuke99@users.noreply.github.com> Date: Wed, 9 Sep 2026 18:59:50 +0900 Subject: [PATCH 4/5] chore: restored original snapshot --- .../cli_snapshots/fixtures/migration_npmx_dev/package.json | 2 +- .../cli_snapshots/fixtures/migration_npmx_dev/snapshots.toml | 2 +- .../migration_npmx_dev/snapshots/migration_npmx_dev.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/package.json index 49e47aefb2..032fa150c6 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/package.json +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/package.json @@ -2,9 +2,9 @@ "name": "npmx", "private": true, "devDependencies": { - "@playwright/test": "1.60.0", "@vitest/browser-playwright": "4.1.10", "@vitest/coverage-v8": "4.1.10", + "playwright": "1.60.0", "vite": "catalog:", "vite-plus": "catalog:", "vitest": "catalog:" diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/snapshots.toml index da02e39063..fad393a279 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/snapshots.toml @@ -3,6 +3,6 @@ name = "migration_npmx_dev" vp = "global" steps = [ { argv = ["vp", "migrate", "--no-interactive"], comment = "npmx.dev shape: existing Vite+ upgrade, concrete @vitest/* should move into the catalog", continue-on-failure = true }, - { argv = ["vpt", "print-file", "package.json"], comment = "@vitest/* should become catalog without adding redundant playwright alongside @playwright/test:", continue-on-failure = true }, + { argv = ["vpt", "print-file", "package.json"], comment = "@vitest/browser-playwright and @vitest/coverage-v8 should become catalog:", continue-on-failure = true }, { argv = ["vpt", "print-file", "pnpm-workspace.yaml"], comment = "the default catalog should own the aligned @vitest/* packages", continue-on-failure = true }, ] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/snapshots/migration_npmx_dev.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/snapshots/migration_npmx_dev.md index da609dc017..47b7ab55d9 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/snapshots/migration_npmx_dev.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_npmx_dev/snapshots/migration_npmx_dev.md @@ -20,16 +20,16 @@ VITE+ - The Unified Toolchain for the Web ## `vpt print-file package.json` -@vitest/* should become catalog without adding redundant playwright alongside @playwright/test: +@vitest/browser-playwright and @vitest/coverage-v8 should become catalog: ``` { "name": "npmx", "private": true, "devDependencies": { - "@playwright/test": "1.60.0", "@vitest/browser-playwright": "catalog:", "@vitest/coverage-v8": "catalog:", + "playwright": "1.60.0", "vite": "catalog:", "vite-plus": "catalog:", "vitest": "catalog:" From 07cbdf96ede040cdba2a51396301d9ec4ce1d133 Mon Sep 17 00:00:00 2001 From: yusuke <66258931+yusuke99@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:02:34 +0900 Subject: [PATCH 5/5] test(migrate): added @playwright/test snapshot case --- .../migration_playwright_test/package.json | 19 +++++ .../pnpm-workspace.yaml | 16 +++++ .../migration_playwright_test/snapshots.toml | 8 +++ .../snapshots/migration_playwright_test.md | 70 +++++++++++++++++++ 4 files changed, 113 insertions(+) create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_playwright_test/package.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_playwright_test/pnpm-workspace.yaml create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_playwright_test/snapshots.toml create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_playwright_test/snapshots/migration_playwright_test.md diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_playwright_test/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_playwright_test/package.json new file mode 100644 index 0000000000..b1246e8cd1 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_playwright_test/package.json @@ -0,0 +1,19 @@ +{ + "name": "playwright-test-app", + "private": true, + "devDependencies": { + "@playwright/test": "1.60.0", + "@vitest/browser-playwright": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "vite": "catalog:", + "vite-plus": "catalog:", + "vitest": "catalog:" + }, + "devEngines": { + "packageManager": { + "name": "pnpm", + "version": "11.1.1", + "onFail": "download" + } + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_playwright_test/pnpm-workspace.yaml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_playwright_test/pnpm-workspace.yaml new file mode 100644 index 0000000000..aae9c0138f --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_playwright_test/pnpm-workspace.yaml @@ -0,0 +1,16 @@ +packages: + - . +catalog: + vite: npm:@voidzero-dev/vite-plus-core@latest + vite-plus: latest + vitest: 4.1.10 +overrides: + vite: 'catalog:' + vitest: 'catalog:' +peerDependencyRules: + allowAny: + - vite + - vitest + allowedVersions: + vite: '*' + vitest: '*' diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_playwright_test/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_playwright_test/snapshots.toml new file mode 100644 index 0000000000..079ebdfc76 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_playwright_test/snapshots.toml @@ -0,0 +1,8 @@ +[[case]] +name = "migration_playwright_test" +vp = "global" +steps = [ + { argv = ["vp", "migrate", "--no-interactive"], comment = "existing Vite+ upgrade: @playwright/test should remain without adding a direct playwright dependency", continue-on-failure = true }, + { argv = ["vpt", "print-file", "package.json"], comment = "@vitest/browser-playwright and @vitest/coverage-v8 should become catalog:", continue-on-failure = true }, + { argv = ["vpt", "print-file", "pnpm-workspace.yaml"], comment = "the default catalog should own the aligned @vitest/* packages", continue-on-failure = true }, +] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_playwright_test/snapshots/migration_playwright_test.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_playwright_test/snapshots/migration_playwright_test.md new file mode 100644 index 0000000000..983c329bc3 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_playwright_test/snapshots/migration_playwright_test.md @@ -0,0 +1,70 @@ +# migration_playwright_test + +## `vp migrate --no-interactive` + +existing Vite+ upgrade: @playwright/test should remain without adding a direct playwright dependency + +``` +VITE+ - The Unified Toolchain for the Web + +◇ Updated . to Vite+ +• Node pnpm +• Dependencies: + vite-plus latest → + vite → + vitest 4.1.10 → + @vitest/browser-playwright 4.1.10 → + @vitest/coverage-v8 4.1.10 → +• Package manager settings configured +``` + +## `vpt print-file package.json` + +@vitest/browser-playwright and @vitest/coverage-v8 should become catalog: + +``` +{ + "name": "playwright-test-app", + "private": true, + "devDependencies": { + "@playwright/test": "1.60.0", + "@vitest/browser-playwright": "catalog:", + "@vitest/coverage-v8": "catalog:", + "vite": "catalog:", + "vite-plus": "catalog:", + "vitest": "catalog:" + }, + "devEngines": { + "packageManager": { + "name": "pnpm", + "version": "", + "onFail": "download" + } + } +} +``` + +## `vpt print-file pnpm-workspace.yaml` + +the default catalog should own the aligned @vitest/* packages + +``` +packages: + - . +catalog: + vite: npm:@voidzero-dev/vite-plus-core@ + vite-plus: + vitest: + '@vitest/browser-playwright': + '@vitest/coverage-v8': +overrides: + vite@*: 'catalog:' + vitest@*: 'catalog:' +peerDependencyRules: + allowAny: + - vite + - vitest + allowedVersions: + vite: '*' + vitest: '*' +```