Просмотр исходного кода

test(vapor-e2e): migrate e2e tests to browser mode (#14618)

edison 4 недель назад
Родитель
Сommit
51ef8b0803

+ 7 - 1
.github/renovate.json5

@@ -11,7 +11,13 @@
     },
     {
       groupName: 'test',
-      matchPackageNames: ['vitest', 'jsdom', 'puppeteer', '@vitest{/,}**'],
+      matchPackageNames: [
+        'vitest',
+        'jsdom',
+        'playwright',
+        'puppeteer',
+        '@vitest{/,}**',
+      ],
     },
     {
       groupName: 'playground',

+ 6 - 4
.github/workflows/test.yml

@@ -70,18 +70,20 @@ jobs:
     steps:
       - uses: actions/checkout@v6
 
-      - name: Setup cache for Chromium binary
+      - name: Setup cache for Playwright browsers
         uses: actions/cache@v5
         with:
-          path: ~/.cache/puppeteer
-          key: chromium-${{ hashFiles('pnpm-lock.yaml') }}
+          path: ~/.cache/ms-playwright
+          key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
 
       - name: Setup Vite+
         uses: voidzero-dev/setup-vp@v1
         with:
           node-version-file: '.node-version'
           cache: true
-      - run: node node_modules/puppeteer/install.mjs
+
+      - name: Install Playwright Chromium
+        run: vp exec playwright install chromium
 
       - name: Run e2e tests
         run: vp run test-e2e-vapor

+ 1 - 2
package.json

@@ -19,7 +19,7 @@
     "test": "vp test",
     "test-unit": "vp test --project unit*",
     "test-e2e": "node scripts/build.js -e vue -f global+esm-browser-vapor -d && vp test --project e2e",
-    "test-e2e-vapor": "vp run prepare-e2e-vapor && VUE_INCLUDE_VAPOR_BROWSER_E2E=1 vp test --project 'e2e-vapor*'",
+    "test-e2e-vapor": "vp run prepare-e2e-vapor && VAPOR_E2E=1 vp test --project e2e-vapor",
     "prepare-e2e-vapor": "node scripts/build.js -e -f cjs+esm-bundler+esm-bundler-runtime && vp build packages-private/vapor-e2e-test",
     "test-dts": "run-s build-dts test-dts-only",
     "test-dts-only": "tsc -p packages-private/dts-built-test/tsconfig.json && tsc -p ./packages-private/dts-test/tsconfig.test.json && tsc -p ./packages-private/dts-test/vapor/tsconfig.json",
@@ -58,7 +58,6 @@
     "@types/serve-handler": "^6.1.4",
     "@vitest/coverage-v8": "^4.0.18",
     "@vitest/ui": "^4.0.18",
-    "@vitest/browser-playwright": "^4.0.14",
     "@vue/consolidate": "1.0.0",
     "conventional-changelog-cli": "^5.0.0",
     "enquirer": "^2.4.1",

+ 64 - 8
packages-private/vapor-e2e-test/__tests__/e2eUtils.ts

@@ -1,17 +1,73 @@
 import { type Locator, page, userEvent } from 'vitest/browser'
 
 export const css = (css: string) => page.getByCSS(css)
-export const E2E_TIMEOUT: number = 30 * 1000
+export const html = (selector: string) => css(selector).element().innerHTML
+export const E2E_TIMEOUT: number = 10 * 1000
 
-export async function enterValue(locator: Locator, text: string) {
-  await locator.fill(text)
-  await userEvent.type(locator, '{enter}')
+const duration = 50
+export function timeout(time: number) {
+  return new Promise(r => {
+    setTimeout(r, time)
+  })
 }
 
-export function nextFrame() {
-  return new Promise(resolve => {
-    requestAnimationFrame(() => {
-      requestAnimationFrame(resolve)
+export const transitionFinish = (time = duration) => timeout(time)
+
+export function waitForInnerHTML(
+  selector: string,
+  expected: string,
+  timeoutMs = 1000,
+) {
+  const container = css(selector).element()
+  const getHTML = () => container.innerHTML
+
+  if (getHTML().includes(expected)) {
+    return Promise.resolve()
+  }
+
+  return new Promise<void>((resolve, reject) => {
+    const cleanup = () => {
+      clearTimeout(timer)
+      observer.disconnect()
+    }
+
+    const check = () => {
+      if (getHTML().includes(expected)) {
+        cleanup()
+        resolve()
+      }
+    }
+
+    const observer = new MutationObserver(check)
+    const timer = setTimeout(() => {
+      const actual = getHTML()
+      cleanup()
+      reject(
+        new Error(
+          `Timed out waiting for innerHTML to contain ${expected} in ${selector}.\nReceived: ${actual}`,
+        ),
+      )
+    }, timeoutMs)
+
+    observer.observe(container, {
+      subtree: true,
+      childList: true,
+      characterData: true,
+      attributes: true,
     })
   })
 }
+
+export const nextFrame = () =>
+  new Promise(resolve => {
+    requestAnimationFrame(resolve)
+  })
+
+export const click = (selector: string) => {
+  ;(css(selector).element() as HTMLButtonElement).click()
+}
+
+export async function enterValue(locator: Locator, text: string) {
+  await locator.fill(text)
+  await userEvent.type(locator, '{enter}')
+}

+ 0 - 41
packages-private/vapor-e2e-test/__tests__/server.ts

@@ -1,41 +0,0 @@
-import path from 'node:path'
-import connect from 'connect'
-import sirv from 'sirv'
-
-export type E2ETestServer = {
-  port: number
-  close: () => void
-}
-
-export async function startE2ETestServer(
-  testName: string,
-  dirname: string,
-): Promise<E2ETestServer> {
-  const app = connect().use(sirv(path.resolve(dirname, '../dist')))
-  let server: any
-  let port = 0
-
-  await new Promise<void>((resolve, reject) => {
-    server = app.listen(0, () => {
-      const address = server.address()
-      if (!address || typeof address === 'string') {
-        reject(new Error(`[${testName}] failed to bind e2e server port`))
-        return
-      }
-      port = address.port
-      resolve()
-    })
-    server.once('error', reject)
-  })
-
-  const onSigterm = () => server && server.close()
-  process.on('SIGTERM', onSigterm)
-
-  return {
-    port,
-    close: () => {
-      process.off('SIGTERM', onSigterm)
-      server.close()
-    },
-  }
-}

Разница между файлами не показана из-за своего большого размера
+ 490 - 359
packages-private/vapor-e2e-test/__tests__/transition-group.spec.ts


Разница между файлами не показана из-за своего большого размера
+ 392 - 399
packages-private/vapor-e2e-test/__tests__/transition.spec.ts


+ 194 - 175
packages-private/vapor-e2e-test/__tests__/vdomInterop.spec.ts

@@ -1,19 +1,29 @@
-import { createApp, vaporInteropPlugin } from 'vue'
+import { createApp, nextTick, vaporInteropPlugin } from 'vue'
 import App from '../interop/App.vue'
-import { E2E_TIMEOUT, css } from './e2eUtils'
-
-describe('vdom / vapor interop', () => {
-  let app: ReturnType<typeof createApp>
-  beforeEach(() => {
-    app = createApp(App).use(vaporInteropPlugin)
-    app.mount('#app')
-  })
+import '../transition/style.css'
+import {
+  E2E_TIMEOUT,
+  click,
+  css,
+  html,
+  nextFrame,
+  transitionFinish,
+  waitForInnerHTML,
+} from './e2eUtils'
+
+let app: ReturnType<typeof createApp>
+beforeEach(() => {
+  app = createApp(App).use(vaporInteropPlugin)
+  app.mount('#app')
+})
 
-  afterEach(() => {
-    app.unmount()
-  })
+afterEach(() => {
+  app.unmount()
+})
 
-  test('should work', { timeout: E2E_TIMEOUT }, async () => {
+test(
+  'should work',
+  async () => {
     await expect
       .element(css('.vapor > h2'))
       .toHaveTextContent('Vapor component in VDOM')
@@ -66,166 +76,175 @@ describe('vdom / vapor interop', () => {
     expect(css('.vdom-slot-in-vapor-default')).toHaveTextContent('bye')
     expect(css('.vdom-prop')).toHaveTextContent('bye')
     expect(css('.vapor-slot-in-vdom-default')).toHaveTextContent('bye')
-  })
-
-  // TODO
-
-  // describe('vdom transition', () => {
-  //   test(
-  //     'render vapor component',
-  //     async () => {
-  //       const btnSelector = '.trans-vapor > button'
-  //       const containerSelector = '.trans-vapor > div'
-
-  //       expect(await html(containerSelector)).toBe(`<div>vapor compA</div>`)
-
-  //       // comp leave
-  //       expect(
-  //         (await transitionStart(btnSelector, containerSelector)).innerHTML,
-  //       ).toBe(
-  //         `<div class="v-leave-from v-leave-active">vapor compA</div><!---->`,
-  //       )
-
-  //       await nextFrame()
-  //       expect(await html(containerSelector)).toBe(
-  //         `<div class="v-leave-active v-leave-to">vapor compA</div><!---->`,
-  //       )
-
-  //       await transitionFinish()
-  //       expect(await html(containerSelector)).toBe(`<!---->`)
-
-  //       // comp enter
-  //       expect(
-  //         (await transitionStart(btnSelector, containerSelector)).innerHTML,
-  //       ).toBe(`<div class="v-enter-from v-enter-active">vapor compA</div>`)
-
-  //       await nextFrame()
-  //       expect(await html(containerSelector)).toBe(
-  //         `<div class="v-enter-active v-enter-to">vapor compA</div>`,
-  //       )
-
-  //       await transitionFinish()
-  //       expect(await html(containerSelector)).toBe(
-  //         `<div class="">vapor compA</div>`,
-  //       )
-  //     },
-  //     E2E_TIMEOUT,
-  //   )
-
-  //   test(
-  //     'switch between vdom/vapor component (out-in mode)',
-  //     async () => {
-  //       const btnSelector = '.trans-vdom-vapor-out-in > button'
-  //       const containerSelector = '.trans-vdom-vapor-out-in > div'
-  //       const childSelector = `${containerSelector} > div`
-
-  //       expect(await html(containerSelector)).toBe(`<div>vdom comp</div>`)
-
-  //       // switch to vapor comp
-  //       // vdom comp leave
-  //       expect(
-  //         (await transitionStart(btnSelector, containerSelector)).innerHTML,
-  //       ).toBe(
-  //         `<div class="fade-leave-from fade-leave-active">vdom comp</div><!---->`,
-  //       )
-
-  //       await nextFrame()
-  //       expect(await html(containerSelector)).toBe(
-  //         `<div class="fade-leave-active fade-leave-to">vdom comp</div><!---->`,
-  //       )
-
-  //       // vapor comp enter
-  //       await waitForElement(childSelector, 'vapor compA', [
-  //         'fade-enter-from',
-  //         'fade-enter-active',
-  //       ])
-
-  //       await nextFrame()
-  //       expect(await html(containerSelector)).toBe(
-  //         `<div class="fade-enter-active fade-enter-to">vapor compA</div>`,
-  //       )
-
-  //       await transitionFinish()
-  //       expect(await html(containerSelector)).toBe(
-  //         `<div class="">vapor compA</div>`,
-  //       )
-
-  //       // switch to vdom comp
-  //       // vapor comp leave
-  //       expect(
-  //         (await transitionStart(btnSelector, containerSelector)).innerHTML,
-  //       ).toBe(
-  //         `<div class="fade-leave-from fade-leave-active">vapor compA</div><!---->`,
-  //       )
-
-  //       await nextFrame()
-  //       expect(await html(containerSelector)).toBe(
-  //         `<div class="fade-leave-active fade-leave-to">vapor compA</div><!---->`,
-  //       )
-
-  //       // vdom comp enter
-  //       await waitForElement(childSelector, 'vdom comp', [
-  //         'fade-enter-from',
-  //         'fade-enter-active',
-  //       ])
-
-  //       await nextFrame()
-  //       expect(await html(containerSelector)).toBe(
-  //         `<div class="fade-enter-active fade-enter-to">vdom comp</div>`,
-  //       )
-
-  //       await transitionFinish()
-  //       expect(await html(containerSelector)).toBe(
-  //         `<div class="">vdom comp</div>`,
-  //       )
-  //     },
-  //     E2E_TIMEOUT,
-  //   )
-  // })
-
-  // describe('vdom transition-group', () => {
-  //   test(
-  //     'render vapor component',
-  //     async () => {
-  //       const btnSelector = '.trans-group-vapor > button'
-  //       const containerSelector = '.trans-group-vapor > div'
-
-  //       expect(await html(containerSelector)).toBe(
-  //         `<div><div>a</div></div>` +
-  //           `<div><div>b</div></div>` +
-  //           `<div><div>c</div></div>`,
-  //       )
-
-  //       // insert
-  //       expect(
-  //         (await transitionStart(btnSelector, containerSelector)).innerHTML,
-  //       ).toBe(
-  //         `<div><div>a</div></div>` +
-  //           `<div><div>b</div></div>` +
-  //           `<div><div>c</div></div>` +
-  //           `<div class="test-enter-from test-enter-active"><div>d</div></div>` +
-  //           `<div class="test-enter-from test-enter-active"><div>e</div></div>`,
-  //       )
-
-  //       await nextFrame()
-  //       expect(await html(containerSelector)).toBe(
-  //         `<div><div>a</div></div>` +
-  //           `<div><div>b</div></div>` +
-  //           `<div><div>c</div></div>` +
-  //           `<div class="test-enter-active test-enter-to"><div>d</div></div>` +
-  //           `<div class="test-enter-active test-enter-to"><div>e</div></div>`,
-  //       )
-
-  //       await transitionFinish()
-  //       expect(await html(containerSelector)).toBe(
-  //         `<div><div>a</div></div>` +
-  //           `<div><div>b</div></div>` +
-  //           `<div><div>c</div></div>` +
-  //           `<div class=""><div>d</div></div>` +
-  //           `<div class=""><div>e</div></div>`,
-  //       )
-  //     },
-  //     E2E_TIMEOUT,
-  //   )
-  // })
+  },
+  E2E_TIMEOUT,
+)
+
+describe('vdom transition', () => {
+  test(
+    'render vapor component',
+    async () => {
+      const btnSelector = '.trans-vapor > button'
+      const containerSelector = '.trans-vapor > div'
+
+      await expect
+        .element(css(containerSelector))
+        .toContainHTML(`<div>vapor compA</div>`)
+
+      // comp leave
+      click(btnSelector)
+      await nextTick()
+      await nextFrame()
+      expect(html(containerSelector)).toContain(
+        `<div class="v-leave-from v-leave-active">vapor compA</div><!--v-if-->`,
+      )
+
+      await nextFrame()
+      expect(html(containerSelector)).toContain(
+        `<div class="v-leave-active v-leave-to">vapor compA</div><!--v-if-->`,
+      )
+
+      await transitionFinish()
+      await expect.element(css(containerSelector)).toContainHTML(`<!--v-if-->`)
+
+      // comp enter
+      click(btnSelector)
+      await nextTick()
+      await nextFrame()
+      expect(html(containerSelector)).toContain(
+        `<div class="v-enter-from v-enter-active">vapor compA</div>`,
+      )
+
+      await nextFrame()
+      expect(html(containerSelector)).toContain(
+        `<div class="v-enter-active v-enter-to">vapor compA</div>`,
+      )
+
+      await transitionFinish()
+      await expect
+        .element(css(containerSelector))
+        .toContainHTML(`<div class="">vapor compA</div>`)
+    },
+    E2E_TIMEOUT,
+  )
+
+  test(
+    'switch between vdom/vapor component (out-in mode)',
+    async () => {
+      const btnSelector = '.trans-vdom-vapor-out-in > button'
+      const containerSelector = '.trans-vdom-vapor-out-in > div'
+
+      await expect
+        .element(css(containerSelector))
+        .toContainHTML(`<div>vdom comp</div>`)
+
+      // switch to vapor comp
+      click(btnSelector)
+      await nextTick()
+      await nextFrame()
+      expect(html(containerSelector)).toContain(
+        `<div class="fade-leave-from fade-leave-active">vdom comp</div><!---->`,
+      )
+
+      await nextFrame()
+      expect(html(containerSelector)).toContain(
+        `<div class="fade-leave-active fade-leave-to">vdom comp</div><!---->`,
+      )
+
+      await waitForInnerHTML(
+        containerSelector,
+        `<div class="fade-enter-from fade-enter-active">vapor compA</div>`,
+      )
+
+      await waitForInnerHTML(
+        containerSelector,
+        `<div class="fade-enter-active fade-enter-to">vapor compA</div>`,
+      )
+
+      await transitionFinish()
+      await expect
+        .element(css(containerSelector))
+        .toContainHTML(`<div class="">vapor compA</div>`)
+
+      // switch to vdom comp
+      click(btnSelector)
+      await nextTick()
+      await nextFrame()
+      expect(html(containerSelector)).toContain(
+        `<div class="fade-leave-from fade-leave-active">vapor compA</div><!---->`,
+      )
+
+      await nextFrame()
+      expect(html(containerSelector)).toContain(
+        `<div class="fade-leave-active fade-leave-to">vapor compA</div><!---->`,
+      )
+
+      await waitForInnerHTML(
+        containerSelector,
+        `<div class="fade-enter-from fade-enter-active">vdom comp</div>`,
+      )
+
+      await waitForInnerHTML(
+        containerSelector,
+        `<div class="fade-enter-active fade-enter-to">vdom comp</div>`,
+      )
+
+      await transitionFinish()
+      await expect
+        .element(css(containerSelector))
+        .toContainHTML(`<div class="">vdom comp</div>`)
+    },
+    E2E_TIMEOUT,
+  )
+})
+
+describe('vdom transition-group', () => {
+  test(
+    'render vapor component',
+    async () => {
+      const btnSelector = '.trans-group-vapor > button'
+      const containerSelector = '.trans-group-vapor > div'
+
+      await expect
+        .element(css(containerSelector))
+        .toContainHTML(
+          `<div><div>a</div></div>` +
+            `<div><div>b</div></div>` +
+            `<div><div>c</div></div>`,
+        )
+
+      // insert
+      click(btnSelector)
+      await nextTick()
+      await nextFrame()
+      expect(html(containerSelector)).toContain(
+        `<div><div>a</div></div>` +
+          `<div><div>b</div></div>` +
+          `<div><div>c</div></div>` +
+          `<div class="test-enter-from test-enter-active"><div>d</div></div>` +
+          `<div class="test-enter-from test-enter-active"><div>e</div></div>`,
+      )
+
+      await nextFrame()
+      expect(html(containerSelector)).toContain(
+        `<div><div>a</div></div>` +
+          `<div><div>b</div></div>` +
+          `<div><div>c</div></div>` +
+          `<div class="test-enter-active test-enter-to"><div>d</div></div>` +
+          `<div class="test-enter-active test-enter-to"><div>e</div></div>`,
+      )
+
+      await transitionFinish()
+      await expect
+        .element(css(containerSelector))
+        .toContainHTML(
+          `<div><div>a</div></div>` +
+            `<div><div>b</div></div>` +
+            `<div><div>c</div></div>` +
+            `<div class=""><div>d</div></div>` +
+            `<div class=""><div>e</div></div>`,
+        )
+    },
+    E2E_TIMEOUT,
+  )
 })

+ 5 - 10
packages-private/vapor-e2e-test/transition-group/App.vue

@@ -1,20 +1,15 @@
 <script setup vapor lang="ts">
 import type { Component } from 'vue'
+const props = defineProps<{
+  caseId: string
+}>()
 
 type CaseModule = { default: Component }
 const caseModules = import.meta.glob<CaseModule>('./cases/**/*.vue', {
   eager: true,
 })
 
-const params = new URLSearchParams(window.location.search)
-const caseId = params.get('case')
-if (!caseId) {
-  throw new Error(
-    '[transition-group] Missing "case" query param. Example: /transition-group/?case=vapor-transition-group/enter',
-  )
-}
-
-const moduleKey = `./cases/${caseId}.vue`
+const moduleKey = `./cases/${props.caseId}.vue`
 const selectedCase = caseModules[moduleKey]
 if (!selectedCase) {
   const availableCases = Object.keys(caseModules)
@@ -22,7 +17,7 @@ if (!selectedCase) {
     .sort()
     .join(', ')
   throw new Error(
-    `[transition-group] Unknown case "${caseId}". Available cases: ${availableCases}`,
+    `[transition-group] Unknown case "${props.caseId}". Available cases: ${availableCases}`,
   )
 }
 

+ 10 - 1
packages-private/vapor-e2e-test/transition-group/main.ts

@@ -2,4 +2,13 @@ import { createVaporApp, vaporInteropPlugin } from 'vue'
 import App from './App.vue'
 import '../../../packages/vue/__tests__/e2e/style.css'
 
-createVaporApp(App).use(vaporInteropPlugin).mount('#app')
+// oxlint-disable-next-line no-restricted-globals
+const params = new URLSearchParams(window.location.search)
+const caseId = params.get('case')
+if (!caseId) {
+  throw new Error(
+    '[transition-group] Missing "case" query param. Example: /transition-group/?case=vapor-transition-group/enter',
+  )
+}
+
+createVaporApp(App, { caseId }).use(vaporInteropPlugin).mount('#app')

+ 5 - 11
packages-private/vapor-e2e-test/transition/App.vue

@@ -1,20 +1,14 @@
 <script setup vapor lang="ts">
 import type { Component } from 'vue'
+const props = defineProps<{
+  caseId: string
+}>()
 
 type CaseModule = { default: Component }
 const caseModules = import.meta.glob<CaseModule>('./cases/**/*.vue', {
   eager: true,
 })
-
-const params = new URLSearchParams(window.location.search)
-const caseId = params.get('case')
-if (!caseId) {
-  throw new Error(
-    '[transition] Missing "case" query param. Example: /transition/?case=transition-with-v-if/basic-transition',
-  )
-}
-
-const moduleKey = `./cases/${caseId}.vue`
+const moduleKey = `./cases/${props.caseId}.vue`
 const selectedCase = caseModules[moduleKey]
 if (!selectedCase) {
   const availableCases = Object.keys(caseModules)
@@ -22,7 +16,7 @@ if (!selectedCase) {
     .sort()
     .join(', ')
   throw new Error(
-    `[transition] Unknown case "${caseId}". Available cases: ${availableCases}`,
+    `[transition] Unknown case "${props.caseId}". Available cases: ${availableCases}`,
   )
 }
 

+ 1 - 1
packages-private/vapor-e2e-test/transition/cases/explicit-durations/enter-with-explicit-durations.vue

@@ -2,7 +2,7 @@
 import { ref } from 'vue'
 
 const toggle = ref(true)
-const duration = window.__TRANSITION_DURATION__ || 50
+const duration = 50
 </script>
 
 <template>

+ 1 - 1
packages-private/vapor-e2e-test/transition/cases/explicit-durations/leave-with-explicit-durations.vue

@@ -2,7 +2,7 @@
 import { ref } from 'vue'
 
 const toggle = ref(true)
-const duration = window.__TRANSITION_DURATION__ || 50
+const duration = 50
 </script>
 
 <template>

+ 1 - 1
packages-private/vapor-e2e-test/transition/cases/explicit-durations/separate-enter-and-leave.vue

@@ -2,7 +2,7 @@
 import { ref } from 'vue'
 
 const toggle = ref(true)
-const duration = window.__TRANSITION_DURATION__ || 50
+const duration = 50
 </script>
 
 <template>

+ 1 - 1
packages-private/vapor-e2e-test/transition/cases/explicit-durations/single-value.vue

@@ -2,7 +2,7 @@
 import { ref } from 'vue'
 
 const toggle = ref(true)
-const duration = window.__TRANSITION_DURATION__ || 50
+const duration = 50
 </script>
 
 <template>

+ 9 - 1
packages-private/vapor-e2e-test/transition/main.ts

@@ -3,4 +3,12 @@ import App from './App.vue'
 import '../../../packages/vue/__tests__/e2e/style.css'
 import './style.css'
 
-createVaporApp(App).use(vaporInteropPlugin).mount('#app')
+// oxlint-disable-next-line no-restricted-globals
+const params = new URLSearchParams(window.location.search)
+const caseId = params.get('case')
+if (!caseId) {
+  throw new Error(
+    '[transition] Missing "case" query param. Example: /transition/?case=transition-with-v-if/basic-transition',
+  )
+}
+createVaporApp(App, { caseId }).use(vaporInteropPlugin).mount('#app')

+ 6 - 24
pnpm-lock.yaml

@@ -56,9 +56,6 @@ importers:
       '@types/serve-handler':
         specifier: ^6.1.4
         version: 6.1.4
-      '@vitest/browser-playwright':
-        specifier: ^4.0.14
-        version: 4.1.0(@voidzero-dev/vite-plus-core@0.1.13(@types/node@24.12.0)(sass@1.98.0)(typescript@5.6.3))(@voidzero-dev/vite-plus-test@0.1.13)(playwright@1.58.2)
       '@vitest/coverage-v8':
         specifier: ^4.0.18
         version: 4.1.0(@vitest/browser@4.1.0(@voidzero-dev/vite-plus-core@0.1.13(@types/node@24.12.0)(sass@1.98.0)(typescript@5.6.3))(@voidzero-dev/vite-plus-test@0.1.13))(@voidzero-dev/vite-plus-test@0.1.13)
@@ -1901,12 +1898,6 @@ packages:
       vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
       vue: ^3.2.25
 
-  '@vitest/browser-playwright@4.1.0':
-    resolution: {integrity: sha512-2RU7pZELY9/aVMLmABNy1HeZ4FX23FXGY1jRuHLHgWa2zaAE49aNW2GLzebW+BmbTZIKKyFF1QXvk7DEWViUCQ==}
-    peerDependencies:
-      playwright: '*'
-      vitest: 4.1.0
-
   '@vitest/browser@4.1.0':
     resolution: {integrity: sha512-tG/iOrgbiHQks0ew7CdelUyNEHkv8NLrt+CqdTivIuoSnXvO7scWMn4Kqo78/UGY1NJ6Hv+vp8BvRnED/bjFdQ==}
     peerDependencies:
@@ -4116,7 +4107,8 @@ snapshots:
 
   '@bcoe/v8-coverage@1.0.2': {}
 
-  '@blazediff/core@1.9.1': {}
+  '@blazediff/core@1.9.1':
+    optional: true
 
   '@conventional-changelog/git-client@2.6.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.3.0)':
     dependencies:
@@ -4876,19 +4868,6 @@ snapshots:
       vite: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@24.12.0)(sass@1.98.0)(typescript@5.6.3)'
       vue: link:packages/vue
 
-  '@vitest/browser-playwright@4.1.0(@voidzero-dev/vite-plus-core@0.1.13(@types/node@24.12.0)(sass@1.98.0)(typescript@5.6.3))(@voidzero-dev/vite-plus-test@0.1.13)(playwright@1.58.2)':
-    dependencies:
-      '@vitest/browser': 4.1.0(@voidzero-dev/vite-plus-core@0.1.13(@types/node@24.12.0)(sass@1.98.0)(typescript@5.6.3))(@voidzero-dev/vite-plus-test@0.1.13)
-      '@vitest/mocker': 4.1.0(@voidzero-dev/vite-plus-core@0.1.13(@types/node@24.12.0)(sass@1.98.0)(typescript@5.6.3))
-      playwright: 1.58.2
-      tinyrainbow: 3.1.0
-      vitest: '@voidzero-dev/vite-plus-test@0.1.13(@types/node@24.12.0)(@vitest/ui@4.1.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@24.12.0)(sass@1.98.0)(typescript@5.6.3))(jsdom@27.4.0)(sass@1.98.0)(typescript@5.6.3)'
-    transitivePeerDependencies:
-      - bufferutil
-      - msw
-      - utf-8-validate
-      - vite
-
   '@vitest/browser@4.1.0(@voidzero-dev/vite-plus-core@0.1.13(@types/node@24.12.0)(sass@1.98.0)(typescript@5.6.3))(@voidzero-dev/vite-plus-test@0.1.13)':
     dependencies:
       '@blazediff/core': 1.9.1
@@ -4905,6 +4884,7 @@ snapshots:
       - msw
       - utf-8-validate
       - vite
+    optional: true
 
   '@vitest/coverage-v8@4.1.0(@vitest/browser@4.1.0(@voidzero-dev/vite-plus-core@0.1.13(@types/node@24.12.0)(sass@1.98.0)(typescript@5.6.3))(@voidzero-dev/vite-plus-test@0.1.13))(@voidzero-dev/vite-plus-test@0.1.13)':
     dependencies:
@@ -4929,12 +4909,14 @@ snapshots:
       magic-string: 0.30.21
     optionalDependencies:
       vite: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@24.12.0)(sass@1.98.0)(typescript@5.6.3)'
+    optional: true
 
   '@vitest/pretty-format@4.1.0':
     dependencies:
       tinyrainbow: 3.1.0
 
-  '@vitest/spy@4.1.0': {}
+  '@vitest/spy@4.1.0':
+    optional: true
 
   '@vitest/ui@4.1.0(@voidzero-dev/vite-plus-test@0.1.13)':
     dependencies:

+ 17 - 25
vite.config.ts

@@ -1,9 +1,7 @@
 import { configDefaults } from 'vitest/config'
+import { playwright } from 'vitest/browser-playwright'
 import { defineConfig } from 'vite-plus'
 import { entries } from './scripts/aliases.js'
-import { playwright } from '@vitest/browser-playwright'
-
-const includeVaporBrowserE2E = process.env.VUE_INCLUDE_VAPOR_BROWSER_E2E === '1'
 
 export default defineConfig({
   define: {
@@ -95,23 +93,19 @@ export default defineConfig({
           include: ['packages/vue/__tests__/e2e/*.spec.ts'],
         },
       },
-      // TODO: merge all vapor e2e tests into browser tests and remove this project
-      {
-        extends: true,
-        test: {
-          name: 'e2e-vapor',
-          isolate: true,
-          include: ['packages-private/vapor-e2e-test/__tests__/*.spec.ts'],
-          exclude: [
-            'packages-private/vapor-e2e-test/__tests__/todomvc.spec.ts',
-            'packages-private/vapor-e2e-test/__tests__/vdomInterop.spec.ts',
-          ],
-        },
-      },
-      // This project pulls in built compiler-sfc output, so keep it out of
-      // unrelated test runs and only enable it for the dedicated e2e script.
-      // @ts-expect-error
-      ...(includeVaporBrowserE2E
+      // @ts-expect-error - https://github.com/vuejs/core/actions/runs/23430103557/job/68154030981
+      // When running `vp test --project unit*`, Vitest still initializes all projects (this is
+      // expected  behavior see https://github.com/vitest-dev/vitest/issues/9849) and loads
+      // `packages-private/vapor-e2e-test/vite.config.ts`. That config immediately imports
+      // `vue/compiler-sfc`, but the package has not been built yet.
+      // To work around this, we conditionally include the vapor e2e test project only when the
+      // `VAPOR_E2E` env variable is set.
+      // for Vitest VSCode extension, we can set this env variable in the `vitest.nodeEnv` config
+      // to ensure the vapor e2e tests are included when running tests in VSCode.
+      // "vitest.nodeEnv": {
+      //   "VAPOR_E2E": "1"
+      // },
+      ...(process.env.VAPOR_E2E === '1'
         ? [
             {
               extends: './packages-private/vapor-e2e-test/vite.config.ts',
@@ -119,7 +113,7 @@ export default defineConfig({
               test: {
                 globals: true,
                 isolate: true,
-                name: 'e2e-vapor-browser',
+                name: 'e2e-vapor',
                 setupFiles: ['./__tests__/setupBrowser.ts'],
                 browser: {
                   enabled: true,
@@ -131,12 +125,10 @@ export default defineConfig({
                     },
                   }),
                   headless: true,
+                  screenshotFailures: false,
                   instances: [{ browser: 'chromium' }],
                 },
-                include: [
-                  './__tests__/todomvc.spec.ts',
-                  './__tests__/vdomInterop.spec.ts',
-                ],
+                include: ['./__tests__/*.spec.ts'],
               },
             },
           ]

Некоторые файлы не были показаны из-за большого количества измененных файлов