Cypress vs Playwright Advent Calendar Day 19
Check the sent Todo object
When the user enters a new “todo” item, the web app sends it via POST /todos network call. Let’s confirm the known properties of the sent object:
the “title” should be the entered string
the “completed” property should be set to false
there should be a string “id” property
Playwright checks the sent object
const { test, expect } = require('@playwright/test')
import todos from '../fixtures/3-todos.json'
test('sends the new todo object', async ({ request, page }) => {
await request.post('/reset', { data: { todos } })
await page.goto('/')
await page.waitForSelector('.loaded')
// enter a new todo item
// and confirm the POST /todos request body has:
// - a 'title' property with the correct value
// - a 'completed' property set to false
// - an 'id' string property
const newTodo = 'walk the dog'
const postTodoPromise = page.waitForRequest(
(request) =>
request.url().endsWith('/todos') && request.method() === 'POST',
)
await page.fill('.new-todo', newTodo)
await page.keyboard.press('Enter')
const postRequest = await postTodoPromise
const postRequestBody = await postRequest.postDataJSON()
expect(postRequestBody)
.toMatchObject({ title: newTodo, completed: false })
expect(postRequestBody).toHaveProperty('id')
expect(typeof postRequestBody.id).toBe('string')
})The test passes, but each assertion for checking the sent object is pretty minimal in its UI. For example, “toMatchObject” simply shows “expected: Object” text, but not the expected matcher or the actual object.
Playwright assertions, just like commands run one-at-a-time.
Cypress checks the sent object
import todos from '../../fixtures/3-todos.json'
it('sends the new todo object', () => {
cy.request('POST', '/reset', { todos })
cy.intercept('POST', '/todos').as('postTodos')
cy.visit('/')
cy.get('.loaded')
// enter a new todo item
// and confirm the POST /todos request body has:
// - a 'title' property with the correct value
// - a 'completed' property set to false
// - an 'id' string property
const newTodo = 'walk the dog'
cy.get('.new-todo').type(`${newTodo}{enter}`)
cy.wait('@postTodos')
.its('request.body')
.should('deep.include', { title: newTodo, completed: false })
.and('have.property', 'id')
.and('be.a', 'string')
})Let’s look at the passing test: the network calls, commands, and assertions are shown in the Command Log in the order they happen
If you want to see the full details for each command and assertion, simply click on the command and open the browser’s DevTools console.
Note: attaching multiple assertions to the same command is pretty common in Cypress. All assertions must pass with the same object. Even if one fails, the entire command fails (usually after retries)
...
.its('request.body')
// example of multiple assertions checking the same object
.should('deep.include', { title: newTodo, completed: false })
.and('have.property', 'id')
// the above assertion yields the value of the property "id"
.and('be.a', 'string')If you want to see more Cypress assertion examples, check out my cypress-examples site.
Promo code 💸
If you like this advent calendar, you will love the full “Cypress vs Playwright” online course, or any of my other end-to-end testing courses. To express my gratitude, I created a 25% discount code ADVENT25 applied to all courses until Jan 1st, 2026
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“.





Hi! I don't think the examples are equivalent. The one for playwright is checking the response body, no the request.
EDIT: this has been fixed :)