๐ŸŒฒ
Websites

E2E Testing with Cypress: Automatically Verifying the User Experience

25.11.2025
โ† All articles

One of the most frustrating situations when building a website or web application is when individual pieces of code work correctly in isolation, yet everything falls apart the moment a real user tries to perform an action in an actual browser. The form refuses to submit, clicking a button does nothing, or the registration page redirects to the wrong place. End-to-end (E2E) testing exists precisely to solve this problem: it checks the application as a whole, without breaking it into pieces, exactly as if a living person were using it.

An E2E test opens a browser, navigates to a page, fills in fields, clicks buttons and confirms that the expected result appears on the screen. This approach simultaneously verifies that the frontend, backend, database and network requests all work together correctly. That is why an E2E test sits closest to the real user experience and catches genuine failures at the earliest possible stage, before a customer ever encounters them.

How E2E tests differ from unit tests

A unit test checks the smallest part of a program โ€” a single function or a single component โ€” in isolation from everything else. For example, you pass numbers into a price-calculation function and see whether the result is correct. Such tests run extremely fast and point precisely to where an error lives, but they cannot verify how the separate parts connect to one another in a single flow.

An E2E test, by contrast, checks the entire chain at once. It begins with the page the user sees and travels through every layer all the way down to the server response and changes in the database. Because of this, E2E tests run more slowly and pinpointing the exact location of an error can be harder, but they guarantee the most important thing: whether a user can actually accomplish their goal. The best projects use both types together โ€” many fast unit tests and a smaller number of deep E2E tests.

What Cypress is and why it is convenient

Cypress is a modern E2E testing tool built for web applications that runs tests directly inside a real browser. This is its greatest advantage: while the test executes, you see every step with your own eyes, watching which button was clicked and how the page changed. If a test fails, Cypress visually shows you exactly where it stopped, which makes finding the problem dramatically easier.

Another strength of Cypress is its simple and highly readable syntax. Test code is written so naturally that even a QA engineer who is just beginning to learn programming immediately understands what is happening. Cypress runs on JavaScript, its installation is completed with a few commands, and it requires no complicated additional configuration. It is precisely this simplicity that leads many teams to begin their journey into E2E testing with Cypress.

A simple test example

Let's look at the most common scenario โ€” a test that checks the login form. In the following code we visit the page, fill in the email and password fields, click the button and finally confirm that the user has moved into their personal dashboard. Notice how the code reads almost like ordinary English sentences.

describe('Login page', () => {
  it('logs in with valid credentials', () => {
    cy.visit('https://sayt.uz/login')

    cy.get('input[name="email"]').type('user@example.com')
    cy.get('input[name="password"]').type('SecretPass123')

    cy.get('button[type="submit"]').click()

    cy.url().should('include', '/cabinet')
    cy.contains('Welcome').should('be.visible')
  })
})

In this test, cy.visit opens the browser and navigates to the page, cy.get finds the field we need, and type writes text into it. The click command presses the button, while should verifies the result. If the URL contains /cabinet and the word "Welcome" is visible on the screen, the test has passed successfully and the login flow works correctly.

Selectors โ€” how to find elements

To locate elements on a page, Cypress uses CSS selectors: a tag name, a class, an identifier or attributes. In the example above we wrote input[name="email"] and selected the input field whose name is "email". Experience shows, however, that when a page's design changes, class names often change with it, and the tests tied to them break as a result.

To prevent exactly this problem, most teams recommend using dedicated data-cy attributes. This attribute is intended solely for testing and does not change even when the design does. For instance, in your HTML you mark an element as <button data-cy="submit-login">, and in the test you reference it through cy.get('[data-cy=submit-login]'). This approach makes tests far more stable and eases their maintenance over time.

Auto-wait โ€” automatic waiting

One of the biggest pains when working with web applications is the question of timing. Time passes before data on the page arrives from the server, animations need to finish, and requests must return a response. In older testing tools, developers were forced to write manual commands like "wait three seconds", which made tests slow and unreliable.

Cypress solves this problem with its auto-wait mechanism. Before each command, it automatically waits for the element to appear, become visible and be ready to be clicked. In other words, when you write cy.get and the element has not yet loaded, Cypress waits for it a little while and only then continues. This simplifies your code and aligns the tests with the speed of a real user, significantly reducing flaky failures as a result.

Running Cypress in CI

To get the full benefit from your tests, they should run automatically after every code change. Continuous integration (CI) systems provide this โ€” for example GitHub Actions, GitLab CI or other platforms. As soon as a developer pushes new code, the system automatically runs the Cypress tests, and if any test has broken, it stops the change from being deployed to the site.

To run Cypress in CI there is a special command that uses the browser in a screen-free, or "headless", mode. The following example shows a simple configuration for GitHub Actions.

name: E2E Tests
on: [push]
jobs:
  cypress:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npx cypress run

This configuration downloads the project after every push, installs the required packages and launches all the tests with the npx cypress run command. If a test ends in failure, the team is immediately notified and fixes the problem before users ever encounter it.

Cypress and Playwright โ€” a brief comparison

The main competitor to Cypress is Playwright, created by Microsoft and rapidly gaining popularity in recent years. Playwright supports several browsers (Chrome, Firefox, Safari) equally well and is considered stronger at running many tests in parallel at once. It also supports several programming languages, including Python and C#.

Cypress, on the other hand, stands out for its convenient visual interface and its ease of learning, especially for beginners. If you are working on a straightforward project that runs in a single browser and you want to write tests quickly and watch them execute, Cypress is an excellent choice. On complex, multi-browser and large-scale projects, the advantage may lie with Playwright. Both are outstanding tools, and the most important thing is simply to start using E2E tests in your project at all.

Related articles

๐ŸŒพ Agriculture and Agribusiness Website: Product Catalog and B2B Sales โค๏ธ Charity Foundation Website: Transparent Fundraising and Donor Trust ๐ŸŽ‰ Wedding Venue and Banquet Hall Website: Event Planning and Online Booking ๐Ÿš™ Car Rental Website: Vehicle Catalog, Price Calculator, and Online Booking
๐ŸŒ Language
๐Ÿ‡บ๐Ÿ‡ฟ O'zbek ๐Ÿ‡บ๐Ÿ‡ฟ ะŽะทะฑะตะบ ๐Ÿ‡ท๐Ÿ‡บ ะ ัƒััะบะธะน ๐Ÿ‡ฌ๐Ÿ‡ง English โœ“