Gaining Confidence with Cypress Tests
AI Generated Video Summary
Welcome to React Advanced London where we'll learn about gaining confidence with Cypress tests. We'll explore browser testing, including tests for specific units of work, business services, APIs, and components using different tools. Cypress provides selectors for easy object selection in tests. We learned how to select objects in our tests and rerun them to check for successes and failures. We also discussed best practices for Cypress tests, including using data-cy elements, commands, mocks, and fixtures.
1. Introduction to Cypress Tests
Welcome to React Advanced London. We'll learn about gaining confidence with Cypress tests. I'll post the slides on my site tonight. Check out RobRich.org for more information. I'm a SIRL developer advocate, Microsoft MVP, Docker captain, and friend of RedGate. AZ Give Camp is a fun event where we build free software for charities. Join us for the next AZ Give Camp or contact me for a gift camp in your area. I've contributed to Gulp and was featured on a .NET Rocks podcast.
Here's the part where I tell you I'm definitely going to post the slides on my site tonight. I've been that person chasing the speaker, and it's never worked out very well for me, either, which is why you can head to RobRich.org right now, click on Presentations here at the top, and here's gaining confidence with Cypress tests. The slides and the code are available online.
While we're here at RobRich.org, let's click on About Me and we'll see some of the things that I've done recently. I'm a SIRL developer advocate. If you're having trouble taming your data mesh, let's chat. I'm also a Microsoft MVP, a Docker captain, a friend of RedGate, and AZ Give Camp is really fun. AZ Give Camp brings volunteer developers together with charities to build free software. We start Friday after work. Sunday afternoon, we deliver the completed software to the charities. Sleep is optional, caffeine provided. If you're in Phoenix, come join us for the next AZ Give Camp, or if you'd like a gift camp here in London, or wherever you're connecting from, hit me up on email or Twitter, and let's get a gift camp in your neighborhood too. Some of the other things that I've done. I was a core contributor to Gulp in version two and version three, and I replied to a .NET Rocks podcast episode. They read my comment on the air and they sent me a mug. Woo-hoo. So there's my claim to fame, my coveted .NET Rocks mug.
2. Gaining Confidence with Cypress Tess
Let's dig in with gaining confidence with Cypress Tess. We'll start with a TypeScript build and then execute our tests. Cypress is an electron app and a browser plugin built on top of Mocha and Chai. We'll explore browser testing, including tests for specific units of work, business services, APIs, and components using different tools.
So let's dig in with gaining confidence with Cypress Tess. Now, I'm not quite sure how this talk is going to go, so let me fire up this thing and let's see if my site is ready. Now, our first step is to do a TypeScript build. So let's kick that off. Once we've got our TypeScript build, then we'll start executing each of our tests.
Looks good so far. It's working pretty well. Yep. It looks like this test is going to work out pretty well. Yep, my site is functioning well. That's great. That's awesome. It looks like my site will work and this talk will turn out just fine.
We saw browser-based functional testing in Cypress. That was pretty cool. Or said differently, we saw end-to-end tests. Cypress is that mechanism for running experiences much like a user would. It's an electron app and a browser plugin. And it's built on top of Mocha and Chai. Like many good stories, we started in the middle. Let's back up and start at the beginning. Let's talk about browser testing.
As I'm doing browser testing, I'll probably create tests in each of these categories. I might have tests around a specific unit of work, a specific business service. Or I might test an API. Maybe it's GraphQL or GRPC or REST. I might test this service. Now, that's not a web service, but rather a business service, and I might want to test that service running in a particular browser. I might want to test components where I'm still doing unit tests, so I may be mocking child components, and for each of these, I might use different tools. If I want to test a small unit of work, I would use mocha, Chai, Jest, or Jasmine.
3. Validating Tests and End-to-End Options
If I wanted to validate tests, I would use Karma. For component validation, include the test utils library. For API testing, no browser is needed. For end-to-end tests, options include Selenium, Cypress, Test Cafe, or Playwright. Selenium is known for fragile and brittle tests. Cypress waits for the DOM or API call. Test Café feels familiar if you use DevExpress. Playwright is next.
If I wanted to validate those tests, I would use Karma. Karma doesn't do end-to-end tests, rather it runs unit tests inside of a browser. If I want to validate a particular component, I might take my mocha or Jest tests and also include the test utils library. There's a test utils version for each spa framework.
If I want to test my API, I don't need a browser at all. I can just fire web requests at it. Check the status code in the response body.
If I wanted to do end-to-end tests, I could use Selenium, Cypress, Test Cafe, or PlayWrite. As you grab these slides from robridge.org, you can click on each of these blue links to learn more about that particular library. Let's double-click into end-to-end tests.
I could use Selenium. Selenium is a web driver and you can program this in lots of different languages. Selenium is known for making fragile and brittle tests. It knows nothing of the browser. It's merely controlling the mouse and looking at the HTML. If the API wasn't ready yet, I could rerun my tests. That makes my tests brittle. Or I could extend the time out. That makes my tests slow. Selenium is known for slow and brittle tests.
Next up, Test Café. If you use DevExpress for other content, you might feel right at home with Test Café. With Test Café, it's not using Mocha or Jest for its assertion library, so it will feel a little weird. You also spend a lot of time marshalling content between the test context and the browser context.
Next up, Playwright.
4. Exploring Cypress Testing
Let's take a look at Cypress today. The first step I'll do is I'll do an NPM install of Cypress and an NPX Cypress open. Once I do an NPX Cypress open, it will scaffold out a whole lot of content in place to make it easier to get started with Cypress.
Now, I can choose to run all my tests or I can choose to run a particular test. So, we saw how we launched this test and we're able to validate that content. Now, we'll notice that here's my regular Chrome and here's a new Chrome. It's launched a new profile to ensure none of my plugins or saved forms interfere with the test run. Now, it is just a browser. So, I can do interesting things. I can open other browser tabs and my tests will continue to run. It's not like I need to walk away from my machine when I have my test run.
Now, the tests will run each step in my content and be able to validate that my tests work as expected. But this is just a browser. So, if I open up the F12 developer tools, I can switch over to this sources tab and I can take a look at my tests. So, if I want to set a breakpoint in my test, I can now hit refresh and start to debug through my test with just normal F10 and F11 debug tools. I can also switch over to the console tab and see the console output at each step. So, here's the console output at this step and at this step. And I can see that console output for each of the tests. Closing the F12 developer tools, I can take a look at the content at each step, but I can also take a look at, for example, where it clicked. It clicked right here to be able to accomplish that task. Now, I can use this tool to be able to select things. We'll come back to selectors, but here's the exact code that I need to be able to select this object where I to put that in my test.
5. Exploring Cypress Project and Configurations
We learned how to select objects in our tests and rerun them to check for successes and failures. We also explored the content behind the To-Do MVC site and the structure of the Cypress project. Additionally, we discussed using TypeScript for tests and configuring Cypress to run from TypeScript. Finally, we looked at shortcuts in the package.json file to launch the IDE and run headless builds.
It clicked right here to be able to accomplish that task. Now, I can use this tool to be able to select things. We'll come back to selectors, but here's the exact code that I need to be able to select this object where I to put that in my test. I can also choose to rerun my tests and take a look at successes and failures.
Now, that's great. We got to see how this IDE worked, and it worked out really well. Now, let's take a look at the content behind this. Now, we were testing the To-Do MVC site. Now, it is a smidge dated, but we have it examples of MVC in various frameworks, and the To-Do MVC project then collects these so that we can start to compare frameworks. So I have my tests, and let's pop open our Cypress project.
Here's Cypress.json, and that's the first file that we'll take a look at. Now, Cypress.json's job is to point us to this plugins file. In this case, I have it in a TypeScript project, so I'll go into test slash Cypress, plugins, and here's my plugins dot index dot HTML. And this will then define the folders for all of the rest of the content in my Cypress project. My fixtures are here in the fixtures folder, my integration folder, that's where all my unit tests are. I have snapshots and videos in the results folder to make sure that they're excluded from source control. And we'll come back to support files that are here.
Next up, I've chosen to make my tests in TypeScript. Now, this is the base TS config in my project, and it doesn't have any details of TypeScript. This is just the TypeScript definition, the build definition that I would have as part of my project. Now here inside the test Cypress folder, I also have a TS config file. Now, these are the specific overrides that I need to be able to launch Cypress from TypeScript. Note that I've extended the previous TypeScript, the TS config file, so that I only need to override those pieces that are specific to TypeScript. In my package.json file, I've chosen to create some shortcuts to be able to do npx cypress open. I'll say npm run cy colon open, and that will launch the IDE. I can also run cy colon run from my automated build, and that will run the headless build. I can identify specific browsers, and I can then string them together. So, if I say npm test, it will run chrome and firefox and edge, and validate that my tests work in all three browsers. That's perfect. So, let's take a look at the test that we wrote to accomplish this.
6. Testing the To-Do MVC Project
Now, I'm testing the to-do MVC project and switching between different implementations. I validate the site URL and check for expected content. Next, I examine the to-do list, interact with the page by typing in the to-do box, and validate the new to-do. Then, I mark a to-do as completed and confirm its status. We can now interact with the page elegantly.
Now, I'm testing the to-do MVC project, and so I can switch between various implementations. In this case, I'm looking at the AngularJS version. I go grab the site names, so I can put it in my described block in a really elegant way, and then before each test, I'm going to go visit that site.
Now, I do this just to validate it so that I don't have to do this CY.visit at the beginning of each test, but I definitely could if I wanted to. Now, let's do a hello world test, where we just go grab the URL, and it should equal that site URL. Now, this was really helpful. When I originally wrote this talk, this site was HTTP, not HTTPS, and so it was really helpful for me to notice that I wasn't testing what I expected. If you refactor your website, it could be nice to know if this content is 301 or 302 to a new page that you're not testing what you expect.
Now, next, let's take a look at the to-do list. We haven't entered any to-do's, so this to-do list should not exist. Perfect. Now, depending on this SPAT implementation, it might exist but be blank, but ultimately, we're looking for the li items within the list, so this might be a safe assumption. Next, let's level up and start interacting with the page. I've got some content and I want to type that in the to-do box. So, let's go select the new to-do box and type that content and push enter. Notice that there's a dollar sign here and not here. This curly brace enter curly brace is so we can enter unique commands. Shift, option, control, F keys, anything we can type in this way. So, once I've done that, I'll go grab the to-do list and validate that it contains my new to-do. Notice that there's no awaits here. As soon as we finish typing that content, it will wait for all of the browser events to finish before Cypress gets onto the next line. So, let's go validate that our to-do box is now blank, and now we've added a to-do. That's perfect! Let's mark a to-do as completed. So, I'll start by creating this irrelevant to-do, because I want to make sure that I'm completing the correct one. Then I'll mark my new to-do that I want to do, and validate that I can get there. So, let's go grab that view that matches this new to-do, and go grab the children the toggle button. Now I'll click that toggle button, and that will mark that task as finished. So, what does it mean to be finished? Well, it should have this class of completed, and I'll go validate that by selecting that to-do item. Perfect. Now we've been able to start to interact with the page in a really elegant way.
7. Deleting a To-Do
Let's delete a to-do by finding the destroy button and clicking on it, even if it's not visible. After deleting, we should have only one to-do left.
8. Creating New Todo and Testing Network Interactions
Let's create a new command called todo add. This command will be able to do the steps associated with creating a new todo. In my test, I describe the user's interactions and add three new todos. I also have a complete action that marks a todo as complete. We validate the filtering of active tasks and completed tasks. Next, we discuss network interactions with the Hacker News PWA, a spiritual successor to TodoMVC. We explore how to validate the content and pull down real stories from Hacker News.
Now, as I start to type these things, I'm getting kind of redundant. I want to show only active tasks, but I don't want to just keep doing this again. Let's create a new command called todo add. Now, this todo add command, I'll take in my thing, and it will be able to do those steps associated with creating the new todo.
So here in my commands, I have an index.ts that says just import those commands. Here's my commands. And I have this todo add command. It takes in a string, it types into the box. And I'm right here. I may as well just validate that that box is now empty. Perfect.
Back in my test, I now can describe the user's interactions. I want to add three new todos. If you've ever used selenium's page object, this will feel right at home. Let's do a similar thing for complete. And so we have a complete action, which will go grab the view, click on the toggle and mark that as complete. Now we're starting to describe users' interactions, so our test is both more terse and more descriptive. Great.
Let's go grab the active button, and we will click it and validate that we only are showing two now, that we're only filtering by active tasks. Finishing up this test, we'll do similar things using our new actions and grab the completed button and validate that we now only are showing that one that we've marked completed. And we'll do a similar thing of clicking the clear completed button, validating that we no longer have any. Now this was great to be able to level up through the various functions in Cypress.
Next, let's start to talk about network interactions. Similar to TodoMVC, we have Hacker News PWA. Now, we can say this is a spiritual successor to TodoMVC. It creates a PWA that shows Hacker News details. Now, it's great because I can do it in lots of different frameworks, but I don't know what the stories will be on Hacker News. So how do I validate that that content is correct? Let's fire up this test and take a look at how it works. Here's my Hacker News PWA test. And as it does that test, it will actually pull down the real stories from Hacker News.
9. Testing with Cypress and Best Practices
Let's look at our Hacker News PWA. In this case, I chose the Angular 2 version, but we could also look at the Next version or the Nuxt version. I'll start off by visiting that page every time, and let's go validate that the URL is as expected. Next, we want to have a specific top story. Now, I don't know what the top story is on Hacker News, so there's no way for me to validate that. But I can intercept this web request. In this case, I chose a regular expression, but we could also use a string here. And whenever the application makes a request to this, I'm going to intercept it, and instead return this fixture. Here's this fixture, the Hacker News. Now I know that this is the first story will be the first story in my list. There's my expected result. As I go visit that Hacker News site, then I should get that title in my results. Perfect. It would be easy for me to lie to myself and presume that my fixture is exactly what the API will return. And in this case, we'll go hit the actual web request not mock it out to validate that it is that my API shape is as expected. I'm going to assume that Hacker News has 30 or more stories on it. Now, if this API request was going to take a really long time, I could choose to name it as a particular thing and then specifically cy.wait. But in this case, the Hacker News API returns quick enough and I don't need to. So we got to see great examples of how to be able to build tests in Cypress in various ways. And we leveled up through different interactions from web requests to interacting with the page validating and selecting particular DOM elements, creating commands to be able to simplify and be more descriptive in our tests and ultimately intercepting web requests.
Now, one of the things that we want to look at is best practices associated with Cypress. As we're starting to do our selectors, we could definitely create the CSS selector with a big full path showing all of the divs how to get from here to there. But if we were to refactor our page, all of that would be for naught. Instead, create a specific selector that identifies an ID or a class.
10. Best Practices for Cypress Tests
Create a data-cy element to specify the desired element in tests. This mechanism allows tests to run in production and documents the association between tests and content. Use commands to simplify test steps and improve test readability. Avoid logging in on every test by obtaining a token at the beginning of all tests. Use mocks and fixtures to separate reusable data from test content, making tests more concise and facilitating reuse. Explore the code and connect with me on Twitter for more information.
Or even better, create a data-cy element on your particular element and then specify that in your test. cy.get data-cy equals that particular thing. Now the beauty here is that not only are you getting exactly the element that you want every time, even if it gets moved around on the page, but you're also documenting that a test is responsible for this particular content. That can be really helpful.
Now are you leaking your test content into production? Yes. But wouldn't it be great to also run tests in production? Maybe every hour or so, I'm going to fire up my home page. I'm going to get into the product catalog. I'm going to put one piece in my shopping cart. I'm going to get to the checkout page. I won't enter a credit card number. I won't finish the purchase, but wouldn't it be cool if you could run that test for all of your hotpaths in production and know if your infrastructure went down before your customers call? That would be ideal. So this data-cy mechanism both makes it easier to document that a test is associated with it and allows you to run tests in production.
Next up, let's use commands. We saw how we could use commands to create a simpler mechanism for being able to identify the steps in our tests. Now the backing content now could be reused in interesting ways and our tests are much more terse and descriptive.
Next up, avoid logging in on every test. We definitely do want to validate the log-in experience but we only want to do that once. Instead, at the beginning of all tests, go hit an API and get a token that you can use for the rest of the tests. Now maybe this is a Dev-only API or maybe you're just hitting the back-end page that the log-in experience would call. Once you've got that token, use that token in all the rest of the tests. You do want to validate your log-in experience but you don't want to validate it at the beginning of every test. That will make your tests slower.
Next up, use mocks to be able to... use fixtures to be able to take reusable data and move it out of your test content into a more reusable spot. It'll make your tests more terse, more shorter, and allow you to reuse that across various tests. Now in this case, we had a fixture that we could use to mock out the Hacker News data. Now this is great, we can attach that to a particular web request via CY.Intercept, and if anything happened, now we can start to validate that our controls render as expected. You can click on the blue link here in the slides that you get from rawbridge.org to get to the code associated with accomplishing that.
This has been fun to be able to show you Cypress and be to wander through all of the different pieces. You can grab this code at rawbridge.org and hit me up on twitter at rob underscore rich. You can also join me at that spot where the conference is designated for live Q&A.