Cypress is a powerful integration testing framework for the browser that promises to aleviate a lot of the common paint points of testing with Selenium. Projects have been trying to unseat Selenium for years, but Cypress differentiates itself with its exceptional UI and developer experience, something that Selenium has always struggled with. Cypress has powerful features like automated screenshots of test failures, time travel, and stubbing built-in. Right now the big limitation of Cypress is that they only support testing in Chrome and Electron. In this article, I'll demonstrate how to test a simple autocomplete app with Cypress.
A Vanilla JS Autocomplete
In order to demo writing a test with Cypress, I put together a simple dependency-free autocomplete that hits npm's public search API to search for npm packages. Below is a video showing the autocomplete in action. This app won't be on Dribbble anytime soon but it is enough for a meaningful demo of Cypress' features.
Below is the HTML for the autocomplete page, the index.html
file:
<html>
<body>
<div id="content">
<div id="search-wrapper">
<input type="text" id="search" placeholder="Press enter to search npm">
</div>
<div id="content-wrapper">
<ul id="results"></ul>
</div>
</div>
<script type="text/javascript" src="/index.js"></script>
</body>
</html>
The HTML isn't particularly interesting. The real functionality is in the
index.js
file. This file is responsible for instrumenting the HTML to send out
an HTTP request to npm using fetch()
and
rendering the results as an unordered list. To avoid promise chaining, the
below example uses async/await.
init(document.querySelector('#search'), document.querySelector('#results'));
const endpoint = 'https://registry.npmjs.org/-/v1/search';
function init(input, results) {
let inProgress = false;
input.addEventListener('keydown', async (ev) => {
if (ev.key !== 'Enter' || inProgress) {
return;
}
const text = input.value;
let result = null;
inProgress = true;
try {
result = await window.fetch(`${endpoint}?text=${text}&size=25`).
then(res => res.json());
} catch (error) {
inProgress = false;
throw error;
}
inProgress = false;
render(result.objects.map(v => v.package));
});
function render(packages) {
results.innerHTML = packages.
map(pkg => `
<li>
<a href="http://www.npmjs.com/package/${pkg.name}">
${pkg.name}
</a>
</li>
`).
join('\n');
}
}
One neat feature of this example is that it doesn't require any bundlers like
webpack. Most real apps will use webpack
to compile the apps they test with Cypress, but this example just uses a
<script>
tag in the interest of staying focused and concise.
Testing the Autocomplete with Cypress
Installing Cypress is easy via npm:
npm install cypress
In order to easily run Cypress, add cypress:open
script to your package.json
file.
{
"name": "cypress-autocomplete",
"devDependencies": {
"cypress": "3.0.2",
"serve": "9.2.0"
},
"scripts": {
"cypress:open": "cypress open",
"serve": "serve ./"
}
}
In one terminal tab, run npm run serve
to start an HTTP server that serves files from your file system. In another tab,
run npm run cypress:open
to start Cypress. When you run Cypress, you should
see a window like what you see below.
Next, add a test file sample.js
to the cypress/integration
folder. Below
is a simple test for the autocomplete that stubs out the npm API and asserts
that the autocomplete renders some fake data.
describe('Autocomplete', function() {
it('renders results', function() {
cy.visit('http://localhost:5000', {
onBeforeLoad: win => {
// Stub out `window.fetch()` so `index.js` doesn't actually hit
// the npm API
cy.stub(win, 'fetch', () => {
return Promise.resolve({
json: () => ({
objects: [
{ package: { name: 'mongoose' } },
{ package: { name: 'mongoose-autopopulate' } }
]
})
});
});
}
});
// `{enter}` is how you simulate hitting the 'enter' key in Cypress
// See: https://docs.cypress.io/api/commands/type.html#Arguments
cy.get('#search').type('mongoose{enter}');
// Don't use `expect()` directly here, chaining calls onto `cy`
// is potentially async
cy.get('#results').
should('contain', 'mongoose').
and('contain', 'mongoose-autopopulate');
});
});
Find this test file in the Cypress test runner and click it. Cypress will then run the test for you. Below is a video of the process end-to-end.
Moving On
Cypress has a lot of excellent features that this article didn't cover, like time travel and screenshots. Although the limited set of browsers is a problem, Cypress' features make it a compelling option for integration tests. Next time you're thinking about testing tools for your frontend, make sure you check out Cypress.