Playwright component testing lets you render and interact with your Vue components in isolation, just like you would a full application, but without the overhead of launching a browser.

Let’s see this in action. Imagine you have a simple Counter.vue component:

<template>
  <div>

    <p>Count: {{ count }}</p>

    <button @click="increment">Increment</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const count = ref(0);

const increment = () => {
  count.value++;
};
</script>

With Playwright, you can write a test like this:

// tests/counter.spec.js
import { test, expect } from '@playwright/experimental-ct-vue/test';
import Counter from '../src/components/Counter.vue';

test('should increment the count', async ({ mount }) => {
  const component = await mount(Counter);

  // Verify initial state
  await expect(component.locator('p')).toHaveText('Count: 0');

  // Interact with the component
  await component.locator('button').click();

  // Verify state change
  await expect(component.locator('p')).toHaveText('Count: 1');
});

When you run this test, Playwright spins up a minimal environment (often a headless browser instance managed by the test runner, but the key is it’s isolated for the component) and renders your Counter component within it. It then executes the assertions: finding the paragraph, checking its text, clicking the button, and checking the paragraph’s text again.

The core problem Playwright component testing solves is the friction in testing UI components. Traditionally, you’d either write brittle unit tests that mock dependencies extensively, or full end-to-end tests that are slow and require a running application. Component testing bridges this gap by providing a realistic rendering environment for individual components, allowing you to test their behavior, styling, and interactions in isolation.

Internally, Playwright uses a test runner (like Vite for Vue projects) to bundle your component and its dependencies. It then mounts this bundled component into a lightweight browser context. The mount function provided by Playwright is central; it takes your component and any props, slots, or initial state you want to provide, and renders it. The returned component object is a Playwright Locator that represents your mounted component, allowing you to query elements within it and trigger events.

You control the testing experience through the mount function’s arguments and Playwright’s standard assertion APIs. For instance, to test a component that accepts props, you’d pass them like this:

// Assuming a component like <MyButton :label="buttonLabel" />
const component = await mount(MyButton, {
  props: {
    label: 'Click Me',
  },
});

Or for slots:

// Assuming a component that accepts a default slot
const component = await mount(Card, {
  slots: {
    default: 'This is the card content.',
  },
});

This allows you to precisely set up the conditions under which your component will be tested, mirroring how it would be used in your application. The power comes from the fact that these interactions and assertions are happening against a real DOM, albeit a virtualized one for the component, so you’re not just testing abstract logic but actual rendered output and user interactions.

The way Playwright handles the DOM within the component mount is fascinating. It doesn’t just inject HTML; it creates a fully functional DOM environment. This means you can reliably use selectors like getByRole, getByLabelText, and getByText that rely on ARIA attributes and semantic HTML, just as you would in end-to-end tests. This adherence to accessibility best practices within component tests is a significant advantage, ensuring your components are not only functional but also usable.

When you’re testing a component that relies on global context or providers (like Vue’s provide/inject or a Pinia store), you can wrap your component with the necessary providers within the mount options. For example, to provide a Pinia store:

import { createPinia } from 'pinia';
import MyComponentWithStore from '../src/components/MyComponentWithStore.vue';

test('should use store', async ({ mount }) => {
  const pinia = createPinia();
  const component = await mount(MyComponentWithStore, {
    global: {
      plugins: [pinia],
    },
  });
  // ... test interactions involving the store
});

This granular control over the component’s environment, combined with Playwright’s robust testing capabilities, makes it incredibly effective for building confidence in your UI.

Beyond simple component rendering, Playwright component testing also excels at simulating complex user flows and edge cases within a component. You can trigger hover states, focus changes, drag-and-drop interactions, and even network requests (if your component fetches data) using Playwright’s extensive API. This means you can test component behavior under a wide range of conditions without needing to build out a full application scaffold for each test scenario.

The next frontier in component testing is often exploring how to integrate these isolated component tests into a broader testing strategy, perhaps alongside end-to-end tests for critical user journeys.

Want structured learning?

Take the full Playwright course →