You can record Playwright tests to generate replays for review and debugging. Replay Playwright can be used with your existing
@playwright/test
suite or it can be used as a Node script importing @playwright
.Replay is up-to-date as of Playwright version 1.19. If you are using a newer version and run into a problem, please open a GitHub issue here to let us know!
Setup
To record Playwright tests, you’ll need to install the following package:
@replayio/playwright
- installs the Replay browsers and configures Playwright to use the Replay browser(s)
Replay Playwright supports Firefox on Mac and Linux, as well as Chrome (beta) on Linux.
You should already have
@playwright
or @playwright/test
installed in your project.Recording with @playwright/test
You can use the Replay browser as drop-in replacement for your existing
@playwright/test
suite. - Update your
playwright.config.js
file to use the Replay version of your preferred browser with the devices object from@replayio/playwright
.
Firefox example:
typescriptconst { devices } = require("@replayio/playwright"); const config = { forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, use: { trace: "on-first-retry", defaultBrowserType: "firefox", }, projects: [ { name: "replay-firefox", use: { ...devices["Replay Firefox"], }, }, ], }; module.exports = config;
- Run
npx playwright test
as usual to execute the tests and record with Replay.
- View the available recordings with
npx @replayio/replay ls
and upload all withnpx @replayio/replay upload-all
or each withnpx @replayio/replay upload <id>
.
Replay will create a recording for each individual test.
Uploading automatically with replayio/action-playwright
You can use our GitHub Action to automatically record your CI test environment, upload any failing tests, and comment on the related PR with links to the failure replays.
To set it up, replace the action step that runs your playwright tests with:
yaml- uses: replayio/action-playwright@v0.4.9 with: command: npx playwright test issue-number: ${{ github.event.pull_request.number }} project: replay-firefox apiKey: ${{ secrets.RECORD_REPLAY_API_KEY }}
And some of those action parameters will have to be passed in from the workflow:
yamljobs: run-playwright-tests: name: Run Playwright Tests runs-on: ubuntu-latest with: project: "replay-firefox" issue-number: ${{ github.event.pull_request.number }} apiKey: ${{ secrets.RECORD_REPLAY_API_KEY }}
You should now get a comment containing links to replays of failing tests on your PR’s! 🎉
Notes:
- Don’t forget to setup your
RECORD_REPLAY_API_KEY
secret! You can read the docs on setting up a Team API Key if you don’t have one already.
- To protect your secrets, GitHub Actions does not share them with forks of your repo so external contributors may not be able to upload replays just yet. We’re working on improving this soon!
- We’ve set the
command
andproject
parameters in the action above explicitly for demonstration, but you can also leave those off to get the default values (command: npx playwright test
andproject: "replay-chromium"
)
Recording with Playwright as a Node script
You can also write tests as a function that uses
playwright.[browser].launch()
. This can give you more control over which tests are recorded. In this configuration, you’ll use the getExecutablePath()
function from @replayio/playwright
to ensure the Replay-enabled browser is used.👉 Passing the RECORD_ALL_CONTENT: 1
env is only required for Firefox.
Metadata
You can add metadata to your playwright recordings using either the
RECORD_REPLAY_METADATA
or RECORD_REPLAY_METADATA_FILE
environment variable. If both are set, RECORD_REPLAY_METADATA_FILE
takes precedence.Currently, this metadata is only available locally except for title
RECORD_REPLAY_METADATA_FILE
- The path to a file containing JSON-formatted metadata
RECORD_REPLAY_METADATA
- JSON-formatted metadata string
Examples
New Browser per Test
javascript// firefox.spec.js const playwright = require("playwright"); const { getExecutablePath } = require("@replayio/playwright"); (async () => { const browser = await playwright.firefox.launch({ headless: true, executablePath: getExecutablePath("firefox"), env: { ...process.env, RECORD_ALL_CONTENT: 1, RECORD_REPLAY_METADATA: JSON.stringify({ title: "Take screenshot of replay.io" }) }, }); const page = await browser.newPage(); await page.goto("<https://replay.io>"); await page.screenshot({ path: "replay.png" }); await page.close(); await browser.close(); })();
Shared Browser between Tests
javascriptconst path = require("path"); const { writeFileSync } = require("fs"); const playwright = require("playwright"); const { getExecutablePath } = require("@replayio/playwright"); (async () => { const metadataFilePath = path.join( process.env.HOME, "replay-metadata-file.json" ); const browser = await playwright.firefox.launch({ headless: true, executablePath: getExecutablePath("firefox"), env: { ...process.env, RECORD_ALL_CONTENT: 1, RECORD_REPLAY_METADATA_FILE: metadataFilePath, }, }); writeFileSync( metadataFilePath, JSON.stringify({ title: "Screenshot of replay.io" }) ); let page = await browser.newPage(); await page.goto("<https://replay.io>"); await page.screenshot({ path: "replay.png" }); await page.close(); writeFileSync( metadataFilePath, JSON.stringify({ title: "Screenshot of google.com" }) ); page = await browser.newPage(); await page.goto("<https://google.com>"); await page.screenshot({ path: "google.png" }); await page.close(); await browser.close(); })();
Uploading recordings
You can then use
node firefox.spec.js
to execute and record your test. This will generate a single recording of all the test code in the file. View the available recordings with npx replay ls
and upload all with npx @replayio/replay upload-all
or each with npx @replayio/replay upload <id>
.You can also use the node API of
@replayio/replay
to upload recordings.javascriptconst cli = require("@replayio/replay"); async function uploadAll() { const recordings = cli.listAllRecordings(); console.log( "Processing", recordings.length, "recordings" ); let failed = []; let success = []; for await (let r of recordings) { try { success.push(await cli.uploadRecording(r.id, { verbose: true })); } catch (e) { failed.push(e); } } failed.forEach((reason) => { console.error("Failed to upload replay:", reason); }); return success; } async function main() { try { const recordingIds = await uploadAll(); console.log("Uploaded", recordingIds.length, "replays"); } catch (e) { console.error("Failed to upload recordings"); console.error(e); return []; } } main().then(() => { console.log("Done!"); });
Using expect
with Node script configuration
You can still use
expect
from @playwright/test
in your test code. Import the command directly like in the example below.javascript// firefox-expect.spec.js const playwright = require("playwright"); const { expect } = require('@playwright/test'); const { getExecutablePath } = require("@replayio/playwright"); (async () => { const browser = await playwright.firefox.launch({ headless: true, executablePath: getExecutablePath("firefox"), env: { ...process.env, RECORD_ALL_CONTENT: 1, }, }); const page = await browser.newPage(); await page.goto('https://demo.playwright.dev/todomvc'); const TODO_ITEMS = [ 'buy some cheese', 'feed the cat' ]; // Create 1st todo. await page.locator('.new-todo').click(); await page.locator('.new-todo').fill(TODO_ITEMS[0]); await page.locator('.new-todo').press('Enter'); // Create 2nd todo. await page.locator('.new-todo').fill(TODO_ITEMS[1]); await page.locator('.new-todo').press('Enter'); // Assert todo content await expect(page.locator('.view label')).toHaveText([ TODO_ITEMS[0], TODO_ITEMS[1] ]); await page.close(); await browser.close(); })()
Uploading automatically as a Node script
Writing your tests as a Node script allows you to upload failed recordings automatically using
@replayio/replay
as a Node module.For the example below, use
REPLAY_API_KEY=123 node upload-failure.spec.js
to execute and record the test.javascript//upload-failure.spec.js const playwright = require("playwright"); const { expect } = require('@playwright/test'); const { getExecutablePath } = require("@replayio/playwright"); const replayCli = require("@replayio/replay"); async function test() { const browser = await playwright.firefox.launch({ headless: true, executablePath: getExecutablePath("firefox"), env: { ...process.env, RECORD_ALL_CONTENT: 1, }, }); const page = await browser.newPage(); await page.goto('https://demo.playwright.dev/todomvc'); const TODO_ITEMS = [ 'buy some cheese', 'feed the cat', 'book a doctors appointment' ]; // Create 1st todo. await page.locator('.new-todo').click(); await page.locator('.new-todo').fill(TODO_ITEMS[0]); await page.locator('.new-todo').press('Enter'); // Create 2nd todo. await page.locator('.new-todo').fill(TODO_ITEMS[1]); await page.locator('.new-todo').press('Enter'); // This purposefully fails to trigger an upload await expect(page.locator('.view label')).toHaveText([ TODO_ITEMS[1], TODO_ITEMS[2] ]); await page.close(); await browser.close(); }; async function testRun() { try { await test() } catch (e) { const recordingId = await replayCli.viewLatestRecording({apiKey: `${process.env.REPLAY_API_KEY}`}) console.log({e, recordingId}) process.exit(1) } } testRun()