waitForResponse is great when you know exactly which API call to wait for. But sometimes you want to see everything. Every request. Every response. Like opening the Network tab in DevTools, but inside your test code.
import { test, expect } from '@playwright/test';
test('log all API requests during checkout', async ({ page }) => {
const apiCalls: string[] = [];
// Listen to every request
page.on('request', (request) => {
if (request.url().includes('/api/')) {
apiCalls.push(`${request.method()} ${request.url()}`);
}
});
await page.goto('/shopping');
await page.getByRole('button', { name: 'Add to Cart' }).first().click();
await page.getByRole('link', { name: 'Checkout' }).click();
// Now verify which API calls were made
console.log('API calls made:', apiCalls);
expect(apiCalls).toContainEqual(expect.stringContaining('GET /api/products'));
expect(apiCalls).toContainEqual(expect.stringContaining('POST /api/cart'));
});test('verify no API errors during navigation', async ({ page }) => {
const errors: { url: string; status: number }[] = [];
page.on('response', (response) => {
if (response.status() >= 400) {
errors.push({
url: response.url(),
status: response.status(),
});
}
});
// Navigate through multiple pages
await page.goto('/shopping');
await page.getByRole('link', { name: 'Electronics' }).click();
await page.getByRole('link', { name: 'Clothing' }).click();
// No API errors should have occurred
expect(errors).toHaveLength(0);
});test('measure API response times', async ({ page }) => {
const timings: { url: string; duration: number }[] = [];
page.on('request', (request) => {
if (request.url().includes('/api/')) {
request.timing = { start: Date.now() };
}
});
page.on('response', (response) => {
if (response.url().includes('/api/')) {
const request = response.request();
timings.push({
url: new URL(response.url()).pathname,
duration: Date.now() - (request as any).timing?.start || 0,
});
}
});
await page.goto('/shopping');
// Check that all APIs responded within 2 seconds
for (const timing of timings) {
console.log(`${timing.url}: ${timing.duration}ms`);
expect(timing.duration).toBeLessThan(2000);
}
});test('no tracking scripts are loaded', async ({ page }) => {
const trackers: string[] = [];
page.on('request', (request) => {
const url = request.url();
if (
url.includes('google-analytics') ||
url.includes('facebook.com/tr') ||
url.includes('mixpanel')
) {
trackers.push(url);
}
});
await page.goto('/banking/login');
// Banking pages should not have tracking scripts
expect(trackers).toHaveLength(0);
});Use page.on("request") and page.on("response") for monitoring and logging. Use page.waitForRequest() and page.waitForResponse() for synchronization. They serve different purposes -- monitoring is passive, waiting is active.
Key Point: page.on("request") and page.on("response") let you monitor all network traffic. Use them for logging, timing, and asserting that no unwanted calls are made.