CYPRESS Tutorial Course

Introduction to Cypress

Overview of Cypress

Cypress is an end-to-end testing framework that is designed to make it easier to write and run tests for web applications. It provides a simple and powerful API for interacting with your application, making it a popular choice for modern web testing.

Key Features
  • Real-time Browser Interaction: Cypress runs directly in the browser, allowing for real-time interaction and debugging.
  • Automatic Waiting: Cypress automatically waits for elements to be available before performing actions, reducing the need for manual waits and timeouts.
  • Built-in Assertions: Cypress comes with a rich set of built-in assertions to verify the state of your application.
  • Time Travel: Cypress provides a unique time-travel feature that lets you see snapshots of your application at different stages of the test.

Installation and Setup

Installing Cypress is straightforward using npm. Run the following command to add Cypress to your project:

npm install cypress --save-dev

After installation, you can open Cypress with the following command:

npx cypress open

Writing Your First Test

Cypress tests are written in JavaScript and use a BDD-style syntax. Here's a simple example of a Cypress test:

1describe('My First Test', () => {
2    it('Visits the Kitchen Sink', () => {
3        cy.visit('https://example.cypress.io')
4        cy.contains('type').click()
5        cy.url().should('include', '/commands/actions')
6    })
7})

Cypress Test Runner

Understanding the Test Runner Interface

The Cypress Test Runner Interface is a crucial part of Cypress that provides a visual interface for running and debugging tests. It simplifies the process of interacting with your tests and understanding their execution results.

Open Cypress
npx cypress open
Cypress Test Runner Layout
  • Tests list - On the left side of the interface, you'll find a list of your test files. Clicking on a test file will run all the tests within that file. The list dynamically updates as you add or remove test files.
  • Test Execution Area - The main area of the interface displays the currently running test. Here, you'll see the progress of your test, including the status of each test step. It also shows the output of console.log statements and other logs generated during test execution.
  • Command Log - The command log at the bottom of the interface provides a detailed record of each action performed during the test execution. It shows commands, assertions, and their status. You can click on any command in the log to see more details and inspect the state of the application at that point in time.
  • Viewport - The viewport displays a live preview of your application as the test runs. This allows you to see exactly how the application looks and behaves during test execution. You can interact with the application through this viewport, providing real-time feedback on the state of your tests.
Cypress Interactive Features
  • Time Travel - Cypress's Time Travel feature allows you to step back in time and view the state of your application at any point during the test. By clicking on a command in the command log, you can see a snapshot of the application at that specific moment.
  • The Test Runner Interface provides several debugging tools
  • Retry - If a test fails, Cypress can automatically retry the test up to a configured number of times. This helps account for flakiness and transient issues, improving test reliability.
  • Viewport size - You can change the viewport size within the Test Runner to simulate different device screens and resolutions. This helps ensure that your application is responsive and behaves correctly across various devices.
  • Cypress allows you to intercept and stub network requests. You can configure these intercepts within the Test Runner to test different network conditions and responses.

Running tests

npx cypress run

Debugging Tests in Test Runner

1it('should do something', () => {
2  cy.visit('https://example.com');
3  cy.get('#element').click();
4  debugger; // Execution will pause here
5  cy.get('#result').should('contain', 'Expected Result');
6});
You can also use cypress.config.js to configure the retries.
1module.exports = {
2  e2e: {
3    retries: {
4      runMode: 2, // Retries when running `npx cypress run`
5      openMode: 0, // Retries when running `npx cypress open`
6    },
7  },
8};

Writing Tests in Cypress

Cypress Test Syntax

1describe('My Test Suite', () => {
2  it('should perform a specific action', () => {
3    // Test code here
4  });
5});

UI automation

1cy.visit('https://example.com');
2cy.get('#button').click();
3cy.get('#input').type('Some Text');
4cy.get('#dropdown').select('Option Value');
5cy.get('#checkbox').should('be.checked');
6
7cy.get('#button').click();
8cy.get('#result').should('contain', 'Success');

Assertions and Expect Statements

1cy.get('#element').should('be.visible');
2cy.get('#element').should('contain', 'Expected Text');
3cy.get('#element').should('exist');
4cy.url().should('include', '/expected-path');
5cy.get('#input').should('have.value', 'Expected Value');

Using Fixtures for Test Data

Fixtures are used to load external data into your tests. You can use them to provide mock data or configuration settings.

In your test,
1cy.fixture('data.json').then((data) => {
2  cy.get('#input').type(data.someField);
3});
In cypress/fixtures/data.json:
1{
2  "someField": "Test Data"
3}

Hooks

1before(() => {
2  // Setup code
3});
4
5beforeEach(() => {
6  // Code to run before each test
7});
8
9after(() => {
10  // Teardown code
11});
12
13afterEach(() => {
14  // Code to run after each test
15});

Handling Network Requests

Intercepting Requests and Responses

1cy.intercept('GET', '/api/data', { fixture: 'data.json' });
2cy.visit('https://example.com');

Stubbing and Spying on Requests

1cy.intercept('POST', '/api/login', {
2  statusCode: 200,
3  body: { token: 'fake-token' },
4});

Assertions Cypress

1cy.intercept('GET', '/api/data').as('getData');
2cy.visit('https://example.com');
3cy.wait('@getData').its('response.statusCode').should('eq', 200);

Testing APIs with Cypress

Sending GET and POST requests
1cy.request('GET', 'https://api.example.com/data')
2  .then((response) => {
3    expect(response.status).to.eq(200);
4    expect(response.body).to.have.property('key', 'value');
5  });
6
7  cy.request({
8  method: 'POST',
9  url: 'https://api.example.com/submit',
10  body: { key: 'value' },
11}).then((response) => {
12  expect(response.status).to.eq(201);
13  expect(response.body).to.have.property('result', 'success');
14});
Custom response
1cy.intercept('POST', '/api/login', {
2  statusCode: 200,
3  body: { token: 'fake-token' },
4});
5cy.visit('https://example.com');
Asserting API
1cy.request('GET', 'https://api.example.com/data')
2  .its('status')
3  .should('equal', 200);
4
5
6  cy.request('GET', 'https://api.example.com/data')
7  .its('body')
8  .should('deep.equal', { key: 'value' });
9
10  cy.request('GET', 'https://api.example.com/data')
11  .its('headers')
12  .should('have.property', 'content-type')
13  .and('include', 'application/json');
Simulating Server Error
1cy.intercept('GET', '/api/data', {
2  statusCode: 500,
3  body: { error: 'Internal Server Error' },
4});
5cy.visit('https://example.com');
6
7cy.intercept('GET', '/api/data', {
8  statusCode: 404,
9  body: { error: 'Not Found' },
10});
11cy.visit('https://example.com');
Using Aliases for Requests
1cy.intercept('GET', '/api/data').as('getData');
2cy.visit('https://example.com');
3
4cy.wait('@getData').its('response.statusCode').should('equal', 200);
Chaining
1cy.request('POST', '/api/login', { username: 'user', password: 'pass' })
2  .then((loginResponse) => {
3    cy.request('GET', '/api/profile')
4      .its('body')
5      .should('include', { username: 'user' });
6  });
Handling dynamic data
1let dynamicValue;
2
3cy.request('GET', '/api/data')
4  .then((response) => {
5    dynamicValue = response.body.key;
6  });
7
8cy.visit('https://example.com');
9cy.get('#element').should('contain', dynamicValue);
Validating Request Payloads
1cy.intercept('POST', '/api/submit', (req) => {
2  expect(req.body).to.have.property('key', 'value');
3  req.reply({ statusCode: 200 });
4});
5cy.visit('https://example.com');

Cypress and Continuous Integration (CI)

Setting Up Cypress in CI

Setting up Cypress in a Continuous Integration (CI) environment involves configuring your CI/CD pipeline to run Cypress tests as part of your build process.

Create a GitHub Actions workflow file (e.g., .github/workflows/cypress.yml):
1name: Cypress Tests
2
3on: [push, pull_request]
4
5jobs:
6  cypress:
7    runs-on: ubuntu-latest
8
9    steps:
10    - name: Checkout code
11      uses: actions/checkout@v3
12
13    - name: Install dependencies
14      run: npm install
15
16    - name: Run Cypress tests
17      run: npx cypress run

Running Tests in Parallel

Running tests in parallel with Cypress can significantly speed up your test suite, especially for large projects. Cypress supports parallel test execution through its Dashboard Service.

Cypress Commands

Custom Commands

1Cypress.Commands.add('login', (username, password) => {
2  cy.get('input[name=username]').type(username);
3  cy.get('input[name=password]').type(password);
4  cy.get('button[type=submit]').click();
5});
6
7cy.login('user', 'pass');

Chaining Commands in Cypress

1cy.visit('https://example.com')
2  .get('#button')
3  .click()
4  .get('#result')
5  .should('contain', 'Success');

Cypress Testing Strategies

Component Testing with Cypress

For React: Install @cypress/react and @testing-library/react.

Visual Testing Strategies

Visual testing compares the rendered output of your application with a reference image to detect visual discrepancies. This is useful for catching unintended visual changes and regressions in your UI.
npm install cypress-image-snapshot --save-dev
1import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin';
2
3addMatchImageSnapshotPlugin();
1describe('Visual Testing', () => {
2  beforeEach(() => {
3    cy.visit('/your-page');
4  });
5
6  it('should match the visual snapshot', () => {
7    cy.get('your-selector').toMatchImageSnapshot();
8  });
9});

Environment Variables in Cypress

Setting Up Environment Variables

You can keep variables in cypress.config.js
1module.exports = {
2  e2e: {
3    setupNodeEvents(on, config) {
4      // Implement node event listeners here
5    },
6    env: {
7      apiUrl: 'https://api.example.com',
8      username: 'testuser',
9      password: 'password123'
10    }
11  }
12}

process.env.NODE_ENV is an environment variable used in Node.js and various JavaScript environments to indicate the current execution environment of the application. It is commonly used to differentiate between various stages of the development lifecycle, such as development, testing, and production.

Common values of NODE_ENV are
  • development
  • production
  • test
e.g. to set the Environment to "test", you can use below command
NODE_ENV=test node app.js

Then you can use below code to read values from specific environment file (.env.development, .env.test etc). Please note that you will need to install dotenv package!

1require('dotenv').config({ path: `.env.${process.env.NODE_ENV}` });
2let apiUrl = process.env.API_URL
1require('dotenv').config({ path: `.env.${process.env.NODE_ENV}` });
2
3module.exports = {
4  e2e: {
5    setupNodeEvents(on, config) {
6      // Implement node event listeners here
7    },
8    env: {
9      apiUrl: process.env.API_URL,
10      username: process.env.USERNAME,
11      password: process.env.PASSWORD
12    }
13  }
14}
Accessing Environment Variables in Tests
1describe('Environment Variables', () => {
2  it('should use environment variables', () => {
3    const apiUrl = Cypress.env('apiUrl');
4    const username = Cypress.env('username');
5    const password = Cypress.env('password');
6
7    cy.log(`API URL: ${apiUrl}`);
8
9  });
10});
Overriding environment variables
1//In command line
2npx cypress run --env apiUrl=https://staging.api.example.com,username=staginguser
3
4//in tests
5describe('Dynamic Environment Variables', () => {
6  beforeEach(() => {
7    Cypress.env('apiUrl', 'https://staging.api.example.com');
8  });
9
10  it('should use overridden environment variables', () => {
11    const apiUrl = Cypress.env('apiUrl');
12    cy.log(`API URL: ${apiUrl}`);
13  });
14});

Advanced Cypress Topics

Performance Testing with Cypress

1describe('Page Load Performance', () => {
2  it('should measure page load time', () => {
3    const startTime = new Date().getTime();
4
5    cy.visit('/your-page', {
6      onLoad: () => {
7        const endTime = new Date().getTime();
8        const loadTime = endTime - startTime;
9        cy.log(`Page load time: ${loadTime} ms`);
10      }
11    });
12  });
13});
1describe('API Performance', () => {
2  it('should measure API response time', () => {
3    cy.intercept('GET', '/api/endpoint', (req) => {
4      req.continue((res) => {
5        const responseTime = res.duration; // Duration in milliseconds
6        cy.log(`API response time: ${responseTime} ms`);
7      });
8    }).as('getEndpoint');
9
10    cy.visit('/your-page');
11    cy.wait('@getEndpoint');
12  });
13});
Using Lighthouse
npm install -g lighthouse
1const { exec } = require('child_process');
2
3describe('Performance Testing with Lighthouse', () => {
4  it('should run Lighthouse audit', (done) => {
5    exec('lighthouse https://your-site.com --output=json --output-path=./report.json', (error, stdout, stderr) => {
6      if (error) {
7        console.error(`Error executing Lighthouse: ${error}`);
8        done(error);
9        return;
10      }
11      cy.readFile('./report.json').then((report) => {
12        cy.log('Lighthouse report generated');
13        // Further analysis of Lighthouse report can be done here
14      });
15      done();
16    });
17  });
18});

Security Testing with Cypress

XSS
1describe('XSS Testing', () => {
2  it('should prevent XSS attacks', () => {
3    cy.visit('/input-page'); // Visit a page with input fields
4
5    cy.get('input[name="userInput"]').type('<script>alert("XSS")</script>');
6    cy.get('form').submit();
7
8    // Check if the alert or injected script is not executed
9    cy.on('window:alert', (text) => {
10      expect(text).to.not.equal('XSS');
11    });
12  });
13});
CSRF
1describe('CSRF Protection', () => {
2  it('should include CSRF token in requests', () => {
3    cy.visit('/protected-page');
4
5    cy.request({
6      method: 'POST',
7      url: '/api/endpoint',
8      form: true,
9      body: {
10        // Ensure CSRF token is included
11        csrfToken: Cypress.env('csrfToken'),
12        data: 'test'
13      },
14      failOnStatusCode: false
15    }).then((response) => {
16      expect(response.status).to.eq(403); // Expect a forbidden status if CSRF is not valid
17    });
18  });
19});
Authentication testing
1describe('Authentication Testing', () => {
2  it('should not allow access to protected routes without login', () => {
3    cy.visit('/protected-page', { failOnStatusCode: false });
4    cy.url().should('include', '/login');
5  });
6
7  it('should allow access to protected routes after login', () => {
8    cy.visit('/login');
9    cy.get('input[name="username"]').type('validUser');
10    cy.get('input[name="password"]').type('validPassword');
11    cy.get('form').submit();
12
13    cy.visit('/protected-page');
14    cy.url().should('not.include', '/login');
15  });
16});
17
18
19describe('Authorization Testing', () => {
20  it('should restrict access based on user roles', () => {
21    cy.loginAs('admin'); // Custom command to login as admin
22    cy.visit('/admin-dashboard');
23    cy.get('h1').should('contain', 'Admin Dashboard');
24
25    cy.loginAs('user'); // Login as a regular user
26    cy.visit('/admin-dashboard', { failOnStatusCode: false });
27    cy.url().should('include', '/403'); // Expect forbidden access
28  });
29});
Security Headers Testing
1describe('Security Headers', () => {
2  it('should include security headers', () => {
3    cy.request('/').then((response) => {
4      expect(response.headers).to.have.property('x-content-type-options', 'nosniff');
5      expect(response.headers).to.have.property('x-frame-options', 'DENY');
6      expect(response.headers).to.have.property('x-xss-protection', '1; mode=block');
7    });
8  });
9});

Accessibility Testing

npm install --save-dev cypress cypress-axe
Example cypress/support/index.js Configuration:
import '@cypress/axe';
1describe('Accessibility Testing', () => {
2  beforeEach(() => {
3    cy.visit('/'); // Visit your application page
4    cy.injectAxe(); // Inject Axe into the page
5  });
6
7  it('should have no accessibility violations', () => {
8    cy.checkA11y(); // Check for accessibility violations
9  });
10});
11
12
13describe('Accessibility Testing with Specific Checks', () => {
14  beforeEach(() => {
15    cy.visit('/'); // Visit your application page
16    cy.injectAxe(); // Inject Axe into the page
17  });
18
19  it('should have no accessibility violations on the main content', () => {
20    cy.checkA11y('main'); // Check for accessibility violations only within the main content area
21  });
22});

Using Plugins in Cypress

Installing and Managing Plugins

1npm install --save-dev mochawesome mochawesome-merge mochawesome-report-generator
2npm install --save-dev cypress-mochawesome-reporter
import 'cypress-mochawesome-reporter/register';
Generate Report
1npx mochawesome-merge cypress/reports/*.json > cypress/reports/report.json
2npx mochawesome-report-generator cypress/reports/report.json
You can find the plugins at Plugins
  • cypress-plugin-snapshots
  • cypress-cucumber-preprocessor
  • cypress-graphql-query

Creating Custom Plugins

Create a cypress/plugins/index.js file if it doesn't already exist.
1module.exports = (on, config) => {
2  on('task', {
3    myCustomTask() {
4      // Your custom task logic
5      return null;
6    }
7  });
8};
Use the plugin
cy.task('myCustomTask');