Cypress vs Playwright Advent Calendar Day 18
Stub GET and POST /todos network calls
Last time we stubbed the GET /todos API call the application makes on load. When the user enters a new “todo” item, the application makes a POST /todos call and sends an object with the “todo” title and some other information. Let’s stub both the GET and POST calls and confirm the app works correctly.
Playwright stubs 2 calls
We need to respond differently depending on the call, and we need to add another “waitForResponse” command
const { test, expect } = require('@playwright/test')
import todos from '../fixtures/3-todos.json'
test('stub GET and POST /todos calls', async ({ page }) => {
await page.route('/todos', (route) => {
if (route.request().method() === 'POST') {
// send the request body back in the response
const postData = route.request().postDataJSON()
route.fulfill({
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(postData),
})
return
}
route.fulfill({
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(todos),
})
})
// now we need to separate waiting for GET vs POST /todos calls
const getTodosPromise = page.waitForResponse(
(response) =>
response.url().endsWith('/todos') &&
response.request().method() === 'GET',
)
await page.goto('/')
await getTodosPromise
await expect(page.locator('.todo-list li'))
.toHaveCount(todos.length)
// enter a new todo item
// and confirm the POST /todos request body
// is sent correctly
const newTodo = 'walk the dog'
const postTodoPromise = page.waitForResponse(
(response) =>
response.url().endsWith('/todos') &&
response.request().method() === 'POST',
)
await page.fill('.new-todo', newTodo)
await page.keyboard.press('Enter')
await expect(page.locator('.todo-list li'))
.toHaveCount(todos.length + 1)
const postResponse = await postTodoPromise
const postResponseBody = await postResponse.json()
expect(postResponseBody.title).toBe(newTodo)
})The test runs and passes, but you do not see where the network calls were made, since they are not shown amongst the test commands.
Cypress stubs 2 calls
Let’s see the same test implemented in Cypress.
import todos from '../../fixtures/3-todos.json'
it('stub GET and POST /todos calls', () => {
cy.intercept('GET', '/todos', { body: todos }).as('getTodos')
cy.intercept('POST', '/todos', (req) => {
// send the request body back in the response
req.reply(req.body)
}).as('postTodos')
cy.visit('/')
cy.wait('@getTodos')
cy.get('.todo-list li').should('have.length', todos.length)
// enter a new todo item
// and confirm the POST /todos request body
// is sent correctly
const newTodo = 'walk the dog'
cy.get('.new-todo').type(`${newTodo}{enter}`)
cy.get('.todo-list li').should('have.length', todos.length + 1)
cy.wait('@postTodos')
.its('request.body')
.should('have.property', 'title', newTodo)
})Two things show how differently Pw and Cy perform network interception
Cypress cy.intercept command can be declared with an HTTP method, so it is easy to define separate route handlers. In Playwright we need “if / else” conditions inside the
page.routehandler
// Playwright
await page.route('/todos', (route) => {
if (route.request().method() === 'POST') {
// send the request body back in the response
const postData = route.request().postDataJSON()
route.fulfill({
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(postData),
})
return
}
route.fulfill({
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(todos),
})
})
// Cypress
cy.intercept('GET', '/todos', { body: todos }).as('getTodos')
cy.intercept('POST', '/todos', (req) => {
// send the request body back in the response
req.reply(req.body)
}).as('postTodos')Cypress can wait on any network alias attached to the network intercept using the cy.as command. In Playwright we need to issue more commands to define spies.
// Playwright
const getTodosPromise = page.waitForResponse(
(response) =>
response.url().endsWith('/todos') &&
response.request().method() === 'GET',
)
...
await getTodosPromise
const postTodoPromise = page.waitForResponse(
(response) =>
response.url().endsWith('/todos') &&
response.request().method() === 'POST',
)
...
const postResponse = await postTodoPromise
const postResponseBody = await postResponse.json()
expect(postResponseBody.title).toBe(newTodo)
// Cypress
cy.intercept('GET', '/todos', ...).as('getTodos')
cy.intercept('POST', '/todos', ...).as('postTodos')
...
cy.wait('@getTodos')
...
cy.wait('@postTodos')
.its('request.body')
.should('have.property', 'title', newTodo)Illuminating.
This advent calendar is based on my online course “Cypress vs Playwright“ and open-source workshop bahmutov/cypress-workshop-cy-vs-pw. You can find links to the previous advent calendar days in my blog post “Cypress vs Playwright Advent Calendar 2025“.



