Mar 23, 2021

Visualize Regressions with Playwright Video Recording

When you're building a web application, you likely want to make sure the most important features don't break when you change business logic. Testing manually isn't feasible when you perform changes often or work with a team that does. For these cases, you'll require a framework for creating tests that ensure your application keeps working for your users.

Playwright is an end-to-end framework sponsored by Microsoft that allows writing cross-browser tests for Chromium, Firefox, and WebKit (the engine powering Safari) using the same syntax. You can spin up a headless browser, navigate to your page and assert that everything works as expected. Playwright offers an extensive API surface for writing your tests, including authentication helpers, persistent storage for resuming sessions, intercepting and modifying network requests, and uploading or downloading files.

While all of these features already provide great value, a recent addition to Playwright enabled an incredibly useful feature: Recording videos. While you could already screenshot pages, or create a PDF file out of them, you can now record the entire testing session as a webm video.

While passing tests are great, if something goes wrong it can be tedious to debug. With the video recording, you get immediate visual feedback of which part failed.

Of course, video recordings aren't limited to debugging! You could record product demos, or other content purely based on code. Unfortunately, audio is not supported yet, but maybe this will be supported at one point.

▶️ Implementing cross-browser video recording

As an example, we'll set up video recording with all three supported browsers. Before we add the actual test (or business logic), we'll have to install the playwright package. I'm also using TypeScript here, so the dependencies will be

yarn add -D playwright typescript
npm i -D playwright typescript

For preparing and running the test, I prepared the following npm scripts

"scripts": {
	"pretest": "rm -rf dist videos images && tsc",
  "test": "node dist"
}

and tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

Now we can get to some code.

import { chromium, devices, firefox, webkit } from 'playwright';
import path from 'path';

// We'll run our recording in the three stable browsers
const browsers = {
  chromium,
  firefox,
  webkit
};

(async () => {
  // Loop over our available browsers
  for (const [browserName, browser] of Object.entries(browsers)) {
    // Launch a new headless browser instance
    // Note: This is expensive, use contexts for different scenarios
    // instead of starting multiple browser instances
    const launched = await browser.launch();

    // Create a new browser context and start a recording
    // We'll record at native viewport size (1080p)
    const ctx = await launched.newContext({
      recordVideo: {
        dir: path.resolve(`videos/${browserName}`),
        size: { width: 1920, height: 1080 }
      },
      viewport: { width: 1920, height: 1080 }
    });

    // Create a new page (tab) and navigate to a page
    const page = await ctx.newPage();
    await page.goto('https://brunoscheufler.com');

    // Once the page is loaded, navigate to another page
    await page.goto(
      'https://brunoscheufler.com/blog/2021-03-18-when-velocity-implies-simplicity'
    );

    // End the recording, close the context and terminate the browser
    await ctx.close();
    await launched.close();
  }
})();

After running yarn test, you can check out the newly-created videos directory in your current working directory. In there, you'll find a directory per browser, containing the recorded videos.

Note that each page in the recorded browser context will have its own video file. If you're testing multiple pages in a browser context, you can get the respective file path for each recording with

const videoPath = await page.video().path();

And that's it! With this feature, we can improve a lot of testing systems. The more context you have when debugging a failing test, the faster you can get to fixing the root cause, and I think that videos will play a huge role!