Cypress Studio - the underrated feature speeding up e2e testing

First published Oct 21, 2021 in newline

Dual monitors

Photo by Farzad Nazifi on Unsplash

Cypress.io just keeps raising the bar on end-to-end testing

When Cypress.io first hit the scene in 2015, it made a splash because it fixed so many of the issues that existed with other end-to-end testing (e2e) competitor frameworks.

Between good documentation, intuitive syntax, improved debugging, and no reliance on Selenium under-the-hood - everything about Cypress was a major step forward for e2es, but it wasn't content just to stop there.

The team behind Cypress regularly keeps releasing new features and functionality to make it more and more useful for devs, and make e2e testing (traditionally kind of a pain) easier and easier as well.

One recent release that's currently tucked behind a feature flag is called Cypress Studio, and it's an absolute game changer.

With Cypress Studio, you can show Cypress exactly how to test by recording interactions against the application under test. You click around in the DOM, and Cypress generates the test code to mimic what you're doing. Test written. Done.

Today, I'll show you how to add Cypress to an existing JavaScript project, enable Cypress Studio, and let it help do the heavy lifting of writing end-to-end tests.

It's such a cool feature to help dev teams save time on testing, deliver new features faster, and still ensure the mission critical functionality of the app continues to work as expected.

Add Cypress to a Project

Although Cypress is kind enough to provide a host of sample scripts to show many of its features in action, it really shines with a local app to test against, and I just so happen to have one that fits the bill.

If you'd like to download a working version of the finished code and follow along, I have a link to the GitHub repo here.

Get an app to test

The app I'm using is a React-based movie database that allows users to see upcoming movies and movies in theaters now, browse movies by genre, and search for movies by title. This will be a good app to demonstrate Cypress Studio's power.

Sample movie database React app

Download Cypress to your app

Once we've got an app to add Cypress to, the first thing we'll need to do is download Cypress to it.

This is another reason Cypress stands head and shoulders above its competitors: one npm download gives you all the tools you need to start writing e2es. No dev dependencies, no extra libraries with mismatched package versions, none of that nonsense to deal with.

At the root of your project, where your package.json file lives, run the following command from the terminal:

npm install cypress 

This will add a bunch of new Cypress-based folders and files to your app, and with just a few small configuration changes we'll be ready to go.

Cypress folder structure inside of new project

See all those new folders under cypress/? That's what you should see after initial installation.

Add Cypress scripts to the package.json to make it easier to run

For ease of use, I like to add npm scripts for the two main Cypress commands we'll be using:

  • cypress open, which is used to open the Cypress Test Runner where we can watch tests run locally, debug them, and where we'll show Cypress how to test our app
  • cypress run, which runs our e2es in "headless mode", which is how they'd run in a build pipeline 

In your package.json file, add the following two lines in your "scripts" section.

    "cy:run": "cypress run",
    "cy:open": "cypress open"

Now when we need to run the tests, a simple npm run cy:run or npm run cy:open, straight from the command line, will do the trick. 

Run Cypress's test scripts to make sure everything works

Ok, before we get to writing our own tests, let's run the pre-populated tests in Cypress to get familiar with the its Test Runner.

From your command line, run the following shell command:

npm run cy:open

This should open the Cypress Test Runner, and from here, click the Run integration spec button in the top right hand corner to run through all the pre-made tests once.

Screenshot of the tests inside of the Cypress Test Runner

Note: Run cypress open first

When we run cypress open for the first time, a new cypress.json file is created at the root of our project. Even though it begins its life empty, this file is required for the headless version of Cypress (cypress run) to work.

So don't try to run Cypress in headless mode until after you've done cypress open in your project at least once. Otherwise, it will throw an error in the console.

After all the Cypress tests run and pass, we're ready to delete them and get to work on our own tests for our app.

Enable Cypress's experimental feature mode

Go ahead and clear all the files out of the Cypress folders of fixtures/ and integration/.

You'll probably also want to add the folders of cypress/screenshots/ and cypress/videos/ to your .gitignore file just so you don't commit those screenshots and videos that Cypress automatically takes during test runs to your GitHub repo (unless you want to, of course).

Add baseUrl variable

With that taken care of, let's set up a baseUrl in our cypress.json file and enable Cypress Studio there too.

Setting this baseUrl variable just means we won't have to type cy.visit('http://localhost:3000/'); in every test. Instead, we'll be able to just write cy.visit('/'); and save ourselves some code.

Turn on experimentalStudio

To enable Cypress studio, just add "experimentalStudio": true to our cypress.json file.

So here's what the cypress.json file will end up with.

{
 "baseUrl":"http://localhost:3000", 
 "experimentalStudio": true
}

Now we can write our first test file and test.

Create a new test file

Inside of the cypress/integration/ folder in your project, create a new test file named movie-search-spec.js. This folder is where Cypress will look for all your e2e test files when it runs.

New test file in Cypress integration folder

Give it a placeholder test: we have to tell Cypress where we want it to record the test steps we're going to show it.

So just create a typical describe test block and inside of that, create an it test.

Cypress accepts all the traditional testing syntaxes familiar to those who've used Jasmine, Chai, Mocha and Testing Library testing frameworks before. Whether you prefer describe, it, or test - they're all good.

A good first test would be to test that a user can navigate to the movie search option, search for a particular movie name, and click into the results based on that search.

Here's what my empty testing placeholder looks like in the movie-search-spec.js file.

describe('Movie app', () => {
  it('should show all results for movies that contain "Star Wars" in their titles', () => {
    
  })
})

I think we're about ready to go.

Start the app & show Cypress how to test

One thing you must do before starting up Cypress to run tests against your local app is to start the app locally.

For Cypress, it's an anti-pattern to start the app from a test, so just fire it up in a separate terminal, then open up the Cypress Test Runner.

Start the movie app

In one terminal run our movie app:

npm run start 

Start the Cypress Test Runner

And in a second terminal, open the Cypress Test Runner:

npm run cy:open 

In Cypress, Add Commands to Test

When the Cypress Test Runner is open, enter our test file and click the tiny blue magic wand that says Add Commands to Test when you hover over it.

Click the blue magic wand to enable Cypress Studio mode

And from here, go for it - test the app.

Test the app in the Cypress Test Runner browser and it will record your actions

Go to town

For me, I clicked the Movie Search link in the nav bar, typed "Star Wars" into the search box, clicked into one of the results, etc.

When you're satisfied with what your test is doing, click the Save Commands button at the bottom of the test, and Cypress will run back through all the commands it's just recorded from your actions.

Tell me that's not cool.

Check out the test in your e2e file now

If you go back to your IDE now, you'll see all the actions Cypress recorded, along with a few comments to tell you it was Cypress generating the code and not a developer.

This is what my test now looks like:

describe('Movie app', () => {
  it('should show all results for movies that contain "Star Wars" in their titles', () => {
    /* ==== Generated with Cypress Studio ==== */
    cy.visit('/');
    cy.get('.navbar-top-links > :nth-child(3) > a').click();
    cy.get('.search-input').clear();
    cy.get('.search-input').type('Star Wars');
    cy.get('[type="submit"]').click();
    cy.get(':nth-child(2) > .breakpoint__medium-up > .movie-component > .movie-poster').click();
    cy.get('.movie-details-reviews > :nth-child(3)').click();
    cy.get('.fa').click();
    /* ==== End Cypress Studio ==== */
  })
})

Just wow, right?

Fill in the gaps Cypress didn't code for

Although our test is good, Cypress can't be expected to test for all the things a developer might know are important.

Things like the number of movies returned from searching "star wars" or checking the title of the movie being clicked into and the contents inside of the movie page itself.

I'll fill in some of those details myself now. 

describe('Movie app', () => {
  it('should show all results for movies that contain "Star Wars" in their titles', () => {
    /* ==== Generated with Cypress Studio ==== */
    cy.visit('/');
    cy.get('.navbar-top-links > :nth-child(3) > a').click();
    cy.get('.search-label').should('contain', 'Search Movie Titles Here:'); // user added
    cy.get('.search-input').clear();
    cy.get('.search-input').type('Star Wars');
    cy.get('[type="submit"]').click();
    cy.get('.card-component').should('have.length', 20); // user added
    cy.get(':nth-child(2) > .breakpoint__medium-up > .movie-component > .movie-poster').click();
    cy.get('.movie-details-info__overview').should('contain', 'Movie Overview'); // user added
    cy.get('div').should('contain', "1977-05-25") // user added
    cy.get('div').should('contain', "8.2") // user added
    cy.get('.movie-details-reviews > :nth-child(3)').click();
    cy.get('.fa').click();
    /* ==== End Cypress Studio ==== */
  })
})

If you look at my code above, I added comments after the extra assertions I added - mainly small things like checking for search text, the count of movies returned, the movie info like rating and release date in this specific movie.

I didn't add a ton of extra code, just a few extra lines of details. Now run the test again and check the results.

npm run cy:run

Terminal display of Cypress tests passing in headless mode

And we're done! Congrats - our first Cypress Studio-assisted e2e test is written. 

Rinse, Repeat

Just repeat these steps for as many end-to-end tests as you need to write and prepare to be amazed at how much time it saves you.

Conclusion

In modern software, testing is a critical piece of any solid enterprise application. It helps ensure our app continues to function as expected while adding new functionality, and end-to-end testing is the closest we can get to mimicking exactly how a user would use the app with automation.

Cypress broke the mold of what e2e testing frameworks are capable of when it debuted back in 2015, and it's only continued to improve with time.

My favorite new feature of late is the ability to show Cypress how a test should act instead of writing it yourself with Cypress Studio - the time saving possibilities are immense. And more time saved means finishing features faster and getting new functionality into the hands of users quicker. Win win win.

If you enjoyed this, you may be interested in checking out my course "The newline Guide to Modernizing an Enterprise React App".

In 10 modules and 54 lessons, I cover all the things I learned while at The Home Depot, that go into building and maintaining large, mission-critical React applications - because it's so much more than just making the code work.

From tooling and refactoring, to testing and design system libraries, there's a ton of material and hands-on practice here to prepare any React developer to build software that lives up to today's high standards. I hope you'll check it out.

Further References and Resources

Want to be notified first when I publish new content? Subscribe to my newsletter.