Exploring Component Testing in Vue with Playwright: The basics

Jan 30, 2024 ยท 5 min read
Exploring Component Testing in Vue with Playwright: The basics

In this post we will dive into using Playwright component testing for Vue applications. By demonstrating a component with search capability, we will walkthrough a step-by-step guide on how to test a component with the preview Playwright's component testing feature.

Table of content

Getting started with Playwright

PlaywrightJS is an open-source E2E testing framework that is cross-browser, cross-platform and cross-language, making it a great choice for any modern applications. Whether you're working on a small project or a large-scale app, Playwright ensures your tests are reliable and comprehensive.

Setting up the component testing

Run the following command to install Playwright with component testing for your Vue project:

npm init playwright@latest -- --ct
Install Playwright component testing

The above command will perform the following:

  • Install @playwright/experimental-ct-vue, the experimental component testing package tailored for Vue.js.
  • Brings in the necessary browsers for your test runner.
  • Add a script command test-ct to your package.json.
  • Add playwright-ct.config.js, a dedicated config file for component testing.
  • Add playwright folder for runtime configuration, such as adding routing.
  • And finally, adds an e2e folder with test suites ready to go.

Modifying the configuration file

Once installed, we need a tiny bit of tweakingin the playwright-ct.config.js file to get everything running smoothly in a Vite and ES6 module environment:

  • Replace require() in line 2 to using ES6 import
  • Replace module.export in line 7 to export default.
  • Change the testDir to ./e2e instead, to avoid Playwright picking up the unit tests by accident.

And our Vue project is now Playwright-ready for component testing. Let's write our first component and test.

We will implement component ItemsView that contains the following features:

  • An input field that binds to a searchTerm variable
  • The component uses useSearch(), a custom composable, that accepts an initial list of items and returns the reactive searchTerm and the filtered searchResults.
  • A list of items based on the search term.

Below is the implementation for template section of ItemsView:

<template>
  <div class="items-view--container">
    <div>
        <input 
            v-model="searchTerm" placeholder="Search for an item" data-testid="search-input" id="searchbox" 
        />
    </div>
    <ul>
      <li v-for="item in searchResults" :key="item.id">
        <h2>{{item}}</h2>
      </li>
    </ul>
  </div>
</template>

And our script setup is as follows:

import { useItems } from "../composables/useItems";
import PizzaCard from "../components/PizzaCard.vue";
import { useSearch } from "../composables/useSearch";

const { items } = useItems();

const { search, searchResults } = useSearch({
  items: items,
});

Launch the app, the browser will display the view with a search input and the list of items, as seen in the below screenshot:

ItemsView component

And with that, it's time to add our first test to ItemsView, focusing on the search.

Component testing ItemsView

Within e2e folder, we create a new ItemsView.spec.js with the following code:

import { test, expect } from '@playwright/experimental-ct-vue';
import ItemsView from "../src/components/ItemsView.vue";

test("", async ({ mount, page }) => {
})

Our first test will do the following:

  1. Mounting the component using mount
  2. Verify the initial value of the search input:
test("should display component with empty input", async ({ mount, page }) => {
    //1. Mount the component
    const component = await mount(ItemsView);

    //2. Locate the search input 
    const elem = await component.getByTestId('search-input');

    //3. Verify the label and the initial value
    await expect(elem).toHaveValue('')
})

Next up, we'll test if the component updates when you type in the search box. We will simulate the user's input by using elem.fill() function:

test("should update the search term", async ({ mount }) => {
    //1. Mount the component 
    //2. Locate the input
    //...

    //3. Fill in the input's value
    await elem.fill('hello')

    //4. Verify
    await expect(elem).toHaveValue('hello')
})

When we run yarn test-ct, Playwright will trigger the test on different browsers defined in playwright-ct.config.js. If something's amiss, you'll get a detailed report, both in your terminal and a UI report in the browser, while waiting for the test to be fixed.

You can view the final result any time with yarn playwright show-report:

Show report per component test

And there you have it! Our ItemsView is now equipped with its first set of search tests.

Next, let's empower ItemsView to accept an initialSearchTerm prop as the initial search value for the input.

Configuring initial search term as prop

First, we'll declare the initialSearchTerm as one of the component's props using defineProps:

const props = defineProps({
  intialSearchTerm: {
    type: String,
    default: "",
  },
});

Next, we pass it as the defaultSearch for useSearch:

const { search, searchResults } = useSearch({
    items: items,
    defaultSearch: props.intialSearchTerm,
});

With that setup, if we pass a value to intialSearchTerm prop, the search input will take it as its initial value.

<ItemsView initial-search-term="hawaii"/>
Component rendered with initial search value

Next, we will cover it with tests.

Testing initial search term props

Unlike Vue Test Utils or Testing Library, the mount() in Playwright does not work the same way. It does not accept a second parameter as additional Vue component options. To pass a prop upon mounting, we will use JSX syntax, as follows:

test("should get the search term from outside", async ({ mount }) => {

    const component = await mount(<ItemsView initialSearchTerm="hello" />);
});

Playwright renders this beautifully and from there we can validate our search input's initial value:

const elem = await component.getByTestId('search-input');
await expect(elem).toHaveValue('hello')

And just like that, run the tests again, and watch as everything passed as expected.

Test on initial value as prop

As an extra tip, you can debug the test by add the --debug flag the test command (yarn test-ct --debug). With this flag, Playwright will open the browser's DevTools, pause the tests, and allow you to inspect the component's DOM while running the tests.

Playwright test run in debug mode

Wrapping up

While Playwright's component testing capabilities sounds promising, it's important to remember they're still in the experimental phase. This means you might encounter some limitations or bugs along the way. Also, the tests we performed in our post today can easily be done using Vitest + Vue Test Utils, just without the actual UI render.

So, the question here is: are there scenarios where using an E2E framework like Playwright for component testing offers a clear advantage over traditional unit testing frameworks? We will explore that in our next post. Until then, it's time to dive into some test writing! ๐Ÿ˜‰๐Ÿงช๐Ÿ’ป

๐Ÿ‘‰ Learn about Vue 3 and TypeScript with my new book Learning Vue!

๐Ÿ‘‰ If you'd like to catch up with me sometimes, follow me on X | LinkedIn.

Like this post or find it helpful? Share it ๐Ÿ‘‡๐Ÿผ ๐Ÿ˜‰