Introduction

Let's face it – testing isn't the most glamorous part of web development, but it's definitely one of the most important. Without solid tests, we're basically crossing our fingers and hoping our apps don't break when users start clicking around.

That's where Playwright comes in. It's a game-changer for end-to-end testing, making the process faster and more reliable than you might expect. In this guide, I'll walk you through setting up Playwright for a React app built with Vite and TypeScript, with special attention to handling API integration testing.

Setting Up Your React Playground

First things first, let's get a React app up and running. We'll use Vite because, honestly, who doesn't love lightning-fast build times?

npm create vite@latest my-react-app --template react-ts
cd my-react-app
npm install

Fire up the dev server with:

npm run dev

Getting Friendly with Playwright

Now for the fun part – adding Playwright to the mix:

npm init playwright@latest

It will create a tests folder and inside there will be an example.spec.ts file already created.There let's write our first test.

import { test, expect } from '@playwright/test';

test.describe('Basic tests', () => {
  test.beforeAll(async () => {
    console.log('Setting up test environment');
  });

  test('basic test', async ({ page }) => {
    // Navigate to the application
    await page.goto('http://localhost:5173');

    // Check if the page title contains the expected text
    await expect(page).toHaveTitle(/Vite \+ React/);
  });
});

What's happening here?

  • We're grouping our tests with test.describe for better organization
  • Using test.beforeAll to set up anything we need before testing
  • Navigating to our app and checking if the page title is what we expect

Run the test with:

npx playwright test

Building Something Worth Testing

Let's create a simple component that actually does something – like fetching user data from an API an display that to an UI:

import { useEffect, useState } from 'react';

type User = {
  id: number;
  name: string;
};

const UserList = () => {
  const [users, setUsers] = useState<User[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/users')
      .then((response) => response.json())
      .then((data) => {
        setUsers(data);
        setLoading(false);
      });
  }, []);

  if (loading) return <p>Loading...p>;

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}li>
      ))}
    ul>
  );
};

export default UserList;

And let's add this component to our App:

import UserList from './components/UserList';

function App() {
  return (
    <div>
      <h1>User Listh1>
      <UserList />
    div>
  );
}

export default App;

Let's test the App

Now comes the really cool part – testing how our app handles API responses. Create a new test file tests/userlist.spec.ts:

import { test, expect } from '@playwright/test';

test.describe('User List API Integration', () => {
  test.beforeAll(async () => {
    console.log('Starting API tests');
  });

  test('User list loads and displays correctly', async ({ page }) => {
    // Intercept API request and provide mock data
    await page.route('https://jsonplaceholder.typicode.com/users', async (route) => {
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify([
          { id: 1, name: 'John Doe' },
          { id: 2, name: 'Jane Doe' }
        ]),
      });
    });

    // Navigate to the app
    await page.goto('http://localhost:5173');

    // Wait for API call and UI update
    await expect(page.getByText('John Doe')).toBeVisible();
    await expect(page.getByText('Jane Doe')).toBeVisible();
  });
});

This is where the Playwright really shines. We're:

  1. Intercepting the API call and sending back our own mock data
  2. Loading up our app
  3. Making sure the UI actually shows the data we expect

No need to worry about flaky tests or timing issues – Playwright intelligently waits for elements to appear.

Run Those Tests!

Time to see if everything works:

npx playwright test

Want to actually see the tests running? Try UI mode:

npx playwright test --ui

Conclusion

And that's it! We've built a React app and set up Playwright for testing, and even tackled API integration testing. The best part? Playwright's API interception means we can test our UI components without worrying about backend dependencies. These techniques should give you confidence that your React components will handle API data correctly – even when things go wrong. If you like this blog and want to learn more about Frontend Development and Software Engineering, you can follow me on Dev.to.