Now we have a good understanding of what End-to-End testing is and what it involves, let's now write some automated tests in Cypress to verify the areas of functionality the User cares about (i.e the User stories) are working as intended.
In your project directory, run npm install cypress --save-dev
.
This will install all of the Cypress dependencies. It may take some time!
Once done, run the command, ./node_modules/.bin/cypress open
. If you have Cypress installed globally, you can just run cypress open
.
This should open the Cypress GUI.
You'll notice that Cypress adds the following directories and a JSON configuration file:
- cypress
- fixtures
- integrations
- plugins
- support
- cypress.json
// other files
In your cypress.json
file, we're going to add the URL of your Live Server so we can tell Cypress which URL to load without repeating it for every test. We're also going to specify which folder to serve our tests from. In your file, add:
{
"baseUrl": "http://127.0.0.1:5500/your_directory/index.html#/",
"integrationFolder": "cypress/tests"
}
In our case, we're running the Live Server on port 5500 and want Cypress to load our index.html
- the same page where Vue mounts itself on the app
component. We've also added a configuration item to serve the tests from a /tests
folder.
You might wonder why we've done this? Well, it's really a matter of preference more than anything but also shows the how useful the config can be in certain situations. Some developers may find it clearer that all tests live in the 'tests' folder, so let's roll with this!
Delete the integrations
folder and create an empty folder in the Cypress directory called tests
.
All of the tests you add in the tests folder will combine to produce the E2E test coverage we want
A good test should follow the pattern:
If you've written unit tests in Jest or another JavaScript testing framework, then you'll be familiar with the callback style test structure:
describe("The thing you're testing", () => {
it('should test an aspect of the thing', () => {
cy.visit('/'); // take an action
cy.get('some-element').should('have.text', 'some text'); // make an assertion
});
});
Our products really should show an "Out of stock" notice if the stock level is equal to zero and we shouldn't show the Add to cart button if the product is out of stock. Let's write a failing test for this first, then add the functionality to make it pass.
Cypress recommends adding data attributes to the elements we want to capture. This ensures that even if IDs and classes change, your tests will still run because they are powered by data attributes.
In your main.js
file, modify your Product component by adding data-cy="product"
to the main div:
// main.js
const Product = Vue.component('Product', {
template: `
<div class="product" data-cy="product">
// other fields omitted
`,
});
Now create new file in your tests
dir called product.spec.js
and add:
// product.spec.js
describe('Product', () => {
beforeEach(() => {
cy.visit('/'); // 1
});
it('should find an out of stock notice but no add to cart button', () => {
cy.get('[data-cy=product]') // 2
.eq(0) // 3
.should('contain', 'Out of stock :(') // 4
.and('not.contain', 'Add to cart'); // 5
});
});
Here, we are:
Using the beforeEach
method to visit our baseUrl
in the cypress.json
file before each test.
Getting the product divs using the attribute [data-cy=product]
Filtering by the first one
Making the assertion that somewhere in this block, the text 'Out of stock :(' will be found
Making the assertion that the text 'Add to cart' will not be found
Save your code and the tests should re-run. Of course, they will fail, but we'll fix that next.
Now we need to turn our attention to our Vue.js code to add an out of stock notice if the stock level is equal to zero and to only show the the 'Add to cart' button if the stock level is greater than zero. We can achieve this by using Vue's v-if
tag.
In your Product
component, add:
<p v-if="product.stockLevel === 0">Out of stock :(</p>
And on the Add to cart button:
<button v-on:click="addToCart(product)" v-if="product.stockLevel > 0">
{{ product.addedToCart ? "Remove from cart" : "Add to cart" }}
</button>
Now if you re-run your Cypress test, it should pass!
"A flaky test is a test that fails to produce a consistent result each time it is run"
Now, thinking about the test that we've just added, would you say it is flaky?
We're very much writing this app in a dev environment, but if it were a live project, having to change the first product's stock level to zero just for this test to pass would not be a good idea. Instead, we would launch the app in a testing/staging environment and use test data to ensure our tests produce consistent results.
In this instance, since we're in practice mode, you can simply change the stock level back to a positive number and skip the test:
it.skip('should find an out of stock notice but no add to cart button', () => {
// test code omitted
});
You'll remember that the footer address and copyright notices are returned by computed methods on the footer component. It makes sense to test these to ensure that they work consistently.
Your challenge is to add a footer.spec.js
test file and test the output is what you expect.