[Feature] Allow request interception chaining (was 'based on request method')
See original GitHub issueCurrently page.route(url, handler, options)
/browserContext.route(url, handler, options)
intercept routes based on url alone. It would be great if we have the ability to intercept based on any request properties like method
, header
, etc.
Use case
A use case would be the common REST scenario where we want to intercept both GET /items
and POST /items
.
Very simplified example:
test('items', async ({ page }) => {
// Intercept GET /api/items
await page.route('/api/items', route => route.fulfill({ body: JSON.stringify([{ id: 1, name: 'Item 1' }]) }))
// Intercept POST /api/items
await page.route('/api/items', route => route.fulfill({ body: JSON.stringify({ id: 2, name: 'Item 2' }) }))
await page.goto('/items')
// expect "Item 1" to be in the list
await page.click('role=button[name="New item"]')
// expect "Item 2" to be created in the list
})
The “matcher”(url<string|RegExp|function(URL):boolean>
) doesn’t allow for any matching on the request itself - only the request url.
The “response”(handler<function(Route, Request)>
) doesn’t allow us to “skip” to the next interceptor - as the comment says in the source code // There is no chaining, first handler wins.
.
Existing workarounds
- Add single interceptor with conditional handler:
await page.route('/api/items', (route, request) => {
if (request.method() === 'GET') {
return route.fulfill({ body: JSON.stringify([{ id: 1, name: 'Item 1' }]) })
}
if (request.method() === 'POST') {
return route.fulfill({ body: JSON.stringify({ id: 2, name: 'Item 2' })
}
return route.continue()
})
While this is enough in some cases, it’s not very atomic and reusable. My idea is to make these a bit more generic and use them in fixtures - making sure that i can easily target just the request i need. Also, the idea of having the matching logic in the handler instead of the matcher just seems wrong.
- Use
options.times
and order the interceptors based on their runtime execution. This seems very fragile and really not ideal.
Proposal
Even though the contribution guide says “Expose as little information as needed. When in doubt, don’t expose new information.”, i feel like we should have an escape hatch in the “matcher” function. Two options:
- Non-breaking change: adding the request as a second parameter to the url function:
url<string|RegExp|function(URL, request):boolean>
Not the best API but that’s the price of non-breaking i guess. - Breaking change: replacing the URL param with the request param in the url function:
url<string|RegExp|function(request):boolean>
This seems like a very robust and flexible way of route matching but it does break BC.
It’s a bit unfortunate that we can’t reuse the standard glob/regex url matching when we use the function. This is how i imagine it could look like:
await page.route(
(url, request) => request.method() === 'GET' && minimatch('/api/items', url.toSting()),
route => route.fulfill({ body: JSON.stringify([{ id: 1, name: 'Item 1' }]) })
)
await page.route(
(url, request) => request.method() === 'POST' && minimatch('/api/items', url.toSting()),
route => route.fulfill({ body: JSON.stringify({ id: 2, name: 'Item 2' })
)
I think that having a convenient way to match by method(in addition to url) would be helpful in a lot of situations. What do you guys think? Do you have other APIs in mind to achieve this or do you think the current workaround methods are good enough?
Issue Analytics
- State:
- Created a year ago
- Comments:9 (3 by maintainers)
~@mrmckeb would you mind filing a request for bubbling? That one I think we should implement, we were waiting for the user request.~
Ah, after reading last @purepear update it seems like original request is also about chaining! Let’s fix it under this issue then.
We’ve started hitting these limitations too, specifically “There is no chaining, first handler wins.”.
Engineers, after using tools like Cypress, expect that if they use
route.continue()
, the request will “bubble up” to the next handler. They also expect to be able to handle POST and GET separately.We’ll add more custom tooling to our project to help mitigate the issue, but an implementation on the Playwright side would be amazing.
I quite like the interface that MSW offers (as an example): https://mswjs.io/