Cypress Testing: The Guide [2024 Updated]
If you’ve ever struggled with the complexities of traditional testing frameworks like Selenium—setting up drivers, dealing with flaky tests, and waiting forever for things to load—Cypress will feel like a lifesaver.
Why? Because Cypress simplifies your testing process in ways that save both time and energy. Imagine being able to test your web app in real time, automatically waiting for elements to load, and having a robust tool that integrates seamlessly with modern JavaScript frameworks like React and Angular. It’s fast, reliable, and ideal for today’s agile development workflows.
In this guide, we'll show you how to do Cypress testing - everything! From the installation to advanced Cypress testing techniques.
1. What is Cypress?
Technically speaking, Cypress is a modern JavaScript-based end-to-end testing framework designed to run in the browser using Node.js. It was built specifically for testing web applications, with features tailored to frontend developers' needs. But, let’s step out of the tech jargon for a moment.
In simple terms, Cypress helps you see your website through the eyes of your users. It interacts with your app the way a real person would—by clicking buttons, filling out forms, and navigating pages. Whether you're testing how your login form handles incorrect passwords or how fast your shopping cart loads, Cypress is designed to mimic these real-world scenarios.
And the best part? It’s all done in real-time, making the process super interactive.
2. Key Features of Cypress
Let’s dive into what makes Cypress such a powerful tool:
- JavaScript-Based Framework: Cypress is built using JavaScript, which means it’s perfect for developers working in modern web stacks. Most web apps today run on JavaScript, so why not test them in the same language? This eliminates the hassle of dealing with different programming languages or configurations. It’s just JavaScript all the way.
- Automatic Waiting: This is where Cypress really shines. Say goodbye to those frustrating moments when your test fails because the element you’re trying to interact with hasn’t loaded yet. Cypress automatically waits for elements, animations, and API calls to complete before moving on to the next step in your test.
- Real-Time Reloading & Time Travel: With Cypress’s Time Travel feature, you can hover over each step of your test and see exactly what happened. Pair that with real-time reloading, where changes to your test file automatically reload the browser, and debugging becomes incredibly intuitive.
3. Setting Up Cypress
Setting up Cypress is surprisingly easy, especially compared to older testing frameworks like Selenium, where multiple configurations are necessary. With Cypress, you can be up and running in just a few steps—no more fumbling around with drivers or complex setups. Let’s break it down.
Step 1: Installing Cypress as a Dev Dependency
Installing Cypress as a dev dependency ensures it’s only used in the development environment and not bundled into your production code. This keeps your production build lightweight and focused on delivering your app’s core functionality, while Cypress stays in your development environment to handle testing.
To do this, open your terminal and make sure you’re in the root directory of the project you want to test. You can do this with the cd command.
cd /path-to-your-project
Now that you’re in your project directory, install Cypress by running the following command:
npm install cypress --save-dev
This command tells npm to download Cypress and install it as a dev dependency. The --save-dev flag is critical here because it tells npm to add Cypress to the devDependencies section of your package.json file. This ensures that Cypress is only used during development, making it a best practice in modern web development.
After installation, confirm that Cypress is installed by checking the node_modules directory and ensuring the cypress folder is present. You can also look for Cypress in your package.json under devDependencies.
Step 2: Launching Cypress
Once Cypress is installed, you can launch it using a simple command. Run the following command to launch the Cypress Test Runner:
npx cypress open
Using npx ensures you’re running the Cypress executable directly from your node_modules directory without needing to install it globally. This is the preferred method because it avoids global dependencies that could conflict with other projects.
Upon running the command, the Cypress Test Runner will launch in a separate window. The Test Runner is a visual interface where you can manage, write, and execute your tests in real-time. Think of it as your command center for running tests and analyzing their results.
What Happens When You First Open Cypress?
Cypress will automatically create a default folder structure in your project, including the following directories:
- /integration/: This is where your actual test files go.
- /fixtures/: Here, you store static data used in your tests (like mock API responses).
- /support/: Contains support files that are automatically included before each test (e.g., custom commands).
- /plugins/: If you need custom plugins or want to extend Cypress’s functionality, this is the place for that.
This directory structure is crucial for organizing your tests, fixtures (mock data), and support files. It’s designed to keep your tests modular and easy to maintain.
Step 3: Running Your First Test
To help you get started, Cypress includes several example tests right out of the box.
1. Locate Example Tests: Navigate to the /cypress/integration/ folder, and you’ll find example test files already set up for you. These example tests are a great way to get familiar with Cypress’s API and the types of commands you’ll be using frequently.
2. Run a Test: Simply click on one of the example test files within the Cypress Test Runner, and watch as Cypress opens a browser and executes the test. You’ll see the test steps play out in the browser itself, with each step logged in real-time in the Test Runner’s command log.
4. Writing Test in Cypress
Every Cypress test follows a standard structure that includes two core components: describe blocks, which group related tests, and it blocks, which define individual test cases.
Let’s look at a clean and concise example:
describe('My First Test', () => {
it('Visits a website and checks the title', () => {
cy.visit('https://example.com')
cy.title().should('include', 'Example Domain')
})
})
What this code snippet means:
- describe(): This is used to group together multiple related test cases. It’s a simple way of logically organizing tests, particularly when you have multiple tests covering different features of your application.
- it(): This defines an individual test case. Inside each it() block, you describe a specific action or behavior that you want to test. The first argument is the name of the test, and the second argument is a function containing the actual test steps.
- cy.visit(): This command opens the specified URL in a browser. Here, we’re using Cypress to visit the https://example.com page. This is a core Cypress function that’s essential for any test that involves navigating to a web page.
- cy.title().should(): This is where the real test happens. cy.title() grabs the title of the current page, and .should() is used to make an assertion. In this case, we’re checking that the title of the page includes the text "Example Domain". Assertions like this verify that your app behaves as expected.
Of course, we recommend that you read through Cypress documentation to gain deeper understanding of Cypress syntax.
5. Cypress Testing Best Practices
1. Use Test Isolation & Clean Up State Between Tests
- Why: Cypress maintains the browser’s state across tests by default, meaning that cookies, local storage, and sessions persist between test cases. This can cause one test's state to interfere with the next, leading to flaky tests.
- Best Practice: Each test should run independently to avoid side effects from prior tests. Use the beforeEach() hook to reset the application state or clear any persistent data. Consider resetting the database or test environment between tests if needed, especially for complex applications.
beforeEach(() => {
cy.clearCookies();
cy.clearLocalStorage();
cy.reload();
});
2. Use cy.intercept() for Network Stubbing
- Why: Cypress allows you to mock and stub HTTP requests, which speeds up tests by avoiding unnecessary reliance on actual network responses.
- Best Practice: Use cy.intercept() to stub API responses, making tests more stable and faster. You can mock successful responses or even simulate failure cases. You can test different scenarios like timeouts, 500 errors, and slow responses using cy.intercept(). This lets you handle various network conditions in a controlled way.
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers');
cy.visit('/users');
cy.wait('@getUsers');
3. Leverage Custom Commands
- Why: Custom commands help reduce repetition and make tests more readable. They allow you to abstract complex actions into reusable functions.
- Best Practice: Define custom commands for commonly repeated tasks like login, form submissions, or navigating through your app. Group related custom commands in separate files under the support/commands.js directory for better organization, and ensure they’re well-documented.
Cypress.Commands.add('login', (username, password) => {
cy.visit('/login');
cy.get('input[name=username]').type(username);
cy.get('input[name=password]').type(password);
cy.get('button[type=submit]').click();
});
4. Utilize Cypress' Built-In Retry-Ability
- Why: Cypress automatically retries commands if an assertion or action fails, ensuring that tests don’t fail unnecessarily due to temporary conditions like slow UI elements.
- Best Practice: Trust Cypress’s built-in retry mechanism instead of adding manual cy.wait() commands. Use assertions that leverage this retry ability. Minimize the use of cy.wait(), and if it’s necessary, use dynamic waits based on UI changes instead of hardcoded waits. For example, wait for a specific network call to finish using cy.intercept() instead of using cy.wait(5000).
cy.get('button').should('be.visible').click();
cy.url().should('include', '/dashboard');
5. Run Tests in Parallel and Leverage CI Pipelines
- Why: Running tests sequentially can be slow, especially in large test suites. Cypress supports parallel test execution, which speeds up your CI/CD pipeline.
- Best Practice: Set up parallelization in your CI pipeline using Cypress's Dashboard Service or third-party CI tools. Configure your CI to distribute tests across multiple machines. Optimize test splitting in your CI pipeline to balance the load across parallel instances, and make sure to run high-priority or critical tests in a dedicated suite.
cypress run --record --parallel --group <group-name>
6. Avoid Hardcoding Selectors
- Why: Hardcoded selectors based on class names or IDs are prone to breaking if the UI changes. Cypress recommends using data attributes (data-*) for more stable and predictable selectors.
- Best Practice: Use data-* attributes in your HTML elements specifically for testing. You can create a consistent selector strategy across your project (e.g., all test-related selectors use the data-cy prefix). This makes tests more maintainable, and ensures that updates to the UI don’t accidentally break your tests. Let's say your HTML code for the button is <button data-cy="submit-button">Submit</button>. Your code can be:
cy.get('[data-cy=submit-button]').click();
6. Debugging in Cypress
Cypress takes the guesswork out of debugging by integrating seamlessly with Chrome DevTools. When a test fails, you don’t have to search through endless logs or re-run the test multiple times to figure out what went wrong. Instead, Cypress gives you real-time feedback, making it easy to pinpoint the exact issue. You can use:
- Time Travel: Cypress takes snapshots at each step of the test execution. You can hover over each command in the Command Log to see what your app looked like at that specific moment.
- Chrome DevTools Integration: Cypress allows you to directly interact with Chrome DevTools while tests are running. You can pause the test execution, inspect elements, view network requests, and step through the code.
Cypress also excels in how it handles test failures. When a test fails, Cypress doesn’t just leave you with a generic error message. It provides a full trace of the failure, including a detailed explanation of the error, a screenshot of the browser at the moment of failure, and even a video of the test execution if video recording is enabled.
- Screenshots on Failure: By default, Cypress captures a screenshot whenever a test fails. This screenshot is automatically saved, allowing you to visually inspect the state of the application at the time of failure. This feature is particularly useful when diagnosing issues in headless test runs, where you don’t have a browser window open.
- Video Recording: In addition to screenshots, Cypress can also record videos of your entire test suite execution. If a test fails, you can replay the video to see exactly what happened.
7. Cypress vs Selenium vs Playwright
Here's a quick comparison of Cypress vs other open-source frameworks for you:
Criteria | Cypress | Selenium | Playwright |
Primary Language | JavaScript (Node.js) | Multiple (Java, Python, C#, Ruby, JavaScript) | JavaScript, Python, C#, Java |
Supported Browsers | Chrome, Firefox, Edge, Electron | Chrome, Firefox, Edge, Safari, IE | Chrome, Firefox, Edge, WebKit |
Cross-browser Support | Limited (no Safari or IE) | Full support for all major browsers | Full support, including WebKit (for Safari-like behavior) |
Setup Complexity | Easy, quick to install with NPM | Moderate, requires WebDriver setup | Easy, built-in browser binaries for faster setup |
Speed | Fast due to direct execution in the browser | Slower due to WebDriver architecture (server-client model) | Fast, like Cypress, due to native browser control |
Test Reliability | High (minimal flakiness, automatic waits) | Lower (prone to flakiness, requires explicit waits) | High (automatic waits, handles flakiness well) |
Parallel Execution | Built-in support through dashboard (paid) | Requires configuration (grid setup) | Built-in support for parallel testing |
API Testing | Supports both frontend and API testing within same framework | Not built-in, requires additional libraries | Supports API testing alongside web testing |
Architecture | Runs directly in the browser with Node.js | Uses WebDriver to interact with browsers via browser drivers | Direct browser control like Cypress |
Debugging Capabilities | Excellent (real-time reloading, time travel, detailed logs) | Good (browser developer tools, log analysis) | Excellent (tracing, screenshots, network activity) |
Flakiness | Low, due to control over browser and automatic waits | Higher, requires more manual handling of waits | Low, automatic handling of waits and retries |
Community & Ecosystem | Growing, but smaller compared to Selenium | Very large, mature, and widely used | Growing rapidly, strong backing by Microsoft |
Mobile Testing Support | No direct support | Supports mobile testing via Appium | No direct support, but can emulate mobile browsers |
Multiple Tabs/Windows | Limited (no multi-tab support) | Full support for multiple tabs | Full support for multiple tabs and browser contexts |
Headless Mode | Yes, supports headless mode for Chrome, Firefox, Edge | Yes, via drivers | Yes, supports headless mode for all supported browsers |
Integration with CI/CD | Easy integration with Jenkins, GitHub Actions, etc. | Requires more setup | Easy integration, built-in tools for CI/CD |
Open Source/License | Open-source, with paid features (Dashboard) | Fully open-source | Fully open-source |
8. Explore No-code and Low-code Options
Cypress is an amazing framework, no doubt. However, you know the struggle. We’ve all been there—running a test suite that works perfectly one day, only to have it mysteriously fail the next. Flaky tests are those unreliable ones that randomly pass or fail, often due to timing issues, async calls, or dependencies on third-party services. They leave you scratching your head, wondering, “Did the code break, or is the test just acting up?”
Then comes the setup headaches. Cypress is among the easier ones to set up. Some testing frameworks require elaborate setup processes with intricate dependencies, browser drivers, and configurations, such as Selenium, just getting your environment up and running can feel like more of a coding project than your actual app development.
And then when tests fail, sometimes you’re left staring at cryptic error logs or digging through endless console messages. Figuring out what went wrong can take longer than writing the actual test.
The solution? No-code/low-code testing tools.
It's no buzzword. No-code/low-code testing tools simplify your testing life in so many ways.
1. Faster Test Creation
Traditional code-based testing can be slow, requiring developers to write test scripts for each scenario. On the other hand, Katalon Studio offers up to 3 testing modes for maximum flexibility:
2. Lower Technical Barrier
No-code/low-code tools empower non-technical team members like QA engineers, business analysts, or even stakeholders to participate in testing. These tools reduce dependency on the development team for creating or maintaining tests, making testing more collaborative.
3. Maintenance Made Easy
As codebases evolve, traditional test scripts often break due to changing elements or workflows, leading to high test maintenance costs. No-code tools simplify this by offering self-healing tests, where the tool automatically adapts to minor changes in the application’s UI or logic.
4. Scalability Without Complexity
No-code tools make it easy to scale test coverage without the complexity of writing more code. You can rapidly create new tests by cloning or modifying existing workflows and scale testing across multiple environments with minimal effort. In Katalon, you can test across a wide range of browsers, devices, and operating systems within one place.