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 Cypressnpx cypress open
- 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.
- 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});
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});
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 requests1cy.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});
1cy.intercept('POST', '/api/login', {
2 statusCode: 200,
3 body: { token: 'fake-token' },
4});
5cy.visit('https://example.com');
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');
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');
1cy.intercept('GET', '/api/data').as('getData');
2cy.visit('https://example.com');
3
4cy.wait('@getData').its('response.statusCode').should('equal', 200);
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 });
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);
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.js1module.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
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}
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});
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});
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
XSS1describe('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});
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});
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});
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
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';
1npx mochawesome-merge cypress/reports/*.json > cypress/reports/report.json
2npx mochawesome-report-generator cypress/reports/report.json
Popular Plugins for Cypress
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};
cy.task('myCustomTask');