Chapter 6: End-to-End Testing


This chapter focuses on how to perform end-to-end (E2E) testing using tools like Selenium (Java-based) and Cypress (JavaScript-based). It includes practical examples, best practices, a hands-on lab, and a quiz to reinforce learning.


Objective

By the end of this chapter, you will:

  • Understand the purpose and importance of end-to-end (E2E) testing in software development.
  • Learn how to use Selenium and Cypress to automate browser-based tests.
  • Write E2E tests to simulate user interactions and verify application workflows.
  • Handle asynchronous behavior and validate UI states effectively.
  • Apply best practices for creating reliable E2E tests.

6.1 Introduction to End-to-End Testing

End-to-end testing ensures an application works as a whole, from the user’s perspective.

  • Definition: E2E testing verifies the complete flow of an application, simulating real user scenarios across all integrated components (frontend, backend, database, etc.).
  • Purpose:
    • Validate that the entire system meets requirements and delivers the expected user experience.
    • Catch issues missed by unit or integration tests (e.g., UI glitches, workflow failures).
    • Ensure the application behaves correctly in a production-like environment.
  • Key Characteristics:
    • Broad Scope: Tests the full stack, not just isolated parts.
    • User-Centric: Mimics how a real user interacts with the application.
    • Slower Execution: More comprehensive and resource-intensive than unit or integration tests.

Think of E2E testing as taking a car for a test drive—you’re checking the whole journey, not just the engine or tires.


6.2 Tools for End-to-End Testing

We’ll explore two popular tools: Selenium and Cypress, each with unique strengths.

  • Selenium:
    • A Java-based automation framework for testing web applications across multiple browsers (e.g., Chrome, Firefox).
    • Uses WebDriver to interact with browser elements.
    • Ideal for cross-browser testing and integration with Java ecosystems like Spring Boot.
  • Cypress:
    • A JavaScript-based testing framework designed for modern web applications.
    • Runs directly in the browser, offering faster execution and real-time debugging.
    • Best for JavaScript-heavy projects (e.g., React, Vue) and single-browser testing.

Both tools automate browser interactions, but their approaches differ—Selenium is broader, while Cypress is more developer-friendly for JavaScript apps.


6.3 Writing E2E Tests with Selenium

Selenium uses Java to automate browser actions, making it a good fit for Spring Boot projects.

6.3.1 Example Scenario

Test a login page that redirects to a dashboard:

  • URL: http://localhost:8080/login
  • Fields: Username, Password
  • Button: “Login”
  • Success: Redirects to http://localhost:8080/dashboard

6.3.2 Writing a Selenium Test

Dependencies (Maven):

<dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-java</artifactId>
    <version>4.11.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.10.0</version>
    <scope>test</scope>
</dependency>

Test Code:

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class LoginE2ETest {
    private WebDriver driver;

    @BeforeEach
    public void setup() {
        // Set path to ChromeDriver executable (download from selenium.dev)
        System.setProperty("webdriver.chrome.driver", "/path/to/chromedriver");
        driver = new ChromeDriver();
    }

    @Test
    public void testSuccessfulLogin() {
        // Arrange
        driver.get("http://localhost:8080/login");

        // Act
        driver.findElement(By.id("username")).sendKeys("testuser");
        driver.findElement(By.id("password")).sendKeys("password123");
        driver.findElement(By.id("login-btn")).click();

        // Assert
        assertEquals("http://localhost:8080/dashboard", driver.getCurrentUrl());
    }

    @AfterEach
    public void teardown() {
        driver.quit();
    }
}

Explanation:

  • WebDriver: Controls the browser (e.g., Chrome).
  • findElement: Locates elements by ID, class, or other selectors.
  • sendKeys: Simulates typing into fields.
  • click: Triggers button actions.
  • assertEquals: Verifies the redirect URL.

6.4 Writing E2E Tests with Cypress

Cypress offers a simpler, JavaScript-based approach for modern web apps.

6.4.1 Example Scenario

Test the same login page as above.

6.4.2 Setting Up Cypress

Install Cypress in a Node.js project:

npm install cypress --save-dev

Add to package.json:

"scripts": {
  "cypress:open": "cypress open"
}

Run npx cypress open to launch the Cypress Test Runner.

6.4.3 Writing a Cypress Test

Create cypress/e2e/login.cy.js:

describe("Login Page", () => {
  it("should redirect to dashboard on successful login", () => {
    // Arrange
    cy.visit("http://localhost:8080/login");

    // Act
    cy.get("#username").type("testuser");
    cy.get("#password").type("password123");
    cy.get("#login-btn").click();

    // Assert
    cy.url().should("eq", "http://localhost:8080/dashboard");
  });
});

Explanation:

  • cy.visit: Navigates to the login page.
  • cy.get: Selects elements by CSS selectors (e.g., #username).
  • type: Enters text into fields.
  • click: Simulates a button click.
  • should: Asserts the URL matches the expected value.

6.5 Handling Asynchronous Behavior

Web applications often involve asynchronous operations (e.g., API calls). Both tools handle this differently:

  • Selenium:

    • Use explicit waits to handle delays:
      WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
      wait.until(ExpectedConditions.urlToBe("http://localhost:8080/dashboard"));
    • Ensures the test waits for the redirect before asserting.
  • Cypress:

    • Automatically retries commands (e.g., cy.get) until the element is available or a timeout occurs.
    • Use cy.wait for explicit delays if needed:
      cy.wait(1000); // Wait 1 second
      

6.6 Best Practices for E2E Testing

To ensure reliable E2E tests:

  • Simulate Real User Flows: Test complete workflows (e.g., login to checkout).
  • Use Stable Selectors: Prefer IDs or data attributes over fragile CSS classes.
  • Minimize Flakiness: Handle asynchronous behavior and avoid hard-coded waits when possible.
  • Run in CI/CD: Integrate with tools like Jenkins or GitHub Actions for consistent execution.
  • Limit Scope: Focus on critical user journeys, not every edge case (leave those to unit/integration tests).

6.7 Lab: Hands-On End-to-End Testing

Apply your knowledge in this practical lab.

Lab Objective

Write E2E tests for a provided web application using Selenium or Cypress.

Lab Setup

  • Provided: A simple web app with a login page and dashboard (e.g., a Spring Boot app or static HTML/JS).
  • Tools: Selenium (Java) or Cypress (JavaScript).

Lab Tasks

  1. Test Successful Login:

    • Navigate to the login page.
    • Enter valid credentials and submit.
    • Verify redirection to the dashboard.
  2. Test Failed Login:

    • Enter invalid credentials.
    • Check for an error message on the login page.
  3. Handle Asynchrony:

    • Add waits to ensure the test accounts for page loading or redirects.

Lab Deliverables

  • Submit your test file(s) (e.g., LoginE2ETest.java or login.cy.js).
  • Include comments explaining each test step.

6.8 Quiz: Reinforcing Key Concepts

Test your understanding with this quiz:

  1. What is the main goal of E2E testing?

    • a) To test individual functions
    • b) To verify the full application workflow
    • c) To check code performance
    • d) To validate database connections
  2. Which tool uses WebDriver to automate browsers?

    • a) Cypress
    • b) Selenium
    • c) Jest
    • d) Rest Assured
  3. In Cypress, how do you select an element by ID?

    • a) driver.findElement(By.id())
    • b) cy.get(’#id')
    • c) expect(’#id')
    • d) document.getElementById()
  4. True or False: E2E tests are typically faster than unit tests.

    • a) True
    • b) False
  5. What does WebDriverWait do in Selenium?

    • a) Simulates a button click
    • b) Waits for a condition before proceeding
    • c) Mocks an API
    • d) Runs assertions

Answers:

  1. b
  2. b
  3. b
  4. b (E2E tests are slower)
  5. b

6.9 Summary

In this chapter, you’ve learned:

  • The critical role of E2E testing in validating complete application workflows.
  • How to use Selenium and Cypress to automate browser-based tests.
  • Techniques for simulating user interactions and handling asynchronous behavior.
  • Best practices for creating reliable and maintainable E2E tests.

These skills enable you to ensure your application delivers a seamless user experience from start to finish.


This chapter equips you with the tools and knowledge to perform E2E testing effectively, ensuring your applications work as intended for end users!


By Wahid Hamdi