Accessing the browser History API from the Cypress tests.
One of the key features of Cypress is its ability to access the native browser API objects used by the application itself. For example, you could stub the browser FileSystem methods or the navigator API to ensure the application is using those APIs correctly. In this blog post I will show how you can test an application that uses the browser History API.
The application shows pictures of cats. When the user clicks on the link, the application changes the image by modifying the image source attribute, and then pushes the state to the browser history. To the user it looks like normal (but very fast) navigation, yet you do see the URL change, the location history, and you can correctly navigate using the browser "Back" button.
The code in the application pushes the new state to the application after switching the image source and the content.
functiongoTo(cat, title, href) { const data = cats[cat] || null// In reality this could be an AJAX request updateContent(data)
// Add an item to the history log console.log('going to', cat, title, href) history.pushState(data, title, href) }
// each links has this handler functionclickHandler(event) { var cat = event.target.getAttribute('href').split('/').pop() goTo(cat, event.target.textContent, event.target.href) return event.preventDefault() }
Can we verify this behavior?
Cypress and History API
First, let's verify the test can access the History object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
it('redirects to a cat at the start', () => { cy.visit('/')
cy.location('pathname').should('equal', '/history/fluffy') cy.log('**has History API**') cy.window() .its('history') .should('respondTo', 'pushState') .and('have.property', 'state') // inspect the state object .should('deep.include', { content: 'Fluffy!', }) })
The test gets the history property from the application's window object. We could validate the entire state object, or just parts of it. We can click on the ITS history command to see the entire object. We could call those methods like back and go from our tests too!
Spy on the history method calls
Before we try to call the history methods, why don't we check how the application uses them. Using the Sinon spies (bundled with Cypress using cy.spy command) let's confirm the application uses the history.pushState correctly.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
it('spies on history.pushState', () => { cy.visit('/', { onBeforeLoad(win) { // spy on the "pushState" method cy.spy(win.history, 'pushState').as('pushState') }, })
cy.log('**go back in history**') cy.window().its('history').invoke('back') cy.contains('#content', 'Whiskers!') // unfortunately Cypress does not change the URL _shown_ // but it does change the URL _in_ the browser cy.location('pathname').should('equal', '/history/whiskers')
Finally, our application restores the saved history state if it finds it at the start
public/app.js
1 2 3 4 5 6 7 8 9 10
const initialCat = document.location.href.split('/').pop() if (!cats[initialCat]) { // maybe there is something in the history? if (history.state) { updateContent(history.state) } else { // go to the first cat goTo('fluffy', 'Fluffy', '/history/fluffy') } }
Let's test it by putting a robot cat 🤖😺 into the History object first and the visiting the application.
it('starts at our state', () => { cy.visit('/', { onBeforeLoad(win) { // populate the state history win.history.pushState( { cat: 'robot-whiskers', content: 'Robot Whiskers!', // we can even use some other photos during testing photo: 'https://robohash.org/CE6.png?set=set4&size=150x150', title: 'Robot Whiskers', href: '/history/robot-whiskers', }, 'Robot Whiskers', '/history/robot-whiskers', ) }, })