We, developers, do know we need to test our applications: it’s in every textbook and in all tutorials online. But actually starting to write tests is another story. Where to start? Which type of tests are required? Which framework should be used? These questions often come to mind when thinking about testing. And let’s be honest, implementing new features for your clients is more fun, right?
But with modern tooling, testing isn’t so hard anymore and should be incorporated in your workflow. Ideally from the start of a new project, but it’s also possible to start writing tests for an existing project.
In this post I’ll discuss our setup for end-to-end testing for the Laravel and Vue projects we are working on.
- Short introduction to end-to-end testing and tools of choice
- End-to-end testing a Vue CLI project
- End-to-end testing a Laravel project
End-to-end testing: an introduction
A modern web application typically consists of components. Individual components, the units, are tested in isolation with unit tests. To be confident that these components work together, integration tests are written. End-to-end tests are suitable to test the important flows of the application. In other words: if a user interacts with the application, does it work as expected? An example would be to test the checkout process in a webshop. Or signing up for a service by filling in a form.
There are various end-to-end testing frameworks available: Cypress, Laravel Dusk, Puppeteer, Nightwatch to name a few that I stumbled upon while doing some research.
We primarily develop our applications in Laravel and/or Vue. For Vue projects we typically scaffold our applications using the Vue CLI which comes with the official plugin @vue/cli-plugin-e2e-cypress. Although Laravel has its own end-to-end testing framework (Laravel Dusk), I prefer Cypress which can be installed in a Laravel project too. Additionally, an excellent laracasts/cypress package (by Jeffrey Way from Laracasts) exists which helps with database seeding, login/logout and more. In the remainder of this post I will discuss my experience with Cypress in a Vue CLI project and a Laravel project.
A quick word on the versions I use in this post:
- Vue CLI and plugins: 4.5.13
- Cypress: 8.7.0
- Laravel: 8.65.0
End-to-end testing with Cypress in a Vue CLI project
Consider setting up a new Vue CLI project using the graphical user interface, hence start it from your terminal with
and navigate to the Vue Project Manager > Create. Then choose a name for the project and continue.
The next screen shows the presets. For this demo I chose manual, and added end-to-end testing. In the final screen I configured the new project to use Cypress for end-to-end testing.
Then wait a minute for the dependencies to be installed…
…and the project is configured. We can run the task serve and open the app. This looks as expected: the standard Vue CLI homepage is displayed. Go back to project tasks in the interface, select test:e2e task and click run. The code compiles and (after a short while), the Cypress program opens in a new window.
As you can see, a popup opens to help you get started. It tells you it added folders to the project. However, it actually did not. At least, it did not create the /cypress folder. But it did enough to get us started, so please don’t be bothered and click the Ok, got it! button.
Side note: installing the cypress e2e plugin installs an outdated version of Cypress as you can see in the Cypress window. A notification in the bottom shows that a newer version is available. I got version 3.8.3 installed by default but at the time of writing the latest version is 8.7.0. Just update by clicking the button.
Ok, back to what we were doing. What files did Cypress create for us?
It created an example test file in /src/tests/e2e/specs/test.js. Go ahead and open this file in your editor. This is a very simple end-to-end test and it’s also quite self explanatory.
Let’s run it!
Go back to the Cypress window and click the test.js link or the button Run all specs.
There we have the magic: a new chrome window opens in which the test runs.
In the left panel the test commands are displayed and on the right you can clearly see what happens: the browser visits the homepage of the project and checks if it contains an h1 tag with the correct text.
Congratulations, your first e2e test is completed.
Adding the e2e cypress plugin for an existing project
Recently we released our playground website which contains demos. With a growing list of demos, testing became important and thus we integrated Cypress into these demos. Let’s have a look at the Product Configurator demo and the tests we wrote for it.
Installing the cypress e2e plugin (using the vue ui) is quite simple. Head to the Plugins section, click the ‘Add Plugin’ button and type ‘cli-plugin-e2e-cypress’ in the search bar:
Then simply install it and you are ready to go. A new task will be available ‘test:e2e’.
Please note that the installation can also be carried out from the command line. Please read the docs if you prefer to work via the command line.
Ok, with the plugin installed, head over to your codebase. A new folder /tests/e2e is created at the root of your project. Cypress automatically created a first test for you in subfolder /spec/test.js. From here I’ll show you the tests I wrote for the Product Configurator demo.
The demo is for a small webshop where you can order personalized postcards. It is available in two languages. You can tweak the amount of cards, type of paper and so on and the price is calculated accordingly. Below are the tests I created:
- Switch language
- Add postcard
- Remove postcard
- Configure postcard and calculate correct price
- Reset postcard
- Proceed to checkout
For each test I will walk you through the test steps. Typically, a test starts with the command to visit the site. To prevent writing cy.visit(‘/’) in each test, you can simply put this command in a beforeEach hook to have it automatically executed before each individual test.
Please note that each individual test starts out with a clean sheet. That is, all actions we have performed in test A will be resetted once test B starts.
To test if this works we visit the site, click the language switcher in the header and check if the text changed from Dutch to English.
We test this by looking up the green button with id: add-cart. We click it and check that the number of postcards equals 2. Note that we use the ‘should’ method from Cypress which comes in handy if it should take some time for the content to appear. We’ll come back to this later on in this post.
This test is very similar to the previous one: we click the ‘add postcard’ button twice, then find the last postcard item’s remove button and click it. Afterwards we test that the number of postcards equals 2 again.
Configure postcard and check price
This test basically checks if we can configure a postcard and that configuring updates the price. The first 5 commands are to configure the postcard. We use the ‘select’ method for the dropdown, ‘type’ in the input fields and click the radio button to set the paper quality. Finally, we test that the price matches by checking that an element with id ‘price’ contains the correct text string.
This test first configures a postcard and then finds the ‘clear configuration’ link and clicks it. Then we check that the various postcard settings are back to their initial value.
Proceed to checkout
The last test configures a single postcard and clicks the ‘Proceed to checkout’ button. Then it checks that a modal with specific text becomes visible.
Note that this is the third time we add five lines of code to configure a product. If you think this is too much repetition, I suggest creating a separate test file for these three tests and utilizing the beforeEach hook as described earlier.
Running the tests from the vue ui is achieved by running the ‘test:e2e’ task. The command line alternative is ‘npm run test:e2e’. Both will open the cypress testrunner window which lists the tests for the project. Here you can either select an individual test(file) to run, or run all tests. Either way, once you run a test (or set of tests), a Chrome window, operated by Cypress, is opened and the tests are performed. You can follow each step of each test and quickly see which tests succeed and/or fail.
Cypress in Laravel
For a typical Laravel project, Cypress is also my tool of choice to do end-to-end testing. However, the setup is slightly different as we are no longer in the @vue/cli environment. This means that the setup (installation and configuration) takes more effort. However, as I mentioned before, the laracasts/cypress package from Jeffrey Way makes it a breeze. Just follow the install/configuration section in the package and you are good to go.
I installed this package for the Librarian app we developed during last summer. This app enables the user to store books and writers. This way you never forget which books you already read.
I decided to create a fresh database for testing and ran the migrations. I added the credentials of this new DB to the .env.cypress file. This enables me to start each test with a clean DB with only the data in it that I need for the test. Typically, each test starts with refreshing and seeding the database. That is, making it ready for the test at hand. I do this in the ‘before’ hook meaning it is performed once for all tests in a file:
For the test case above, I like to test the sorting of writers in the app. We have a list of writers we can sort in various ways: alphabetical A-Z, alphabetical Z-A and by created_at date. Setting up the test involves adding some writers to the database using a dedicated seeding class: HardcodedWriters. I created a factory function for the Writer and User model and created a user with five writers.
Then, for each test in this testsuite, the user just created is logged in and we visit the /writers page. This lists the five writers that were seeded.
The test is then rather simple: finding the desired buttons to sort and assert that the first writer is the correct one.
The key thing in these tests is that we hardcoded the writer names in order to know the order that these writers should be in after using the sorting buttons. If I had seeded the writers with random names this would not be possible!
More tests and more logins
I created more tests, and for each, an existing user needed to login to start the test. Running all tests in serial, I stumbled on an unexpected error: 429 Too many requests. It took a while to figure out what happened and I am still not totally sure. My guess is that logging in the user via Cypress – by visiting the login page, filling the details and clicking the button – for each test, resulted in too many login attempts in a short time interval.
Luckily, the laracasts/cypress package has a command to login a user with just their name. Hence, I added the beforeEach hook like this
This way, the tests ran successfully in serial.
Summary and conclusion
In this post I introduced the Cypress end to end testing framework.
Installation and configuration in a @vue/cli project (new and existing) was quite easy and we looked at a series of tests for the Product configurator demo.
For a Laravel project, the laracasts/cypress package was installed and made writing tests so much easier. The 429 too many requests error was solved by changing manual login with the cy/login command.