Gleb Bahmutov PRO
JavaScript ninja, image processing expert, software quality fanatic
https://slides.com/bahmutov/network-control-e2e
brought to you by the fossil fuel companies
image source: https://www.dailysabah.com/world/americas/more-damage-anticipated-as-california-fire-season-sets-records
Greenpeace Β 350 Β Sunrise Β Citizen Climate Lobby
π¦ bahmutov.bsky.social
C / C++ / C# / Java / CoffeeScript / JavaScript / Node / Angular / Vue / Cycle.js / functional programming / testing
π π₯ 350.orgΒ π π₯ citizensclimatelobby.orgΒ π π₯
A typical Mercari US Cypress E2E test
// branch a1
// cypress/e2e/add-todo.cy.js
beforeEach(() => {
cy.request('POST', '/reset', { todos: [] })
})
it('adds a todo', () => {
cy.visit('/')
cy.log('**confirm the items are loaded**')
cy.get('.loaded')
cy.get('.new-todo').type('item 1{enter}')
cy.get('li.todo').should('have.length', 1)
cy.get('.new-todo').type('item 2{enter}')
cy.get('li.todo').should('have.length', 2)
cy.log('**confirm the items are saved**')
cy.reload()
cy.get('li.todo').should('have.length', 2)
})
Reminder: Switch to branch "a1"
// branch a1
// cypress/e2e/add-todo.cy.js
beforeEach(() => {
cy.request('POST', '/reset', { todos: [] })
})
it('adds a todo', () => {
cy.visit('/')
cy.log('**confirm the items are loaded**')
cy.get('.loaded')
cy.get('.new-todo').type('item 1{enter}')
cy.get('li.todo').should('have.length', 1)
cy.get('.new-todo').type('item 2{enter}')
cy.get('li.todo').should('have.length', 2)
cy.log('**confirm the items are saved**')
cy.reload()
cy.get('li.todo').should('have.length', 2)
})
Reminder: Switch to branch "a1"
Reminder: Switch to branch "a1"
add-todo.cy.js spec
The test is slow!
// branch a2
// cypress.config.js
// https://github.com/bahmutov/cypress-magic-backend
magicBackend: {
// this app makes "XHR" calls to load and update "/todos"
// match calls like
// GET /todos
// POST /todos
// DELETE /todos/1234
apiCallsToIntercept: [
// TODO: insert the intercept definitions
],
},
Press the "πͺ π₯" Record button
Reminder: Switch to branch "a2" and restart the app
// branch a2
// cypress.config.js
// https://github.com/bahmutov/cypress-magic-backend
magicBackend: {
// this app makes "XHR" calls to load and update "/todos"
// match calls like
// GET /todos
// POST /todos
// DELETE /todos/1234
apiCallsToIntercept: [
{
method: '+(GET|POST)',
pathname: '/todos',
},
{
method: 'DELETE',
pathname: '/todos/*',
},
],
},
Recording API Calls During The Test
Saved JSON file with API calls
{
"pluginName": "cypress-magic-backend",
"pluginVersion": "1.11.0",
"specName": "cypress/e2e/add-todo.cy.js",
"testName": "adds a todo",
"apiCallsInThisTest": [
{
"method": "GET",
"url": "/todos",
"request": "",
"response": [],
"duration": 1013
},
{
"method": "POST",
"url": "/todos",
"request": {
"title": "item 1",
"completed": false,
"id": "6895127373"
},
"response": {
"title": "item 1",
"completed": false,
"id": "6895127373"
},
"duration": 1009
},
{
"method": "POST",
"url": "/todos",
"request": {
"title": "item 2",
"completed": false,
"id": "6787924392"
},
"response": {
"title": "item 2",
"completed": false,
"id": "6787924392"
},
"duration": 1008
},
{
"method": "GET",
"url": "/todos",
"request": "",
"response": [
{
"title": "item 1",
"completed": false,
"id": "6895127373"
},
{
"title": "item 2",
"completed": false,
"id": "6787924392"
}
],
"duration": 1010
}
]
}
cypress/magic-backend/cypress/e2e/add-todo.cy.js_adds_a_todo_api_calls.json
Replaying API Calls During The Test
Magic Replay Mode
2 seconds!
We do not need the "POST /reset" API call in the replay mode
// branch a2
// cypress/e2e/add-todo.cy.js
beforeEach(() => {
const mode = Cypress.env('magic_backend_mode')
if (mode !== 'playback') {
mode && cy.log(`during the test the mode is "${mode}"`)
cy.request('POST', '/reset', { todos: [] })
}
})
cypress-magic-backend: from 6s to 0.6s
// branch a3
// cypress.config.js
// list the files and file patterns to watch
// https://github.com/bahmutov/cypress-watch-and-reload
'cypress-watch-and-reload': {
watch: ['index.html', 'app.js'],
},
Use the "locked" Replay Mode "πͺ ποΈ βοΈ"
Reminder: Switch to branch "a3" and restart the app
Normal vs Locked replay mode while watching the "index.html" file
What does this "πͺ π§" button do?
The "Inspect" button is my β€οΈ
Reminder: Switch to branch "a4" and restart the app
Normal vs Inspect replay mode "πͺ π§"
Hmm, something has changed in our API,
the "POST /todos" call is now twice slower!
You can see more details about the observed timing differences by clicking on the warning in the Command Log
Reminder: Switch to branch "a5" and restart the app
Hmm, something isn't right
Even if the test is green...
Normal vs Inspect replay mode
But where could the problem be ...
Run in the "Inspect Mode" and see exactly what the problem is and where it started
A change in the front-end code started the entire chain of errors!
Reminder: Switch to branch "a6"
run "npm install" and restart the app
using the "as-a ." command to use the API key
// branch a6
// cypress.config.js
magicBackend: {
// where to store recorded API calls?
// local: store all API calls locally in JSON files
// remote: send API calls to a remote server at cypress.tips
store: 'remote',
}
Works just like before but without local JSON files
Run the "number.cy.js" spec
Change the input number
// branch a6
// cypress/e2e/number.cy.js
// change to produce some negative numbers
const random = Cypress._.random(3, 30)
Try getting both passing and failing tests
then open DevTools console and see a failed test
Can you guess which input leads to a failure?
cypress-magic-backend with remote API recordings
cypress-magic-backend
plus example repos, blog posts, videos, GH issues triage
Can this continue?
Is it fair?
Dual license:
< 100 employees, free to use non-transferable
>= 100 employees, small license, non-transferable
I would like to give OSS the effort it deserves. OSS is worth it.
https://slides.com/bahmutov/network-control-e2e
By Gleb Bahmutov
A modern web application probably makes tens of network calls during a typical user flow. A good end-to-end web test must observe and in some cases mock the network calls to make the test reliable, complete, and easy to debug. I will show some of my tips and tricks for using and controlling network API calls during tests. I will use Cypress E2E test runner for my examples plus the "record - replay - inspect" pattern for API mocking. Presented at Testmu online conference, 40 minutes
JavaScript ninja, image processing expert, software quality fanatic