From 462f0534677467f73f1af5a96eb5d0b04c55330f Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:42:58 +0000 Subject: [PATCH 1/9] build: update vitest monorepo to v5 See associated pull request for more information. Closes #34030 as a pr takeover --- modules/testing/builder/package.json | 4 +- packages/angular/build/BUILD.bazel | 1 + packages/angular/build/package.json | 5 +- .../unit-test/runners/vitest/plugins.ts | 7 +- .../utility/latest-versions/package.json | 10 +- pnpm-lock.yaml | 220 ++++++------------ tests/e2e/utils/vitest.ts | 4 +- 7 files changed, 92 insertions(+), 159 deletions(-) diff --git a/modules/testing/builder/package.json b/modules/testing/builder/package.json index 176c6825b53e..d61b5633635a 100644 --- a/modules/testing/builder/package.json +++ b/modules/testing/builder/package.json @@ -4,12 +4,12 @@ "@angular-devkit/build-angular": "workspace:*", "@angular-devkit/core": "workspace:*", "@angular/ssr": "workspace:*", - "@vitest/coverage-v8": "4.1.11", + "@vitest/coverage-v8": "5.0.0", "browser-sync": "3.0.4", "istanbul-lib-instrument": "6.0.3", "jsdom": "30.0.1", "ng-packagr": "22.2.0-next.5", "rxjs": "7.8.2", - "vitest": "4.1.11" + "vitest": "5.0.0" } } diff --git a/packages/angular/build/BUILD.bazel b/packages/angular/build/BUILD.bazel index 06f931d2b712..9a4de3b5aa64 100644 --- a/packages/angular/build/BUILD.bazel +++ b/packages/angular/build/BUILD.bazel @@ -268,6 +268,7 @@ ts_project( "//:node_modules/@types/node", # unit test specific test deps + ":node_modules/@vitest/coverage-v8", ":node_modules/vitest", ":node_modules/jsdom", diff --git a/packages/angular/build/package.json b/packages/angular/build/package.json index 475889f9f496..c4b02d20234d 100644 --- a/packages/angular/build/package.json +++ b/packages/angular/build/package.json @@ -53,6 +53,7 @@ "@angular-devkit/core": "workspace:*", "@angular/ssr": "workspace:*", "@oxc-project/types": "0.149.0", + "@vitest/coverage-v8": "5.0.0", "istanbul-lib-instrument": "6.0.3", "jsdom": "30.0.1", "less": "4.9.1", @@ -60,7 +61,7 @@ "postcss": "8.5.28", "rollup": "4.63.1", "rxjs": "7.8.2", - "vitest": "4.1.11" + "vitest": "5.0.0" }, "peerDependencies": { "@angular/compiler": "0.0.0-ANGULAR-FW-PEER-DEP", @@ -80,7 +81,7 @@ "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", "tslib": "^2.3.0", "typescript": ">=6.0 <6.1", - "vitest": "^4.0.8" + "vitest": "^4.0.8 || ^5.0.0" }, "peerDependenciesMeta": { "@angular/core": { diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts index 29455b50cc7c..0dfd1dc4a8ce 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts @@ -16,7 +16,6 @@ import type { ResolvedConfig, UserWorkspaceConfig, Vite, - VitestPlugin, VitestPluginContext, } from 'vitest/node'; import { createBuildAssetsMiddleware } from '../../../../tools/vite/middlewares/assets-middleware'; @@ -32,8 +31,6 @@ interface ExistingRawSourceMap { mappings?: string; } -type VitestPlugins = Awaited>; - interface PluginOptions { workspaceRoot: string; projectSourceRoot: string; @@ -151,7 +148,7 @@ function getBrowsersToCheck( export async function createVitestConfigPlugin( options: VitestConfigPluginOptions, -): Promise { +): Promise { const { include, browser, @@ -313,7 +310,7 @@ async function loadResultFile(file: ResultFile): Promise { return readFile(file.inputPath, 'utf-8'); } -export function createVitestPlugins(pluginOptions: PluginOptions): VitestPlugins { +export function createVitestPlugins(pluginOptions: PluginOptions): Vite.Plugin[] { const { workspaceRoot, buildResultFiles, testFileToEntryPoint } = pluginOptions; const isWindows = platform() === 'win32'; let vitestConfig: ResolvedConfig; diff --git a/packages/schematics/angular/utility/latest-versions/package.json b/packages/schematics/angular/utility/latest-versions/package.json index 41ba63058ad4..92b667724aaf 100644 --- a/packages/schematics/angular/utility/latest-versions/package.json +++ b/packages/schematics/angular/utility/latest-versions/package.json @@ -25,12 +25,12 @@ "@tailwindcss/postcss": "^4.1.12", "tslib": "^2.3.0", "typescript": "~6.0.2", - "vitest": "^4.0.8", - "@vitest/coverage-v8": "^4.0.8", - "@vitest/coverage-istanbul": "^4.0.8", - "@vitest/browser-playwright": "^4.0.8", + "vitest": "^5.0.0", + "@vitest/coverage-v8": "^5.0.0", + "@vitest/coverage-istanbul": "^5.0.0", + "@vitest/browser-playwright": "^5.0.0", "@vitest/browser-webdriverio": "^4.0.8", - "@vitest/browser-preview": "^4.0.8", + "@vitest/browser-preview": "^5.0.0", "playwright": "^1.48.0", "webdriverio": "^9.0.0", "zone.js": "~0.16.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c4fd8a17359e..bed00e1e16d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -394,8 +394,8 @@ importers: specifier: workspace:* version: link:../../../packages/angular/ssr '@vitest/coverage-v8': - specifier: 4.1.11 - version: 4.1.11(vitest@4.1.11) + specifier: 5.0.0 + version: 5.0.0(vitest@5.0.0) browser-sync: specifier: 3.0.4 version: 3.0.4(bufferutil@4.1.0)(debug@4.4.3(supports-color@11.0.0))(supports-color@11.0.0)(utf-8-validate@6.0.6) @@ -412,8 +412,8 @@ importers: specifier: 7.8.2 version: 7.8.2 vitest: - specifier: 4.1.11 - version: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.0.1)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0) + specifier: 5.0.0 + version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(@vitest/coverage-v8@5.0.0)(jsdom@30.0.1)(vite@8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) packages/angular/build: dependencies: @@ -508,6 +508,9 @@ importers: '@oxc-project/types': specifier: 0.149.0 version: 0.149.0 + '@vitest/coverage-v8': + specifier: 5.0.0 + version: 5.0.0(vitest@5.0.0) istanbul-lib-instrument: specifier: 6.0.3 version: 6.0.3(supports-color@11.0.0) @@ -530,8 +533,8 @@ importers: specifier: 7.8.2 version: 7.8.2 vitest: - specifier: 4.1.11 - version: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.0.1)(less@4.9.1(supports-color@11.0.0))(sass-embedded@1.104.0)(sass@1.104.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0) + specifier: 5.0.0 + version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(@vitest/coverage-v8@5.0.0)(jsdom@30.0.1)(vite@8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@11.0.0))(sass-embedded@1.104.0)(sass@1.104.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) optionalDependencies: lmdb: specifier: 3.5.6 @@ -3990,20 +3993,25 @@ packages: peerDependencies: vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - '@vitest/coverage-v8@4.1.11': - resolution: {integrity: sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw==} + '@vitest/coverage-v8@5.0.0': + resolution: {integrity: sha512-toMg6PZGCIa/lQNCDoASrfb1ly4hsUKXFtFYC9kD4t78o5Y6LyNJU7AENt8eHPr3quYdxaxK7hj2mnbFfUk9NA==} peerDependencies: - '@vitest/browser': 4.1.11 - vitest: 4.1.11 + '@vitest/browser': 5.0.0 + vitest: 5.0.0 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@4.1.11': - resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} + '@vitest/istanbul-lib-coverage@1.0.1': + resolution: {integrity: sha512-k3DJZ8LhMBK9NS4SclF1ASD3OgXEWDorbIcPTRDK0/Zae6fRvu+fJRxtFdLfHsa9Y24beCdPnoNZ4LviTNstfA==} + engines: {node: '>=22'} - '@vitest/mocker@4.1.11': - resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} + '@vitest/istanbul-lib-report@1.0.1': + resolution: {integrity: sha512-1EOLRfsTMnyAr3+kEAsP4o9dhaDlGPpD7H5iLBBeq//YpNB1VIahkPhB+eRp9N2Dkfw8oySROjE3yf9XDeaIkQ==} + engines: {node: '>=22'} + + '@vitest/mocker@5.0.0': + resolution: {integrity: sha512-66PGTMIiVJP3t4a5yxU9qPtf7MdTBs8jmToMvy+HVflB3Yy13WJZTtPePdvU+wjRV02SKK5doLbSA6o9pwOmiA==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -4013,20 +4021,8 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.11': - resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} - - '@vitest/runner@4.1.11': - resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} - - '@vitest/snapshot@4.1.11': - resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} - - '@vitest/spy@4.1.11': - resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} - - '@vitest/utils@4.1.11': - resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} + '@vitest/spy@5.0.0': + resolution: {integrity: sha512-uy+luWBAPw9XfthoHi5AkfHUnuPYEESjl0p/r+meoBnU8bxg5GDQ3Ey8MjcJ6sqahkL4PFyrvfMJJBw7LbU06g==} '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -7070,9 +7066,6 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - peek-stream@1.1.3: resolution: {integrity: sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==} @@ -8190,11 +8183,12 @@ packages: tiny-inflate@1.0.3: resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinybench@6.1.4: + resolution: {integrity: sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==} + engines: {node: '>=20.0.0'} - tinyexec@1.3.1: - resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + tinyexec@1.3.0: + resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} engines: {node: '>=18'} tinyglobby@0.2.17: @@ -8525,22 +8519,23 @@ packages: yaml: optional: true - vitest@4.1.11: - resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + vitest@5.0.0: + resolution: {integrity: sha512-gpsMNoRhMjMktVxPtstOH4/PJuPyovVaMDr4oDilXaGH1EcqM2OE96SoHT2VIQ6fTGtTjqmHDrEu2X9RQiXf8Q==} + engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.11 - '@vitest/browser-preview': 4.1.11 - '@vitest/browser-webdriverio': 4.1.11 - '@vitest/coverage-istanbul': 4.1.11 - '@vitest/coverage-v8': 4.1.11 - '@vitest/ui': 4.1.11 + '@types/node': ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 5.0.0 + '@vitest/browser-preview': 5.0.0 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + '@vitest/coverage-istanbul': 5.0.0 + '@vitest/coverage-v8': 5.0.0 + '@vitest/ui': 5.0.0 happy-dom: '*' jsdom: '*' + vite: ^6.4.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -12020,68 +12015,43 @@ snapshots: dependencies: vite: 8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@11.0.0))(sass-embedded@1.104.0)(sass@1.104.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0) - '@vitest/coverage-v8@4.1.11(vitest@4.1.11)': + '@vitest/coverage-v8@5.0.0(vitest@5.0.0)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.1.11 + '@vitest/istanbul-lib-coverage': 1.0.1 + '@vitest/istanbul-lib-report': 1.0.1 ast-v8-to-istanbul: 1.0.6 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-reports: 3.2.0 magicast: 0.5.4 obug: 2.1.4 std-env: 4.2.0 tinyrainbow: 3.1.1 - vitest: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.0.1)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0) + vitest: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(@vitest/coverage-v8@5.0.0)(jsdom@30.0.1)(vite@8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) - '@vitest/expect@4.1.11': + '@vitest/istanbul-lib-coverage@1.0.1': {} + + '@vitest/istanbul-lib-report@1.0.1': dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 - chai: 6.2.2 - tinyrainbow: 3.1.1 + '@vitest/istanbul-lib-coverage': 1.0.1 - '@vitest/mocker@4.1.11(vite@8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))': + '@vitest/mocker@5.0.0(vite@8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))': dependencies: - '@vitest/spy': 4.1.11 + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.0 estree-walker: 3.0.3 - magic-string: 0.30.21 + magic-string: 1.2.3 optionalDependencies: vite: 8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0) - '@vitest/mocker@4.1.11(vite@8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@11.0.0))(sass-embedded@1.104.0)(sass@1.104.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))': + '@vitest/mocker@5.0.0(vite@8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@11.0.0))(sass-embedded@1.104.0)(sass@1.104.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))': dependencies: - '@vitest/spy': 4.1.11 + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.0 estree-walker: 3.0.3 - magic-string: 0.30.21 + magic-string: 1.2.3 optionalDependencies: vite: 8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@11.0.0))(sass-embedded@1.104.0)(sass@1.104.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0) - '@vitest/pretty-format@4.1.11': - dependencies: - tinyrainbow: 3.1.1 - - '@vitest/runner@4.1.11': - dependencies: - '@vitest/utils': 4.1.11 - pathe: 2.0.3 - - '@vitest/snapshot@4.1.11': - dependencies: - '@vitest/pretty-format': 4.1.11 - '@vitest/utils': 4.1.11 - magic-string: 0.30.21 - pathe: 2.0.3 - - '@vitest/spy@4.1.11': {} - - '@vitest/utils@4.1.11': - dependencies: - '@vitest/pretty-format': 4.1.11 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.1 + '@vitest/spy@5.0.0': {} '@webassemblyjs/ast@1.14.1': dependencies: @@ -15462,8 +15432,6 @@ snapshots: path-type@4.0.0: {} - pathe@2.0.3: {} - peek-stream@1.1.3: dependencies: buffer-from: 1.1.2 @@ -16732,9 +16700,9 @@ snapshots: tiny-inflate@1.0.3: {} - tinybench@2.9.0: {} + tinybench@6.1.4: {} - tinyexec@1.3.1: {} + tinyexec@1.3.0: {} tinyglobby@0.2.17: dependencies: @@ -17082,87 +17050,53 @@ snapshots: tsx: 4.23.13 yaml: 2.9.0 - vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.0.1)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0): + vitest@5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(@vitest/coverage-v8@5.0.0)(jsdom@30.0.1)(vite@8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)): dependencies: - '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(vite@8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.11 - '@vitest/runner': 4.1.11 - '@vitest/snapshot': 4.1.11 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.0(vite@8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) + chai: 6.2.2 es-module-lexer: 2.3.2 expect-type: 1.4.0 - magic-string: 0.30.21 + magic-string: 1.2.3 obug: 2.1.4 - pathe: 2.0.3 picomatch: 4.0.7 std-env: 4.2.0 - tinybench: 2.9.0 - tinyexec: 1.3.1 + tinybench: 6.1.4 + tinyexec: 1.3.0 tinyglobby: 0.2.17 - tinyrainbow: 3.1.1 vite: 8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 '@types/node': 22.20.1 - '@vitest/coverage-v8': 4.1.11(vitest@4.1.11) + '@vitest/coverage-v8': 5.0.0(vitest@5.0.0) jsdom: 30.0.1 transitivePeerDependencies: - - '@vitejs/devtools' - - esbuild - - jiti - - less - msw - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - yaml - - vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.0.1)(less@4.9.1(supports-color@11.0.0))(sass-embedded@1.104.0)(sass@1.104.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0): - dependencies: - '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(vite@8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@11.0.0))(sass-embedded@1.104.0)(sass@1.104.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.11 - '@vitest/runner': 4.1.11 - '@vitest/snapshot': 4.1.11 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 + + vitest@5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(@vitest/coverage-v8@5.0.0)(jsdom@30.0.1)(vite@8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@11.0.0))(sass-embedded@1.104.0)(sass@1.104.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.0(vite@8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@11.0.0))(sass-embedded@1.104.0)(sass@1.104.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) + chai: 6.2.2 es-module-lexer: 2.3.2 expect-type: 1.4.0 - magic-string: 0.30.21 + magic-string: 1.2.3 obug: 2.1.4 - pathe: 2.0.3 picomatch: 4.0.7 std-env: 4.2.0 - tinybench: 2.9.0 - tinyexec: 1.3.1 + tinybench: 6.1.4 + tinyexec: 1.3.0 tinyglobby: 0.2.17 - tinyrainbow: 3.1.1 vite: 8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@11.0.0))(sass-embedded@1.104.0)(sass@1.104.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 '@types/node': 22.20.1 - '@vitest/coverage-v8': 4.1.11(vitest@4.1.11) + '@vitest/coverage-v8': 5.0.0(vitest@5.0.0) jsdom: 30.0.1 transitivePeerDependencies: - - '@vitejs/devtools' - - esbuild - - jiti - - less - msw - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - yaml void-elements@2.0.1: {} diff --git a/tests/e2e/utils/vitest.ts b/tests/e2e/utils/vitest.ts index 471ca030169e..ec681368147e 100644 --- a/tests/e2e/utils/vitest.ts +++ b/tests/e2e/utils/vitest.ts @@ -4,8 +4,8 @@ import { updateJsonFile } from './project'; /** Updates the `test` builder in the current workspace to use Vitest. */ export async function applyVitestBuilder(): Promise { // These deps matches the deps in `@schematics/angular` - await installPackage('vitest@^4.0.8'); - await installPackage('jsdom@^27.1.0'); + await installPackage('vitest@^5.0.0'); + await installPackage('jsdom@^30.0.0'); await updateJsonFile('angular.json', (json) => { const projects = Object.values(json['projects']); From 341fe87cd14aa12b129ce76efbbcf5b111a32d5d Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Mon, 7 Sep 2026 08:24:44 +0000 Subject: [PATCH 2/9] test: resolve vitest dependencies from latest-versions in e2e tests Refactor `applyVitestBuilder` to read dependency versions directly from `@schematics/angular/utility/latest-versions/package.json` and install them in a single batch using `installWorkspacePackages`. Update all Vitest E2E test suites to pass dependency options (`playwright`, `coverageV8`, `coverageIstanbul`, `webdriver`) directly to `applyVitestBuilder`, eliminating hardcoded major version installations and preventing version mismatches during major dependency upgrades. --- .../behavior/runner-config-vitest_spec.ts | 76 +++++++++---------- .../tests/options/runner-config_spec.ts | 8 +- ...pported.ts => unix-esbuild-unsupported.ts} | 0 ...ements.ts => unix-rebuild-replacements.ts} | 0 .../tests/vitest/browser-coverage-istanbul.ts | 7 +- .../vitest/browser-coverage-sourcemaps.ts | 9 +-- .../vitest/browser-coverage-validation.ts | 28 ++++--- .../e2e/tests/vitest/browser-custom-config.ts | 8 +- tests/e2e/tests/vitest/browser-no-globals.ts | 8 +- tests/e2e/tests/vitest/browser-playwright.ts | 7 +- tests/e2e/tests/vitest/browser-sourcemaps.ts | 8 +- tests/e2e/tests/vitest/browser-webdriverio.ts | 7 +- .../tests/vitest/larger-project-coverage.ts | 11 +-- tests/e2e/tests/vitest/larger-project.ts | 9 +-- tests/e2e/tests/vitest/tslib-resolution.ts | 7 +- tests/e2e/utils/vitest.ts | 52 +++++++++++-- tests/e2e_runner.ts | 9 +++ 17 files changed, 148 insertions(+), 106 deletions(-) rename tests/e2e/tests/build/{esbuild-unsupported.ts => unix-esbuild-unsupported.ts} (100%) rename tests/e2e/tests/build/{rebuild-replacements.ts => unix-rebuild-replacements.ts} (100%) diff --git a/packages/angular/build/src/builders/unit-test/tests/behavior/runner-config-vitest_spec.ts b/packages/angular/build/src/builders/unit-test/tests/behavior/runner-config-vitest_spec.ts index ccd745ba5def..95107d1baec2 100644 --- a/packages/angular/build/src/builders/unit-test/tests/behavior/runner-config-vitest_spec.ts +++ b/packages/angular/build/src/builders/unit-test/tests/behavior/runner-config-vitest_spec.ts @@ -32,10 +32,10 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it('should use custom reporters defined in runnerConfig file', async () => { harness.useTarget('test', { ...BASE_OPTIONS, - runnerConfig: 'vitest.config.ts', + runnerConfig: 'vitest.config.mts', }); - harness.writeFile('vitest.config.ts', VITEST_CONFIG_CONTENT); + harness.writeFile('vitest.config.mts', VITEST_CONFIG_CONTENT); const { result } = await harness.executeOnce(); expect(result?.success).toBeTrue(); @@ -45,11 +45,11 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it('should use custom reporters and outputFile defined as an object in runnerConfig file', async () => { harness.useTarget('test', { ...BASE_OPTIONS, - runnerConfig: 'vitest.config.ts', + runnerConfig: 'vitest.config.mts', }); harness.writeFile( - 'vitest.config.ts', + 'vitest.config.mts', ` import { defineConfig } from 'vitest/config'; export default defineConfig({ @@ -71,12 +71,12 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it('should preserve custom coverage options (e.g. clean: false) from runnerConfig file', async () => { harness.useTarget('test', { ...BASE_OPTIONS, - runnerConfig: 'vitest.config.ts', + runnerConfig: 'vitest.config.mts', coverage: true, }); harness.writeFile( - 'vitest.config.ts', + 'vitest.config.mts', ` import { defineConfig } from 'vitest/config'; export default defineConfig({ @@ -102,11 +102,11 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it('should override reporters defined in runnerConfig file when CLI option is present', async () => { harness.useTarget('test', { ...BASE_OPTIONS, - runnerConfig: 'vitest.config.ts', + runnerConfig: 'vitest.config.mts', reporters: ['default'], }); - harness.writeFile('vitest.config.ts', VITEST_CONFIG_CONTENT); + harness.writeFile('vitest.config.mts', VITEST_CONFIG_CONTENT); const { result } = await harness.executeOnce(); expect(result?.success).toBeTrue(); @@ -117,12 +117,12 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it('should use custom reportsDirectory defined in runnerConfig file', async () => { harness.useTarget('test', { ...BASE_OPTIONS, - runnerConfig: 'vitest.config.ts', + runnerConfig: 'vitest.config.mts', coverage: true, }); harness.writeFile( - 'vitest.config.ts', + 'vitest.config.mts', ` import { defineConfig } from 'vitest/config'; export default defineConfig({ @@ -144,11 +144,11 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { harness.useTarget('test', { ...BASE_OPTIONS, coverage: true, - runnerConfig: 'vitest.config.ts', + runnerConfig: 'vitest.config.mts', }); harness.writeFile( - 'vitest.config.ts', + 'vitest.config.mts', ` import { defineConfig } from 'vitest/config'; export default defineConfig({ @@ -167,11 +167,11 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it('should enable coverage when set in runnerConfig file without builder option', async () => { harness.useTarget('test', { ...BASE_OPTIONS, - runnerConfig: 'vitest.config.ts', + runnerConfig: 'vitest.config.mts', }); harness.writeFile( - 'vitest.config.ts', + 'vitest.config.mts', ` import { defineConfig } from 'vitest/config'; export default defineConfig({ @@ -192,11 +192,11 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it('should exclude test files based on runnerConfig file', async () => { harness.useTarget('test', { ...BASE_OPTIONS, - runnerConfig: 'vitest.config.ts', + runnerConfig: 'vitest.config.mts', }); harness.writeFile( - 'vitest.config.ts', + 'vitest.config.mts', ` import { defineConfig } from 'vitest/config'; export default defineConfig({ @@ -240,12 +240,12 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { harness.useTarget('test', { ...BASE_OPTIONS, coverage: true, - runnerConfig: 'vitest.config.ts', + runnerConfig: 'vitest.config.mts', coverageExclude: ['src/app/cli-excluded.ts'], }); harness.writeFile( - 'vitest.config.ts', + 'vitest.config.mts', ` import { defineConfig } from 'vitest/config'; export default defineConfig({ @@ -292,11 +292,11 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it('should allow overriding globals to false via runnerConfig file', async () => { harness.useTarget('test', { ...BASE_OPTIONS, - runnerConfig: 'vitest.config.ts', + runnerConfig: 'vitest.config.mts', }); harness.writeFile( - 'vitest.config.ts', + 'vitest.config.mts', ` import { defineConfig } from 'vitest/config'; export default defineConfig({ @@ -325,11 +325,11 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it('should initialize environment even when globals are disabled in runnerConfig file', async () => { harness.useTarget('test', { ...BASE_OPTIONS, - runnerConfig: 'vitest.config.ts', + runnerConfig: 'vitest.config.mts', }); harness.writeFile( - 'vitest.config.ts', + 'vitest.config.mts', ` import { defineConfig } from 'vitest/config'; export default defineConfig({ @@ -357,11 +357,11 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it('should fail when a DOM-dependent test is run in a node environment', async () => { harness.useTarget('test', { ...BASE_OPTIONS, - runnerConfig: 'vitest.config.ts', + runnerConfig: 'vitest.config.mts', }); harness.writeFile( - 'vitest.config.ts', + 'vitest.config.mts', ` import { defineConfig } from 'vitest/config'; export default defineConfig({ @@ -379,11 +379,11 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it('should warn and ignore "test.projects" option from runnerConfig file', async () => { harness.useTarget('test', { ...BASE_OPTIONS, - runnerConfig: 'vitest.config.ts', + runnerConfig: 'vitest.config.mts', }); harness.writeFile( - 'vitest.config.ts', + 'vitest.config.mts', ` import { defineConfig } from 'vitest/config'; export default defineConfig({ @@ -411,11 +411,11 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it('should warn and ignore "test.include" option from runnerConfig file', async () => { harness.useTarget('test', { ...BASE_OPTIONS, - runnerConfig: 'vitest.config.ts', + runnerConfig: 'vitest.config.mts', }); harness.writeFile( - 'vitest.config.ts', + 'vitest.config.mts', ` import { defineConfig } from 'vitest/config'; export default defineConfig({ @@ -444,11 +444,11 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { harness.useTarget('test', { ...BASE_OPTIONS, watch: false, - runnerConfig: 'vitest.config.ts', + runnerConfig: 'vitest.config.mts', }); harness.writeFile( - 'vitest.config.ts', + 'vitest.config.mts', ` import { defineConfig } from 'vitest/config'; export default defineConfig({ @@ -476,11 +476,11 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it('should warn about performance when "test.exclude" option is in runnerConfig file', async () => { harness.useTarget('test', { ...BASE_OPTIONS, - runnerConfig: 'vitest.config.ts', + runnerConfig: 'vitest.config.mts', }); harness.writeFile( - 'vitest.config.ts', + 'vitest.config.mts', ` import { defineConfig } from 'vitest/config'; export default defineConfig({ @@ -508,11 +508,11 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it(`should append "test.setupFiles" (string) from runnerConfig to the CLI's setup`, async () => { harness.useTarget('test', { ...BASE_OPTIONS, - runnerConfig: 'vitest.config.ts', + runnerConfig: 'vitest.config.mts', }); harness.writeFile( - 'vitest.config.ts', + 'vitest.config.mts', ` import { defineConfig } from 'vitest/config'; export default defineConfig({ @@ -542,11 +542,11 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it(`should append "test.setupFiles" (array) from runnerConfig to the CLI's setup`, async () => { harness.useTarget('test', { ...BASE_OPTIONS, - runnerConfig: 'vitest.config.ts', + runnerConfig: 'vitest.config.mts', }); harness.writeFile( - 'vitest.config.ts', + 'vitest.config.mts', ` import { defineConfig } from 'vitest/config'; export default defineConfig({ @@ -578,11 +578,11 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it('should merge and apply custom Vite plugins from runnerConfig file', async () => { harness.useTarget('test', { ...BASE_OPTIONS, - runnerConfig: 'vitest.config.ts', + runnerConfig: 'vitest.config.mts', }); harness.writeFile( - 'vitest.config.ts', + 'vitest.config.mts', ` import { defineConfig } from 'vitest/config'; export default defineConfig({ diff --git a/packages/angular/build/src/builders/unit-test/tests/options/runner-config_spec.ts b/packages/angular/build/src/builders/unit-test/tests/options/runner-config_spec.ts index b061b88e990c..9f3b3972f1ee 100644 --- a/packages/angular/build/src/builders/unit-test/tests/options/runner-config_spec.ts +++ b/packages/angular/build/src/builders/unit-test/tests/options/runner-config_spec.ts @@ -31,10 +31,10 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { describe('Vitest Runner', () => { it('should use a specified config file path', async () => { - harness.writeFile('custom-vitest.config.ts', VITEST_CONFIG_CONTENT); + harness.writeFile('custom-vitest.config.mts', VITEST_CONFIG_CONTENT); harness.useTarget('test', { ...BASE_OPTIONS, - runnerConfig: 'custom-vitest.config.ts', + runnerConfig: 'custom-vitest.config.mts', }); const { result } = await harness.executeOnce(); @@ -96,7 +96,7 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it('should find and use a `vitest-base.config.js` in the workspace root', async () => { // This file should be ignored because the new logic looks for `vitest-base.config.*`. - harness.writeFile('vitest.config.ts', VITEST_CONFIG_CONTENT); + harness.writeFile('vitest.config.mts', VITEST_CONFIG_CONTENT); // The workspace root is the directory containing the project root in the test harness. harness.writeFile('vitest-base.config.js', VITEST_CONFIG_CONTENT); harness.useTarget('test', { @@ -113,7 +113,7 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it('should fallback to in-memory config when no base config is found', async () => { // This file should be ignored because the new logic looks for `vitest-base.config.*` // and when `runnerConfig` is true, it should not fall back to the default search. - harness.writeFile('vitest.config.ts', VITEST_CONFIG_CONTENT); + harness.writeFile('vitest.config.mts', VITEST_CONFIG_CONTENT); harness.useTarget('test', { ...BASE_OPTIONS, runnerConfig: true, diff --git a/tests/e2e/tests/build/esbuild-unsupported.ts b/tests/e2e/tests/build/unix-esbuild-unsupported.ts similarity index 100% rename from tests/e2e/tests/build/esbuild-unsupported.ts rename to tests/e2e/tests/build/unix-esbuild-unsupported.ts diff --git a/tests/e2e/tests/build/rebuild-replacements.ts b/tests/e2e/tests/build/unix-rebuild-replacements.ts similarity index 100% rename from tests/e2e/tests/build/rebuild-replacements.ts rename to tests/e2e/tests/build/unix-rebuild-replacements.ts diff --git a/tests/e2e/tests/vitest/browser-coverage-istanbul.ts b/tests/e2e/tests/vitest/browser-coverage-istanbul.ts index 21f3987a3d1d..66f73162d586 100644 --- a/tests/e2e/tests/vitest/browser-coverage-istanbul.ts +++ b/tests/e2e/tests/vitest/browser-coverage-istanbul.ts @@ -1,16 +1,15 @@ import { ng } from '../../utils/process'; import { applyVitestBuilder } from '../../utils/vitest'; import assert from 'node:assert'; -import { installPackage } from '../../utils/packages'; import { expectFileToExist, readFile } from '../../utils/fs'; import { updateJsonFile } from '../../utils/project'; export default async function (): Promise { - await applyVitestBuilder(); - // Install ONLY Istanbul coverage package. // This will trigger the auto-detection logic to use Istanbul even for Node tests. - await installPackage('@vitest/coverage-istanbul@4'); + await applyVitestBuilder({ + coverageIstanbul: true, + }); // Use the 'json' reporter to get a machine-readable output for assertions. await updateJsonFile('angular.json', (json) => { diff --git a/tests/e2e/tests/vitest/browser-coverage-sourcemaps.ts b/tests/e2e/tests/vitest/browser-coverage-sourcemaps.ts index db8b91a0abbe..476c15c41d38 100644 --- a/tests/e2e/tests/vitest/browser-coverage-sourcemaps.ts +++ b/tests/e2e/tests/vitest/browser-coverage-sourcemaps.ts @@ -1,14 +1,13 @@ import assert from 'node:assert/strict'; import { applyVitestBuilder } from '../../utils/vitest'; import { ng } from '../../utils/process'; -import { installPackage } from '../../utils/packages'; import { expectFileToExist, readFile } from '../../utils/fs'; export default async function (): Promise { - await applyVitestBuilder(); - await installPackage('playwright@1'); - await installPackage('@vitest/browser-playwright@4'); - await installPackage('@vitest/coverage-v8@4'); + await applyVitestBuilder({ + playwright: true, + coverageV8: true, + }); // Run tests with coverage in browser mode. // We use the default passing tests generated for the project. diff --git a/tests/e2e/tests/vitest/browser-coverage-validation.ts b/tests/e2e/tests/vitest/browser-coverage-validation.ts index 9124af1bb3a8..e98da6495f0b 100644 --- a/tests/e2e/tests/vitest/browser-coverage-validation.ts +++ b/tests/e2e/tests/vitest/browser-coverage-validation.ts @@ -2,17 +2,14 @@ import assert from 'node:assert/strict'; import path from 'node:path'; import { applyVitestBuilder } from '../../utils/vitest'; import { execAndCaptureError } from '../../utils/process'; -import { installPackage } from '../../utils/packages'; import { writeFile } from '../../utils/fs'; import { stripVTControlCharacters } from 'node:util'; import { unlink } from 'node:fs/promises'; export default async function (): Promise { - await applyVitestBuilder(); - - // Install necessary packages to pass the browser provider check - await installPackage('playwright@1'); - await installPackage('@vitest/browser-playwright@4'); + await applyVitestBuilder({ + playwright: true, + }); // === Case 1: Browser configured via CLI option === const error1 = await execAndCaptureError('ng', [ @@ -29,24 +26,29 @@ export default async function (): Promise { 'Expected validation error for missing coverage packages.', ); - await installPackage('@vitest/coverage-v8@4'); + await applyVitestBuilder({ + playwright: true, + coverageV8: true, + }); - const configPath = 'vitest.config.ts'; + const configPath = 'vitest.config.mts'; const absoluteConfigPath = path.resolve(configPath); try { - // === Case 2: Browser configured via vitest.config.ts (name) === + // === Case 2: Browser configured via vitest.config.mts (name) === await writeFile( configPath, ` import { defineConfig } from 'vitest/config'; + import { playwright } from '@vitest/browser-playwright'; + export default defineConfig({ test: { coverage: { provider: 'v8' }, browser: { enabled: true, name: 'firefox', - provider: 'playwright', + provider: playwright(), }, }, }); @@ -66,17 +68,19 @@ export default async function (): Promise { 'Expected validation error for unsupported browser with coverage (config name).', ); - // === Case 3: Browser configured via vitest.config.ts (instances) === + // === Case 3: Browser configured via vitest.config.mts (instances) === await writeFile( configPath, ` import { defineConfig } from 'vitest/config'; + import { playwright } from '@vitest/browser-playwright'; + export default defineConfig({ test: { coverage: { provider: 'v8' }, browser: { enabled: true, - provider: 'playwright', + provider: playwright(), instances: [{ browser: 'firefox' }], } as any, }, diff --git a/tests/e2e/tests/vitest/browser-custom-config.ts b/tests/e2e/tests/vitest/browser-custom-config.ts index a523f421853f..969b6efcacb7 100644 --- a/tests/e2e/tests/vitest/browser-custom-config.ts +++ b/tests/e2e/tests/vitest/browser-custom-config.ts @@ -1,13 +1,13 @@ import assert from 'node:assert/strict'; import { applyVitestBuilder } from '../../utils/vitest'; import { ng } from '../../utils/process'; -import { installPackage } from '../../utils/packages'; import { writeFile } from '../../utils/fs'; export default async function (): Promise { - await applyVitestBuilder(); - await installPackage('playwright@1'); - await installPackage('@vitest/browser-playwright@4'); + await applyVitestBuilder({ + playwright: true, + }); + await ng('generate', 'component', 'my-comp'); // Create vitest-base.config.ts diff --git a/tests/e2e/tests/vitest/browser-no-globals.ts b/tests/e2e/tests/vitest/browser-no-globals.ts index b25f8168c5f7..3a4fcde4b96c 100644 --- a/tests/e2e/tests/vitest/browser-no-globals.ts +++ b/tests/e2e/tests/vitest/browser-no-globals.ts @@ -1,6 +1,5 @@ import assert from 'node:assert/strict'; import { writeFile } from '../../utils/fs'; -import { installPackage } from '../../utils/packages'; import { ng } from '../../utils/process'; import { applyVitestBuilder } from '../../utils/vitest'; @@ -9,10 +8,9 @@ import { applyVitestBuilder } from '../../utils/vitest'; * @see https://github.com/angular/angular-cli/issues/31745 */ export default async function (): Promise { - await applyVitestBuilder(); - - await installPackage('playwright@1'); - await installPackage('@vitest/browser-playwright@4'); + await applyVitestBuilder({ + playwright: true, + }); await writeFile( 'src/app/app.spec.ts', diff --git a/tests/e2e/tests/vitest/browser-playwright.ts b/tests/e2e/tests/vitest/browser-playwright.ts index fc8e7e5b53b3..54d744e10208 100644 --- a/tests/e2e/tests/vitest/browser-playwright.ts +++ b/tests/e2e/tests/vitest/browser-playwright.ts @@ -1,14 +1,13 @@ import assert from 'node:assert/strict'; import { applyVitestBuilder } from '../../utils/vitest'; import { ng } from '../../utils/process'; -import { installPackage } from '../../utils/packages'; import { writeFile } from '../../utils/fs'; import { updateJsonFile } from '../../utils/project'; export default async function (): Promise { - await applyVitestBuilder(); - await installPackage('playwright@1'); - await installPackage('@vitest/browser-playwright@4'); + await applyVitestBuilder({ + playwright: true, + }); await ng('generate', 'component', 'my-comp'); await writeFile( diff --git a/tests/e2e/tests/vitest/browser-sourcemaps.ts b/tests/e2e/tests/vitest/browser-sourcemaps.ts index 90c04457c7c8..4bc025a7d2b9 100644 --- a/tests/e2e/tests/vitest/browser-sourcemaps.ts +++ b/tests/e2e/tests/vitest/browser-sourcemaps.ts @@ -1,14 +1,14 @@ import assert from 'node:assert/strict'; import { applyVitestBuilder } from '../../utils/vitest'; import { ng, noSilentNg } from '../../utils/process'; -import { installPackage } from '../../utils/packages'; import { writeFile } from '../../utils/fs'; import { stripVTControlCharacters } from 'node:util'; export default async function (): Promise { - await applyVitestBuilder(); - await installPackage('playwright@1'); - await installPackage('@vitest/browser-playwright@4'); + await applyVitestBuilder({ + playwright: true, + }); + await ng('generate', 'component', 'my-comp'); // Add a failing test to verify source map support diff --git a/tests/e2e/tests/vitest/browser-webdriverio.ts b/tests/e2e/tests/vitest/browser-webdriverio.ts index 0838761eb2ad..231f5fca4041 100644 --- a/tests/e2e/tests/vitest/browser-webdriverio.ts +++ b/tests/e2e/tests/vitest/browser-webdriverio.ts @@ -1,14 +1,13 @@ import assert from 'node:assert/strict'; import { applyVitestBuilder } from '../../utils/vitest'; import { ng } from '../../utils/process'; -import { installPackage } from '../../utils/packages'; import { writeFile } from '../../utils/fs'; import { updateJsonFile } from '../../utils/project'; export default async function (): Promise { - await applyVitestBuilder(); - await installPackage('webdriverio@9'); - await installPackage('@vitest/browser-webdriverio@4'); + await applyVitestBuilder({ + webdriver: true, + }); await ng('generate', 'component', 'my-comp'); diff --git a/tests/e2e/tests/vitest/larger-project-coverage.ts b/tests/e2e/tests/vitest/larger-project-coverage.ts index 3594bdc7dfee..d5fa94c0b82c 100644 --- a/tests/e2e/tests/vitest/larger-project-coverage.ts +++ b/tests/e2e/tests/vitest/larger-project-coverage.ts @@ -1,13 +1,14 @@ import { ng } from '../../utils/process'; import { applyVitestBuilder } from '../../utils/vitest'; import assert from 'node:assert'; -import { installPackage } from '../../utils/packages'; import { updateJsonFile } from '../../utils/project'; import { readFile } from '../../utils/fs'; export default async function () { - await applyVitestBuilder(); - await installPackage('@vitest/coverage-v8@4'); + await applyVitestBuilder({ + coverageV8: true, + playwright: true, + }); // Add coverage and threshold configuration to ensure coverage is calculated. // Use the 'json' reporter to get a machine-readable output for assertions. @@ -44,10 +45,6 @@ export default async function () { assert.ok(found, `Expected ${file} to be in the JSDOM coverage report.`); } - // Setup for browser mode - await installPackage('playwright@1'); - await installPackage('@vitest/browser-playwright@4'); - // Run tests in browser mode with coverage const { stdout: browserStdout } = await ng( 'test', diff --git a/tests/e2e/tests/vitest/larger-project.ts b/tests/e2e/tests/vitest/larger-project.ts index 7b8db953c30c..9b9db25bb067 100644 --- a/tests/e2e/tests/vitest/larger-project.ts +++ b/tests/e2e/tests/vitest/larger-project.ts @@ -1,12 +1,13 @@ import assert from 'node:assert'; import { mkdir, writeFile } from 'node:fs/promises'; import { dirname } from 'node:path'; -import { installPackage } from '../../utils/packages'; import { ng } from '../../utils/process'; import { applyVitestBuilder } from '../../utils/vitest'; export default async function () { - await applyVitestBuilder(); + await applyVitestBuilder({ + playwright: true, + }); const artifactCount = 500; // A new project starts with 1 test file (app.spec.ts) @@ -22,10 +23,6 @@ export default async function () { const { stdout: jsdomStdout } = await ng('test', '--no-watch'); assert.match(jsdomStdout, expectedMessage, `Expected ${totalTests} tests to pass in JSDOM mode.`); - // Setup for browser mode - await installPackage('playwright@1'); - await installPackage('@vitest/browser-playwright@4'); - // Run tests in browser mode const { stdout: browserStdout } = await ng( 'test', diff --git a/tests/e2e/tests/vitest/tslib-resolution.ts b/tests/e2e/tests/vitest/tslib-resolution.ts index 759d5e2b5728..091f5105ba7f 100644 --- a/tests/e2e/tests/vitest/tslib-resolution.ts +++ b/tests/e2e/tests/vitest/tslib-resolution.ts @@ -1,13 +1,12 @@ import { writeFile } from '../../utils/fs'; -import { installPackage } from '../../utils/packages'; import { ng } from '../../utils/process'; import { applyVitestBuilder } from '../../utils/vitest'; import assert from 'node:assert'; export default async function () { - await applyVitestBuilder(); - await installPackage('playwright@1'); - await installPackage('@vitest/browser-playwright@4'); + await applyVitestBuilder({ + playwright: true, + }); // Add a custom decorator to trigger tslib usage await writeFile( diff --git a/tests/e2e/utils/vitest.ts b/tests/e2e/utils/vitest.ts index ec681368147e..01639c38996f 100644 --- a/tests/e2e/utils/vitest.ts +++ b/tests/e2e/utils/vitest.ts @@ -1,11 +1,53 @@ -import { installPackage } from './packages'; +import { createRequire } from 'node:module'; +import { installWorkspacePackages } from './packages'; import { updateJsonFile } from './project'; +import { readFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; /** Updates the `test` builder in the current workspace to use Vitest. */ -export async function applyVitestBuilder(): Promise { - // These deps matches the deps in `@schematics/angular` - await installPackage('vitest@^5.0.0'); - await installPackage('jsdom@^30.0.0'); +export async function applyVitestBuilder(options?: { + coverageV8?: boolean; + coverageIstanbul?: boolean; + playwright?: boolean; + webdriver?: boolean; +}): Promise { + const { coverageV8, coverageIstanbul, playwright, webdriver } = options ?? {}; + + const schematicsAngular = createRequire(process.cwd() + '/').resolve( + '@schematics/angular/package.json', + ); + + const latestVersionsPath = resolve(schematicsAngular, '../utility/latest-versions/package.json'); + + const { dependencies: latestVersion } = JSON.parse(await readFile(latestVersionsPath, 'utf-8')); + + await updateJsonFile('package.json', (json) => { + json.devDependencies['vitest'] = latestVersion['vitest']; + json.devDependencies['jsdom'] = latestVersion['jsdom']; + + if (webdriver) { + json.devDependencies['webdriverio'] = latestVersion['webdriverio']; + json.devDependencies['@vitest/browser-webdriverio'] = + latestVersion['@vitest/browser-webdriverio']; + } + + if (playwright) { + json.devDependencies['playwright'] = latestVersion['playwright']; + json.devDependencies['@vitest/browser-playwright'] = + latestVersion['@vitest/browser-playwright']; + } + + if (coverageV8) { + json.devDependencies['@vitest/coverage-v8'] = latestVersion['@vitest/coverage-v8']; + } + + if (coverageIstanbul) { + json.devDependencies['@vitest/coverage-istanbul'] = + latestVersion['@vitest/coverage-istanbul']; + } + }); + + await installWorkspacePackages(); await updateJsonFile('angular.json', (json) => { const projects = Object.values(json['projects']); diff --git a/tests/e2e_runner.ts b/tests/e2e_runner.ts index f77d6e367e9b..3f1945905089 100644 --- a/tests/e2e_runner.ts +++ b/tests/e2e_runner.ts @@ -116,6 +116,7 @@ const testGlob = (process.env.TESTBRIDGE_TEST_ONLY ?? argv.glob).replace(/\.ts$/ const e2eRoot = path.join(__dirname, 'e2e'); const allSetups = glob.sync(`setup/**/*.js`, { cwd: e2eRoot }).sort(); const allInitializers = glob.sync(`initialize/**/*.js`, { cwd: e2eRoot }).sort(); +const isWindows = process.platform === 'win32'; const allTests = glob .sync(testGlob, { cwd: e2eRoot, ignore: argv.ignore }) @@ -140,6 +141,14 @@ const allTests = glob return false; } + if (isWindows) { + if (fileName.startsWith('unix-')) { + return false; + } + } else if (fileName.startsWith('windows-')) { + return false; + } + return true; }) .sort(); From 154031ab398ed40511957778056a05239235191c Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Mon, 7 Sep 2026 08:53:16 +0000 Subject: [PATCH 3/9] refactor(@angular/build): deprecate splitting option in unit-test builder DEPRECATED: The `splitting` option in the unit-test builder is deprecated as it is no longer needed with Vitest 5. Disabling code splitting is no longer necessary with Vitest 5, as the issue where shared-module exports were uninitialized in class-field initializers under JSDOM has been resolved. --- .../build/src/builders/unit-test/schema.json | 5 +++-- .../behavior/vitest-shared-chunk-init_spec.ts | 17 ----------------- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/packages/angular/build/src/builders/unit-test/schema.json b/packages/angular/build/src/builders/unit-test/schema.json index 5e9a62cf495d..58a9592986b1 100644 --- a/packages/angular/build/src/builders/unit-test/schema.json +++ b/packages/angular/build/src/builders/unit-test/schema.json @@ -21,7 +21,7 @@ }, "runnerConfig": { "type": ["string", "boolean"], - "description": "Specifies the configuration file for the selected test runner. If a string is provided, it will be used as the path to the configuration file. If `true`, the builder will search for a default configuration file (e.g., `vitest-base.config.ts` or `karma.conf.js`). If `false`, no external configuration file will be used.\\nFor Vitest, this enables advanced options and the use of custom plugins. Please note that while the file is loaded, the Angular team does not provide direct support for its specific contents or any third-party plugins used within it.", + "description": "Specifies the configuration file for the selected test runner. If a string is provided, it will be used as the path to the configuration file. If `true`, the builder will search for a default configuration file (e.g., `vitest-base.config.mts` or `karma.conf.js`). If `false`, no external configuration file will be used.\\nFor Vitest, this enables advanced options and the use of custom plugins. Please note that while the file is loaded, the Angular team does not provide direct support for its specific contents or any third-party plugins used within it.", "default": false }, "browsers": { @@ -80,7 +80,8 @@ "splitting": { "type": "boolean", "description": "Enables code splitting for test execution. When enabled, shared code between test files is split into separate chunks. Issues with live ESM bindings in Node.js environments (such as uninitialized exports or mocking failures) can be resolved by disabling splitting. This option is only available for the Vitest runner.", - "default": true + "default": true, + "x-deprecated": "No longer needed with Vitest 5." }, "quiet": { "type": "boolean", diff --git a/packages/angular/build/src/builders/unit-test/tests/behavior/vitest-shared-chunk-init_spec.ts b/packages/angular/build/src/builders/unit-test/tests/behavior/vitest-shared-chunk-init_spec.ts index afccfc2c06e1..1cddec79a14d 100644 --- a/packages/angular/build/src/builders/unit-test/tests/behavior/vitest-shared-chunk-init_spec.ts +++ b/packages/angular/build/src/builders/unit-test/tests/behavior/vitest-shared-chunk-init_spec.ts @@ -16,28 +16,11 @@ import { describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { describe('Behavior: "Vitest shared chunk initialization"', () => { - // Regression test for https://github.com/angular/angular-cli/issues/33728. - // - // Without `disableCodeSplitting`, esbuild hoists a module imported by more than one spec - // entry point into a shared chunk behind a lazy `__esm` initializer, and a class-field - // initializer in another chunk reads the exported value as `undefined` under the jsdom - // runner. All four trigger conditions are required and encoded below: - // 1. two spec entry points import the shared module (so it lands in a shared chunk); - // 2. a component in one entry reads the export during class-field initialization; - // 3. that component's spec file contains an `async` test callback (no `await` needed); - // 4. zone.js is in the polyfills (the `setupApplicationTarget` default), which downlevels - // async and makes esbuild emit the spec entry CommonJS-wrapped. - // - // NOTE: the failure this guards against is sensitive to inert content — adding a top-level - // side effect (even a `console.log`) to the shared or importing module below defused it - // during reduction. Mirror https://github.com/jonmarozick/ng-shared-chunk-repro when - // modifying these fixtures. it('should provide shared-module exports to class-field initializers in async specs', async () => { setupApplicationTarget(harness); harness.useTarget('test', { ...BASE_OPTIONS, - splitting: false, }); // Keep the default project's spec deterministic; a third spec entry that does not touch From c6222df368cfc9158bd102abe0daea519e78ea5a Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Mon, 7 Sep 2026 08:54:49 +0000 Subject: [PATCH 4/9] refactor(@schematics/angular): use .mts extension for generated Vitest configuration Update the generated Vitest base configuration file extension from `.ts` to `.mts` to ensure it is treated as an ES module by default. This avoids warnings from Vite regarding ESM syntax in CommonJS files when the workspace does not define `"type": "module"`. Also updates related schematics tests, karma migration hints, and builder integration test specs to use `vitest-base.config.mts`. --- .../builders/unit-test/tests/options/isolate_spec.ts | 2 +- .../tests/options/runner-config-coverage_spec.ts | 6 +++--- .../unit-test/tests/options/runner-config_spec.ts | 10 +++++----- ...fig.ts.template => vitest-base.config.mts.template} | 0 packages/schematics/angular/config/index.ts | 6 +++--- packages/schematics/angular/config/index_spec.ts | 4 ++-- .../migrate-karma-to-vitest/karma-processor.ts | 2 +- tests/e2e/tests/vitest/browser-custom-config.ts | 4 ++-- tests/e2e/utils/vitest.ts | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) rename packages/schematics/angular/config/files/{vitest-base.config.ts.template => vitest-base.config.mts.template} (100%) diff --git a/packages/angular/build/src/builders/unit-test/tests/options/isolate_spec.ts b/packages/angular/build/src/builders/unit-test/tests/options/isolate_spec.ts index a37bf597e91a..28fbb855a91f 100644 --- a/packages/angular/build/src/builders/unit-test/tests/options/isolate_spec.ts +++ b/packages/angular/build/src/builders/unit-test/tests/options/isolate_spec.ts @@ -57,7 +57,7 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it('should override isolate from the Vitest config file when set to false', async () => { harness.writeFile( - 'vitest-base.config.ts', + 'vitest-base.config.mts', ` import { defineConfig } from 'vitest/config'; diff --git a/packages/angular/build/src/builders/unit-test/tests/options/runner-config-coverage_spec.ts b/packages/angular/build/src/builders/unit-test/tests/options/runner-config-coverage_spec.ts index c46c38da79ad..9998caea72e3 100644 --- a/packages/angular/build/src/builders/unit-test/tests/options/runner-config-coverage_spec.ts +++ b/packages/angular/build/src/builders/unit-test/tests/options/runner-config-coverage_spec.ts @@ -23,7 +23,7 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { describe('Vitest Runner', () => { it('should preserve thresholds from Vitest config when not overridden by CLI', async () => { harness.writeFile( - 'vitest-base.config.ts', + 'vitest-base.config.mts', ` import { defineConfig } from 'vitest/config'; export default defineConfig({ @@ -52,7 +52,7 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it('should override Vitest config thresholds with CLI thresholds', async () => { harness.writeFile( - 'vitest-base.config.ts', + 'vitest-base.config.mts', ` import { defineConfig } from 'vitest/config'; export default defineConfig({ @@ -84,7 +84,7 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it('should merge partial CLI thresholds with Vitest config thresholds', async () => { harness.writeFile( - 'vitest-base.config.ts', + 'vitest-base.config.mts', ` import { defineConfig } from 'vitest/config'; export default defineConfig({ diff --git a/packages/angular/build/src/builders/unit-test/tests/options/runner-config_spec.ts b/packages/angular/build/src/builders/unit-test/tests/options/runner-config_spec.ts index 9f3b3972f1ee..fa467f21e174 100644 --- a/packages/angular/build/src/builders/unit-test/tests/options/runner-config_spec.ts +++ b/packages/angular/build/src/builders/unit-test/tests/options/runner-config_spec.ts @@ -44,7 +44,7 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { }); it('should search for a config file when `true`', async () => { - harness.writeFile('vitest-base.config.ts', VITEST_CONFIG_CONTENT); + harness.writeFile('vitest-base.config.mts', VITEST_CONFIG_CONTENT); harness.useTarget('test', { ...BASE_OPTIONS, runnerConfig: true, @@ -57,7 +57,7 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { }); it('should ignore config file when `false`', async () => { - harness.writeFile('vitest-base.config.ts', VITEST_CONFIG_CONTENT); + harness.writeFile('vitest-base.config.mts', VITEST_CONFIG_CONTENT); harness.useTarget('test', { ...BASE_OPTIONS, runnerConfig: false, @@ -70,7 +70,7 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { }); it('should ignore config file by default', async () => { - harness.writeFile('vitest-base.config.ts', VITEST_CONFIG_CONTENT); + harness.writeFile('vitest-base.config.mts', VITEST_CONFIG_CONTENT); harness.useTarget('test', { ...BASE_OPTIONS, }); @@ -96,9 +96,9 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { it('should find and use a `vitest-base.config.js` in the workspace root', async () => { // This file should be ignored because the new logic looks for `vitest-base.config.*`. - harness.writeFile('vitest.config.mts', VITEST_CONFIG_CONTENT); + harness.writeFile('vitest.config.ts', VITEST_CONFIG_CONTENT); // The workspace root is the directory containing the project root in the test harness. - harness.writeFile('vitest-base.config.js', VITEST_CONFIG_CONTENT); + harness.writeFile('vitest-base.config.mjs', VITEST_CONFIG_CONTENT); harness.useTarget('test', { ...BASE_OPTIONS, runnerConfig: true, diff --git a/packages/schematics/angular/config/files/vitest-base.config.ts.template b/packages/schematics/angular/config/files/vitest-base.config.mts.template similarity index 100% rename from packages/schematics/angular/config/files/vitest-base.config.ts.template rename to packages/schematics/angular/config/files/vitest-base.config.mts.template diff --git a/packages/schematics/angular/config/index.ts b/packages/schematics/angular/config/index.ts index 73818699a134..131ab6dc1ceb 100644 --- a/packages/schematics/angular/config/index.ts +++ b/packages/schematics/angular/config/index.ts @@ -72,7 +72,7 @@ function addVitestConfig(options: ConfigOptions): Rule { if (testTarget.options.runner === 'karma') { context.logger.warn( `The "test" target is configured to use the "karma" runner in the main options.` + - ' The generated "vitest-base.config.ts" file may not be used.', + ' The generated "vitest-base.config.mts" file may not be used.', ); } @@ -85,14 +85,14 @@ function addVitestConfig(options: ConfigOptions): Rule { ) { context.logger.warn( `The "test" target's "${name}" configuration is configured to use the "karma" runner.` + - ' The generated "vitest-base.config.ts" file may not be used for that configuration.', + ' The generated "vitest-base.config.mts" file may not be used for that configuration.', ); } } return mergeWith( apply(url('./files'), [ - filter((p) => p.endsWith('vitest-base.config.ts.template')), + filter((p) => p.endsWith('vitest-base.config.mts.template')), applyTemplates({}), move(project.root), ]), diff --git a/packages/schematics/angular/config/index_spec.ts b/packages/schematics/angular/config/index_spec.ts index bc1715c4866a..a76c4e70567d 100644 --- a/packages/schematics/angular/config/index_spec.ts +++ b/packages/schematics/angular/config/index_spec.ts @@ -194,9 +194,9 @@ describe('Config Schematic', () => { applicationTree.overwrite('angular.json', JSON.stringify(angularJson)); }); - it('should create a vitest-base.config.ts file', async () => { + it('should create a vitest-base.config.mts file', async () => { const tree = await runConfigSchematic(ConfigType.Vitest); - expect(tree.exists('projects/foo/vitest-base.config.ts')).toBeTrue(); + expect(tree.exists('projects/foo/vitest-base.config.mts')).toBeTrue(); }); it(`should set 'runnerConfig' in test builder`, async () => { diff --git a/packages/schematics/angular/migrations/migrate-karma-to-vitest/karma-processor.ts b/packages/schematics/angular/migrations/migrate-karma-to-vitest/karma-processor.ts index 43e64eb2033a..a02eb01f8bdd 100644 --- a/packages/schematics/angular/migrations/migrate-karma-to-vitest/karma-processor.ts +++ b/packages/schematics/angular/migrations/migrate-karma-to-vitest/karma-processor.ts @@ -163,7 +163,7 @@ export async function processKarmaConfig( context.logger.warn( `Project "${projectName}" uses a custom Karma configuration file "${karmaConfig}". ` + `Tests have been migrated to use Vitest, but you may need to manually migrate custom settings ` + - `from this Karma config to a Vitest config (e.g. "vitest-base.config.ts") ` + + `from this Karma config to a Vitest config (e.g. "vitest-base.config.mts") ` + `and set the "runnerConfig" option to true.`, ); manualMigrationFiles.push(karmaConfig); diff --git a/tests/e2e/tests/vitest/browser-custom-config.ts b/tests/e2e/tests/vitest/browser-custom-config.ts index 969b6efcacb7..ab9092d038af 100644 --- a/tests/e2e/tests/vitest/browser-custom-config.ts +++ b/tests/e2e/tests/vitest/browser-custom-config.ts @@ -10,9 +10,9 @@ export default async function (): Promise { await ng('generate', 'component', 'my-comp'); - // Create vitest-base.config.ts + // Create vitest-base.config.mts await writeFile( - 'vitest-base.config.ts', + 'vitest-base.config.mts', ` import { defineConfig } from 'vitest/config'; import { playwright } from '@vitest/browser-playwright'; diff --git a/tests/e2e/utils/vitest.ts b/tests/e2e/utils/vitest.ts index 01639c38996f..007c9287cece 100644 --- a/tests/e2e/utils/vitest.ts +++ b/tests/e2e/utils/vitest.ts @@ -47,7 +47,7 @@ export async function applyVitestBuilder(options?: { } }); - await installWorkspacePackages(); + await installWorkspacePackages({ force: true }); await updateJsonFile('angular.json', (json) => { const projects = Object.values(json['projects']); From 0f959bf95316ef1a1fcc209372b0eedc973fbcdf Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Mon, 7 Sep 2026 09:46:10 +0000 Subject: [PATCH 5/9] fix(@angular/build): disable root config extension for inline test project MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In Vitest 5, inline projects declared under `test.projects` extend the declaring root configuration by default (`extends: true`). Because the Angular CLI test runner already explicitly merges user-provided options from the configuration file into `projectConfig`, automatic root inheritance caused array options—such as `browser.instances` and plugins—to be merged and concatenated twice. Setting `extends: false` on the inline project configuration keeps it isolated from the declaring root config and prevents duplicate browser instance registrations and repeated test runs. --- .../build/src/builders/unit-test/runners/vitest/plugins.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts index 0dfd1dc4a8ce..ee81e417bb5d 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts @@ -245,6 +245,7 @@ export async function createVitestConfigPlugin( const projectResolver = createProjectResolver(projectSourceRoot); const projectDefaults: Vite.UserConfig & UserWorkspaceConfig = { + extends: false, test: { setupFiles, globals: true, @@ -265,6 +266,7 @@ export async function createVitestConfigPlugin( const { optimizeDeps, resolve } = config; const projectOverrides: Vite.UserConfig & UserWorkspaceConfig = { + extends: false, test: { name: projectName, include, From 3b485703c2699476f678b2b1e8bebcc4c6094826 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Mon, 7 Sep 2026 09:47:14 +0000 Subject: [PATCH 6/9] build: update @vitest/browser-webdriverio to 5.0.0-rc.1 Updates `@vitest/browser-webdriverio` to `^5.0.0-rc.1` in `latest-versions` and drops `{ force: true }` from package installation in Vitest E2E utilities. --- .../schematics/angular/utility/latest-versions/package.json | 2 +- tests/e2e/utils/vitest.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/schematics/angular/utility/latest-versions/package.json b/packages/schematics/angular/utility/latest-versions/package.json index 92b667724aaf..30a45ad40fe2 100644 --- a/packages/schematics/angular/utility/latest-versions/package.json +++ b/packages/schematics/angular/utility/latest-versions/package.json @@ -29,7 +29,7 @@ "@vitest/coverage-v8": "^5.0.0", "@vitest/coverage-istanbul": "^5.0.0", "@vitest/browser-playwright": "^5.0.0", - "@vitest/browser-webdriverio": "^4.0.8", + "@vitest/browser-webdriverio": "^5.0.0-rc.1", "@vitest/browser-preview": "^5.0.0", "playwright": "^1.48.0", "webdriverio": "^9.0.0", diff --git a/tests/e2e/utils/vitest.ts b/tests/e2e/utils/vitest.ts index 007c9287cece..01639c38996f 100644 --- a/tests/e2e/utils/vitest.ts +++ b/tests/e2e/utils/vitest.ts @@ -47,7 +47,7 @@ export async function applyVitestBuilder(options?: { } }); - await installWorkspacePackages({ force: true }); + await installWorkspacePackages(); await updateJsonFile('angular.json', (json) => { const projects = Object.values(json['projects']); From de957530c213e4bedd093e14179688ca91d77b6a Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Mon, 7 Sep 2026 10:04:24 +0000 Subject: [PATCH 7/9] test: add e2e test for Vitest browser configuration with Vitest v4 This commits adds a test to validate vitest version 4. --- .../vitest/browser-custom-config-vitest-v4.ts | 67 +++++++++++++++++++ tests/e2e/utils/vitest.ts | 7 +- 2 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 tests/e2e/tests/vitest/browser-custom-config-vitest-v4.ts diff --git a/tests/e2e/tests/vitest/browser-custom-config-vitest-v4.ts b/tests/e2e/tests/vitest/browser-custom-config-vitest-v4.ts new file mode 100644 index 000000000000..7865830f3b82 --- /dev/null +++ b/tests/e2e/tests/vitest/browser-custom-config-vitest-v4.ts @@ -0,0 +1,67 @@ +import assert from 'node:assert/strict'; +import { applyVitestBuilder } from '../../utils/vitest'; +import { ng } from '../../utils/process'; +import { writeFile } from '../../utils/fs'; +import { installWorkspacePackages } from '../../utils/packages'; +import { updateJsonFile } from '../../utils/project'; + +export default async function (): Promise { + await applyVitestBuilder({ + skipInstall: true, + }); + + await updateJsonFile('package.json', (json) => { + json.devDependencies = { + ...json.devDependencies, + '@vitest/browser-playwright': '^4', + 'vitest': '^4', + 'playwright': '^1', + }; + }); + + await installWorkspacePackages(); + + await ng('generate', 'component', 'my-comp'); + + // Create vitest-base.config.mts + await writeFile( + 'vitest-base.config.mts', + ` + import { defineConfig } from 'vitest/config'; + import { playwright } from '@vitest/browser-playwright'; + + export default defineConfig({ + test: { + browser: { + enabled: true, + provider: playwright({launchOptions: {executablePath: process.env['CHROME_BIN']}}), + instances: [ + { browser: 'chromium' }, + ], + headless: true, + }, + }, + }); + `, + ); + + // Create a spec file that asserts browser environment + await writeFile( + 'src/browser.spec.ts', + ` + describe('Browser Environment Check', () => { + it('should be running in Chromium', () => { + const ua = navigator.userAgent; + // Fail the test if it's not Chrome/Chromium + if (!ua.includes('Chrome') && !ua.includes('Chromium')) { + throw new Error('Expected to be running in Chrome/Chromium, but User Agent is: ' + ua); + } + }); + }); + `, + ); + + const { stdout } = await ng('test', '--no-watch', '--runner-config'); + + assert.match(stdout, /3 passed/, 'Expected 3 tests to pass.'); +} diff --git a/tests/e2e/utils/vitest.ts b/tests/e2e/utils/vitest.ts index 01639c38996f..f454a2784ee0 100644 --- a/tests/e2e/utils/vitest.ts +++ b/tests/e2e/utils/vitest.ts @@ -10,8 +10,9 @@ export async function applyVitestBuilder(options?: { coverageIstanbul?: boolean; playwright?: boolean; webdriver?: boolean; + skipInstall?: boolean; }): Promise { - const { coverageV8, coverageIstanbul, playwright, webdriver } = options ?? {}; + const { coverageV8, coverageIstanbul, playwright, webdriver, skipInstall } = options ?? {}; const schematicsAngular = createRequire(process.cwd() + '/').resolve( '@schematics/angular/package.json', @@ -47,7 +48,9 @@ export async function applyVitestBuilder(options?: { } }); - await installWorkspacePackages(); + if (!skipInstall) { + await installWorkspacePackages(); + } await updateJsonFile('angular.json', (json) => { const projects = Object.values(json['projects']); From be2e339ef397482e22ff936a31b351f24b4f75be Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Mon, 7 Sep 2026 11:56:58 +0000 Subject: [PATCH 8/9] refactor: remove Karma and Jasmine dependencies when initializing Vitest in e2e tests Reduce installations times --- tests/e2e/utils/vitest.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/e2e/utils/vitest.ts b/tests/e2e/utils/vitest.ts index f454a2784ee0..ebb9d9d12298 100644 --- a/tests/e2e/utils/vitest.ts +++ b/tests/e2e/utils/vitest.ts @@ -23,6 +23,19 @@ export async function applyVitestBuilder(options?: { const { dependencies: latestVersion } = JSON.parse(await readFile(latestVersionsPath, 'utf-8')); await updateJsonFile('package.json', (json) => { + [ + 'karma', + 'karma-chrome-launcher', + 'karma-coverage', + 'karma-jasmine', + 'karma-jasmine-html-reporter', + 'jasmine-core', + '@types/jasmine', + 'istanbul-lib-instrument', + ].forEach((dep) => { + delete json.devDependencies[dep]; + }); + json.devDependencies['vitest'] = latestVersion['vitest']; json.devDependencies['jsdom'] = latestVersion['jsdom']; From f98e7b626ad4a004a49877e1945fad6aedb5f5c0 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:09:10 +0000 Subject: [PATCH 9/9] build: update @vitest/browser-webdriverio to version 5.0.0 Update `@vitest/browser-webdriverio` to the latest version. --- .../schematics/angular/utility/latest-versions/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/schematics/angular/utility/latest-versions/package.json b/packages/schematics/angular/utility/latest-versions/package.json index 30a45ad40fe2..111c8296c202 100644 --- a/packages/schematics/angular/utility/latest-versions/package.json +++ b/packages/schematics/angular/utility/latest-versions/package.json @@ -29,7 +29,7 @@ "@vitest/coverage-v8": "^5.0.0", "@vitest/coverage-istanbul": "^5.0.0", "@vitest/browser-playwright": "^5.0.0", - "@vitest/browser-webdriverio": "^5.0.0-rc.1", + "@vitest/browser-webdriverio": "^5.0.0", "@vitest/browser-preview": "^5.0.0", "playwright": "^1.48.0", "webdriverio": "^9.0.0",