diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5b6da83feeda..a25bb0e4609d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -40,13 +40,15 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest]
-        node_version: [18, 20]
+        # Reset back to 20 after https://github.com/nodejs/node/issues/53648
+        # (The issues is closed, but the error persist even after 20.14)
+        node_version: [18, 20.14]
         # node_version: [18, 20, 22] 22 when LTS is close enough
         include:
           - os: macos-14
-            node_version: 20
+            node_version: 20.14
           - os: windows-latest
-            node_version: 20
+            node_version: 20.14
       fail-fast: false
 
     steps:
diff --git a/packages/browser/package.json b/packages/browser/package.json
index 323ad8030670..5b38326a7799 100644
--- a/packages/browser/package.json
+++ b/packages/browser/package.json
@@ -44,6 +44,10 @@
       "types": "./dist/locators/index.d.ts",
       "default": "./dist/locators/index.js"
     },
+    "./utils": {
+      "types": "./utils.d.ts",
+      "default": "./dist/utils.js"
+    },
     "./*": "./*"
   },
   "main": "./dist/index.js",
diff --git a/packages/browser/rollup.config.js b/packages/browser/rollup.config.js
index dde7cfce2ed8..b1b901bbdeb2 100644
--- a/packages/browser/rollup.config.js
+++ b/packages/browser/rollup.config.js
@@ -66,6 +66,7 @@ export default () =>
         'locators/webdriverio': './src/client/tester/locators/webdriverio.ts',
         'locators/preview': './src/client/tester/locators/preview.ts',
         'locators/index': './src/client/tester/locators/index.ts',
+        'utils': './src/client/tester/public-utils.ts',
       },
       output: {
         dir: 'dist',
@@ -129,9 +130,11 @@ export default () =>
       ],
     },
     {
-      input: './src/client/tester/locators/index.ts',
+      input: {
+        'locators/index': './src/client/tester/locators/index.ts',
+      },
       output: {
-        file: 'dist/locators/index.d.ts',
+        dir: 'dist',
         format: 'esm',
       },
       external,
diff --git a/packages/browser/src/client/tester/locators/index.ts b/packages/browser/src/client/tester/locators/index.ts
index 229e33eeeac7..0816f2fd54dd 100644
--- a/packages/browser/src/client/tester/locators/index.ts
+++ b/packages/browser/src/client/tester/locators/index.ts
@@ -12,7 +12,6 @@ import type { BrowserRPC } from '@vitest/browser/client'
 import {
   Ivya,
   type ParsedSelector,
-  asLocator,
   getByAltTextSelector,
   getByLabelSelector,
   getByPlaceholderSelector,
@@ -24,6 +23,7 @@ import {
 import type { WorkerGlobalState } from 'vitest'
 import type { BrowserRunnerState } from '../../utils'
 import { getBrowserState, getWorkerState } from '../../utils'
+import { getElementError } from '../public-utils'
 
 // we prefer using playwright locators because they are more powerful and support Shadow DOM
 export const selectorEngine = Ivya.create({
@@ -45,8 +45,8 @@ export abstract class Locator {
   public abstract selector: string
 
   private _parsedSelector: ParsedSelector | undefined
+  protected _container?: Element | undefined
   protected _pwSelector?: string | undefined
-  protected _forceElement?: Element | undefined
 
   public click(options: UserEventClickOptions = {}): Promise<void> {
     return this.triggerCommand<void>('__vitest_click', this.selector, options)
@@ -143,9 +143,6 @@ export abstract class Locator {
   }
 
   public query(): Element | null {
-    if (this._forceElement) {
-      return this._forceElement
-    }
     const parsedSelector = this._parsedSelector || (this._parsedSelector = selectorEngine.parseSelector(this._pwSelector || this.selector))
     return selectorEngine.querySelector(parsedSelector, document.documentElement, true)
   }
@@ -153,15 +150,12 @@ export abstract class Locator {
   public element(): Element {
     const element = this.query()
     if (!element) {
-      throw new Error(`element not found: ${asLocator('javascript', this._pwSelector || this.selector)}`)
+      throw getElementError(this._pwSelector || this.selector, this._container || document.documentElement)
     }
     return element
   }
 
   public elements(): Element[] {
-    if (this._forceElement) {
-      return [this._forceElement]
-    }
     const parsedSelector = this._parsedSelector || (this._parsedSelector = selectorEngine.parseSelector(this._pwSelector || this.selector))
     return selectorEngine.querySelectorAll(parsedSelector, document.documentElement)
   }
diff --git a/packages/browser/src/client/tester/locators/playwright.ts b/packages/browser/src/client/tester/locators/playwright.ts
index 750f8552dd2a..60e9075a185a 100644
--- a/packages/browser/src/client/tester/locators/playwright.ts
+++ b/packages/browser/src/client/tester/locators/playwright.ts
@@ -34,20 +34,26 @@ page.extend({
   },
 
   elementLocator(element: Element) {
-    return new PlaywrightLocator(selectorEngine.generateSelectorSimple(element), element)
+    return new PlaywrightLocator(
+      selectorEngine.generateSelectorSimple(element),
+      element,
+    )
   },
 })
 
 class PlaywrightLocator extends Locator {
-  constructor(public selector: string, protected _forceElement?: Element) {
+  constructor(public selector: string, protected _container?: Element) {
     super()
   }
 
   protected locator(selector: string) {
-    return new PlaywrightLocator(`${this.selector} >> ${selector}`)
+    return new PlaywrightLocator(`${this.selector} >> ${selector}`, this._container)
   }
 
   protected elementLocator(element: Element) {
-    return new PlaywrightLocator(selectorEngine.generateSelectorSimple(element), element)
+    return new PlaywrightLocator(
+      selectorEngine.generateSelectorSimple(element),
+      element,
+    )
   }
 }
diff --git a/packages/browser/src/client/tester/locators/preview.ts b/packages/browser/src/client/tester/locators/preview.ts
index fac183581e1a..f1484af9bef9 100644
--- a/packages/browser/src/client/tester/locators/preview.ts
+++ b/packages/browser/src/client/tester/locators/preview.ts
@@ -10,6 +10,7 @@ import {
   getByTitleSelector,
 } from 'ivya'
 import { convertElementToCssSelector } from '../../utils'
+import { getElementError } from '../public-utils'
 import { Locator, selectorEngine } from './index'
 
 page.extend({
@@ -36,19 +37,22 @@ page.extend({
   },
 
   elementLocator(element: Element) {
-    return new PreviewLocator(selectorEngine.generateSelectorSimple(element), element)
+    return new PreviewLocator(
+      selectorEngine.generateSelectorSimple(element),
+      element,
+    )
   },
 })
 
 class PreviewLocator extends Locator {
-  constructor(protected _pwSelector: string, protected _forceElement?: Element) {
+  constructor(protected _pwSelector: string, protected _container?: Element) {
     super()
   }
 
   override get selector() {
     const selectors = this.elements().map(element => convertElementToCssSelector(element))
     if (!selectors.length) {
-      throw new Error(`element not found: ${this._pwSelector}`)
+      throw getElementError(this._pwSelector, this._container || document.documentElement)
     }
     return selectors.join(', ')
   }
@@ -100,10 +104,13 @@ class PreviewLocator extends Locator {
   }
 
   protected locator(selector: string) {
-    return new PreviewLocator(`${this._pwSelector} >> ${selector}`)
+    return new PreviewLocator(`${this._pwSelector} >> ${selector}`, this._container)
   }
 
   protected elementLocator(element: Element) {
-    return new PreviewLocator(selectorEngine.generateSelectorSimple(element), element)
+    return new PreviewLocator(
+      selectorEngine.generateSelectorSimple(element),
+      element,
+    )
   }
 }
diff --git a/packages/browser/src/client/tester/locators/webdriverio.ts b/packages/browser/src/client/tester/locators/webdriverio.ts
index 6e37200f0a56..7313d9493ff5 100644
--- a/packages/browser/src/client/tester/locators/webdriverio.ts
+++ b/packages/browser/src/client/tester/locators/webdriverio.ts
@@ -9,6 +9,7 @@ import {
   getByTitleSelector,
 } from 'ivya'
 import { convertElementToCssSelector } from '../../utils'
+import { getElementError } from '../public-utils'
 import { Locator, selectorEngine } from './index'
 
 page.extend({
@@ -35,19 +36,19 @@ page.extend({
   },
 
   elementLocator(element: Element) {
-    return new WebdriverIOLocator(selectorEngine.generateSelectorSimple(element), element)
+    return new WebdriverIOLocator(selectorEngine.generateSelectorSimple(element))
   },
 })
 
 class WebdriverIOLocator extends Locator {
-  constructor(protected _pwSelector: string, protected _forceElement?: Element) {
+  constructor(protected _pwSelector: string, protected _container?: Element) {
     super()
   }
 
   override get selector() {
     const selectors = this.elements().map(element => convertElementToCssSelector(element))
     if (!selectors.length) {
-      throw new Error(`element not found: ${this._pwSelector}`)
+      throw getElementError(this._pwSelector, this._container || document.documentElement)
     }
     return selectors.join(', ')
   }
@@ -58,7 +59,7 @@ class WebdriverIOLocator extends Locator {
   }
 
   protected locator(selector: string) {
-    return new WebdriverIOLocator(`${this._pwSelector} >> ${selector}`)
+    return new WebdriverIOLocator(`${this._pwSelector} >> ${selector}`, this._container)
   }
 
   protected elementLocator(element: Element) {
diff --git a/packages/browser/src/client/tester/public-utils.ts b/packages/browser/src/client/tester/public-utils.ts
new file mode 100644
index 000000000000..760f957415cf
--- /dev/null
+++ b/packages/browser/src/client/tester/public-utils.ts
@@ -0,0 +1,72 @@
+import { type Locator, type LocatorSelectors, page } from '@vitest/browser/context'
+import { type StringifyOptions, stringify } from 'vitest/utils'
+import { asLocator } from 'ivya'
+
+export function getElementLocatorSelectors(element: Element): LocatorSelectors {
+  const locator = page.elementLocator(element)
+  return {
+    getByAltText: (altText, options) => locator.getByAltText(altText, options),
+    getByLabelText: (labelText, options) => locator.getByLabelText(labelText, options),
+    getByPlaceholder: (placeholderText, options) => locator.getByPlaceholder(placeholderText, options),
+    getByRole: (role, options) => locator.getByRole(role, options),
+    getByTestId: testId => locator.getByTestId(testId),
+    getByText: (text, options) => locator.getByText(text, options),
+    getByTitle: (title, options) => locator.getByTitle(title, options),
+  }
+}
+
+type PrettyDOMOptions = Omit<StringifyOptions, 'maxLength'>
+
+export function debug(
+  el?: Element | Locator | null | (Element | Locator)[],
+  maxLength?: number,
+  options?: PrettyDOMOptions,
+): void {
+  if (Array.isArray(el)) {
+    // eslint-disable-next-line no-console
+    el.forEach(e => console.log(prettyDOM(e, maxLength, options)))
+  }
+  else {
+    // eslint-disable-next-line no-console
+    console.log(prettyDOM(el, maxLength, options))
+  }
+}
+
+export function prettyDOM(
+  dom?: Element | Locator | undefined | null,
+  maxLength: number = Number(import.meta.env.DEBUG_PRINT_LIMIT ?? 7000),
+  prettyFormatOptions: PrettyDOMOptions = {},
+): string {
+  if (maxLength === 0) {
+    return ''
+  }
+
+  if (!dom) {
+    dom = document.body
+  }
+
+  if ('element' in dom && 'all' in dom) {
+    dom = dom.element()
+  }
+
+  const type = typeof dom
+  if (type !== 'object' || !dom.outerHTML) {
+    const typeName = type === 'object' ? dom.constructor.name : type
+    throw new TypeError(`Expecting a valid DOM element, but got ${typeName}.`)
+  }
+
+  const pretty = stringify(dom, Number.POSITIVE_INFINITY, {
+    maxLength,
+    highlight: true,
+    ...prettyFormatOptions,
+  })
+  return dom.outerHTML.length > maxLength
+    ? `${pretty.slice(0, maxLength)}...`
+    : pretty
+}
+
+export function getElementError(selector: string, container: Element): Error {
+  const error = new Error(`Cannot find element with locator: ${asLocator('javascript', selector)}\n\n${prettyDOM(container)}`)
+  error.name = 'VitestBrowserElementError'
+  return error
+}
diff --git a/packages/browser/src/client/tester/tester.html b/packages/browser/src/client/tester/tester.html
index f437c975d506..1bf9f3ff32f1 100644
--- a/packages/browser/src/client/tester/tester.html
+++ b/packages/browser/src/client/tester/tester.html
@@ -21,7 +21,7 @@
     {__VITEST_INTERNAL_SCRIPTS__}
     {__VITEST_SCRIPTS__}
   </head>
-  <body data-vitest-body>
+  <body>
     <script type="module" src="./tester.ts"></script>
     {__VITEST_APPEND__}
   </body>
diff --git a/packages/browser/src/client/tester/tester.ts b/packages/browser/src/client/tester/tester.ts
index cb100a2bb71f..631111c0fc9e 100644
--- a/packages/browser/src/client/tester/tester.ts
+++ b/packages/browser/src/client/tester/tester.ts
@@ -1,4 +1,5 @@
 import { SpyModule, collectTests, setupCommonEnv, startTests } from 'vitest/browser'
+import { page } from '@vitest/browser/context'
 import { channel, client, onCancel } from '@vitest/browser/client'
 import { getBrowserState, getConfig, getWorkerState } from '../utils'
 import { setupDialogsSpy } from './dialog'
@@ -8,6 +9,8 @@ import { browserHashMap, initiateRunner } from './runner'
 import { VitestBrowserClientMocker } from './mocker'
 import { setupExpectDom } from './expect-element'
 
+const cleanupSymbol = Symbol.for('vitest:component-cleanup')
+
 const url = new URL(location.href)
 const reloadStart = url.searchParams.get('__reloadStart')
 
@@ -123,6 +126,18 @@ async function executeTests(method: 'run' | 'collect', files: string[]) {
     }
   }
   finally {
+    try {
+      if (cleanupSymbol in page) {
+        (page[cleanupSymbol] as any)()
+      }
+    }
+    catch (error: any) {
+      await client.rpc.onUnhandledError({
+        name: error.name,
+        message: error.message,
+        stack: String(error.stack),
+      }, 'Cleanup Error')
+    }
     state.environmentTeardownRun = true
     debug('finished running tests')
     done(files)
diff --git a/packages/browser/utils.d.ts b/packages/browser/utils.d.ts
new file mode 100644
index 000000000000..fa8790aeef97
--- /dev/null
+++ b/packages/browser/utils.d.ts
@@ -0,0 +1,21 @@
+// should be in sync with tester/public-utils.ts
+// we cannot bundle it because vitest depend on the @vitest/browser and vise versa
+// fortunately, the file is quite small
+
+import { LocatorSelectors } from '@vitest/browser/context'
+import { StringifyOptions } from 'vitest/utils'
+
+type PrettyDOMOptions = Omit<StringifyOptions, 'maxLength'>
+
+export declare function getElementLocatorSelectors(element: Element): LocatorSelectors
+export declare function debug(
+  el?: Element | Locator | null | (Element | Locator)[],
+  maxLength?: number,
+  options?: PrettyDOMOptions,
+): void
+export declare function prettyDOM(
+  dom?: Element | Locator | undefined | null,
+  maxLength?: number,
+  prettyFormatOptions?: PrettyDOMOptions,
+): string
+export declare function getElementError(selector: string, container?: Element): Error
diff --git a/packages/utils/src/display.ts b/packages/utils/src/display.ts
index 67c9902b1776..8b89da59cdb1 100644
--- a/packages/utils/src/display.ts
+++ b/packages/utils/src/display.ts
@@ -41,10 +41,14 @@ const PLUGINS = [
   AsymmetricMatcher,
 ]
 
+export interface StringifyOptions extends PrettyFormatOptions {
+  maxLength?: number
+}
+
 export function stringify(
   object: unknown,
   maxDepth = 10,
-  { maxLength, ...options }: PrettyFormatOptions & { maxLength?: number } = {},
+  { maxLength, ...options }: StringifyOptions = {},
 ): string {
   const MAX_LENGTH = maxLength ?? 10000
   let result
diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts
index 6b87a5c4fcb2..ea8d8456f15d 100644
--- a/packages/utils/src/index.ts
+++ b/packages/utils/src/index.ts
@@ -29,6 +29,7 @@ export {
   inspect,
   objDisplay,
 } from './display'
+export type { StringifyOptions } from './display'
 export {
   positionToOffset,
   offsetToLineNumber,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ceea91672c41..1acb35bcf8f0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -37,7 +37,7 @@ importers:
     devDependencies:
       '@antfu/eslint-config':
         specifier: ^2.24.1
-        version: 2.24.1(@vue/compiler-sfc@3.4.35)(eslint@9.8.0)(svelte@3.59.1)(typescript@5.5.4)(vitest@packages+vitest)
+        version: 2.24.1(@vue/compiler-sfc@3.4.36)(eslint@9.8.0)(svelte@3.59.1)(typescript@5.5.4)(vitest@packages+vitest)
       '@antfu/ni':
         specifier: ^0.22.0
         version: 0.22.0
@@ -736,7 +736,7 @@ importers:
         version: 1.1.44
       '@testing-library/vue':
         specifier: ^8.1.0
-        version: 8.1.0(@vue/compiler-sfc@3.4.35)(vue@3.4.35(typescript@5.5.4))
+        version: 8.1.0(@vue/compiler-sfc@3.4.36)(vue@3.4.35(typescript@5.5.4))
       '@types/codemirror':
         specifier: ^5.60.15
         version: 5.60.15
@@ -802,7 +802,7 @@ importers:
         version: 5.3.3(@types/node@20.14.14)(terser@5.22.0)
       vite-plugin-pages:
         specifier: ^0.32.3
-        version: 0.32.3(@vue/compiler-sfc@3.4.35)(vite@5.3.3(@types/node@20.14.14)(terser@5.22.0))(vue-router@4.4.2(vue@3.4.35(typescript@5.5.4)))
+        version: 0.32.3(@vue/compiler-sfc@3.4.36)(vite@5.3.3(@types/node@20.14.14)(terser@5.22.0))(vue-router@4.4.2(vue@3.4.35(typescript@5.5.4)))
       vue:
         specifier: ^3.4.35
         version: 3.4.35(typescript@5.5.4)
@@ -1102,6 +1102,9 @@ importers:
       react-dom:
         specifier: ^18.3.1
         version: 18.3.1(react@18.3.1)
+      strip-ansi:
+        specifier: ^7.1.0
+        version: 7.1.0
       url:
         specifier: ^0.11.3
         version: 0.11.3
@@ -1248,7 +1251,7 @@ importers:
         version: 3.0.3
       '@vitejs/plugin-vue':
         specifier: latest
-        version: 5.1.2(vite@5.3.3(@types/node@20.14.14)(terser@5.22.0))(vue@3.4.35(typescript@5.5.4))
+        version: 5.1.2(vite@5.3.3(@types/node@20.14.14)(terser@5.22.0))(vue@3.4.36(typescript@5.5.4))
       '@vitest/browser':
         specifier: workspace:*
         version: link:../../packages/browser
@@ -1287,10 +1290,10 @@ importers:
         version: link:../../packages/vitest
       vue:
         specifier: latest
-        version: 3.4.35(typescript@5.5.4)
+        version: 3.4.36(typescript@5.5.4)
       webdriverio:
         specifier: latest
-        version: 8.39.1(typescript@5.5.4)
+        version: 8.40.0
 
   test/global-setup:
     devDependencies:
@@ -1408,7 +1411,7 @@ importers:
         version: link:../../packages/vitest
       webdriverio:
         specifier: latest
-        version: 8.39.1(typescript@5.5.4)
+        version: 8.40.0
 
   test/workspaces:
     devDependencies:
@@ -3103,6 +3106,11 @@ packages:
     engines: {node: '>=16.3.0'}
     hasBin: true
 
+  '@puppeteer/browsers@1.9.1':
+    resolution: {integrity: sha512-PuvK6xZzGhKPvlx3fpfdM2kYY3P/hB1URtK8wA7XUJ6prn6pp22zvJHu48th0SGcHL9SutbPHrFuQgfXTFobWA==}
+    engines: {node: '>=16.3.0'}
+    hasBin: true
+
   '@remix-run/router@1.16.0':
     resolution: {integrity: sha512-Quz1KOffeEf/zwkCBM3kBtH4ZoZ+pT3xIXBG4PPW/XFtDP7EGhtTiC2+gpL9GnR7+Qdet5Oa6cYSvwKYg6kN9Q==}
     engines: {node: '>=14.0.0'}
@@ -4019,6 +4027,9 @@ packages:
   '@vue/compiler-core@3.4.35':
     resolution: {integrity: sha512-gKp0zGoLnMYtw4uS/SJRRO7rsVggLjvot3mcctlMXunYNsX+aRJDqqw/lV5/gHK91nvaAAlWFgdVl020AW1Prg==}
 
+  '@vue/compiler-core@3.4.36':
+    resolution: {integrity: sha512-qBkndgpwFKdupmOPoiS10i7oFdN7a+4UNDlezD0GlQ1kuA1pNrscg9g12HnB5E8hrWSuEftRsbJhL1HI2zpJhg==}
+
   '@vue/compiler-dom@3.4.26':
     resolution: {integrity: sha512-4CWbR5vR9fMg23YqFOhr6t6WB1Fjt62d6xdFPyj8pxrYub7d+OgZaObMsoxaF9yBUHPMiPFK303v61PwAuGvZA==}
 
@@ -4028,18 +4039,27 @@ packages:
   '@vue/compiler-dom@3.4.35':
     resolution: {integrity: sha512-pWIZRL76/oE/VMhdv/ovZfmuooEni6JPG1BFe7oLk5DZRo/ImydXijoZl/4kh2406boRQ7lxTYzbZEEXEhj9NQ==}
 
+  '@vue/compiler-dom@3.4.36':
+    resolution: {integrity: sha512-eEIjy4GwwZTFon/Y+WO8tRRNGqylaRlA79T1RLhUpkOzJ7EtZkkb8MurNfkqY6x6Qiu0R7ESspEF7GkPR/4yYg==}
+
   '@vue/compiler-sfc@3.4.26':
     resolution: {integrity: sha512-It1dp+FAOCgluYSVYlDn5DtZBxk1NCiJJfu2mlQqa/b+k8GL6NG/3/zRbJnHdhV2VhxFghaDq5L4K+1dakW6cw==}
 
   '@vue/compiler-sfc@3.4.35':
     resolution: {integrity: sha512-xacnRS/h/FCsjsMfxBkzjoNxyxEyKyZfBch/P4vkLRvYJwe5ChXmZZrj8Dsed/752H2Q3JE8kYu9Uyha9J6PgA==}
 
+  '@vue/compiler-sfc@3.4.36':
+    resolution: {integrity: sha512-rhuHu7qztt/rNH90dXPTzhB7hLQT2OC4s4GrPVqmzVgPY4XBlfWmcWzn4bIPEWNImt0CjO7kfHAf/1UXOtx3vw==}
+
   '@vue/compiler-ssr@3.4.26':
     resolution: {integrity: sha512-FNwLfk7LlEPRY/g+nw2VqiDKcnDTVdCfBREekF8X74cPLiWHUX6oldktf/Vx28yh4STNy7t+/yuLoMBBF7YDiQ==}
 
   '@vue/compiler-ssr@3.4.35':
     resolution: {integrity: sha512-7iynB+0KB1AAJKk/biENTV5cRGHRdbdaD7Mx3nWcm1W8bVD6QmnH3B4AHhQQ1qZHhqFwzEzMwiytXm3PX1e60A==}
 
+  '@vue/compiler-ssr@3.4.36':
+    resolution: {integrity: sha512-Wt1zyheF0zVvRJyhY74uxQbnkXV2Le/JPOrAxooR4rFYKC7cFr+cRqW6RU3cM/bsTy7sdZ83IDuy/gLPSfPGng==}
+
   '@vue/devtools-api@6.6.3':
     resolution: {integrity: sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw==}
 
@@ -4074,18 +4094,27 @@ packages:
   '@vue/reactivity@3.4.35':
     resolution: {integrity: sha512-Ggtz7ZZHakriKioveJtPlStYardwQH6VCs9V13/4qjHSQb/teE30LVJNrbBVs4+aoYGtTQKJbTe4CWGxVZrvEw==}
 
+  '@vue/reactivity@3.4.36':
+    resolution: {integrity: sha512-wN1aoCwSoqrt1yt8wO0gc13QaC+Vk1o6AoSt584YHNnz6TGDhh1NCMUYgAnvp4HEIkLdGsaC1bvu/P+wpoDEXw==}
+
   '@vue/runtime-core@3.4.26':
     resolution: {integrity: sha512-AFJDLpZvhT4ujUgZSIL9pdNcO23qVFh7zWCsNdGQBw8ecLNxOOnPcK9wTTIYCmBJnuPHpukOwo62a2PPivihqw==}
 
   '@vue/runtime-core@3.4.35':
     resolution: {integrity: sha512-D+BAjFoWwT5wtITpSxwqfWZiBClhBbR+bm0VQlWYFOadUUXFo+5wbe9ErXhLvwguPiLZdEF13QAWi2vP3ZD5tA==}
 
+  '@vue/runtime-core@3.4.36':
+    resolution: {integrity: sha512-9+TR14LAVEerZWLOm/N/sG2DVYhrH2bKgFrbH/FVt/Q8Jdw4OtdcGMRC6Tx8VAo0DA1eqAqrZaX0fbOaOxxZ4A==}
+
   '@vue/runtime-dom@3.4.26':
     resolution: {integrity: sha512-UftYA2hUXR2UOZD/Fc3IndZuCOOJgFxJsWOxDkhfVcwLbsfh2CdXE2tG4jWxBZuDAs9J9PzRTUFt1PgydEtItw==}
 
   '@vue/runtime-dom@3.4.35':
     resolution: {integrity: sha512-yGOlbos+MVhlS5NWBF2HDNgblG8e2MY3+GigHEyR/dREAluvI5tuUUgie3/9XeqhPE4LF0i2wjlduh5thnfOqw==}
 
+  '@vue/runtime-dom@3.4.36':
+    resolution: {integrity: sha512-2Qe2fKkLxgZBVvHrG0QMNLL4bsx7Ae88pyXebY2WnQYABpOnGYvA+axMbcF9QwM4yxnsv+aELbC0eiNVns7mGw==}
+
   '@vue/server-renderer@3.4.26':
     resolution: {integrity: sha512-xoGAqSjYDPGAeRWxeoYwqJFD/gw7mpgzOvSxEmjWaFO2rE6qpbD1PC172YRpvKhrihkyHJkNDADFXTfCyVGhKw==}
     peerDependencies:
@@ -4096,6 +4125,11 @@ packages:
     peerDependencies:
       vue: 3.4.35
 
+  '@vue/server-renderer@3.4.36':
+    resolution: {integrity: sha512-2XW90Rq8+Y7S1EIsAuubZVLm0gCU8HYb5mRAruFdwfC3XSOU5/YKePz29csFzsch8hXaY5UHh7ZMddmi1XTJEA==}
+    peerDependencies:
+      vue: 3.4.36
+
   '@vue/shared@3.4.26':
     resolution: {integrity: sha512-Fg4zwR0GNnjzodMt3KRy2AWGMKQXByl56+4HjN87soxLNU9P5xcJkstAlIeEF3cU6UYOzmJl1tV0dVPGIljCnQ==}
 
@@ -4108,6 +4142,9 @@ packages:
   '@vue/shared@3.4.35':
     resolution: {integrity: sha512-hvuhBYYDe+b1G8KHxsQ0diDqDMA8D9laxWZhNAjE83VZb5UDaXl9Xnz7cGdDSyiHM90qqI/CyGMcpBpiDy6VVQ==}
 
+  '@vue/shared@3.4.36':
+    resolution: {integrity: sha512-fdPLStwl1sDfYuUftBaUVn2pIrVFDASYerZSrlBvVBfylObPA1gtcWJHy5Ox8jLEJ524zBibss488Q3SZtU1uA==}
+
   '@vue/test-utils@2.4.6':
     resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==}
 
@@ -4222,6 +4259,10 @@ packages:
     resolution: {integrity: sha512-yNuGPMPibY91s936gnJCHWlStvIyDrwLwGfLC/NCdTin4F7HL4Gp5iJnHWkJFty1/DfFi8jjoIUBNLM8HEez+A==}
     engines: {node: ^16.13 || >=18}
 
+  '@wdio/config@8.40.0':
+    resolution: {integrity: sha512-sE+sBXUz4ZggS253hLNVu64ZCpm5ZidQ/IJNeM9Exh5OcsuZEnSeuqCZnd4ytK68A2heyZk8r2OjYZriA4l/Sg==}
+    engines: {node: ^16.13 || >=18}
+
   '@wdio/logger@8.28.0':
     resolution: {integrity: sha512-/s6zNCqwy1hoc+K4SJypis0Ud0dlJ+urOelJFO1x0G0rwDRWyFiUP6ijTaCcFxAm29jYEcEPWijl2xkVIHwOyA==}
     engines: {node: ^16.13 || >=18}
@@ -4256,6 +4297,10 @@ packages:
     resolution: {integrity: sha512-jY+n6jlGeK+9Tx8T659PKLwMQTGpLW5H78CSEWgZLbjbVSr2LfGR8Lx0CRktNXxAtqEVZPj16Pi74OtAhvhE6Q==}
     engines: {node: ^16.13 || >=18}
 
+  '@wdio/utils@8.40.0':
+    resolution: {integrity: sha512-P9b6XbRDRfCsZvdA70VYQrnsbkDVwEXlAGe4v4hcdgFxz81w+k4IX5bFUb7IB33E+3EZ/GhJWVU3QHgI9Y0u6w==}
+    engines: {node: ^16.13 || >=18}
+
   '@yeger/debounce@2.0.9':
     resolution: {integrity: sha512-yapSa71O56W0zotDlgv7z/equgK61Ozb8rSEWsNX7oN543oUMucWVWIlDp73/Jg7D08xAkw+E23OVLe1xIBS7A==}
 
@@ -4707,6 +4752,11 @@ packages:
     peerDependencies:
       devtools-protocol: '*'
 
+  chromium-bidi@0.5.8:
+    resolution: {integrity: sha512-blqh+1cEQbHBKmok3rVJkBlBxt9beKBgOsxbFgs7UJcoVbbeZ+K7+6liAsjgpc8l1Xd55cQUy14fXZdGSb4zIw==}
+    peerDependencies:
+      devtools-protocol: '*'
+
   ci-info@3.9.0:
     resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==}
     engines: {node: '>=8'}
@@ -5168,12 +5218,18 @@ packages:
   devtools-protocol@0.0.1147663:
     resolution: {integrity: sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==}
 
+  devtools-protocol@0.0.1232444:
+    resolution: {integrity: sha512-pM27vqEfxSxRkTMnF+XCmxSEb6duO5R+t8A9DEEJgy4Wz2RVanje2mmj99B6A3zv2r/qGfYlOvYznUhuokizmg==}
+
   devtools-protocol@0.0.1261483:
     resolution: {integrity: sha512-7vJvejpzA5DTfZVkr7a8sGpEAzEiAqcgmRTB0LSUrWeOicwL09lMQTzxHtFNVhJ1OOJkgYdH6Txvy9E5j3VOUQ==}
 
   devtools-protocol@0.0.1302984:
     resolution: {integrity: sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==}
 
+  devtools-protocol@0.0.1335233:
+    resolution: {integrity: sha512-bNTJw/m+v0JvQEsaI0l+i6mETHHf7VwZbQzT5GNSveGuYjip8uyjeF/qg84bsIPU+lFypnZr10a+cbcee6I8pg==}
+
   dezalgo@1.0.4:
     resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==}
 
@@ -5278,6 +5334,10 @@ packages:
     resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
     engines: {node: '>=0.12'}
 
+  entities@5.0.0:
+    resolution: {integrity: sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==}
+    engines: {node: '>=0.12'}
+
   error-ex@1.3.2:
     resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
 
@@ -7527,6 +7587,10 @@ packages:
     resolution: {integrity: sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==}
     engines: {node: '>= 14'}
 
+  proxy-agent@6.3.1:
+    resolution: {integrity: sha512-Rb5RVBy1iyqOtNl15Cw/llpeLH8bsb37gM1FUfKQ+Wck6xHlbAhWGUFiTRHtkjqGTA5pSHz6+0hrPW/oECihPQ==}
+    engines: {node: '>= 14'}
+
   proxy-from-env@1.1.0:
     resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
 
@@ -7555,6 +7619,10 @@ packages:
       typescript:
         optional: true
 
+  puppeteer-core@21.11.0:
+    resolution: {integrity: sha512-ArbnyA3U5SGHokEvkfWjW+O8hOxV1RSJxOgriX/3A4xZRqixt9ZFHD0yPgZQF05Qj0oAqi8H/7stDorjoHY90Q==}
+    engines: {node: '>=16.13.2'}
+
   qs@6.10.3:
     resolution: {integrity: sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==}
     engines: {node: '>=0.6'}
@@ -8750,6 +8818,9 @@ packages:
   url@0.11.3:
     resolution: {integrity: sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==}
 
+  urlpattern-polyfill@10.0.0:
+    resolution: {integrity: sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==}
+
   use-sync-external-store@1.2.0:
     resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
     peerDependencies:
@@ -8976,6 +9047,14 @@ packages:
       typescript:
         optional: true
 
+  vue@3.4.36:
+    resolution: {integrity: sha512-mIFvbLgjODfx3Iy1SrxOsiPpDb8Bo3EU+87ioimOZzZTOp15IEdAels70IjBOLO3ZFlLW5AhdwY4dWbXVQKYow==}
+    peerDependencies:
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
   w3c-xmlserializer@5.0.0:
     resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
     engines: {node: '>=18'}
@@ -9003,6 +9082,10 @@ packages:
     resolution: {integrity: sha512-Kc3+SfiH4ufyrIht683VT2vnJocx0pfH8rYdyPvEh1b2OYewtFTHK36k9rBDHZiBmk6jcSXs4M2xeFgOuon9Lg==}
     engines: {node: ^16.13 || >=18}
 
+  webdriver@8.40.0:
+    resolution: {integrity: sha512-pAuU8FbFXox837UgxjC2yT4s+goLBcqohdCSZJ1f1wG/XMsgjDHmouU6+f1SHHG7/I0IDGEZIsRD01RM57F3OA==}
+    engines: {node: ^16.13 || >=18}
+
   webdriverio@8.32.2:
     resolution: {integrity: sha512-Z0Wc/dHFfWGWJZpaQ8u910/LG0E9EIVTO7J5yjqWx2XtXz2LzQMxYwNRnvNLhY/1tI4y/cZxI6kFMWr8wD2TtA==}
     engines: {node: ^16.13 || >=18}
@@ -9021,6 +9104,15 @@ packages:
       devtools:
         optional: true
 
+  webdriverio@8.40.0:
+    resolution: {integrity: sha512-AYFLdfVt3wcDdnyxRDBlysOgB3XryLZrZdmtjUU842IyMcoV4Cq3SdVgz9aj9tskSeIJ3G37KgDEf5znnd5f3Q==}
+    engines: {node: ^16.13 || >=18}
+    peerDependencies:
+      devtools: ^8.14.0
+    peerDependenciesMeta:
+      devtools:
+        optional: true
+
   webidl-conversions@3.0.1:
     resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
 
@@ -9394,7 +9486,7 @@ snapshots:
       '@jridgewell/gen-mapping': 0.3.5
       '@jridgewell/trace-mapping': 0.3.25
 
-  '@antfu/eslint-config@2.24.1(@vue/compiler-sfc@3.4.35)(eslint@9.8.0)(svelte@3.59.1)(typescript@5.5.4)(vitest@packages+vitest)':
+  '@antfu/eslint-config@2.24.1(@vue/compiler-sfc@3.4.36)(eslint@9.8.0)(svelte@3.59.1)(typescript@5.5.4)(vitest@packages+vitest)':
     dependencies:
       '@antfu/install-pkg': 0.3.3
       '@clack/prompts': 0.7.0
@@ -9422,7 +9514,7 @@ snapshots:
       eslint-plugin-vitest: 0.5.4(@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4)(vitest@packages+vitest)
       eslint-plugin-vue: 9.27.0(eslint@9.8.0)
       eslint-plugin-yml: 1.14.0(eslint@9.8.0)
-      eslint-processor-vue-blocks: 0.1.2(@vue/compiler-sfc@3.4.35)(eslint@9.8.0)
+      eslint-processor-vue-blocks: 0.1.2(@vue/compiler-sfc@3.4.36)(eslint@9.8.0)
       globals: 15.8.0
       jsonc-eslint-parser: 2.4.0
       local-pkg: 0.5.0
@@ -9579,7 +9671,7 @@ snapshots:
 
   '@babel/generator@7.24.10':
     dependencies:
-      '@babel/types': 7.24.9
+      '@babel/types': 7.25.2
       '@jridgewell/gen-mapping': 0.3.5
       '@jridgewell/trace-mapping': 0.3.25
       jsesc: 2.5.2
@@ -9716,7 +9808,7 @@ snapshots:
   '@babel/helper-member-expression-to-functions@7.24.7':
     dependencies:
       '@babel/traverse': 7.24.8
-      '@babel/types': 7.24.9
+      '@babel/types': 7.25.2
     transitivePeerDependencies:
       - supports-color
 
@@ -9802,7 +9894,7 @@ snapshots:
 
   '@babel/helper-optimise-call-expression@7.24.7':
     dependencies:
-      '@babel/types': 7.24.9
+      '@babel/types': 7.25.2
 
   '@babel/helper-plugin-utils@7.24.0': {}
 
@@ -9843,7 +9935,7 @@ snapshots:
   '@babel/helper-skip-transparent-expression-wrappers@7.24.7':
     dependencies:
       '@babel/traverse': 7.24.8
-      '@babel/types': 7.24.9
+      '@babel/types': 7.25.2
     transitivePeerDependencies:
       - supports-color
 
@@ -10602,8 +10694,8 @@ snapshots:
       '@babel/helper-function-name': 7.24.7
       '@babel/helper-hoist-variables': 7.24.7
       '@babel/helper-split-export-declaration': 7.24.7
-      '@babel/parser': 7.24.8
-      '@babel/types': 7.24.9
+      '@babel/parser': 7.25.3
+      '@babel/types': 7.25.2
       debug: 4.3.6
       globals: 11.12.0
     transitivePeerDependencies:
@@ -11242,6 +11334,18 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  '@puppeteer/browsers@1.9.1':
+    dependencies:
+      debug: 4.3.4
+      extract-zip: 2.0.1
+      progress: 2.0.3
+      proxy-agent: 6.3.1
+      tar-fs: 3.0.4
+      unbzip2-stream: 1.4.3
+      yargs: 17.7.2
+    transitivePeerDependencies:
+      - supports-color
+
   '@remix-run/router@1.16.0': {}
 
   '@rollup/plugin-babel@5.3.1(@babel/core@7.25.2)(@types/babel__core@7.20.5)(rollup@4.20.0)':
@@ -11693,14 +11797,14 @@ snapshots:
     dependencies:
       '@testing-library/dom': 10.4.0
 
-  '@testing-library/vue@8.1.0(@vue/compiler-sfc@3.4.35)(vue@3.4.35(typescript@5.5.4))':
+  '@testing-library/vue@8.1.0(@vue/compiler-sfc@3.4.36)(vue@3.4.35(typescript@5.5.4))':
     dependencies:
       '@babel/runtime': 7.24.4
       '@testing-library/dom': 9.3.4
       '@vue/test-utils': 2.4.6
       vue: 3.4.35(typescript@5.5.4)
     optionalDependencies:
-      '@vue/compiler-sfc': 3.4.35
+      '@vue/compiler-sfc': 3.4.36
 
   '@tootallnate/quickjs-emscripten@0.23.0': {}
 
@@ -12284,6 +12388,11 @@ snapshots:
       vite: 5.3.3(@types/node@20.14.14)(terser@5.22.0)
       vue: 3.4.35(typescript@5.5.4)
 
+  '@vitejs/plugin-vue@5.1.2(vite@5.3.3(@types/node@20.14.14)(terser@5.22.0))(vue@3.4.36(typescript@5.5.4))':
+    dependencies:
+      vite: 5.3.3(@types/node@20.14.14)(terser@5.22.0)
+      vue: 3.4.36(typescript@5.5.4)
+
   '@vitest/browser@2.0.5(playwright@1.45.3)(typescript@5.5.4)(vitest@packages+vitest)(webdriverio@8.32.2(typescript@5.5.4))':
     dependencies:
       '@testing-library/dom': 10.4.0
@@ -12360,7 +12469,7 @@ snapshots:
 
   '@vue/compiler-core@3.4.33':
     dependencies:
-      '@babel/parser': 7.24.8
+      '@babel/parser': 7.25.3
       '@vue/shared': 3.4.33
       entities: 4.5.0
       estree-walker: 2.0.2
@@ -12374,6 +12483,14 @@ snapshots:
       estree-walker: 2.0.2
       source-map-js: 1.2.0
 
+  '@vue/compiler-core@3.4.36':
+    dependencies:
+      '@babel/parser': 7.25.3
+      '@vue/shared': 3.4.36
+      entities: 5.0.0
+      estree-walker: 2.0.2
+      source-map-js: 1.2.0
+
   '@vue/compiler-dom@3.4.26':
     dependencies:
       '@vue/compiler-core': 3.4.26
@@ -12389,6 +12506,11 @@ snapshots:
       '@vue/compiler-core': 3.4.35
       '@vue/shared': 3.4.35
 
+  '@vue/compiler-dom@3.4.36':
+    dependencies:
+      '@vue/compiler-core': 3.4.36
+      '@vue/shared': 3.4.36
+
   '@vue/compiler-sfc@3.4.26':
     dependencies:
       '@babel/parser': 7.24.4
@@ -12413,6 +12535,18 @@ snapshots:
       postcss: 8.4.40
       source-map-js: 1.2.0
 
+  '@vue/compiler-sfc@3.4.36':
+    dependencies:
+      '@babel/parser': 7.25.3
+      '@vue/compiler-core': 3.4.36
+      '@vue/compiler-dom': 3.4.36
+      '@vue/compiler-ssr': 3.4.36
+      '@vue/shared': 3.4.36
+      estree-walker: 2.0.2
+      magic-string: 0.30.11
+      postcss: 8.4.40
+      source-map-js: 1.2.0
+
   '@vue/compiler-ssr@3.4.26':
     dependencies:
       '@vue/compiler-dom': 3.4.26
@@ -12423,6 +12557,11 @@ snapshots:
       '@vue/compiler-dom': 3.4.35
       '@vue/shared': 3.4.35
 
+  '@vue/compiler-ssr@3.4.36':
+    dependencies:
+      '@vue/compiler-dom': 3.4.36
+      '@vue/shared': 3.4.36
+
   '@vue/devtools-api@6.6.3': {}
 
   '@vue/devtools-api@7.3.6':
@@ -12477,6 +12616,10 @@ snapshots:
     dependencies:
       '@vue/shared': 3.4.35
 
+  '@vue/reactivity@3.4.36':
+    dependencies:
+      '@vue/shared': 3.4.36
+
   '@vue/runtime-core@3.4.26':
     dependencies:
       '@vue/reactivity': 3.4.26
@@ -12487,6 +12630,11 @@ snapshots:
       '@vue/reactivity': 3.4.35
       '@vue/shared': 3.4.35
 
+  '@vue/runtime-core@3.4.36':
+    dependencies:
+      '@vue/reactivity': 3.4.36
+      '@vue/shared': 3.4.36
+
   '@vue/runtime-dom@3.4.26':
     dependencies:
       '@vue/runtime-core': 3.4.26
@@ -12500,6 +12648,13 @@ snapshots:
       '@vue/shared': 3.4.35
       csstype: 3.1.3
 
+  '@vue/runtime-dom@3.4.36':
+    dependencies:
+      '@vue/reactivity': 3.4.36
+      '@vue/runtime-core': 3.4.36
+      '@vue/shared': 3.4.36
+      csstype: 3.1.3
+
   '@vue/server-renderer@3.4.26(vue@3.4.26(typescript@5.5.4))':
     dependencies:
       '@vue/compiler-ssr': 3.4.26
@@ -12512,6 +12667,12 @@ snapshots:
       '@vue/shared': 3.4.35
       vue: 3.4.35(typescript@5.5.4)
 
+  '@vue/server-renderer@3.4.36(vue@3.4.36(typescript@5.5.4))':
+    dependencies:
+      '@vue/compiler-ssr': 3.4.36
+      '@vue/shared': 3.4.36
+      vue: 3.4.36(typescript@5.5.4)
+
   '@vue/shared@3.4.26': {}
 
   '@vue/shared@3.4.31': {}
@@ -12520,6 +12681,8 @@ snapshots:
 
   '@vue/shared@3.4.35': {}
 
+  '@vue/shared@3.4.36': {}
+
   '@vue/test-utils@2.4.6':
     dependencies:
       js-beautify: 1.15.1
@@ -12617,6 +12780,18 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  '@wdio/config@8.40.0':
+    dependencies:
+      '@wdio/logger': 8.38.0
+      '@wdio/types': 8.39.0
+      '@wdio/utils': 8.40.0
+      decamelize: 6.0.0
+      deepmerge-ts: 5.1.0
+      glob: 10.4.1
+      import-meta-resolve: 4.0.0
+    transitivePeerDependencies:
+      - supports-color
+
   '@wdio/logger@8.28.0':
     dependencies:
       chalk: 5.3.0
@@ -12683,6 +12858,24 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  '@wdio/utils@8.40.0':
+    dependencies:
+      '@puppeteer/browsers': 1.7.0
+      '@wdio/logger': 8.38.0
+      '@wdio/types': 8.39.0
+      decamelize: 6.0.0
+      deepmerge-ts: 5.1.0
+      edgedriver: 5.6.0
+      geckodriver: 4.3.2
+      get-port: 7.0.0
+      import-meta-resolve: 4.0.0
+      locate-app: 2.1.0
+      safaridriver: 0.1.2
+      split2: 4.2.0
+      wait-port: 1.0.4
+    transitivePeerDependencies:
+      - supports-color
+
   '@yeger/debounce@2.0.9': {}
 
   '@zip.js/zip.js@2.7.45': {}
@@ -13189,6 +13382,12 @@ snapshots:
       devtools-protocol: 0.0.1147663
       mitt: 3.0.0
 
+  chromium-bidi@0.5.8(devtools-protocol@0.0.1232444):
+    dependencies:
+      devtools-protocol: 0.0.1232444
+      mitt: 3.0.1
+      urlpattern-polyfill: 10.0.0
+
   ci-info@3.9.0: {}
 
   ci-info@4.0.0: {}
@@ -13602,10 +13801,14 @@ snapshots:
 
   devtools-protocol@0.0.1147663: {}
 
+  devtools-protocol@0.0.1232444: {}
+
   devtools-protocol@0.0.1261483: {}
 
   devtools-protocol@0.0.1302984: {}
 
+  devtools-protocol@0.0.1335233: {}
+
   dezalgo@1.0.4:
     dependencies:
       asap: 2.0.6
@@ -13709,6 +13912,8 @@ snapshots:
 
   entities@4.5.0: {}
 
+  entities@5.0.0: {}
+
   error-ex@1.3.2:
     dependencies:
       is-arrayish: 0.2.1
@@ -14104,9 +14309,9 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  eslint-processor-vue-blocks@0.1.2(@vue/compiler-sfc@3.4.35)(eslint@9.8.0):
+  eslint-processor-vue-blocks@0.1.2(@vue/compiler-sfc@3.4.36)(eslint@9.8.0):
     dependencies:
-      '@vue/compiler-sfc': 3.4.35
+      '@vue/compiler-sfc': 3.4.36
       eslint: 9.8.0
 
   eslint-rule-composer@0.3.0: {}
@@ -16484,6 +16689,19 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  proxy-agent@6.3.1:
+    dependencies:
+      agent-base: 7.1.0
+      debug: 4.3.6
+      http-proxy-agent: 7.0.2
+      https-proxy-agent: 7.0.5
+      lru-cache: 7.18.3
+      pac-proxy-agent: 7.0.1
+      proxy-from-env: 1.1.0
+      socks-proxy-agent: 8.0.2
+    transitivePeerDependencies:
+      - supports-color
+
   proxy-from-env@1.1.0: {}
 
   psl@1.9.0: {}
@@ -16515,6 +16733,20 @@ snapshots:
       - supports-color
       - utf-8-validate
 
+  puppeteer-core@21.11.0:
+    dependencies:
+      '@puppeteer/browsers': 1.9.1
+      chromium-bidi: 0.5.8(devtools-protocol@0.0.1232444)
+      cross-fetch: 4.0.0
+      debug: 4.3.4
+      devtools-protocol: 0.0.1232444
+      ws: 8.16.0
+    transitivePeerDependencies:
+      - bufferutil
+      - encoding
+      - supports-color
+      - utf-8-validate
+
   qs@6.10.3:
     dependencies:
       side-channel: 1.0.4
@@ -17875,6 +18107,8 @@ snapshots:
       punycode: 1.4.1
       qs: 6.11.2
 
+  urlpattern-polyfill@10.0.0: {}
+
   use-sync-external-store@1.2.0(react@18.3.1):
     dependencies:
       react: 18.3.1
@@ -17915,7 +18149,7 @@ snapshots:
       unist-util-stringify-position: 4.0.0
       vfile-message: 4.0.2
 
-  vite-plugin-pages@0.32.3(@vue/compiler-sfc@3.4.35)(vite@5.3.3(@types/node@20.14.14)(terser@5.22.0))(vue-router@4.4.2(vue@3.4.35(typescript@5.5.4))):
+  vite-plugin-pages@0.32.3(@vue/compiler-sfc@3.4.36)(vite@5.3.3(@types/node@20.14.14)(terser@5.22.0))(vue-router@4.4.2(vue@3.4.35(typescript@5.5.4))):
     dependencies:
       '@types/debug': 4.1.12
       debug: 4.3.6
@@ -17928,7 +18162,7 @@ snapshots:
       vite: 5.3.3(@types/node@20.14.14)(terser@5.22.0)
       yaml: 2.4.5
     optionalDependencies:
-      '@vue/compiler-sfc': 3.4.35
+      '@vue/compiler-sfc': 3.4.36
       vue-router: 4.4.2(vue@3.4.35(typescript@5.5.4))
     transitivePeerDependencies:
       - supports-color
@@ -18125,6 +18359,16 @@ snapshots:
     optionalDependencies:
       typescript: 5.5.4
 
+  vue@3.4.36(typescript@5.5.4):
+    dependencies:
+      '@vue/compiler-dom': 3.4.36
+      '@vue/compiler-sfc': 3.4.36
+      '@vue/runtime-dom': 3.4.36
+      '@vue/server-renderer': 3.4.36(vue@3.4.36(typescript@5.5.4))
+      '@vue/shared': 3.4.36
+    optionalDependencies:
+      typescript: 5.5.4
+
   w3c-xmlserializer@5.0.0:
     dependencies:
       xml-name-validator: 5.0.0
@@ -18183,6 +18427,24 @@ snapshots:
       - supports-color
       - utf-8-validate
 
+  webdriver@8.40.0:
+    dependencies:
+      '@types/node': 20.14.14
+      '@types/ws': 8.5.12
+      '@wdio/config': 8.40.0
+      '@wdio/logger': 8.38.0
+      '@wdio/protocols': 8.38.0
+      '@wdio/types': 8.39.0
+      '@wdio/utils': 8.40.0
+      deepmerge-ts: 5.1.0
+      got: 12.6.1
+      ky: 0.33.3
+      ws: 8.18.0
+    transitivePeerDependencies:
+      - bufferutil
+      - supports-color
+      - utf-8-validate
+
   webdriverio@8.32.2(typescript@5.5.4):
     dependencies:
       '@types/node': 20.14.13
@@ -18250,6 +18512,39 @@ snapshots:
       - typescript
       - utf-8-validate
 
+  webdriverio@8.40.0:
+    dependencies:
+      '@types/node': 20.14.13
+      '@wdio/config': 8.40.0
+      '@wdio/logger': 8.38.0
+      '@wdio/protocols': 8.38.0
+      '@wdio/repl': 8.24.12
+      '@wdio/types': 8.39.0
+      '@wdio/utils': 8.40.0
+      archiver: 7.0.1
+      aria-query: 5.3.0
+      css-shorthand-properties: 1.1.1
+      css-value: 0.0.1
+      devtools-protocol: 0.0.1335233
+      grapheme-splitter: 1.0.4
+      import-meta-resolve: 4.0.0
+      is-plain-obj: 4.1.0
+      jszip: 3.10.1
+      lodash.clonedeep: 4.5.0
+      lodash.zip: 4.2.0
+      minimatch: 9.0.5
+      puppeteer-core: 21.11.0
+      query-selector-shadow-dom: 1.0.1
+      resq: 1.11.0
+      rgb2hex: 0.2.5
+      serialize-error: 11.0.2
+      webdriver: 8.40.0
+    transitivePeerDependencies:
+      - bufferutil
+      - encoding
+      - supports-color
+      - utf-8-validate
+
   webidl-conversions@3.0.1: {}
 
   webidl-conversions@4.0.2: {}
diff --git a/test/browser/fixtures/locators/query.test.ts b/test/browser/fixtures/locators/query.test.ts
new file mode 100644
index 000000000000..4b74bd6bac70
--- /dev/null
+++ b/test/browser/fixtures/locators/query.test.ts
@@ -0,0 +1,16 @@
+import { page } from '@vitest/browser/context';
+import { afterEach, expect, test } from 'vitest';
+
+afterEach(() => {
+  document.body.innerHTML = ''
+})
+
+test('can find a body element', () => {
+  expect(page.elementLocator(document.body).element()).toBe(document.body);
+})
+
+test('can find elements inside the body', () => {
+  document.body.innerHTML = '<div><span>hello</span></div>'
+  const screen = page.elementLocator(document.body)
+  expect(screen.getByText('hello').element()).toBe(document.querySelector('span'));
+})
\ No newline at end of file
diff --git a/test/browser/package.json b/test/browser/package.json
index 173e6c7cdd5a..57609afb6308 100644
--- a/test/browser/package.json
+++ b/test/browser/package.json
@@ -29,6 +29,7 @@
     "playwright": "^1.41.0",
     "react": "^18.3.1",
     "react-dom": "^18.3.1",
+    "strip-ansi": "^7.1.0",
     "url": "^0.11.3",
     "vitest": "workspace:*",
     "webdriverio": "^8.32.2"
diff --git a/test/browser/specs/locators.test.ts b/test/browser/specs/locators.test.ts
index 1a9a615dbfbf..753926422536 100644
--- a/test/browser/specs/locators.test.ts
+++ b/test/browser/specs/locators.test.ts
@@ -9,5 +9,6 @@ test('locators work correctly', async () => {
 
   expect(stderr).toBe('')
   expect(stdout).toContain('✓ blog.test.tsx')
-  expect(stdout).toContain('1 passed (1)')
+  expect(stdout).toContain('✓ query.test.ts')
+  expect(stdout).toContain('Test Files  2 passed (2)')
 })
diff --git a/test/browser/test/__snapshots__/utils.test.ts.snap b/test/browser/test/__snapshots__/utils.test.ts.snap
new file mode 100644
index 000000000000..c0ab66b34255
--- /dev/null
+++ b/test/browser/test/__snapshots__/utils.test.ts.snap
@@ -0,0 +1,37 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`prints default document 1`] = `"<body />"`;
+
+exports[`prints default document 2`] = `
+"<body>
+  <div>
+    <span>
+      hello
+    </span>
+  </div>
+</body>"
+`;
+
+exports[`prints the element 1`] = `
+"<body>
+  <div>
+    <span>
+      hello
+    </span>
+  </div>
+</body>"
+`;
+
+exports[`prints the element with attributes 1`] = `
+"<body>
+  <div>
+    <span
+      class="some-name"
+      data-test-id="33"
+      id="5"
+    >
+      hello
+    </span>
+  </div>
+</body>"
+`;
diff --git a/test/browser/test/utils.test.ts b/test/browser/test/utils.test.ts
index 1e004a202c12..35da40c046c8 100644
--- a/test/browser/test/utils.test.ts
+++ b/test/browser/test/utils.test.ts
@@ -1,6 +1,39 @@
 import { inspect } from 'vitest/utils'
-import { expect, it } from 'vitest'
+import { afterEach, expect, it, test } from 'vitest'
+import stripAnsi from 'strip-ansi'
+
+import { prettyDOM } from '@vitest/browser/utils'
+
+afterEach(() => {
+  document.body.innerHTML = ''
+})
 
 it('utils package correctly uses loupe', async () => {
   expect(inspect({ test: 1 })).toBe('{ test: 1 }')
 })
+
+test('prints default document', () => {
+  expect(stripAnsi(prettyDOM())).toMatchSnapshot()
+
+  const div = document.createElement('div')
+  div.innerHTML = '<span>hello</span>'
+  document.body.append(div)
+
+  expect(stripAnsi(prettyDOM())).toMatchSnapshot()
+})
+
+test('prints the element', () => {
+  const div = document.createElement('div')
+  div.innerHTML = '<span>hello</span>'
+  document.body.append(div)
+
+  expect(stripAnsi(prettyDOM())).toMatchSnapshot()
+})
+
+test('prints the element with attributes', () => {
+  const div = document.createElement('div')
+  div.innerHTML = '<span class="some-name" data-test-id="33" id="5">hello</span>'
+  document.body.append(div)
+
+  expect(stripAnsi(prettyDOM())).toMatchSnapshot()
+})