Intercepting Calls with Playwright
- Paul Scholes

- Nov 6, 2023
- 2 min read
Today we continue our delve into the delights of Playwright, focussing on what is one of my favourite aspects of Cypress, intercepting API calls.
For anyone unfamiliar with this functionality, it allows you to specify what the page you are testing should receive for certain API calls. This can be defined by URL (full or partial matches) or methods (GET, POST, PATCH etc).
This gives you the ability to, for instance, test what your application will do when the back end fails to respond, be it an HTTP error or malformed content.
Once you get to grips with it, it is an extremely powerful tool, as you can, in effect, generate a custom stub for each test.
So, how does this work in Playwright?
Well, extremely easily!
I continued my experiments on https://playwright.dev, as I was already in there, so it seemed logical. I decided to see if I could do the following:
Intercept the call to load the page and replace the HTTP status
Intercept the call to load the page and replace the body of the response
So off to the documentation I went - yes, I do sometimes read the manual.
The main driver of this functionality in Playwright is the page.route() command, which you put at the start of your test to define what you want to intercept:
await page.route('https://playwright.dev/', route => route.fulfill({ //new content goes here }));
Pretty simple so far, and defining the content is just as easy. Do you want to replace the status of the response with a 404 error?
await page.route('https://playwright.dev/', route => route.fulfill({
status: 404
}));
So I wrote a test to intercept 5 statuses and see what happens:
const stauses = [200, 400, 404, 503, 500]; for (const status of stauses) { test(`intercept and return ${status}`, async ({page}) => { await page.route('https://playwright.dev/', route => route.fulfill({ status: status })); const response = await page.goto('https://playwright.dev/'); expect(response.status()).toBe(status); }); }
And when you run this test, you get this in the Playwright UI:

But, it is even better, if you drill into the cases where the the HTTP status is a failure (in the case of a 400 error):

The page fails to load, but doesn't immediately fail the test!! This was one of my mini-gripes with Cypress. To do the same thing, even with a stubbed response, you have to add an extra option to the visit or request, otherwise, the test fails immediately:
cy.visit('https://playwright.dev/', {failOnStatusCode: false});
So, one point to Playwright!
For the second test, replacing the body, the code looks very similar:
const contents = ['Ok', 'Not Found', 'Internal Server Error']; for (const content of contents) { test(`intercept and return ${content} in the body`, async ({page}) => { await page.route('https://playwright.dev/', route => route.fulfill({ body: content, })); const response = await page.goto('https://playwright.dev/'); expect(response.status()).toBe(200); await expect(page.getByText(content)).toBeVisible(); }) }
Again, pretty simple and intuitive. Slightly more so than Cypress, which gets very upset with incomplete responses, so you have to do this to include the content-type:
cy.intercept('https://playwright.dev/', (req) => { req.reply({ headers: { 'content-type': 'text/html' }, statusCode: 200, body: text, }); }).as('intercepted');
The only thing I need to get to grips with is amending an existing response but leaving the rest intact. That will be something to dig into next time.
Comments