
UI testing is essential to ensure web applications behave correctly, but writing reliable tests can be challenging. Flaky tests, dynamic elements, and inconsistent browser behavior often slow down development and increase maintenance overhead, leaving testers frustrated and releases at risk.
Cypress addresses these pain points with a developer-friendly framework that handles asynchronous behavior automatically, reloads tests in real time, and provides clear feedback on every interaction. Its architecture supports both end-to-end and component-level testing, making it easier to write fast, reliable, and maintainable UI tests.
This guide covers Cypress fundamentals, practical approaches to building stable tests, best practices for reliability, and strategies for testing on real devices to catch issues before users do.
Cypress is a modern JavaScript-based testing framework designed specifically for web applications. Unlike traditional testing tools that operate outside the browser, Cypress runs directly inside it, giving testers real-time access to the DOM and network activity. This approach reduces flakiness, speeds up execution, and simplifies debugging.
Key ways Cypress supports UI testing include:
Cypress has a unique architecture that sets it apart from traditional testing tools. Instead of running tests outside the browser, Cypress executes them within the same run-loop as the application. This direct integration gives testers real-time access to the DOM, network requests, and browser events, which makes tests faster, more reliable, and easier to debug.
Key components of Cypress architecture include:
This architecture allows Cypress to deliver fast, deterministic UI tests that accurately reflect how users interact with the application, while reducing flakiness and maintenance overhead.
End-to-end (E2E) testing in Cypress validates complete user workflows, ensuring the application behaves as expected from start to finish. It simulates real user actions, including navigation, form submissions, and API interactions, to catch issues before they reach production.
Step 1: Set up the testing environment
Install Cypress in your project with npm install cypress --save-dev and ensure your application is running locally or on a staging server. Configure the cypress.config.js file with the correct baseUrl and any required environment variables for consistent testing.
Step 2: Identify critical user workflows
Determine the most important user journeys, such as logging in, adding items to a cart, or updating a profile. Document the steps you want to test, including expected outcomes for each action.
Step 3: Select elements and simulate interactions
Use Cypress commands to interact with the UI. For example, to type into an input field and click a button:
cy.get('[data-cy="username"]').type('testuser');
cy.get('[data-cy="login-button"]').click();
Cypress automatically waits for elements to appear and actions to complete, reducing flakiness in your tests.
Step 4: Validate UI behavior
Assert that UI elements behave correctly after interactions. For example, check that a success message appears after login:
cy.get('[data-cy="welcome-message"]').should('contain.text', 'Welcome, testuser');
You can also validate dynamic content, form submissions, and navigation results to ensure workflows perform as intended.
Step 5: Handle network requests
Intercept API calls with cy.intercept() to monitor requests, simulate responses, or test edge cases. For example:
cy.intercept('GET', '/api/user', { fixture: 'user.json' }).as('getUser');
cy.wait('@getUser');
This ensures your frontend reacts correctly to different server responses without relying on a live backend.
Step 6: Run and review tests
Execute tests in Cypress’s interactive Test Runner for real-time feedback or in headless mode for CI/CD pipelines using npx cypress run. Review logs, snapshots, and any failures to quickly identify issues and maintain stable workflows.
Component-level testing in Cypress focuses on verifying individual UI components in isolation, rather than entire user workflows. This helps catch issues early, ensures components behave consistently, and reduces debugging time when something breaks.
Step 1: Set up component testing
Ensure Cypress is configured for component testing by installing the necessary dependencies, such as @cypress/react for React or @cypress/vue for Vue components. Then configure cypress.config.js with the appropriate component setup:
import { defineConfig } from 'cypress';
export default defineConfig({
component: {
devServer: {
framework: 'react',
bundler: 'webpack',
},
},
});
Step 2: Mount the component
Use Cypress commands to mount the component in a test environment. For React, for example:
import { mount } from '@cypress/react';
import Button from './Button';
mount(<Button label="Click Me" />);
This allows the component to render in isolation, so tests focus only on its behavior.
Step 3: Interact with the component
Simulate user actions on the component using Cypress commands. For example, clicking a button and checking its effect:
cy.get('button').click();
cy.get('button').should('contain.text', 'Clicked');
Cypress automatically waits for state updates and DOM changes, reducing flakiness.
Step 4: Validate component behavior
Assert that the component responds correctly to different inputs, props, or events. This includes checking text content, visibility, style changes, or emitted events:
cy.get('button').should('have.class', 'active');
Step 5: Integrate with test suites
Component tests can be run independently or alongside E2E tests. Running them early in the development cycle helps catch regressions before integrating into full workflows.
Writing stable UI tests in Cypress requires more than just interacting with elements—it involves planning for asynchronous behavior, dynamic content, and consistent selectors. Implementing best practices and leveraging Cypress’s built-in features can significantly reduce flakiness and make tests easier to maintain over time.
Key tips and tricks for building stable Cypress tests include:
Even with Cypress’s powerful features, testers often encounter challenges that can make UI automation less reliable or harder to maintain. Understanding these pitfalls helps teams anticipate issues and implement strategies to avoid them.
Common challenges in Cypress UI testing include:
Testing on real devices is crucial because emulators and local browsers cannot fully replicate how users experience an application. Differences in screen size, touch interactions, performance, and browser rendering can lead to layout issues, broken functionality, or slow responses that only appear on actual devices. End-to-end tests on real devices catch these problems before they reach users.
Running tests on real devices ensures compatibility across different operating systems, browser versions, and hardware configurations. This is especially important for responsive designs, dynamic content, or applications with complex client-side logic.
Platforms like BrowserStack provide instant access to hundreds of real devices and browsers, allowing testers to run Cypress tests in parallel without managing physical hardware. This means you can validate your UI across multiple environments, catch device-specific issues early, and accelerate release cycles—all while integrating seamlessly with existing CI/CD pipelines.
Cypress enables fast, reliable UI testing by running directly in the browser, providing real-time feedback, automatic waiting, and interactive debugging. Following best practices like using stable data-* selectors, managing network requests, and structuring tests with beforeEach() reduces flakiness and keeps tests maintainable as applications evolve.
Even with robust Cypress tests, some issues only appear on actual devices. Running tests on real devices with platforms like BrowserStack ensures cross-device and cross-browser consistency, uncovers layout or interaction problems that emulators miss, and accelerates release cycles with confidence that the UI works correctly for all users.
Get visual proof, steps to reproduce and technical logs with one click
Try Bird on your next bug - you’ll love it
“Game changer”
Julie, Head of QA
Try Bird later, from your desktop