Skip to content

BrianGator/Cypress-Automation-Testing-Hero-JavaScript-Showcase

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cypress Automation Testing Framework - Zero To Hero 🚀

A comprehensive JavaScript-based Cypress automation testing framework designed to take you from beginner to advanced testing hero. This repository contains complete examples, best practices, and practical guides for mastering Cypress testing.


📋 Table of Contents


Overview

This project is your complete guide to mastering Cypress for automated testing. Whether you're testing web applications or APIs, you'll learn everything from basic setup to advanced frameworks and real-world testing scenarios.

What You'll Learn:

  • Cypress fundamentals and core concepts
  • Web and API testing automation
  • Advanced selector strategies
  • Real-world testing scenarios
  • CI/CD integration (Jenkins, GitHub Actions)
  • Git and GitHub workflows
  • Page Object Model patterns
  • Data-driven testing approaches

Why Cypress?

  • ⚡ Fast, reliable, and intuitive
  • 🎯 Better debugging experience
  • 📱 Responsive testing capabilities
  • 🔄 Excellent CI/CD integration
  • 💪 Strong community and support

Prerequisites

Before you begin, ensure you have the following installed on your system:

System Requirements

  • Node.js (v14.0 or higher) - Download
  • npm (v6.0 or higher) - Comes with Node.js
  • Git (v2.0 or higher) - Download

Required Knowledge

  • Basic JavaScript fundamentals (variables, functions, arrays, objects)
  • Understanding of HTML/CSS selectors
  • Basic command line/terminal usage
  • Familiarity with testing concepts

Recommended Tools

  • Visual Studio Code or any preferred code editor - Download
  • Chrome/Firefox/Edge browser (for testing)
  • Git Bash (for Windows users)
  • Jenkins (for CI/CD examples, optional)

Languages & Technologies

Primary Language

  • JavaScript (100%) - All test scripts and automation code

Core Technologies & Frameworks

Technology Version Purpose
Cypress Latest Web and API automation testing
Node.js v14+ JavaScript runtime environment
npm v6+ Package manager
Git v2+ Version control
Jenkins Optional CI/CD pipeline automation

Testing & Assertion Libraries

  • Mocha - Test framework (built into Cypress)
  • Chai - Assertion library (built into Cypress)
  • Cypress Plugins - Extended functionality

File Structure

Cypress-Automation-Testing-Hero-JavaScript/
│
├── README.md                          # This file
├── LICENSE                            # MIT License
├── package.json                       # Project dependencies and scripts
├── cypress.config.js                  # Cypress configuration file
│
├── cypress/
│   ├── e2e/                          # End-to-end test files
│   │   ├── basic-tests/              # Basic Cypress tests
│   │   ├── advanced-tests/           # Advanced testing scenarios
│   │   └── api-tests/                # API testing examples
│   │
│   ├── support/                      # Support files and utilities
│   │   ├── commands.js               # Custom Cypress commands
│   │   ├── e2e.js                    # E2E test setup
│   │   └── helpers.js                # Helper functions
│   │
│   ├── fixtures/                     # Test data and mock responses
│   │   ├── test-data.json            # Sample test data
│   │   └── mock-responses/           # API mock responses
│   │
│   └── pages/                        # Page Object Model (POM)
│       ├── base.page.js              # Base page class
│       ├── login.page.js             # Login page object
│       ├── dashboard.page.js         # Dashboard page object
│       └── common.page.js            # Common page elements
│
├── config/
│   ├── cypress.env.json              # Environment variables
│   ├── dev.config.js                 # Development configuration
│   └── prod.config.js                # Production configuration
│
├── scripts/
│   ├── run-tests.js                  # Test runner script
│   └── generate-report.js            # Report generation
│
├── reports/                          # Test reports (generated)
│   └── screenshots/                  # Failure screenshots
│
└── .gitignore                         # Git ignore file

Key Directory Descriptions

cypress/e2e/ - Contains all test files organized by test type
cypress/support/ - Custom commands, hooks, and helper functions
cypress/fixtures/ - Test data files in JSON format
cypress/pages/ - Page Object Model classes for maintaining tests
config/ - Environment-specific configurations
reports/ - Generated test reports and screenshots


Installation & Setup

Step 1: Clone the Repository

git clone https://github.com/BrianGator/Cypress-Automation-Testing-Hero-JavaScript.git
cd Cypress-Automation-Testing-Hero-JavaScript

Step 2: Install Dependencies

npm install

This installs:

  • Cypress
  • Required plugins
  • Testing libraries and dependencies listed in package.json

Step 3: Verify Installation

npx cypress --version

Step 4: Configure Environment Variables

Create a cypress.env.json file in the config/ directory:

{
  "baseUrl": "https://example.com",
  "apiUrl": "https://api.example.com",
  "username": "testuser",
  "password": "testpassword",
  "env": "staging"
}

Step 5: Open Cypress

npm run cypress:open

How to Use

Running Tests

Open Cypress Test Runner (Interactive Mode)

npm run cypress:open

This opens the Cypress Test Runner GUI where you can select and run tests interactively.

Run All Tests (Headless Mode)

npm run cypress:run

Run Specific Test File

npx cypress run --spec "cypress/e2e/basic-tests/login.cy.js"

Run Tests in Specific Browser

npx cypress run --browser chrome
npx cypress run --browser firefox

Run Tests with Custom Configuration

npx cypress run --config baseUrl=https://staging.example.com

Generate Test Report

npm run report:generate

Example Test Structure

describe('Login Functionality', () => {
  beforeEach(() => {
    cy.visit('/login');
  });

  it('should login with valid credentials', () => {
    cy.get('[data-testid="username"]').type('testuser');
    cy.get('[data-testid="password"]').type('password123');
    cy.get('button[type="submit"]').click();
    cy.url().should('include', '/dashboard');
  });

  it('should display error with invalid credentials', () => {
    cy.get('[data-testid="username"]').type('invaliduser');
    cy.get('[data-testid="password"]').type('wrongpassword');
    cy.get('button[type="submit"]').click();
    cy.get('.error-message').should('be.visible');
  });
});

Cypress Methodologies Covered

Selector Strategies

Selecting elements efficiently is crucial for reliable tests. Cypress supports multiple selector strategies:

CSS Selectors

// By element type
cy.get('button');

// By class name
cy.get('.login-button');

// By ID
cy.get('#username');

// By attribute
cy.get('[data-testid="submit-button"]');

// By multiple selectors
cy.get('input[type="password"]');
cy.get('form .error-message');

// Descendant selector
cy.get('.login-form input[type="text"]');

Back to Top

XPath Expressions

// XPath selectors
cy.xpath('//button[contains(text(), "Login")]');
cy.xpath('//input[@name="email"]');
cy.xpath('//div[@class="error"]//span');

Data Attributes (Best Practice)

// Using data-testid (recommended approach)
cy.get('[data-testid="username"]').type('testuser');
cy.get('[data-testid="password"]').type('password123');
cy.get('[data-testid="submit-button"]').click();

Cypress Best Practices for Selectors

// ✅ GOOD: Specific, stable selectors
cy.get('[data-testid="login-button"]');
cy.get('#userId');
cy.get('.primary-button');

// ❌ AVOID: Too generic, fragile selectors
cy.get('div');
cy.get('.container .row .col .btn');
cy.xpath('//div[1]/div[2]/div[3]/button[1]');

Back to Top


Command Chaining

Cypress commands can be chained together for efficient test writing. Each command returns a Cypress object for the next command.

Basic Chaining

// Chaining multiple actions
cy.get('[data-testid="username"]')
  .type('testuser')
  .should('have.value', 'testuser')
  .clear()
  .type('newuser');

Chaining with Assertions

// Perform action and verify result
cy.get('button')
  .click()
  .should('have.class', 'active')
  .should('be.enabled');

Chaining Multiple Elements

// Work with multiple elements
cy.get('li')
  .should('have.length', 3)
  .first()
  .should('have.text', 'Item 1')
  .last()
  .should('have.text', 'Item 3');

Complex Chaining Pattern

describe('Form Submission', () => {
  it('should submit form successfully', () => {
    cy.visit('/contact')
      .get('[data-testid="name"]')
      .type('John Doe')
      .get('[data-testid="email"]')
      .type('john@example.com')
      .get('[data-testid="message"]')
      .type('Hello World')
      .get('button[type="submit"]')
      .click()
      .get('.success-message')
      .should('contain', 'Form submitted successfully');
  });
});

Back to Top


Waiting & Synchronization

Cypress automatically waits for elements and commands, but sometimes explicit waits are needed for asynchronous operations.

Implicit Waits

// Cypress waits automatically (default 4000ms)
cy.get('[data-testid="element"]'); // Waits up to 4 seconds

// Configure default timeout
cy.get('[data-testid="slow-element"]', { timeout: 10000 });

Explicit Waits

// Wait for specific condition
cy.get('button[type="submit"]').click();
cy.get('.loader').should('not.exist');
cy.get('.success-message').should('be.visible');

Waiting for Network Requests

// Wait for API response
cy.intercept('GET', '/api/users').as('getUsers');
cy.visit('/users');
cy.wait('@getUsers').then((interception) => {
  expect(interception.response.statusCode).to.equal(200);
});

Waiting for Element States

// Wait for element to be enabled
cy.get('button').should('be.enabled');

// Wait for element to be visible
cy.get('.modal').should('be.visible');

// Wait for text to appear
cy.get('.status').should('contain', 'Processing complete');

// Wait for attribute value
cy.get('input').should('have.value', 'expected-value');

Complex Async Operations

describe('Async Operations', () => {
  it('should handle async data loading', () => {
    cy.visit('/dashboard');
    
    // Intercept the API call
    cy.intercept('GET', '/api/dashboard').as('loadDashboard');
    
    // Trigger action that makes API call
    cy.get('[data-testid="refresh"]').click();
    
    // Wait for the API response
    cy.wait('@loadDashboard');
    
    // Verify data is displayed
    cy.get('.dashboard-content').should('be.visible');
    cy.get('.card').should('have.length.greaterThan', 0);
  });
});

Back to Top


Page Object Model (POM)

The Page Object Model is a design pattern for creating reusable, maintainable test code by encapsulating page elements and actions.

Base Page Class

// cypress/pages/base.page.js
export class BasePage {
  constructor() {
    this.timeout = 10000;
  }

  visit(url) {
    cy.visit(url);
    return this;
  }

  getElement(selector) {
    return cy.get(selector);
  }

  clickElement(selector) {
    this.getElement(selector).click();
    return this;
  }

  typeText(selector, text) {
    this.getElement(selector).type(text);
    return this;
  }

  verifyText(selector, text) {
    this.getElement(selector).should('contain', text);
    return this;
  }

  verifyElementVisible(selector) {
    this.getElement(selector).should('be.visible');
    return this;
  }

  verifyElementNotVisible(selector) {
    cy.get(selector).should('not.be.visible');
    return this;
  }
}

Login Page Object

// cypress/pages/login.page.js
import { BasePage } from './base.page';

export class LoginPage extends BasePage {
  constructor() {
    super();
  }

  // Element selectors
  usernameInput = '[data-testid="username"]';
  passwordInput = '[data-testid="password"]';
  loginButton = 'button[type="submit"]';
  errorMessage = '.error-message';
  rememberMeCheckbox = '[data-testid="remember-me"]';

  // Page actions
  navigate() {
    this.visit('/login');
    return this;
  }

  enterUsername(username) {
    this.typeText(this.usernameInput, username);
    return this;
  }

  enterPassword(password) {
    this.typeText(this.passwordInput, password);
    return this;
  }

  clickLoginButton() {
    this.clickElement(this.loginButton);
    return this;
  }

  toggleRememberMe() {
    this.clickElement(this.rememberMeCheckbox);
    return this;
  }

  login(username, password) {
    this.enterUsername(username);
    this.enterPassword(password);
    this.clickLoginButton();
    return this;
  }

  verifyErrorMessage(expectedMessage) {
    this.getElement(this.errorMessage)
      .should('be.visible')
      .should('contain', expectedMessage);
    return this;
  }

  verifySuccessfulLogin() {
    cy.url().should('include', '/dashboard');
    return this;
  }
}

Dashboard Page Object

// cypress/pages/dashboard.page.js
import { BasePage } from './base.page';

export class DashboardPage extends BasePage {
  constructor() {
    super();
  }

  // Element selectors
  welcomeMessage = '.welcome-message';
  userProfile = '[data-testid="user-profile"]';
  logoutButton = '[data-testid="logout"]';
  navigationMenu = '[data-testid="nav-menu"]';
  dashboardTitle = '.dashboard-title';

  // Page actions
  verifyUserLoggedIn(username) {
    this.verifyElementVisible(this.welcomeMessage);
    this.getElement(this.welcomeMessage)
      .should('contain', `Welcome, ${username}`);
    return this;
  }

  navigateTo(menuItem) {
    this.getElement(this.navigationMenu)
      .contains(menuItem)
      .click();
    return this;
  }

  logout() {
    this.clickElement(this.logoutButton);
    return this;
  }

  verifyDashboardLoaded() {
    this.verifyElementVisible(this.dashboardTitle);
    return this;
  }
}

Using Page Objects in Tests

// cypress/e2e/basic-tests/login.cy.js
import { LoginPage } from '../../pages/login.page';
import { DashboardPage } from '../../pages/dashboard.page';

describe('Login Feature', () => {
  let loginPage;
  let dashboardPage;

  beforeEach(() => {
    loginPage = new LoginPage();
    dashboardPage = new DashboardPage();
    loginPage.navigate();
  });

  it('should login successfully with valid credentials', () => {
    loginPage
      .enterUsername('testuser')
      .enterPassword('password123')
      .clickLoginButton();

    dashboardPage
      .verifyUserLoggedIn('Test User')
      .verifyDashboardLoaded();
  });

  it('should display error with invalid credentials', () => {
    loginPage
      .login('invaliduser', 'wrongpassword')
      .verifyErrorMessage('Invalid username or password');
  });

  it('should remember user when checkbox is selected', () => {
    loginPage
      .toggleRememberMe()
      .login('testuser', 'password123');

    dashboardPage.verifyUserLoggedIn('Test User');
  });
});

Back to Top


Assertions & Validations

Assertions verify that the application behaves as expected. Cypress uses Chai assertion library with BDD syntax.

Basic Assertions

// Equality assertions
cy.get('[data-testid="count"]').should('have.text', '5');
cy.get('input').should('have.value', 'expected-value');
cy.get('button').should('have.class', 'active');

// Visibility assertions
cy.get('.modal').should('be.visible');
cy.get('.hidden-element').should('not.be.visible');

// Enable/Disable assertions
cy.get('button').should('be.enabled');
cy.get('input[disabled]').should('be.disabled');

Text Assertions

// Exact text match
cy.get('h1').should('have.text', 'Dashboard');

// Partial text match
cy.get('h1').should('contain', 'Dashboard');

// Case-insensitive match
cy.get('h1').invoke('text').then(text => {
  expect(text.toLowerCase()).to.include('dashboard');
});

Array/Collection Assertions

// Check number of elements
cy.get('li').should('have.length', 5);
cy.get('.item').should('have.length.greaterThan', 0);

// Check first/last elements
cy.get('li').first().should('contain', 'Item 1');
cy.get('li').last().should('contain', 'Item 5');

// Filter and check
cy.get('li').contains('Item 3').should('exist');

URL and Navigation Assertions

// Check URL
cy.url().should('include', '/dashboard');
cy.url().should('eq', 'https://example.com/dashboard');

// Check location pathname
cy.location('pathname').should('eq', '/dashboard');

Custom Assertions

// Chain multiple assertions
cy.get('[data-testid="error"]')
  .should('be.visible')
  .should('have.class', 'error-message')
  .should('contain', 'Error occurred');

// Assertion with callback
cy.get('.count').then($el => {
  expect(parseInt($el.text())).to.be.greaterThan(0);
});

Advanced Assertion Patterns

describe('Advanced Assertions', () => {
  it('should validate form submission', () => {
    // Submit form
    cy.get('form').submit();

    // Multiple assertions
    cy.url().should('include', '/success');
    cy.get('.success-message')
      .should('be.visible')
      .should('contain', 'Form submitted successfully');
    cy.get('.confirmation-number').should('exist');
  });

  it('should validate API response data', () => {
    cy.intercept('GET', '/api/users').as('getUsers');
    cy.visit('/users');
    
    cy.wait('@getUsers').then((interception) => {
      // Validate response structure
      expect(interception.response.statusCode).to.equal(200);
      expect(interception.response.body).to.have.property('data');
      expect(interception.response.body.data).to.be.an('array');
    });
  });
});

Back to Top


API Testing

Test APIs directly using Cypress's cy.request() and cy.intercept() commands.

Basic API Requests

// GET request
cy.request('GET', '/api/users').then((response) => {
  expect(response.status).to.equal(200);
  expect(response.body).to.have.property('data');
});

// POST request
cy.request('POST', '/api/users', {
  name: 'John Doe',
  email: 'john@example.com'
}).then((response) => {
  expect(response.status).to.equal(201);
  expect(response.body).to.have.property('id');
});

// PUT request
cy.request('PUT', '/api/users/1', {
  name: 'Jane Doe'
}).then((response) => {
  expect(response.status).to.equal(200);
});

// DELETE request
cy.request('DELETE', '/api/users/1').then((response) => {
  expect(response.status).to.equal(204);
});

API Testing with Headers

cy.request({
  method: 'GET',
  url: '/api/protected-resource',
  headers: {
    'Authorization': 'Bearer token123',
    'Content-Type': 'application/json'
  }
}).then((response) => {
  expect(response.status).to.equal(200);
});

Handling API Errors

// Expect error response
cy.request({
  method: 'POST',
  url: '/api/users',
  body: { email: 'invalid-email' },
  failOnStatusCode: false
}).then((response) => {
  expect(response.status).to.equal(400);
  expect(response.body).to.have.property('error');
});

Mocking API Responses

// Intercept and mock API response
cy.intercept('GET', '/api/users', {
  statusCode: 200,
  body: {
    data: [
      { id: 1, name: 'User 1' },
      { id: 2, name: 'User 2' }
    ]
  }
}).as('getUsers');

cy.visit('/users');
cy.wait('@getUsers');
cy.get('.user-item').should('have.length', 2);

API Testing Scenarios

describe('API Testing', () => {
  beforeEach(() => {
    cy.visit('/api-test');
  });

  it('should fetch users list', () => {
    cy.intercept('GET', '/api/users').as('getUsers');
    cy.get('[data-testid="load-users"]').click();
    
    cy.wait('@getUsers').then((interception) => {
      expect(interception.response.statusCode).to.equal(200);
      expect(interception.response.body).to.be.an('array');
    });
  });

  it('should create user via API', () => {
    const newUser = {
      name: 'John Doe',
      email: 'john@example.com',
      role: 'user'
    };

    cy.request('POST', '/api/users', newUser).then((response) => {
      expect(response.status).to.equal(201);
      expect(response.body.id).to.exist;
      expect(response.body.name).to.equal('John Doe');
    });
  });

  it('should validate API error handling', () => {
    cy.request({
      method: 'POST',
      url: '/api/users',
      body: { email: 'invalid' },
      failOnStatusCode: false
    }).then((response) => {
      expect(response.status).to.equal(400);
      expect(response.body.errors).to.exist;
    });
  });
});

Back to Top


Fixtures & Test Data

Use fixtures to load test data from JSON files for data-driven testing.

Creating Fixture Files

// cypress/fixtures/users.json
{
  "validUser": {
    "username": "testuser",
    "password": "password123",
    "email": "test@example.com"
  },
  "invalidUser": {
    "username": "invaliduser",
    "password": "wrongpassword"
  },
  "users": [
    { "id": 1, "name": "User 1", "email": "user1@example.com" },
    { "id": 2, "name": "User 2", "email": "user2@example.com" }
  ]
}

Loading and Using Fixtures

describe('Using Fixtures', () => {
  beforeEach(() => {
    // Load fixture before each test
    cy.fixture('users.json').as('userData');
  });

  it('should login with fixture data', function() {
    cy.visit('/login');
    cy.get('[data-testid="username"]').type(this.userData.validUser.username);
    cy.get('[data-testid="password"]').type(this.userData.validUser.password);
    cy.get('button[type="submit"]').click();
    cy.url().should('include', '/dashboard');
  });

  it('should display error with invalid fixture user', function() {
    cy.visit('/login');
    cy.get('[data-testid="username"]').type(this.userData.invalidUser.username);
    cy.get('[data-testid="password"]').type(this.userData.invalidUser.password);
    cy.get('button[type="submit"]').click();
    cy.get('.error-message').should('be.visible');
  });
});

Data-Driven Testing

describe('Data-Driven Testing', () => {
  const testCases = [
    { username: 'user1', password: 'pass1', expected: 'success' },
    { username: 'user2', password: 'pass2', expected: 'success' },
    { username: 'invalid', password: 'wrong', expected: 'error' }
  ];

  testCases.forEach(testCase => {
    it(`should handle login for ${testCase.username}`, () => {
      cy.visit('/login');
      cy.get('[data-testid="username"]').type(testCase.username);
      cy.get('[data-testid="password"]').type(testCase.password);
      cy.get('button[type="submit"]').click();

      if (testCase.expected === 'success') {
        cy.url().should('include', '/dashboard');
      } else {
        cy.get('.error-message').should('be.visible');
      }
    });
  });
});

Back to Top


Environment Variables

Configure environment-specific settings for different environments (dev, staging, production).

Environment Configuration File

// config/cypress.env.json
{
  "baseUrl": "https://example.com",
  "apiUrl": "https://api.example.com",
  "username": "testuser",
  "password": "testpassword",
  "env": "staging"
}

Cypress Configuration

// cypress.config.js
import { defineConfig } from 'cypress';

export default defineConfig({
  e2e: {
    baseUrl: process.env.CYPRESS_BASE_URL || 'https://example.com',
    apiUrl: process.env.CYPRESS_API_URL || 'https://api.example.com',
    env: {
      environment: process.env.CYPRESS_ENV || 'staging',
      username: process.env.CYPRESS_USERNAME || 'testuser',
      password: process.env.CYPRESS_PASSWORD || 'testpassword'
    }
  }
});

Using Environment Variables in Tests

describe('Environment Configuration', () => {
  it('should use environment variables', () => {
    // Access via cy.config()
    const baseUrl = Cypress.config('baseUrl');
    const apiUrl = Cypress.env('apiUrl');
    
    expect(baseUrl).to.include('example.com');
    expect(apiUrl).to.include('api');
  });

  it('should login to correct environment', () => {
    const username = Cypress.env('username');
    const password = Cypress.env('password');
    
    cy.visit('/login');
    cy.get('[data-testid="username"]').type(username);
    cy.get('[data-testid="password"]').type(password);
    cy.get('button[type="submit"]').click();
  });
});

Running Tests with Different Environments

# Development environment
npx cypress run --env baseUrl=https://dev.example.com

# Staging environment
npx cypress run --env baseUrl=https://staging.example.com

# Production environment (for smoke tests)
npx cypress run --env baseUrl=https://example.com

Back to Top


Debugging & Troubleshooting

Learn various techniques for debugging and troubleshooting Cypress tests.

Using Debug Commands

describe('Debugging Techniques', () => {
  it('should debug test execution', () => {
    cy.visit('/dashboard');
    
    // Pause execution for inspection
    cy.pause();
    
    cy.get('[data-testid="user-profile"]')
      // Output to browser console
      .debug()
      .click();
    
    // Step through execution
    cy.step('Check dashboard content');
  });
});

Browser DevTools Integration

it('should leverage browser devtools', () => {
  cy.visit('/dashboard');
  
  // Open DevTools (in headed mode)
  cy.get('body').then(() => {
    debugger; // This pauses in Chrome DevTools
  });
});

Screenshot and Video Recording

describe('Screenshots and Videos', () => {
  it('should capture screenshots', () => {
    cy.visit('/dashboard');
    cy.get('.important-element').screenshot('dashboard-view');
    cy.screenshot('full-page-screenshot');
  });

  afterEach(function() {
    // Automatically capture screenshot on failure
    if (this.currentTest.state === 'failed') {
      cy.screenshot('failure-screenshot');
    }
  });
});

Console Logging

describe('Console Logging', () => {
  it('should log information', () => {
    cy.visit('/dashboard');
    
    cy.get('h1').then(($heading) => {
      // Log to Cypress command log
      cy.log('Heading text: ' + $heading.text());
      
      // Log to browser console
      console.log('Element found:', $heading);
    });
  });
});

Testing in Debug Mode

# Run tests with extended debugging
npm run cypress:run:debug

# Or with specific debug flags
DEBUG=cypress:* npm run cypress:run

Back to Top


CI/CD Integration

Integrate Cypress tests into CI/CD pipelines for automated testing.

GitHub Actions Workflow

# .github/workflows/cypress-tests.yml
name: Cypress Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
      
      - name: Setup Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '16'
      
      - name: Install dependencies
        run: npm install
      
      - name: Run Cypress tests
        run: npm run cypress:run
      
      - name: Upload artifacts
        if: always()
        uses: actions/upload-artifact@v2
        with:
          name: cypress-screenshots
          path: cypress/screenshots

Jenkins Pipeline

pipeline {
    agent any
    
    stages {
        stage('Setup') {
            steps {
                sh 'npm install'
            }
        }
        
        stage('Run Tests') {
            steps {
                sh 'npm run cypress:run'
            }
        }
        
        stage('Generate Report') {
            steps {
                sh 'npm run report:generate'
            }
        }
    }
    
    post {
        always {
            junit 'cypress/results/*.xml'
            archiveArtifacts artifacts: 'cypress/screenshots/**'
        }
    }
}

Docker Integration

FROM cypress/base:latest

WORKDIR /app
COPY . .
RUN npm install

CMD ["npm", "run", "cypress:run"]

Back to Top


Advanced Features

Explore advanced Cypress features for complex testing scenarios.

Custom Commands

// cypress/support/commands.js
Cypress.Commands.add('login', (username, password) => {
  cy.visit('/login');
  cy.get('[data-testid="username"]').type(username);
  cy.get('[data-testid="password"]').type(password);
  cy.get('button[type="submit"]').click();
  cy.url().should('include', '/dashboard');
});

Cypress.Commands.add('selectDropdown', (selector, value) => {
  cy.get(selector).click();
  cy.contains(value).click();
});

// Usage in tests
describe('Custom Commands', () => {
  it('should use custom commands', () => {
    cy.login('testuser', 'password123');
    cy.selectDropdown('[data-testid="role-dropdown"]', 'Admin');
  });
});

Handling iframes

describe('iframe Handling', () => {
  it('should interact with iframe content', () => {
    cy.visit('/page-with-iframe');
    
    // Get iframe element
    cy.get('iframe[id="myFrame"]')
      .then($iframe => {
        const $body = $iframe.contents().find('body');
        cy.wrap($body).find('[data-testid="button"]').click();
      });
  });
});

Shadow DOM Testing

describe('Shadow DOM Testing', () => {
  it('should interact with shadow DOM elements', () => {
    cy.get('custom-element')
      .shadow()
      .find('button')
      .click();
  });
});

File Uploads and Downloads

describe('File Operations', () => {
  it('should upload file', () => {
    cy.visit('/upload');
    cy.get('input[type="file"]')
      .selectFile('cypress/fixtures/test-file.csv');
    cy.get('[data-testid="upload-btn"]').click();
    cy.get('.success-message').should('be.visible');
  });

  it('should verify file download', () => {
    cy.visit('/download');
    cy.get('[data-testid="download-btn"]').click();
    cy.readFile('cypress/downloads/report.pdf')
      .should('exist');
  });
});

Back to Top


Best Practices

Follow these best practices for writing maintainable, reliable tests.

Test Organization

// ✅ GOOD: Well-organized test structure
describe('Login Feature', () => {
  beforeEach(() => {
    cy.visit('/login');
  });

  describe('Valid Credentials', () => {
    it('should login successfully', () => {
      // Test implementation
    });

    it('should remember user when checkbox is selected', () => {
      // Test implementation
    });
  });

  describe('Invalid Credentials', () => {
    it('should display error message', () => {
      // Test implementation
    });

    it('should not navigate to dashboard', () => {
      // Test implementation
    });
  });
});

Avoiding Flaky Tests

// ❌ AVOID: Flaky test with arbitrary waits
it('should load data', () => {
  cy.get('button').click();
  cy.wait(2000); // Arbitrary wait - unreliable
  cy.get('.data').should('be.visible');
});

// ✅ GOOD: Explicit wait for specific condition
it('should load data', () => {
  cy.get('button').click();
  cy.get('.loader').should('not.exist'); // Wait for loader to disappear
  cy.get('.data').should('be.visible');
});

Performance Optimization

// ✅ GOOD: Efficient selector usage
cy.get('[data-testid="submit"]').click();

// ❌ AVOID: Inefficient selectors
cy.get('div').get('.container').get('form').get('button').click();

Security Considerations

// ✅ GOOD: Use environment variables for sensitive data
const username = Cypress.env('testUsername');
const password = Cypress.env('testPassword');

// ❌ AVOID: Hardcoding credentials
const username = 'admin';
const password = 'admin123';

DRY Principle (Don't Repeat Yourself)

// ✅ GOOD: Extract common logic
const loginSteps = (username, password) => {
  cy.get('[data-testid="username"]').type(username);
  cy.get('[data-testid="password"]').type(password);
  cy.get('button[type="submit"]').click();
};

describe('Login Tests', () => {
  it('should login successfully', () => {
    cy.visit('/login');
    loginSteps('testuser', 'password123');
    cy.url().should('include', '/dashboard');
  });

  it('should show error with invalid credentials', () => {
    cy.visit('/login');
    loginSteps('invalid', 'wrong');
    cy.get('.error-message').should('be.visible');
  });
});

Back to Top


Key Features

Comprehensive Examples - From basic to advanced testing scenarios
Page Object Model - Well-structured, maintainable tests
API Testing - Complete guide to testing APIs with Cypress
CI/CD Ready - Jenkins and GitHub Actions configurations
Environment Configuration - Easy switching between dev, staging, production
Real-World Scenarios - Practical examples you can use immediately
Custom Commands - Reusable test utilities
Data-Driven Tests - Parameterized testing with fixtures
Detailed Reports - Comprehensive test reporting
Best Practices - Industry-standard patterns and conventions
Advanced Features - iframes, Shadow DOM, file operations


Getting Started

Quick Start (5 minutes)

  1. Clone the repository

    git clone https://github.com/BrianGator/Cypress-Automation-Testing-Hero-JavaScript.git
    cd Cypress-Automation-Testing-Hero-JavaScript
  2. Install dependencies

    npm install
  3. Open Cypress

    npm run cypress:open
  4. Run your first test

    • Select a test file from the Test Runner
    • Watch it execute in real-time

Next Steps


npm Scripts

{
  "scripts": {
    "cypress:open": "cypress open",
    "cypress:run": "cypress run",
    "cypress:run:headed": "cypress run --headed",
    "cypress:run:debug": "cypress run --headed --browser chrome --no-exit",
    "report:generate": "cypress run && node scripts/generate-report.js",
    "test": "cypress run"
  }
}

Troubleshooting

Cypress Won't Install

# Clear npm cache and reinstall
npm cache clean --force
npm install

Tests Timing Out

  • Increase timeout in cypress.config.js: defaultCommandTimeout: 10000
  • Use explicit waits: cy.get('selector', { timeout: 15000 })
  • Check Waiting & Synchronization section

Selector Not Found

  • Use Cypress Selector Playground (highlighted in Test Runner)
  • Verify element exists and is visible
  • Check for dynamic IDs or class names
  • Review Selector Strategies section

API Requests Failing

Test Failures and Debugging

  • Enable debug mode: npm run cypress:run:debug
  • Check Debugging & Troubleshooting section
  • Review test videos and screenshots in cypress/screenshots/

Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Resources

Cypress Resources

Learning Resources


License

This project is licensed under the MIT License - see the LICENSE file for details.

Copyright © 2024 Packt


Support & Contact

For issues, questions, or feedback:


Happy Testing! 🎉

Transform yourself into a Cypress Automation Testing Hero! Start with the basics and master advanced techniques step by step. Use the table of contents to navigate to specific topics and expand your testing expertise.

About

Cypress Automation Testing Framework Hero w JavaScript

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors