jonelantha: Blog


How-To: Use Playwright and Create React App to setup E2E component testing with a test-harness

15th September 2021

A look at a simple yet effective approach for implementing automated browser-based testing for a standalone React component

What this is about

When it comes to testing a React component, using Jest with React Testing Library can offer a straightforward solution. The tests won't be running in a real browser, instead they'll make use of a DOM simulator - something which accurately emulates many aspects of how a browser works. For the majority of testing situations this works well, but what about components which rely on more complex interactions with browser APIs?

Automated end-to-end testing in real browsers offers an answer but typically a component will need to be embedded in a web page before it can be tested in a browser. One solution is to create some kind of test-harness - a way of hosting the component and providing a target for an automated browser testing tool.

What's going to be built

This article looks at how to create a custom test-harness with Create React App and then how to setup Playwright Test for automated end-to-end testing of a component in the test-harness.

So this means:

  • Test-harness and tests in the same project folder
  • Headless (or headed) testing with Chrome, Firefox and Safari
  • Completely self-contained, works locally, great for a CI system

The Component under test

So here's the WindowWidth component that will be tested:

import { useEffect, useState } from 'react';

export default function WindowWidth() {
  const [windowWidth, setWindowWidth] = useState(window.innerWidth);

  useEffect(() => {
    const listener = () => setWindowWidth(window.innerWidth);

    window.addEventListener('resize', listener);

    return () => window.removeEventListener('resize', listener);
  }, []);

  return (
    <p>Window is {windowWidth > 600 ? 'wide' : 'narrow'}</p>
  );
}

It's pretty straightforward, it sets up a listener on the window's resize event and then outputs either 'Window is wide' or 'Window is narrow' depending on the browser width.

Note how the above component makes use of window.addEventListener - this makes testing in non-browser environments a little more difficult. It may still be possible to test but it's far harder to test with complete confidence - so that makes this component a good candidate for testing in a real browser.

Step 1: Create the Test-Harness

First create a test-harness with Create React App

  1. Use the Create React App CLI to create a new project:

    npx create-react-app component-test-harness

    Which creates a standard directory structure:

    • component-test-harness
      • node_modules
      • public
      • src
      • package-lock.json
      • package.json
      • ...
  2. In the src folder delete everything

  3. Copy the component source above into a new file ./src/WindowWidth.jsx

  4. Create a /src/index.js file with the following contents:

    import React from 'react';
    import ReactDOM from 'react-dom';
    import WindowWidth from './WindowWidth';
    
    ReactDOM.render(
      <div id='component-wrapper'>
        <WindowWidth />
      </div>,
      document.getElementById('root')
    );
  5. This should create a directory structure as follows:

    • component-test-harness
      • node_modules
      • public
      • src
        • index.js
        • WindowWidth.jsx
      • package-lock.json
      • package.json
      • ...
  6. Start the app with:

    npm start

    A browser window should appear with either 'Window is wide' or 'Window is narrow' (resizing the window should change the message). Press ^C when done to stop the local server.

The test-harness is now complete!

Note that typically the component under test would not be defined in the test-harness project, this is done here just for convenience. More likely the component might be pulled in from another package (in a mono repo structure or otherwise)

Step 2: Create some automated tests

The next step is to create some tests to run against the test-harness

  1. First pull in two dependencies, playwright (for browser automation) and @playwright/test (to provide a test runner configured for playwright):

    npm install playwright
    npm install @playwright/test
  2. Create a playwright.config.js in the project root. This tells the test runner how to start the test-harness and also the local port where the test-harness will be served from (3000 is the default for Create React App)

    const config = {
      webServer: {
        command: 'npm run start',
        port: 3000,
        timeout: 120 * 1000,
      },
    };
    
    module.exports = config;
  3. Next create a test file in the src folder as ./src/WindowWidth.spec.js

    const { test, expect } = require('@playwright/test');
    
    test.describe('width test', () => {
      test.beforeEach(async ({ page, baseURL }) => {
        await page.goto(baseURL);
        
        await page.waitForSelector('#component-wrapper');
      });
    
      test('when window is wide', async ({ page }) => {
        await page.setViewportSize({ width: 700, height: 200 });
    
        const contents = page.locator('#component-wrapper');
        await expect(contents).toHaveText('Window is wide');
      });
    
      test('when window is narrow', async ({ page }) => {
        await page.setViewportSize({ width: 500, height: 200 });
        
        const contents = page.locator('#component-wrapper');
        await expect(contents).toHaveText('Window is narrow');
      });
    });

    The contents of this file should be familiar to those who've worked with jest or other testing packages, for more information on the specifics see the Playwright Test Getting started guide

    Note how baseURL is used in the beforeEach hook, baseURL will be derived from the info in the config file (it should be http://localhost:3000/)

  4. In package.json change the existing test script line:

    {
      ...
      "scripts": {
        ...
        "test": "playwright test",
        ...
      }
    }
  5. The directory structure should now look like the following:

    • component-test-harness
      • node_modules
      • public
      • src
        • index.js
        • WindowWidth.jsx
        • WindowWidth.spec.js
      • package-lock.json
      • package.json
      • playwright.config.js
      • ...

Step 3: Run the tests

To run the tests:

npm test

The results should appear in the console:

  ✓  src/WindowWidth.spec.js:10:3 › width test when window is wide (2s)
  ✓  src/WindowWidth.spec.js:17:3 › width test when window is narrow (344ms)

  2 passed (3s)

So the tests ran, but no browser appeared. That's because the tests were run in a headless version of Chrome. To actually see the tests running in a browser add the --headed parameter:

npm test -- --headed

Also, tests can be run in all three of the supported browsers by using the --browser parameter:

npm test -- --browser=all

(valid values of browser include all, webkit, chromium, & firefox)

Running the above command should show results similar to this:

  ✓  [chromium] › src/WindowWidth.spec.js:10:3 › width test when window is wide (6s)
  ✓  [firefox] › src/WindowWidth.spec.js:10:3 › width test when window is wide (6s)
  ✓  [chromium] › src/WindowWidth.spec.js:17:3 › width test when window is narrow (5s)
  ✓  [webkit] › src/WindowWidth.spec.js:10:3 › width test when window is wide (2s)
  ✓  [firefox] › src/WindowWidth.spec.js:17:3 › width test when window is narrow (2s)
  ✓  [webkit] › src/WindowWidth.spec.js:17:3 › width test when window is narrow (601ms)

  6 passed (10s)

All done!

So building and testing with a test-harness was quite straightforward.

Another approach would be to use Storybook to present multiple components and then setup automation tests to run on the Storybook instance. Storybook's main purpose is as a component explorer, so there are many other benefits to taking this approach. See Testing with Storybook for more information.

On the other hand if it's just one or two components, creating a custom test-harness may be the best approach, particularly where a high degree of control is needed. It's also a little more lightweight and may be less overhead to setup - so definitely something to consider.

Thanks for reading 😀


© 2003-2021 jonelantha