Skip to main content


Vue.js is an approachable, performant and versatile framework for building web user interfaces. You can test Vue.js components directly in a real browser using WebdriverIO and its browser runner.


To setup WebdriverIO within your Vue.js project, follow the instructions in our component testing docs. Make sure to select vue as preset within your runner options, e.g.:

// wdio.conf.js
export const config = {
// ...
runner: ['browser', {
preset: 'vue'
// ...

If you are already using Vite as development server you can also just re-use your configuration in vite.config.ts within your WebdriverIO config. For more information, see viteConfig in runner options.

The Vue preset requires @vitejs/plugin-vue to be installed. Also we recommend using Testing Library for rendering the component into the test page. Therefor you'll need to install the following additional dependencies:

npm install --save-dev @testing-library/vue @vitejs/plugin-vue

You can then start the tests by running:

npx wdio run ./wdio.conf.js

Writing Tests

Given you have the following Vue.js component:

<p>Times clicked: {{ count }}</p>
<button @click="increment">increment</button>

export default {
data: () => ({
count: 0,

methods: {
increment() {

In your test render the component into the DOM and run assertions on it. We recommend to either use @vue/test-utils or @testing-library/vue to attach the component to the test page. To interact with the component use WebdriverIO commands as they behave more close to actual user interactions, e.g.:

import { $, expect } from '@wdio/globals'
import { mount } from '@vue/test-utils'
import Component from './components/Component.vue'

describe('Vue Component Testing', () => {
it('increments value on click', async () => {
// The render method returns a collection of utilities to query your component.
const wrapper = mount(Component, { attachTo: document.body })
expect(wrapper.text()).toContain('Times clicked: 0')

const button = await $('aria/increment')

// Dispatch a native click event to our button element.

expect(wrapper.text()).toContain('Times clicked: 2')
await expect($('p=Times clicked: 2')).toExist() // same assertion with WebdriverIO

You can find a full example of a WebdriverIO component test suite for Vue.js in our example repository.

Testing Async Components in Vue3

If you are using Vue v3 and are testing async components like the following:

<script setup>
const res = await fetch(...)
const posts = await res.json()

{{ posts }}

We recommend to use @vue/test-utils and a little suspence wrapper to get the component rendered. Unfortunately @testing-library/vue has no support for this yet. Create a helper.ts file with the following content:

import { mount, type VueWrapper as VueWrapperImport } from '@vue/test-utils'
import { Suspense } from 'vue'

export type VueWrapper = VueWrapperImport<any>
const scheduler = typeof setImmediate === 'function' ? setImmediate : setTimeout

export function flushPromises(): Promise<void> {
return new Promise((resolve) => {
scheduler(resolve, 0)

export function wrapInSuspense(
component: ReturnType<typeof defineComponent>,
{ props }: { props: object },
): ReturnType<typeof defineComponent> {
return defineComponent({
render() {
return h(
{ id: 'root' },
h(Suspense, null, {
default() {
return h(component, props)
fallback: h('div', 'fallback'),

export function renderAsyncComponent(vueComponent: ReturnType<typeof defineComponent>, props: object): VueWrapper{
const component = wrapInSuspense(vueComponent, { props })
return mount(component, { attachTo: document.body })

Then import and test the component as following:

import { $, expect } from '@wdio/globals'

import { renderAsyncComponent, flushPromises, type VueWrapper } from './helpers.js'
import AsyncComponent from '/components/SomeAsyncComponent.vue'

describe('Testing Async Components', () => {
let wrapper: VueWrapper

it('should display component correctly', async () => {
const props = {}
wrapper = renderAsyncComponent(AsyncComponent, { props })
await flushPromises()
await expect($('...')).toBePresent()

afterEach(() => {

Testing Vue Components in Nuxt

If you are using the web framework Nuxt, WebdriverIO will automatically enable the auto-import feature and makes testing your Vue components and Nuxt pages easy. However any Nuxt modules that you might define in your config and requires context to the Nuxt application can not be supported.

Reasons for that are:

  • WebdriverIO can't initiate a Nuxt application soley in a browser environment
  • Having component tests depend too much on the Nuxt environment creates complexity and we recommend to run these tests as e2e tests

WebdriverIO also provides a service for running e2e tests on Nuxt applications, see webdriverio-community/wdio-nuxt-service for information.

Mocking built-in composables

In case your component uses a native Nuxt composable, e.g. useNuxtData, WebdriverIO will automatically mock these functions and allows you to modify their behavior or assert against them, e.g.:

import { mocked } from '@wdio/browser-runner'

// e.g. your component uses calls `useNuxtData` the following way
// `const { data: posts } = useNuxtData('posts')`
// in your test you can assert against it
// and change their behavior
data: [...]

Handling 3rd party composables

All 3rd party modules that can supercharge your Nuxt project can't automatically get mocked. In those cases you need to manually mock them, e.g. given your application uses the Supabase module plugin:

export default defineNuxtConfig({
modules: [
// ...
// ...

and you create an instance of Supabase somewhere in your composables, e.g.:

const superbase = useSupabaseClient()

the test will fail due to:

ReferenceError: useSupabaseClient is not defined

Here, we recommend to either mock out the whole module that uses the useSupabaseClient function or create a global variable that mocks this function, e.g.:

import { fn } from '@wdio/browser-runner'
globalThis.useSupabaseClient = fn().mockReturnValue({})

Welcome! How can I help?

WebdriverIO AI Copilot