DEV Community

Soumaya Erradi
Soumaya Erradi

Posted on

Cypress and Angular: A Step-by-Step Guide to Efficient E2E Testing

Testing plays a crucial role in ensuring the stability and performance of modern web applications. As applications grow more complex, the need for reliable, fast, and easy-to-use testing tools has become more apparent. This is where Cypress steps in.

Cypress is a modern E2E testing framework designed to address some of the pain points developers face with traditional testing tools like Selenium. It is fast, reliable and provides a great developer experience by integrating seamlessly with JavaScript frameworks such as Angular. Unlike other testing tools that execute outside the browser, Cypress runs directly inside the browser, giving it deep access to the DOM, network requests and user events. This architecture makes Cypress an ideal solution for testing complex Angular applications, which often rely on heavy client-side logic and asynchronous interactions.

One of the standout features of Cypress is its ability to automatically wait for commands to complete. In traditional testing tools, developers often need to introduce manual waits or timeouts to handle asynchronous tasks. With Cypress, however, it intelligently waits for elements to appear, HTTP requests to finish, and Angular’s rendering cycle to complete, making your tests more stable and less prone to timing issues.

Setting Up Cypress in an Angular Project

To get started, you’ll need to integrate Cypress into your existing Angular project. First, ensure that you have the necessary prerequisites: Node.js, the Angular CLI and an Angular project ready to go.

Step 1: Installing Cypress

Begin by navigating to your Angular project’s root directory and installing Cypress as a development dependency using npm:

npm install cypress --save-dev
Enter fullscreen mode Exit fullscreen mode

Step 2: Configuring Cypress

Once installed, Cypress uses a TypeScript configuration file (cypress.config.ts). This file allows more flexibility and type support. In your project, Cypress will create this file automatically after running npm run cypress:open. You can also add the following to package.json to allow launching Cypress:

{
  "scripts": {
    "cypress:open": "cypress open",
    "cypress:run": "cypress run"
  }
}
Enter fullscreen mode Exit fullscreen mode

These scripts will allow you to either open the Cypress Test Runner (npm run cypress:open) or run your tests headlessly (npm run cypress:run).

The Cypress configuration will be created in a new cypress.config.ts file. Here's an example configuration:

import { defineConfig } from "cypress";

export default defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      // implement node event listeners here
    },
    baseUrl: "http://localhost:4200",
    supportFile: "cypress/support/e2e.ts",
  },
});
Enter fullscreen mode Exit fullscreen mode

This setup ensures that Cypress points to the correct base URL for your Angular app.

Step 3: Cypress Directory Structure

After running npm run cypress:open for the first time, Cypress automatically creates a directory structure for managing your tests and configurations. The default directory tree looks like this:

/cypress
  ├── /fixtures
  ├── /support
  │   └── e2e.ts
  ├── /e2e
  └── cypress.config.ts
Enter fullscreen mode Exit fullscreen mode

Here’s a brief overview of each folder and its purpose:

  • /fixtures: This folder is used to store test data (e.g., JSON files) that can be imported into your tests. You can use fixtures to mock API responses or provide predefined data.
  • /support: This folder contains reusable functions, custom commands, or global configuration for your tests. For example, you can define shared commands here.
  • /e2e: This folder is where your actual test files live. Each .spec.ts file in this directory corresponds to a different set of tests, often grouped by component or feature.

Writing Your First Cypress Test

Once the setup is done, ensure that your Angular application is running by using:

ng serve
Enter fullscreen mode Exit fullscreen mode

Then, let’s write a simple test inside the cypress/e2e folder. Create a new test file named first-test.spec.ts:

describe('My First Test', () => {
  it('Visits the Angular app', () => {
    cy.visit('/');
    cy.contains('Welcome');
  });
});
Enter fullscreen mode Exit fullscreen mode

This test navigates to the root URL of your Angular app (using the baseUrl defined in cypress.config.ts) and checks if the text "Welcome" appears on the page.

To run this test, open Cypress using the following command:

npm run cypress:open
Enter fullscreen mode Exit fullscreen mode

Once Cypress launches, you should see the test file listed in the test runner. Clicking on it will automatically open a browser window, where Cypress will execute the test and display the results.

Understanding Cypress Configuration and Test Structure

Cypress provides a flexible configuration system that allows you to customize its behavior to suit your project’s needs. The cypress.config.ts file is the central hub for managing various settings, including base URLs, test timeouts and environment variables.

For example, you might have a different API endpoint for each environment and you can specify these in the env section of the config file:

{
  env: {
    apiUrl: "https://api.dev.example.com"
  }
}
Enter fullscreen mode Exit fullscreen mode

Within your test files, you can then access this environment variable like so:

cy.request(Cypress.env('apiUrl') + '/items')
  .its('status')
  .should('equal', 200);
Enter fullscreen mode Exit fullscreen mode

Writing E2E Tests for Angular Components

Once you’ve set up Cypress, you can start writing meaningful E2E tests for your Angular components. Let’s begin with a simple test that verifies user interactions with an input field:

describe('Input Field Test', () => {
  it('Types into the input field', () => {
    cy.visit('/');
    cy.get('input[name="username"]').type('CypressUser');
    cy.get('input[name="username"]').should('have.value', 'CypressUser');
  });
});
Enter fullscreen mode Exit fullscreen mode

This test simulates typing into a username field and verifies that the input value matches what the user typed.

When dealing with Angular’s asynchronous operations, such as Observables, Cypress’s automatic waiting for network requests and UI updates makes testing these flows easier:

describe('Async Test with Observables', () => {
  it('Waits for an observable to complete', () => {
    cy.visit('/');
    cy.get('button.load-data').click();
    cy.get('.data').should('have.length', 5);
  });
});
Enter fullscreen mode Exit fullscreen mode

This test clicks a button to load data, then verifies if the data is rendered correctly once the operation completes.

Directory Structure and Organization

As your test suite grows, it’s important to maintain an organized directory structure. Typically, Cypress test files are structured according to your application’s components or features. For example:

/cypress
  ├── /e2e
  │   ├── /components
  │   │   └── header.spec.ts
  │   └── /features
  │       └── user-login.spec.ts
Enter fullscreen mode Exit fullscreen mode

Organizing tests this way makes it easier to locate and manage them, especially in large projects. You can also group tests based on the scope of functionality, such as components, services, or end-to-end flows.

Best Practices for Cypress in Angular Projects

To ensure that your Cypress tests are maintainable and scalable, it’s important to follow best practices. This includes writing custom commands for common interactions. For example, you can add a reusable login command to the cypress/support/e2e.ts file:

Cypress.Commands.add('login', (username, password) => {
  cy.get('input[name="username"]').type(username);
  cy.get('input[name="password"]').type(password);
  cy.get('button[type="submit"]').click();
});
Enter fullscreen mode Exit fullscreen mode

This approach improves test readability and maintainability, allowing you to call cy.login('user', 'password') whenever a login step is required.

To enhance performance, avoid excessive use of cy.wait() and leverage Cypress’s built-in retry logic. Cypress automatically retries failed assertions and commands until they pass, reducing the need for explicit waits.

Conclusion

Now that Cypress is set up with your Angular project, you have a solid foundation for writing reliable end-to-end tests. With its intuitive features and seamless integration, you can easily test complex interactions and ensure your application behaves as expected. All that’s left is to start writing your tests and enjoy a smoother, more efficient development process.

Top comments (1)

Collapse
 
jangelodev profile image
João Angelo

Hi Soumaya Erradi,
Thanks for sharing.