Difference between cy.route and cy.route2

The new command cy.route2 is everything one needs to spy and stub network requests from the application under test

route

Cypress has two commands for controlling the network during tests: cy.route and newer cy.route2. This blog post explains the difference.

Cypress executes tests in the same browser window as the application itself, just in a different iframe. When loading the application Cypress wraps all methods of the object XMLHttpRequest. Thus when the application executes new XMLHttpRequest() ... to fire a network request, Cypress is aware of the network call by the virtue of the application calling the already wrapped methods.

cy.route intercepts network calls by wrapping XMLHttpRequest object

While the above approach works, it is very limited. For example, you could not spy or stub HTTP calls made using fetch function. If your application was using fetch and you wanted to observe or control those network calls from your tests you had to either delete window.fetch and force your application to use a polyfill built on top of XMLHttpRequest, or you could stub the window.fetch method using cy.stub via Sinon library.

For a short workaround we have implemented automatic fetch polyfill via experimentalFetchPolyfill option, but this was still not enough. We really wanted to allow the test code to observe, stub, and modify any HTTP request.

route2

Enter route2 - a universal HTTP network control function. It fundamentally is a different beast from route. It no longer works in the browser - instead the network interception and control happen in the network proxy module outside of the browser. When Cypress launches a browser, it points the browser back at Cypress, so that the browser "thinks" Cypress is its network proxy. You can see this command line switch when running Chrome for example by going to the chrome://version tab

Cypress launches Chrome browser with proxy command line switch pointing back at the Cypress app

If Cypress is the proxy for the application, then all HTTP traffic from the application is observable: the page loaded, the static resources like CSS, JavaScript, and images, and any Ajax request, no matter how it is made by the app. You can observe these requests flowing through the proxy module by running Cypress with an environment variable DEBUG set

1
2
3
4
5
6
7
8
DEBUG=cypress:network:* npx cypress open
...
cypress:network:agent addRequest called { isHttps: false, href: 'http://localhost:7080/' }
...
cypress:network:agent addRequest called { isHttps: false, href: 'http://localhost:7080/styles.css' }
...
cypress:network:agent addRequest called { isHttps: false, href: 'http://localhost:7080/app.js' }
...

The above terminal log shows the application request the document itself, then requesting styles.css and app.js resources. These requests all go through the Cypress network proxy module where they can be observed (spied upon), modified (both outgoing and incoming), and stubbed (where the proxy responds with mock data).

All application requests travel through the Cypress Network Proxy

🗃 Find the code examples from this post in the "cy.route2" recipe in the cypress-example-recipes

We can modify any request, for example we might want to insert some CSS into the styles.css during a test

1
2
3
4
5
6
7
8
9
cy.route2('styles.css', (req) => {
req.reply((res) => {
res.send(`${res.body}
li {
border: 1px solid pink;
}
`)
})
})

The above code modifies the response from the server by appending an extra CSS rule.

Modified CSS highlights the list elements during the test

Similarly, it is simple to stub fetch calls and avoid going to the server completely.

1
2
3
4
5
6
7
8
9
it('shows fruits', function () {
const fruits = ['Apple', 'Banana', 'Cantaloupe']

cy.route2('/favorite-fruits', fruits)
cy.visit('/')
fruits.forEach((fruit) => {
cy.contains('.favorite-fruits li', fruit)
})
})

Checking if the list of fruits is displayed correctly

Even better is to spy on the server's response to make sure both the server responds correctly and the application renders the list the right way.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
beforeEach(function () {
cy.route2('/favorite-fruits').as('fetchFruits')
cy.visit('/')
})
})

it('requests favorite fruits', function () {
cy.wait('@fetchFruits')
.its('response.body')
.then(JSON.parse) // convert string to array
.then((fruits) => {
cy.get('.favorite-fruits li').should('have.length', fruits.length)

fruits.forEach((fruit) => {
cy.contains('.favorite-fruits li', fruit)
})
})
})

Spying on the server response

Because we can inspect the request before deciding how to proceed, we can deal with the application's network requests in any way we want. For example, we can create a flexible way to spy on or stub GraphQL requests. Something like this is possible:

1
2
3
4
5
6
7
8
9
10
11
12
const allTodos = [...]
// routeG is built on top of cy.route2
routeG({
// stub any call to "operation: allTodos" with this response
// that will be placed into "body: data: {...}"
allTodos: {
allTodos,
},
})
cy.visit('/')
// the application shows the expected number of todos
cy.get('.todo-list li').should('have.length', allTodos.length)

Read my blog post Smart GraphQL Stubbing in Cypress for details.

You can even rewrite the page itself (using plain text operations, or via bundled Cypress.$)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
it('modifies the page itself', () => {
const pageUrl = `${Cypress.config('baseUrl')}/`

cy.route2('/', (req) => {
// we are only interested in the HTML root resource
if (req.url !== pageUrl) {
return
}

req.reply((res) => {
const style = `
position: absolute;
bottom: 0;
left: 0;
width: 100%;
background-color: pink;
text-align: center;
text-size: large;
padding: 1em;
`

res.body += `<footer style="${style}">⚠️ This is a Cypress test ⚠️</footer>`
})
})

cy.visit('/')
cy.contains('footer', 'Cypress test').should('be.visible')
})

The test modifies the page it loads

👏 cy.route2 would not become a reality without hard work by Zach Bloomquist who does not have time to tweet much, but lots of time to code.

See more

In the future we plan to retire the original cy.route and replace it with cy.route2. We also plan to retire experimentalFetchPolyfill in favor of cy.route2.