Playwright! Improving Logging and Debugging

Vinicius Gabriel Cabral Paulino
4 min readJul 11, 2024

--

Playwright has valuable tools that help understand what happens when a test fails, such as the HTML report, tracing, videos, and others.

Sometimes, a deeper understanding is crucial, especially in scenarios with flaky tests, or worse, when they fail only in a CI environment and even worse when they fail only when running all tests together.

Playwright has many features, and understanding how to combine them is crucial to improving our testing projects. Aiming for better logging and debugging, some tricks can be added, like an “on/off” switch that will provide more powerful logs that you can customize.

Summary

  1. Playwright Event Listeners
  2. Playwright API Logs
  3. Creating an “on/off” Switch
  4. Logging Output
  5. Conclusion

Playwright Event Listeners

You can use many listeners to gain more insights into what was triggered in the browser context and page. Some examples are:

  • load
  • console
  • page error
  • request / request finished / request failed

But when you add this to a test, it might not be useful anymore after fixing the code. So you remove it until you have another test that you need again.

Playwright API Logs

You can access internal Playwright API logs, which will provide more in-depth information on API usage.

The most common ways of enabling it are by making it an Environment Variable or adding it inside the Playwright Config as a launch option.

Extra logs might be really handy, but the “problem” with both options mentioned above is that they will enable the entire test suite, and finding the information for that particular test might be overwhelming. When using the Playwright Config, you will be able to work around using different projects, but then you will need to run more changes to run only that particular test with the new project.

Creating an “on/off” Switch

Here comes the part where we combine features to achieve something more reliable.

The most important piece of the “puzzle” is Playwright Fixtures. Combining two types of fixtures, “override” and “automatic” fixtures.

One of the options that can be overridden is the Launch Options. So, let’s take a look at how to create a fixture with the logger that will enable the Playwright API logs.

import { LaunchOptions, Logger, test as base } from "@playwright/test";

type LogSeverity = 'verbose' | 'info' | 'warning' | 'error';

export const test = base.extend<{
launchOptions: LaunchOptions;
}>({
launchOptions: async ({ }, use) => {
const logger: Logger = {
isEnabled: (name: string, severity: LogSeverity) => name === 'api',
log: (name: string, severity: LogSeverity, message: string, args: any[]) => console.log(`${name} ${severity} ${message} ${args.join(' ')}`)
}
await use({ logger });
}
});

export { expect } from '@playwright/test';

The tests that use the fixture will start prompting the Playwright API logs. It is important to remember that if you already have a base fixture, you can use it instead of Playwright’s one, for example.

Now, let’s combine that with the Event Listeners, creating one more fixture that is set as “automatic.” In the following code, you can see that I added the “saveLogs” that contains the automatic flag, which means the fixture will run without you having to list it inside the test.

import { ConsoleMessage, LaunchOptions, Logger, Page, Request, test as base } from "@playwright/test";

type LogSeverity = 'verbose' | 'info' | 'warning' | 'error';

export const test = base.extend<{
launchOptions: LaunchOptions;
saveLogs: void,
}>({
launchOptions: async ({ }, use) => {
const logger: Logger = {
isEnabled: (name: string, severity: LogSeverity) => name === 'api',
log: (name: string, severity: LogSeverity, message: string, args: any[]) => console.log(`${name} ${severity} ${message} ${args.join(' ')}`)
}
await use({ logger });
},
saveLogs: [async ({ page }, use) => {
const getDate = () => new Date().toISOString();

const listenerPageLoad = (page: Page, label: string) => { console.log(`${getDate()} ${label}: ${page.url()}`); }
page.on('domcontentloaded', page => listenerPageLoad(page, 'Event DOMContentLoad'));
page.on('load', page => listenerPageLoad(page, 'Event Load'));
page.on('console', (message: ConsoleMessage) => console.log(`${getDate()} Event Console: ${message.text()}`));
page.on('pageerror', (error: Error) => console.log(`${getDate()} ## PAGE ERROR ##: ${error.message}`));

const listenerRequest = (request: Request, label: string) => { console.log(`${getDate()} ${label}: ${request.url()} ${request.resourceType()}`) };
page.on('request', request => listenerRequest(request, 'Request'));
page.on('requestfinished', request => listenerRequest(request, 'Request Finished'));
page.on('requestfailed', request => listenerRequest(request, '## REQUEST FAILED ##'));

await use();
}, { auto: true }],
});
export { expect } from '@playwright/test';

Running and Logging Output

In order to have an example, I’ve created this small test that will provide some of the logs matching the implementation above.

import { test, expect } from '../debug.fixture';

test.describe('Playwright Debug Logger', () => {
test('Test 1', async ({ page }) => {
await page.goto('data:text/html,<script>throw new Error("Sending Page Error!!!")</script>');

await page.goto('https://react-shopping-cart-67954.firebaseapp.com/');
await expect(page).toHaveTitle('Typescript React Shopping cart');

const checkboxLocator = page.getByTestId('checkbox');
await checkboxLocator.nth(0).click();
await expect(checkboxLocator).toBeVisible();
});
});

When running the test, the logging will be displayed at the console, but if you use the HTML Reporter, the information will be attached in the “stdout” section at the end.

The following image shows that the Playwright API logs show when a call is performed and which method was called with the status.

Conclusion

This is just an example of how different features of Playwright can be combined to improve test logging and help with debugging when needed.

The example provided is very simple and for demonstration purposes, but you can customize it based on your needs. For example, adding logs with the “body” of a response for some requests and adding a throttling for all/some requests or adding other types of event listeners.

Playwright is an incredible tool. Exploring it and finding creative solutions will help elevate testing projects to higher and more mature levels and improve the development experience and efficiency.

--

--