Cypress Testing Library: Getting Started Tutorial
Discover how Cypress Testing Library improves your testing strategy. Create adaptable tests that focus on business behavior to ensure code quality verification.
Join the DZone community and get the full member experience.
Join For FreeWriting maintainable tests for web user interfaces is critical for ensuring the reliability and stability of applications. As the software evolves and changes, it's vital to have tests that can be easily maintained and updated to reflect the latest changes in the code.
As part of UI testing, it is necessary to focus on the application's behavior rather than its implementation details. This is because implementation details can undergo frequent changes, and testing them can result in fragile tests that easily break when the implementation is modified.
On the other hand, focusing on the application's behavior allows for more resilient tests that are less prone to breaking as the code changes. Remember that this approach also helps catch regressions and ensure the application continues functioning as intended, even if new features are added and others are modified.
To maintain the long-term viability of a test automation suite, it's also vital to ensure it can handle components' refactors without breaking. Refactors can involve changes to implementing components but not necessarily their functionality; also, those could be related to technical debt. Therefore, tests that rely on implementation details may fail when a refactor occurs, even though the component's functionality remains unchanged.
In this blog on Cypress Testing Library, we will delve deeper into the concept of removing implementation details from our tests and instead focusing on behavior. By adopting this approach, your test scripts can remain robust and effective even when there are changes in the implementation of components.
Why Cypress for Test Automation?
The big question for you at this moment is why Cypress is still relevant compared to other tools like Playwright, WebdriverIO, or TestCafe in terms of UI test automation. At the time of writing this blog on Cypress Testing Library, Cypress had just announced the 12.12.0 release, which supports Angular 16 and updates the Debug page as part of the significant features. But for those still waiting on Safari as part of cross browser testing, sadly, you will be waiting a bit longer.
If you are looking at Safari or Multi-tabs Testing for your UI testing, you know Cypress is not your first option; perhaps you have already switched to Playwright or keeping your Selenium test suite. The question is still there, and it looks like Cypress is not relevant anymore, but Cypress is still relevant (Let's rephrase it, tools like Cypress are more appropriate to these new Modern times), but why is this true? Let's dig in a bit more.
Cypress and similar tools want to simplify writing tests; imagine Developers coding all day and following the Test Driven Development (TDD) methodology; it could be more challenging for them. Now, what about the new QAs in the past (Or present situation)? Installing Selenium and maintaining your WebDriver version or adding extra complexity to test a single component with more code doesn't sound efficient.
Cypress wants to simplify that by providing easy solutions and making test automation an enjoyable experience for Developers, Testers, DevOps Engineers, and others.
Understanding the Problematic DOM-Testing
When writing tests for your web UI, prioritizing maintainability is essential. It means writing tests that avoid relying on the implementation details of your components. Instead, you should focus on testing the functionality of your application in a way that gives you confidence that it works as expected. By decoupling your tests from implementation details, you can ensure that your test scripts remain resilient to coding changes.
To achieve maintainable tests, you should focus on testing the external behavior of your components. It means testing what your users will see and interact with rather than how your components are implemented under the hood. You can achieve this by combining functional tests and integration tests, which help ensure our application works as intended from an end-to-end testing perspective.
If you want to prioritize maintainability, you must avoid the typical pitfall of tests that break after minor UI changes to our coding. In addition, it can help us and the team to save time and effort in the long run, as you won't need to spend as much time fixing broken tests or updating them after every refactor. Finally, writing maintainable tests can help you build a more reliable and robust web UI that meets the needs of your users.
It sounds hard to write highly maintainable test scripts and avoid breaking changes; I know the solution could be more apparent, but believe me, the utilities this library provides facilitate querying the DOM as the user would. For example, it found form elements by their label text (just like a user would), links and buttons from their text (Similar like a user would), and more. In addition, it exposes a suggested way to find elements by a data-testid as an "escape hatch" for elements where the text content and label do not make sense or are impractical.
The DOM-Testing library encourages your applications to be more accessible. It allows you to get our tests closer to using your components the way a user will, which allows your tests to give you more confidence that your application will work when a real user uses it.
What Is the Cypress Testing Library?
Cypress Testing Library is a JavaScript library that provides utilities and best practices for testing web applications using the Cypress testing framework. It is built on top of the popular testing library family of libraries, which promotes a user-centric approach to testing by emphasizing real-world user interactions and behavior. Cypress Testing Library extends the capabilities of Cypress and enables developers to write more maintainable and reliable Cypress UI tests for their web applications.
With Cypress Testing Library, you can write tests that closely resemble how users interact with your application. It encourages testing the application from the user's perspective, focusing on the rendered output and the expected behavior. This approach helps to decouple the tests from implementation details, making them more resilient to changes in the application code.
The library provides a set of intuitive query methods to select and interact with elements on the page, just like a user would. It also offers various utility functions for common testing scenarios, such as checking for the presence of elements, interacting with form inputs, and making assertions on the rendered content. Cypress Testing Library promotes accessible and inclusive testing practices, ensuring that your application can be tested for a wide range of users.
Overall, Cypress Testing Library simplifies the process of writing effective and maintainable tests for web applications using Cypress, making it a valuable tool for developers seeking to enhance the quality and reliability of their Cypress e2e testing efforts.
Installing Cypress Testing Library
Now, it's time to install the Cypress Testing Library in your Cypress project. Please type the following command:
npm install --save-dev @testing-library/cypress
Cypress Testing Library extends Cypress's cy commands. You must add the following line to our project's cypress/support/commands.js:
import '@testing-library/cypress/add-commands'
You can now use the DOM Testing Library's findBy, findAllBy, queryBy, and queryAllBy commands off the global cy object. Let's start with a simple test, FindAllByTitle; we will use the following URL -> "https://ecommerce-playground.lambdatest.io/" as this is a good Automation playground from LambdaTest.
it.only('findAllByText', () => {
cy.findAllByText('Search')
.should('have.length', 1)
})
For this test, we are trying to find all the elements having the text "Search" in the current webpage as part of the DOM page; you should see only one.
As you can see in the image above, our webpage only has one element; that's why it says "length of 1" But what about if the requirements mention two elements with Search's Text? Let's update our code and rerun it.
it.only('findAllByText', () => {
cy.findAllByText('Search')
.should('have.length', 2)
})
As you noticed, the Cypress assertion failed; we were expecting two elements with the Search text, but our web page's current state displays only one; as the first part of our approach, it worked smoothly, we validated the behavior, so let's try another example.
it.only('findAllByText', () => {
cy.findAllByText('SHOP NOW')
.should('have.length', 3)
})
For the following example, we will find all the elements with the text "SHOP NOW"; I did a quick search, and it's displaying 3, let's go with that approach.
We got a failure; it looks like our test only found two elements; let's see the Cypress highlight page; this is a good option if you are debugging your tests.
After debugging, we found the problem; we were looking for "SHOP NOW" in all Upper cases, but the other button contains the text "Shop Now" in Lower and Upper cases; let's see if this is correct, rerun it with "Shop Now" text.
Now, let's create our first UX bug per UX web design guidelines; you should have a standard design applicable to buttons, labels, titles, etc. Just kidding, but now you can witness the potential of DOM testing with Cypress.
It's addictive; let's use the Placeholder attribute, which is relevant for web Inputs; inputs are the gates nearly all eCommerce has to pass. Regardless of your feelings about empathy in design, unusable inputs leave money on the table. In this case, we will use findByPlaceholderText.
it.only('findPlaceholders', () => {
cy.get(`li:nth-of-type(6) > a[role='button']`).click()
//
cy.findByText('Continue').click()
//
cy.findByPlaceholderText('First Name').click().type('Hello Placeholder')
})
As you can notice from the code above, we are using "findByText" and "cy.get"; the "get" we are using to find a specific element without any particular identifier; the rest is straightforward; let's run it our test.
Last but not least, here you can find the list of queries you can perform using Cypress Testing Library
- 'findByLabelText',
- 'findByPlaceholderText',
- 'findByText',
- 'findByDisplayValue',
- 'findByAltText',
- 'findByTitle',
- 'findByRole',
- 'findByTestId'
Code Walkthrough Till now, you have understood the importance of the Cypress Testing Library. So, with the Cypress project already set, let's look at how the code works using Cypress. For starters, please look at the link to set up Cypress; as mentioned before, Cypress enables you to write faster, easier, and more reliable tests. Let's start defining our config.json file; here, you can include some data and URLs.
{
"URL1": "https://www.lambdatest.com/blog/",
"URL2": "https://ecommerce-playground.lambdatest.io"
}
Next, you need to establish our structure. As demonstrated in the following line of code, import MainPage from '../../page-objects/components/MainPage'; we aim to separate our Cypress locators from our tests using the Page Object Model (POM) approach. This structure is located under the "page-objects" folder.
Let's start defining a hook for our tests; with a couple of tests, things might get repetitive. For this case, you might instead use a beforeEach() hook that will open up your page before all of your tests; remember that Cypress clears out the state of the browser in between tests.
describe('Testing Cypress Library', () => {
beforeEach(() => {
cy.visit(`${config.URL2}`)
Now, you probably noticed the following line of code -> "cy.visit(`${config.URL2}`) "; as I mentioned before, I like to put my URLs in a different file, "config.json" in this case you are wondering why not using the baseUrl inside of the cypress.config.js file, being honest, this is because switching URLs can be tricky, in my case, I use around four to five web pages to validate.
// Test each of the types of queries: LabelText, PlaceholderText, Text, DisplayValue, AltText, Title, Role, TestId
it('findAllByText', () => {
MainPage.FindByTextAll('Search', 1)
})
it('findAllByText', () => {
MainPage.FindByTextAll('Shop Now', 1)
})
it('findAllByText Second', () => {
MainPage.FindByTextAll('SHOP NOW', 2)
})
it('findPlaceholders', () => {
MainPage.GoToCreateAccount()
//
MainPage.findPlaceholders()
})
})
Our methods and locators can be found in the "components/MainPage". You can see that our tests look cleaner and easy to follow; currently, we have four tests; let's explore the methods one by one.
export default class MainPage {
static GoToCreateAccount(){
cy.get(`li:nth-of-type(6) > a[role='button']`).click()
//
cy.findByText('Continue').click()
}
static FindByTextAll(El1, qty){
cy.findAllByText(El1)
.should('have.length', qty)
}
static findPlaceholders(){
//cy.findByPlaceholderText(El1).click().type(PlaceText)
cy.findByPlaceholderText('First Name').click().type('Hello Placeholder')
cy.findByPlaceholderText('Last Name').click().type('Hello Last Name')
cy.findByPlaceholderText('E-Mail').click().type('Hello E-Mail')
cy.findByPlaceholderText('Telephone').click().type('Hello Telephone')
cy.findByPlaceholderText('Password').click().type('Hello Password')
cy.findByPlaceholderText('Password Confirm').click().type('Hello Password Confirm')
}
}
As you can see from the code above, we have three methods, one for registering a new customer, in this case, "GoToCreateAccount", another one for the "FindByTextAll" receiving two parameters, text, and the length, finally the "findPlaceholders" method, basically looking for all the Placeholders in the registering page, as this is a one-time method, no need to encapsulate more the repeatable FindByTextAll sentence. Here is the result of our tests, which indicates that our Cypress Testing Library testing approach is working smoothly.
When To Use the Cypress Testing Library?
We try only to expose methods and utilities that encourage you to write tests that closely resemble how web pages are used. Utilities are included in the Cypress Testing Library, and we encourage the following principles about when to use this library:
- If it relates to rendering components, it should deal with DOM nodes rather than component instances and not encourage dealing with them.
- It should generally test the application components and how the user would use them. We are making some trade-offs here because we're using a computer and often a simulated browser environment, but in general, utilities should encourage tests that use the components how they're intended to be used.
- Utility implementations and APIs should be simple and flexible.
You must resemble how users interact with your code (component, page, etc.); for Cypress Testing Library, we recommend the following priority:
- Semantic Queries HTML5. Remember that the user experience of interacting with these attributes varies greatly across browsers and assistive technology.
- findByRole: It can be used to find every element exposed in the accessibility tree. With the name option, you can filter the returned elements by their accessible name. It should be your top preference for just about everything. There's not much you can't get with this (if you can't, your UI may be inaccessible). Most often, this will be used with the name option like so: getByRole('button,' {name: /submit/i}).
- findByLabelText: This method is perfect for form fields. Users find elements using label text when navigating through a web page form. This method emulates that behavior, so it should be your top preference.
- findByPlaceholderText: A placeholder is not a substitute for a label. But if that's all you have, it's better than alternatives.
- findByText: Text content is the primary way users find elements outside forms. This method can find non-interactive elements (like divs, spans, and paragraphs).
- findByDisplayValue: The current value of a form element can be helpful when navigating a page with filled-in values.
- findByAltText: If your element supports alt text (img, area, input, and any custom element), you can use this to find that element.
- findByTitle: The title attribute is not consistently read by screen readers and is not visible by default for sighted users
- Test IDs
- findByTestId: This approach is advisable only in situations where matching by role, or text is not feasible or meaningful (such as when the text is dynamic) since these attributes are not visible or audible to the user.
It’s a Wrap
Writing maintainable tests for web user interfaces is essential to guarantee that applications are reliable and stable. Tests ensure the code works as expected and catch potential issues before production. However, as software evolves and changes over time, it's essential to have tests that are easy to maintain and update. Maintaining tests that are tightly coupled to the implementation details of the code can take time and effort to manage, leading to test code that is complex and brittle. This Cypress tutorial will help you create modular and flexible tests using Cypress Testing Library, making them current and focusing on business behavior. In addition, it helps to ensure that tests remain an effective tool for verifying the quality and correctness of the code.
"We must guarantee what we deliver works for everyone." - Enrique A Decoss.
I hope the Cypress Testing Library works for your team; in the meantime, let's continue exploring it and adapting it to our current testing needs. Feel free to leave any comments or suggestions.
Note: If you are curious about 'cypressdocker'? Discover all you want to know about it for seamless testing and deployment! Happy Bug Hunting!
Published at DZone with permission of Enrique A Decoss. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments