Selenium Test Automation — Detailed Course Notes

Comprehensive notes covering Selenium WebDriver, TestNG/PyTest frameworks, Page Object Model, advanced interactions, Selenium Grid, and CI/CD integration with Jenkins.

📑 Table of Contents

Week 1 — Selenium Fundamentals & Web Automation Basics Foundations

Introduction to Selenium WebDriver and Browser Automation

Selenium is the most widely used open-source test automation framework for web applications. It supports multiple programming languages (Java, Python, C#, JavaScript, Ruby, and more) and all major browsers (Chrome, Firefox, Edge, Safari, and Opera).

Selenium WebDriver is the core component that directly communicates with the browser using browser-specific drivers. Unlike its predecessor Selenium RC (Remote Control), WebDriver does not require a separate server and interacts directly with the browser's native automation API. This makes it faster, more reliable, and capable of handling modern web applications with dynamic content.

Key Features of Selenium WebDriver:

💡 Why Selenium? Selenium is free, open-source, cross-platform, and supports all major browsers. It has a large community, extensive documentation, and integrates seamlessly with CI/CD pipelines, making it the industry standard for web automation.

Setting Up Selenium with Java and Python Environments

Java Setup Java

<!-- pom.xml dependencies -->
<dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-java</artifactId>
    <version>4.15.0</version>
</dependency>
<dependency>
    <groupId>io.github.bonigarcia</groupId>
    <artifactId>webdrivermanager</artifactId>
    <version>5.6.2</version>
</dependency>

Python Setup Python

# Install Selenium and WebDriver Manager
pip install selenium webdriver-manager

Understanding Browser Drivers and WebDriver Architecture

Selenium WebDriver uses a client-server architecture where the test script (client) communicates with the browser driver (server) via HTTP. The browser driver controls the actual browser instance.

Browser Drivers:

WebDriver Architecture Flow:

  1. Test script sends a command to WebDriver (e.g., driver.findElement(By.id("username"))).
  2. WebDriver translates the command into a JSON payload (using the W3C WebDriver protocol).
  3. The browser driver receives the HTTP request and executes the command in the browser.
  4. The browser driver sends the result back to WebDriver.
  5. The test script receives the result and continues execution.

Locating Web Elements Using ID, Name, XPath, CSS Selector, and Class Name

Locating elements accurately is the foundation of successful test automation. Selenium provides multiple locator strategies:

Java
// By ID (most reliable)
WebElement username = driver.findElement(By.id("username"));

// By Name
WebElement password = driver.findElement(By.name("password"));

// By Class Name
WebElement message = driver.findElement(By.className("alert-success"));

// By CSS Selector
WebElement loginBtn = driver.findElement(By.cssSelector("button.submit-btn"));

// By XPath (use sparingly)
WebElement link = driver.findElement(By.xpath("//a[text()='Forgot Password']"));

// By Link Text
WebElement homeLink = driver.findElement(By.linkText("Home"));

// By Partial Link Text
WebElement aboutLink = driver.findElement(By.partialLinkText("About"));

// By Tag Name
WebElement heading = driver.findElement(By.tagName("h1"));
Python
# By ID (most reliable)
username = driver.find_element(By.ID, "username")

# By Name
password = driver.find_element(By.NAME, "password")

# By Class Name
message = driver.find_element(By.CLASS_NAME, "alert-success")

# By CSS Selector
login_btn = driver.find_element(By.CSS_SELECTOR, "button.submit-btn")

# By XPath (use sparingly)
link = driver.find_element(By.XPATH, "//a[text()='Forgot Password']")

# By Link Text
home_link = driver.find_element(By.LINK_TEXT, "Home")

# By Partial Link Text
about_link = driver.find_element(By.PARTIAL_LINK_TEXT, "About")

# By Tag Name
heading = driver.find_element(By.TAG_NAME, "h1")
📌 Best Practice: Prefer ID and CSS Selector over XPath as they are faster and more reliable. Use XPath only when other locators are not feasible. Always use WebDriverWait with appropriate conditions for dynamic elements.

Browser Navigation and Web Interaction Techniques

Java
// Navigate to a URL
driver.get("https://example.com");

// Navigate back and forward
driver.navigate().back();
driver.navigate().forward();

// Refresh the page
driver.navigate().refresh();

// Get page title
String title = driver.getTitle();

// Get current URL
String currentUrl = driver.getCurrentUrl();

// Maximize window
driver.manage().window().maximize();

// Set window size
driver.manage().window().setSize(new Dimension(1024, 768));
Python
# Navigate to a URL
driver.get("https://example.com")

# Navigate back and forward
driver.back()
driver.forward()

# Refresh the page
driver.refresh()

# Get page title
title = driver.title

# Get current URL
current_url = driver.current_url

# Maximize window
driver.maximize_window()

# Set window size
driver.set_window_size(1024, 768)

Handling Forms, Buttons, Dropdowns, and Dynamic Web Elements

Java
// Text input
driver.findElement(By.id("username")).sendKeys("admin");

// Clear and type
driver.findElement(By.id("search")).clear();
driver.findElement(By.id("search")).sendKeys("Selenium");

// Button click
driver.findElement(By.id("login-btn")).click();

// Dropdown (using Select class)
Select dropdown = new Select(driver.findElement(By.id("country")));
dropdown.selectByVisibleText("United Kingdom");
dropdown.selectByValue("GB");
dropdown.selectByIndex(2);

// Checkbox
WebElement checkbox = driver.findElement(By.id("terms"));
if (!checkbox.isSelected()) {
    checkbox.click();
}

// Radio button
driver.findElement(By.cssSelector("input[value='male']")).click();

// Dynamic elements - wait for visibility
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement dynamicElement = wait.until(
    ExpectedConditions.visibilityOfElementLocated(By.id("dynamic-content"))
);
Python
# Text input
driver.find_element(By.ID, "username").send_keys("admin")

# Clear and type
driver.find_element(By.ID, "search").clear()
driver.find_element(By.ID, "search").send_keys("Selenium")

# Button click
driver.find_element(By.ID, "login-btn").click()

# Dropdown (using Select class)
from selenium.webdriver.support.ui import Select
dropdown = Select(driver.find_element(By.ID, "country"))
dropdown.select_by_visible_text("United Kingdom")
dropdown.select_by_value("GB")
dropdown.select_by_index(2)

# Checkbox
checkbox = driver.find_element(By.ID, "terms")
if not checkbox.is_selected():
    checkbox.click()

# Radio button
driver.find_element(By.CSS_SELECTOR, "input[value='male']").click()

# Dynamic elements - wait for visibility
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 10)
dynamic_element = wait.until(
    EC.visibility_of_element_located((By.ID, "dynamic-content"))
)

Synchronisation Techniques: Implicit and Explicit Waits

Implicit Wait — sets a default timeout for all find operations. It tells WebDriver to wait for a specified time before throwing an exception if an element is not found.

Java
// Implicit wait (global timeout)
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
Python
# Implicit wait (global timeout)
driver.implicitly_wait(10)

Explicit Wait — waits for a specific condition on a specific element. More precise and flexible than implicit waits.

Java
// Explicit wait for element visibility
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement element = wait.until(
    ExpectedConditions.visibilityOfElementLocated(By.id("result"))
);

// Clickable condition
WebElement button = wait.until(
    ExpectedConditions.elementToBeClickable(By.id("submit-btn"))
);

// Alert presence
wait.until(ExpectedConditions.alertIsPresent());

// Custom condition
wait.until(driver -> driver.findElement(By.id("status")).getText().equals("Complete"));
Python
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# Explicit wait for element visibility
wait = WebDriverWait(driver, 10)
element = wait.until(
    EC.visibility_of_element_located((By.ID, "result"))
)

# Clickable condition
button = wait.until(
    EC.element_to_be_clickable((By.ID, "submit-btn"))
)

# Alert presence
wait.until(EC.alert_is_present())

# Custom condition
wait.until(lambda d: d.find_element(By.ID, "status").text == "Complete")
⚠️ Important: Avoid mixing implicit and explicit waits in the same test as they can cause unpredictable timeouts. Use explicit waits for specific elements and implicit waits as a fallback.

Building and Executing Basic Automation Scripts

Java
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import io.github.bonigarcia.wdm.WebDriverManager;

public class LoginTest {
    public static void main(String[] args) {
        // Setup WebDriverManager
        WebDriverManager.chromedriver().setup();
        WebDriver driver = new ChromeDriver();

        try {
            // Navigate to login page
            driver.get("https://example.com/login");

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

            // Verify login
            String expectedTitle = "Dashboard";
            String actualTitle = driver.getTitle();

            if (actualTitle.equals(expectedTitle)) {
                System.out.println("Login successful!");
            } else {
                System.out.println("Login failed. Title: " + actualTitle);
            }

        } finally {
            // Clean up
            driver.quit();
        }
    }
}
Python
from selenium import webdriver
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service

# Setup WebDriver
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))

try:
    # Navigate to login page
    driver.get("https://example.com/login")

    # Login
    driver.find_element(By.ID, "username").send_keys("testuser")
    driver.find_element(By.ID, "password").send_keys("password123")
    driver.find_element(By.ID, "login-btn").click()

    # Verify login
    expected_title = "Dashboard"
    actual_title = driver.title

    if actual_title == expected_title:
        print("Login successful!")
    else:
        print(f"Login failed. Title: {actual_title}")

finally:
    # Clean up
    driver.quit()

Week 2 — TestNG & PyTest Frameworks Frameworks

Introduction to Automation Testing Frameworks

A test automation framework provides a structured environment for writing, organizing, and executing tests. Key components include:

TestNG Architecture and Test Lifecycle Management Java

TestNG is a powerful testing framework inspired by JUnit but with additional features. It provides annotations to control test execution order, grouping, and dependencies.

Java
import org.testng.annotations.*;

public class TestNGExample {

    @BeforeSuite
    public void beforeSuite() {
        System.out.println("Before Suite - Setup once before all tests");
    }

    @BeforeTest
    public void beforeTest() {
        System.out.println("Before Test - Setup before test block");
    }

    @BeforeClass
    public void beforeClass() {
        System.out.println("Before Class - Setup before class");
    }

    @BeforeMethod
    public void beforeMethod() {
        System.out.println("Before Method - Setup before each test");
    }

    @Test(priority = 1, groups = {"smoke", "regression"})
    public void testLogin() {
        System.out.println("Test Login");
        // Test logic
    }

    @Test(priority = 2, groups = {"regression"})
    public void testDashboard() {
        System.out.println("Test Dashboard");
        // Test logic
    }

    @Test(expectedExceptions = NullPointerException.class)
    public void testException() {
        // Test that expects an exception
        throw new NullPointerException();
    }

    @Test(timeOut = 5000)
    public void testTimeout() {
        // Test that must complete within 5 seconds
    }

    @AfterMethod
    public void afterMethod() {
        System.out.println("After Method - Cleanup after each test");
    }

    @AfterClass
    public void afterClass() {
        System.out.println("After Class - Cleanup after class");
    }

    @AfterTest
    public void afterTest() {
        System.out.println("After Test - Cleanup after test block");
    }

    @AfterSuite
    public void afterSuite() {
        System.out.println("After Suite - Cleanup after all tests");
    }
}

PyTest Fundamentals and Python-Based Automation Testing Python

PyTest is a mature, feature-rich testing framework for Python. It's simple to learn, highly extensible, and widely used in the Python community.

Python
import pytest

def setup_module():
    print("Setup Module - Runs once before all tests in module")

def teardown_module():
    print("Teardown Module - Runs once after all tests in module")

class TestLogin:
    @classmethod
    def setup_class(cls):
        print("Setup Class - Runs once before all tests in class")

    @classmethod
    def teardown_class(cls):
        print("Teardown Class - Runs once after all tests in class")

    def setup_method(self):
        print("Setup Method - Runs before each test")

    def teardown_method(self):
        print("Teardown Method - Runs after each test")

    @pytest.mark.smoke
    def test_valid_login(self):
        print("Test Valid Login")
        assert True

    @pytest.mark.regression
    def test_dashboard(self):
        print("Test Dashboard")
        assert True

    @pytest.mark.skip(reason="Feature not implemented yet")
    def test_forgot_password(self):
        print("Test Forgot Password")

    @pytest.mark.xfail
    def test_failing_feature(self):
        print("This test is expected to fail")
        assert False

    @pytest.mark.parametrize("username,password,expected", [
        ("admin", "admin123", "success"),
        ("user", "wrongpass", "failure"),
        ("", "password", "error")
    ])
    def test_multiple_login_scenarios(self, username, password, expected):
        print(f"Testing login with {username} and {password}")
        # Test logic
        assert expected in ["success", "failure", "error"]

Assertions, Validations, and Exception Handling

TestNG Assertions Java

Java
import org.testng.Assert;
import org.testng.annotations.Test;

public class AssertionExample {
    @Test
    public void testAssertions() {
        // Equals assertions
        Assert.assertEquals(actual, expected);
        Assert.assertEquals(actual, expected, "Custom error message");

        // Boolean assertions
        Assert.assertTrue(condition);
        Assert.assertFalse(condition);

        // Null assertions
        Assert.assertNull(object);
        Assert.assertNotNull(object);

        // Fail test
        Assert.fail("Test failed due to unexpected condition");

        // Exception handling with try-catch
        try {
            driver.findElement(By.id("non-existent"));
        } catch (NoSuchElementException e) {
            // Log or handle exception
            System.out.println("Element not found: " + e.getMessage());
        }

        // Soft assertions (multiple assertions in one test)
        SoftAssert softAssert = new SoftAssert();
        softAssert.assertEquals(actual1, expected1);
        softAssert.assertTrue(condition2);
        softAssert.assertEquals(actual3, expected3);
        softAssert.assertAll();  // Call at the end to report all failures
    }
}

PyTest Assertions Python

Python
import pytest

def test_assertions():
    # Equals assertions
    assert actual == expected
    assert actual == expected, "Custom error message"

    # Boolean assertions
    assert condition
    assert not condition

    # Null assertions
    assert obj is None
    assert obj is not None

    # Contains assertions
    assert "text" in string
    assert item in list

    # Type assertions
    assert isinstance(value, int)

    # Exception handling with pytest.raises
    with pytest.raises(ValueError):
        int("not a number")

    # Multiple assertions with soft assertions
    # Using pytest-check package or manual collection
    errors = []
    try:
        assert actual1 == expected1
    except AssertionError as e:
        errors.append(str(e))

    try:
        assert condition2
    except AssertionError as e:
        errors.append(str(e))

    if errors:
        pytest.fail("\n".join(errors))

Data-Driven Testing Using Data Providers and Parameterisation

TestNG Data Providers Java

Java
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class DataDrivenTest {
    @DataProvider(name = "loginData")
    public Object[][] getLoginData() {
        return new Object[][] {
            {"admin", "admin123", true},
            {"user", "wrongpass", false},
            {"", "password", false}
        };
    }

    @Test(dataProvider = "loginData")
    public void testLogin(String username, String password, boolean expectedResult) {
        System.out.println("Testing: " + username + " | " + password);
        // Test logic with provided data
        // Assert result matches expectedResult
    }

    // Data provider from Excel or CSV file
    @DataProvider(name = "excelData")
    public Object[][] getExcelData() {
        return ExcelUtils.readData("testdata.xlsx", "Sheet1");
    }
}

PyTest Parameterization Python

Python
import pytest

# Single parameter
@pytest.mark.parametrize("browser", ["chrome", "firefox", "edge"])
def test_cross_browser(browser):
    print(f"Testing on {browser}")
    # Test logic

# Multiple parameters
@pytest.mark.parametrize("username,password,expected", [
    ("admin", "admin123", True),
    ("user", "wrongpass", False),
    ("", "password", False)
])
def test_login_data(username, password, expected):
    print(f"Testing: {username} | {password}")
    # Test logic
    # Assert result matches expected

# Parameterize with fixtures
@pytest.fixture(params=["chrome", "firefox", "edge"])
def browser(request):
    return request.param

def test_browser_launch(browser):
    print(f"Launching {browser} browser")
    # Test logic

# Dynamic parameterization from external source
@pytest.fixture
def test_data():
    return ExcelUtils.read_data("testdata.xlsx", "Sheet1")

def test_dynamic_data(test_data):
    for username, password, expected in test_data:
        # Test logic for each row
        pass

Test Grouping, Prioritisation, and Dependency Management

TestNG Grouping and Priority Java

Java
import org.testng.annotations.Test;

public class TestGrouping {
    @Test(groups = {"smoke", "regression"}, priority = 1)
    public void testLogin() {
        // Smoke and regression test
    }

    @Test(groups = {"regression"}, priority = 2)
    public void testDashboard() {
        // Regression test only
    }

    @Test(groups = {"smoke"}, priority = 3)
    public void testLogout() {
        // Smoke test only
    }

    @Test(groups = {"regression", "integration"}, priority = 4)
    public void testPayment() {
        // Regression and integration test
    }

    // Dependent tests
    @Test(dependsOnMethods = "testLogin")
    public void testUserProfile() {
        // Runs only if testLogin passes
    }

    // Always run even if dependent method fails
    @Test(dependsOnMethods = "testLogin", alwaysRun = true)
    public void testCleanup() {
        // Runs even if testLogin fails
    }
}

// Run specific groups in testng.xml:
// <groups>
//   <run>
//     <include name="smoke"/>
//   </run>
// </groups>

PyTest Markers and Ordering Python

Python
import pytest

# Define custom markers in pytest.ini:
# [pytest]
# markers =
#     smoke: Smoke tests
#     regression: Regression tests
#     integration: Integration tests

@pytest.mark.smoke
@pytest.mark.regression
def test_login():
    # Smoke and regression test
    pass

@pytest.mark.regression
def test_dashboard():
    # Regression test only
    pass

@pytest.mark.smoke
def test_logout():
    # Smoke test only
    pass

@pytest.mark.regression
@pytest.mark.integration
def test_payment():
    # Regression and integration test
    pass

# Run specific markers:
# pytest -m smoke
# pytest -m "smoke and regression"
# pytest -m "not integration"

# Ordering with pytest-order plugin
@pytest.mark.order(1)
def test_first():
    pass

@pytest.mark.order(2)
def test_second():
    pass

# Dependency with pytest-dependency plugin
@pytest.mark.dependency()
def test_login():
    assert True

@pytest.mark.dependency(depends=["test_login"])
def test_user_profile():
    # Runs only if test_login passes
    pass

Generating Test Execution Reports and Logs

TestNG Reports Java

Java
// ExtentReports setup
ExtentReports extent = new ExtentReports();
ExtentSparkReporter spark = new ExtentSparkReporter("extent-report.html");
extent.attachReporter(spark);

@Test
public void testWithReporting() {
    ExtentTest test = extent.createTest("Login Test");
    test.info("Navigating to login page");
    // Test logic
    test.pass("Login successful");
}

// TestNG Listener for custom logging
public class TestListener implements ITestListener {
    @Override
    public void onTestStart(ITestResult result) {
        System.out.println("Started: " + result.getName());
    }
    @Override
    public void onTestSuccess(ITestResult result) {
        System.out.println("Passed: " + result.getName());
    }
    @Override
    public void onTestFailure(ITestResult result) {
        System.out.println("Failed: " + result.getName());
        // Capture screenshot
        File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
    }
}

PyTest Reports Python

Python
# Generate HTML reports with pytest-html
# pytest --html=report.html --self-contained-html

# Allure reports for PyTest
# pytest --alluredir=allure-results
# allure serve allure-results

# Custom logging
import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('test.log'),
        logging.StreamHandler()
    ]
)

def test_with_logging():
    logger = logging.getLogger(__name__)
    logger.info("Starting test")
    # Test logic
    logger.info("Test completed")

# pytest.ini configuration
# [pytest]
# log_cli = True
# log_cli_level = INFO
# log_cli_format = %(asctime)s - %(levelname)s - %(message)s
# log_file = test.log
# log_file_level = DEBUG

Week 3 — Page Object Model (POM) & Page Factory Design POM

Introduction to Automation Design Patterns

Design patterns are reusable solutions to common problems in software design. In test automation, they help create maintainable, scalable, and readable test code. The most important patterns for Selenium are:

Page Object Model (POM) Architecture and Implementation

The Page Object Model is a design pattern that creates an object repository for web UI elements. Each web page is represented as a class that:

Java
// Base Page class
public class BasePage {
    protected WebDriver driver;
    protected WebDriverWait wait;

    public BasePage(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    }

    public String getPageTitle() {
        return driver.getTitle();
    }

    public void takeScreenshot(String name) {
        File screenshot = ((TakesScreenshot) driver)
            .getScreenshotAs(OutputType.FILE);
        // Save screenshot
    }
}

// Login Page
public class LoginPage extends BasePage {
    @FindBy(id = "username")
    private WebElement usernameInput;

    @FindBy(id = "password")
    private WebElement passwordInput;

    @FindBy(id = "login-btn")
    private WebElement loginButton;

    @FindBy(className = "error-message")
    private WebElement errorMessage;

    public LoginPage(WebDriver driver) {
        super(driver);
        PageFactory.initElements(driver, this);
    }

    public LoginPage enterUsername(String username) {
        usernameInput.clear();
        usernameInput.sendKeys(username);
        return this;
    }

    public LoginPage enterPassword(String password) {
        passwordInput.clear();
        passwordInput.sendKeys(password);
        return this;
    }

    public DashboardPage clickLogin() {
        loginButton.click();
        return new DashboardPage(driver);
    }

    public String getErrorMessage() {
        wait.until(ExpectedConditions.visibilityOf(errorMessage));
        return errorMessage.getText();
    }

    public boolean isLoginPageDisplayed() {
        return usernameInput.isDisplayed();
    }
}

// Dashboard Page
public class DashboardPage extends BasePage {
    @FindBy(id = "welcome-message")
    private WebElement welcomeMessage;

    @FindBy(id = "logout-btn")
    private WebElement logoutButton;

    public DashboardPage(WebDriver driver) {
        super(driver);
        PageFactory.initElements(driver, this);
    }

    public String getWelcomeMessage() {
        wait.until(ExpectedConditions.visibilityOf(welcomeMessage));
        return welcomeMessage.getText();
    }

    public LoginPage logout() {
        logoutButton.click();
        return new LoginPage(driver);
    }

    public boolean isDashboardDisplayed() {
        return welcomeMessage.isDisplayed();
    }
}
Python
# Base Page
class BasePage:
    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)

    def get_page_title(self):
        return self.driver.title

    def take_screenshot(self, name):
        self.driver.save_screenshot(f"screenshots/{name}.png")

# Login Page
class LoginPage(BasePage):
    def __init__(self, driver):
        super().__init__(driver)
        self.username_input = driver.find_element(By.ID, "username")
        self.password_input = driver.find_element(By.ID, "password")
        self.login_button = driver.find_element(By.ID, "login-btn")
        self.error_message = driver.find_element(By.CLASS_NAME, "error-message")

    def enter_username(self, username):
        self.username_input.clear()
        self.username_input.send_keys(username)
        return self

    def enter_password(self, password):
        self.password_input.clear()
        self.password_input.send_keys(password)
        return self

    def click_login(self):
        self.login_button.click()
        return DashboardPage(self.driver)

    def get_error_message(self):
        self.wait.until(EC.visibility_of(self.error_message))
        return self.error_message.text

    def is_login_page_displayed(self):
        return self.username_input.is_displayed()

# Dashboard Page
class DashboardPage(BasePage):
    def __init__(self, driver):
        super().__init__(driver)
        self.welcome_message = driver.find_element(By.ID, "welcome-message")
        self.logout_button = driver.find_element(By.ID, "logout-btn")

    def get_welcome_message(self):
        self.wait.until(EC.visibility_of(self.welcome_message))
        return self.welcome_message.text

    def logout(self):
        self.logout_button.click()
        return LoginPage(self.driver)

    def is_dashboard_displayed(self):
        return self.welcome_message.is_displayed()

Page Factory Concepts and Annotation-Based Element Handling

Page Factory is a built-in Selenium class that initializes page elements with lazy loading. It uses annotations like @FindBy (Java) or @FindBy (Python with pom.py) to define elements.

Java
// Page Factory annotations
public class LoginPage extends BasePage {
    @FindBy(id = "username")
    private WebElement usernameInput;

    @FindBy(name = "password")
    private WebElement passwordInput;

    @FindBy(css = "button[type='submit']")
    private WebElement loginButton;

    @FindBy(xpath = "//div[@class='error']")
    private WebElement errorMessage;

    @FindBy(how = How.ID, using = "remember-me")
    private WebElement rememberMeCheckbox;

    // Element with multiple locators
    @FindAll({
        @FindBy(id = "username"),
        @FindBy(name = "user")
    })
    private WebElement usernameAlternative;

    // Caching element (for elements that are found once)
    @CacheLookup
    @FindBy(id = "page-header")
    private WebElement pageHeader;

    public LoginPage(WebDriver driver) {
        super(driver);
        PageFactory.initElements(driver, this);
    }
}
Python
# Python equivalent using selenium-page-factory
from selenium_page_factory import PageFactory

class LoginPage(BasePage):
    def __init__(self, driver):
        super().__init__(driver)
        self.username_input = driver.find_element(By.ID, "username")
        self.password_input = driver.find_element(By.NAME, "password")
        self.login_button = driver.find_element(By.CSS_SELECTOR, "button[type='submit']")
        self.error_message = driver.find_element(By.XPATH, "//div[@class='error']")
        self.remember_me = driver.find_element(By.ID, "remember-me")

    # For lazy loading, use properties
    @property
    def username_input(self):
        return self.driver.find_element(By.ID, "username")

    @property
    def password_input(self):
        return self.driver.find_element(By.NAME, "password")

Managing Locators and Reducing Code Duplication

Java
// Locator constants class
public class Locators {
    public static final String USERNAME_INPUT = "username";
    public static final String PASSWORD_INPUT = "password";
    public static final String LOGIN_BUTTON = "login-btn";
}

// Using constants
driver.findElement(By.id(Locators.USERNAME_INPUT)).sendKeys("admin");

Framework Structuring and Modular Test Design

Project Structure:
src/
├── main/
│   └── java/
│       └── com/company/
│           ├── pages/
│           │   ├── BasePage.java
│           │   ├── LoginPage.java
│           │   └── DashboardPage.java
│           ├── utils/
│           │   ├── DriverManager.java
│           │   ├── ConfigReader.java
│           │   └── ExcelReader.java
│           └── base/
│               └── TestBase.java
└── test/
    └── java/
        └── com/company/
            ├── tests/
            │   ├── LoginTest.java
            │   └── DashboardTest.java
            └── testdata/
                └── TestData.java

Implementing Utility Classes and Configuration Management

Java
// ConfigReader utility
public class ConfigReader {
    private static Properties properties;

    static {
        properties = new Properties();
        try {
            FileInputStream fis = new FileInputStream("config.properties");
            properties.load(fis);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static String getProperty(String key) {
        return properties.getProperty(key);
    }

    public static String getBaseUrl() {
        return getProperty("base.url");
    }

    public static String getBrowser() {
        return getProperty("browser");
    }
}

// DriverManager (Singleton pattern)
public class DriverManager {
    private static WebDriver driver;

    private DriverManager() {}

    public static WebDriver getDriver() {
        if (driver == null) {
            String browser = ConfigReader.getBrowser();
            switch (browser) {
                case "chrome":
                    WebDriverManager.chromedriver().setup();
                    driver = new ChromeDriver();
                    break;
                case "firefox":
                    WebDriverManager.firefoxdriver().setup();
                    driver = new FirefoxDriver();
                    break;
                default:
                    throw new IllegalArgumentException("Browser not supported");
            }
            driver.manage().window().maximize();
            driver.manage().timeouts().implicitlyWait(
                Duration.ofSeconds(Long.parseLong(
                    ConfigReader.getProperty("implicit.wait"))
                )
            );
        }
        return driver;
    }

    public static void quitDriver() {
        if (driver != null) {
            driver.quit();
            driver = null;
        }
    }
}

Best Practices for Scalable Test Automation Projects


Week 4 — Advanced Selenium Interactions Advanced

Handling JavaScript Alerts and Confirmation Dialogs

Java
// Alert handling
Alert alert = driver.switchTo().alert();

// Get alert text
String alertText = alert.getText();

// Accept alert (OK)
alert.accept();

// Dismiss alert (Cancel)
alert.dismiss();

// Send text to prompt
alert.sendKeys("Enter some text");
alert.accept();

// Check if alert is present
try {
    Alert alert = driver.switchTo().alert();
    alert.accept();
} catch (NoAlertPresentException e) {
    // No alert present
}

// Using WebDriverWait for alert
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.alertIsPresent());
Python
# Alert handling
alert = driver.switch_to.alert

# Get alert text
alert_text = alert.text

# Accept alert (OK)
alert.accept()

# Dismiss alert (Cancel)
alert.dismiss()

# Send text to prompt
alert.send_keys("Enter some text")
alert.accept()

# Check if alert is present
try:
    alert = driver.switch_to.alert
    alert.accept()
except NoAlertPresentException:
    # No alert present
    pass

# Using WebDriverWait for alert
wait = WebDriverWait(driver, 10)
wait.until(EC.alert_is_present())

Working with Iframes and Nested Frames

Java
// Switch to iframe by index
driver.switchTo().frame(0);

// Switch to iframe by name/id
driver.switchTo().frame("iframe-name");
driver.switchTo().frame("iframe-id");

// Switch to iframe by WebElement
WebElement iframe = driver.findElement(By.id("my-iframe"));
driver.switchTo().frame(iframe);

// Switch to parent frame
driver.switchTo().parentFrame();

// Switch to default content
driver.switchTo().defaultContent();

// Handle nested frames
driver.switchTo().frame("outer-frame");
driver.switchTo().frame("inner-frame");
// Interact with elements in inner frame
driver.switchTo().defaultContent();  // Back to top
Python
# Switch to iframe by index
driver.switch_to.frame(0)

# Switch to iframe by name/id
driver.switch_to.frame("iframe-name")
driver.switch_to.frame("iframe-id")

# Switch to iframe by WebElement
iframe = driver.find_element(By.ID, "my-iframe")
driver.switch_to.frame(iframe)

# Switch to parent frame
driver.switch_to.parent_frame()

# Switch to default content
driver.switch_to.default_content()

# Handle nested frames
driver.switch_to.frame("outer-frame")
driver.switch_to.frame("inner-frame")
# Interact with elements in inner frame
driver.switch_to.default_content()  # Back to top

Managing Multiple Browser Windows and Tabs

Java
// Get current window handle
String mainWindow = driver.getWindowHandle();

// Get all window handles
Set windowHandles = driver.getWindowHandles();

// Switch to new window
for (String handle : windowHandles) {
    if (!handle.equals(mainWindow)) {
        driver.switchTo().window(handle);
        break;
    }
}

// Close current window
driver.close();

// Switch back to main window
driver.switchTo().window(mainWindow);

// Open new tab
driver.switchTo().newWindow(WindowType.TAB);
driver.get("https://example.com");

// Open new window
driver.switchTo().newWindow(WindowType.WINDOW);
driver.get("https://example.com");
Python
# Get current window handle
main_window = driver.current_window_handle

# Get all window handles
window_handles = driver.window_handles

# Switch to new window
for handle in window_handles:
    if handle != main_window:
        driver.switch_to.window(handle)
        break

# Close current window
driver.close()

# Switch back to main window
driver.switch_to.window(main_window)

# Open new tab
driver.switch_to.new_window('tab')
driver.get("https://example.com")

# Open new window
driver.switch_to.new_window('window')
driver.get("https://example.com")

Advanced Mouse and Keyboard Interactions Using ActionChains

Java
import org.openqa.selenium.interactions.Actions;

Actions actions = new Actions(driver);

// Hover over element
WebElement element = driver.findElement(By.id("menu"));
actions.moveToElement(element).perform();

// Click and hold
actions.clickAndHold(element).perform();

// Double click
actions.doubleClick(element).perform();

// Right click
actions.contextClick(element).perform();

// Drag and drop
WebElement source = driver.findElement(By.id("source"));
WebElement target = driver.findElement(By.id("target"));
actions.dragAndDrop(source, target).perform();

// Key press (Ctrl + A)
actions.keyDown(Keys.CONTROL)
       .sendKeys("a")
       .keyUp(Keys.CONTROL)
       .perform();

// Mouse wheel scroll
actions.scrollByAmount(0, 100).perform();  // Scroll down 100px

// Scroll to element
actions.scrollToElement(element).perform();

// Build complex action sequence
actions.moveToElement(element)
       .click()
       .keyDown(Keys.CONTROL)
       .sendKeys("a")
       .keyUp(Keys.CONTROL)
       .build()
       .perform();
Python
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys

actions = ActionChains(driver)

# Hover over element
element = driver.find_element(By.ID, "menu")
actions.move_to_element(element).perform()

# Click and hold
actions.click_and_hold(element).perform()

# Double click
actions.double_click(element).perform()

# Right click
actions.context_click(element).perform()

# Drag and drop
source = driver.find_element(By.ID, "source")
target = driver.find_element(By.ID, "target")
actions.drag_and_drop(source, target).perform()

# Key press (Ctrl + A)
actions.key_down(Keys.CONTROL)\
       .send_keys("a")\
       .key_up(Keys.CONTROL)\
       .perform()

# Mouse wheel scroll
actions.scroll_by_amount(0, 100).perform()  # Scroll down 100px

# Scroll to element
actions.scroll_to_element(element).perform()

# Build complex action sequence
actions.move_to_element(element)\
       .click()\
       .key_down(Keys.CONTROL)\
       .send_keys("a")\
       .key_up(Keys.CONTROL)\
       .perform()

Executing JavaScript Within Selenium Scripts

Java
// Execute JavaScript
JavascriptExecutor js = (JavascriptExecutor) driver;

// Scroll to element
js.executeScript("arguments[0].scrollIntoView(true);", element);

// Scroll to bottom of page
js.executeScript("window.scrollTo(0, document.body.scrollHeight);");

// Get page title
String title = (String) js.executeScript("return document.title;");

// Highlight element
js.executeScript("arguments[0].style.border='3px solid red'", element);

// Click element via JavaScript
js.executeScript("arguments[0].click();", element);

// Set attribute
js.executeScript("arguments[0].setAttribute('disabled', 'true')", element);

// Get attribute
String value = (String) js.executeScript(
    "return arguments[0].getAttribute('value');", element
);

// Create a custom JavaScript function
js.executeScript("window.testFunction = function() { return 'test'; }");
String result = (String) js.executeScript("return window.testFunction();");
Python
# Execute JavaScript
js = driver.execute_script

# Scroll to element
js("arguments[0].scrollIntoView(true);", element)

# Scroll to bottom of page
js("window.scrollTo(0, document.body.scrollHeight);")

# Get page title
title = js("return document.title;")

# Highlight element
js("arguments[0].style.border='3px solid red'", element)

# Click element via JavaScript
js("arguments[0].click();", element)

# Set attribute
js("arguments[0].setAttribute('disabled', 'true')", element)

# Get attribute
value = js("return arguments[0].getAttribute('value');", element)

# Create a custom JavaScript function
js("window.testFunction = function() { return 'test'; }")
result = js("return window.testFunction();")

Handling Dynamic and AJAX-Based Web Applications

Java
// Wait for AJAX to complete
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

// Wait for element to be present
wait.until(ExpectedConditions.presenceOfElementLocated(By.id("ajax-content")));

// Wait for element to be visible
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("ajax-content")));

// Wait for element to be clickable
wait.until(ExpectedConditions.elementToBeClickable(By.id("ajax-button")));

// Wait for text to appear
wait.until(ExpectedConditions.textToBePresentInElementLocated(
    By.id("status"), "Complete"
));

// Wait for attribute to contain value
wait.until(ExpectedConditions.attributeContains(
    By.id("progress"), "aria-valuenow", "100"
));

// Wait for jQuery to finish
wait.until(driver -> (Boolean) ((JavascriptExecutor) driver)
    .executeScript("return jQuery.active == 0"));

// Wait for document ready state
wait.until(driver -> "complete".equals(
    ((JavascriptExecutor) driver)
        .executeScript("return document.readyState")
));

File Upload, Download, and Browser-Based Validations

Java
// File upload
WebElement fileInput = driver.findElement(By.id("file-upload"));
fileInput.sendKeys("/path/to/file.txt");

// Multiple file upload
WebElement fileInput = driver.findElement(By.id("files"));
fileInput.sendKeys("/path/to/file1.txt\n/path/to/file2.txt");

// Download verification
// Use HTTP client or wait for file to appear in downloads folder
String downloadDir = System.getProperty("user.dir") + "/downloads";
File downloadedFile = new File(downloadDir + "/report.pdf");

// Wait for download to complete
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
wait.until(driver -> downloadedFile.exists() && downloadedFile.length() > 0);

// Validate file content
// Use Java I/O or Apache POI to read file content
Python
# File upload
file_input = driver.find_element(By.ID, "file-upload")
file_input.send_keys("/path/to/file.txt")

# Multiple file upload
file_input = driver.find_element(By.ID, "files")
file_input.send_keys("/path/to/file1.txt\n/path/to/file2.txt")

# Download verification
import os
import time

download_dir = os.path.join(os.getcwd(), "downloads")
downloaded_file = os.path.join(download_dir, "report.pdf")

# Wait for download to complete
timeout = 30
start_time = time.time()
while time.time() - start_time < timeout:
    if os.path.exists(downloaded_file) and os.path.getsize(downloaded_file) > 0:
        break
    time.sleep(1)

# Validate file content
with open(downloaded_file, 'rb') as f:
    content = f.read()
    assert len(content) > 0

Week 5 — Selenium Grid & Parallel Test Execution Grid

Introduction to Selenium Grid Architecture

Selenium Grid allows you to run tests on different machines and browsers simultaneously. It uses a hub-node architecture:

Hub-Node Configuration and Distributed Test Execution

# Start Hub (on hub machine)
java -jar selenium-server-standalone-4.15.0.jar hub

# Start Node (on node machine)
java -jar selenium-server-standalone-4.15.0.jar node \
    --hub http://hub-ip:4444 \
    --browser "chrome" \
    --browser "firefox" \
    --max-sessions 5

# Start Node with specific capabilities
java -jar selenium-server-standalone-4.15.0.jar node \
    --hub http://hub-ip:4444 \
    --browser "chrome" \
    --browser "edge" \
    --max-sessions 5 \
    --detect-drivers false \
    --driver-factory "chrome" "org.openqa.selenium.chrome.ChromeDriver" \
    --driver-factory "edge" "org.openqa.selenium.edge.EdgeDriver"

Parallel Test Execution Strategies for Faster Automation

TestNG Parallel Execution Java

<!-- testng.xml -->
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Parallel Test Suite" parallel="tests" thread-count="4">
    <test name="Test on Chrome">
        <parameter name="browser" value="chrome"/>
        <classes>
            <class name="com.company.tests.LoginTest"/>
            <class name="com.company.tests.DashboardTest"/>
        </classes>
    </test>
    <test name="Test on Firefox">
        <parameter name="browser" value="firefox"/>
        <classes>
            <class name="com.company.tests.LoginTest"/>
            <class name="com.company.tests.DashboardTest"/>
        </classes>
    </test>
    <test name="Test on Edge">
        <parameter name="browser" value="edge"/>
        <classes>
            <class name="com.company.tests.LoginTest"/>
            <class name="com.company.tests.DashboardTest"/>
        </classes>
    </test>
</suite>

PyTest Parallel Execution Python

# Install pytest-xdist
pip install pytest-xdist

# Run tests in parallel with 4 workers
pytest -n 4

# Run tests in parallel with auto-detected CPU count
pytest -n auto

# Run tests in parallel with specific test pattern
pytest -n 4 tests/test_login.py tests/test_dashboard.py

# Run tests across multiple nodes
pytest -n 4 --dist loadscope  # Distribute by test class/module
pytest -n 4 --dist loadfile   # Distribute by test file

Containerised Selenium Grid Setup Using Docker

# Docker Compose for Selenium Grid 4
# docker-compose.yml
version: '3'
services:
  selenium-hub:
    image: selenium/hub:4.15.0
    container_name: selenium-hub
    ports:
      - "4442:4442"
      - "4443:4443"
      - "4444:4444"

  chrome:
    image: selenium/node-chrome:4.15.0
    depends_on:
      - selenium-hub
    environment:
      - SE_EVENT_BUS_HOST=selenium-hub
      - SE_EVENT_BUS_PUBLISH_PORT=4442
      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
    ports:
      - "6900:5900"

  firefox:
    image: selenium/node-firefox:4.15.0
    depends_on:
      - selenium-hub
    environment:
      - SE_EVENT_BUS_HOST=selenium-hub
      - SE_EVENT_BUS_PUBLISH_PORT=4442
      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
    ports:
      - "6901:5900"

  edge:
    image: selenium/node-edge:4.15.0
    depends_on:
      - selenium-hub
    environment:
      - SE_EVENT_BUS_HOST=selenium-hub
      - SE_EVENT_BUS_PUBLISH_PORT=4442
      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
    ports:
      - "6902:5900"

# Start grid
docker-compose up -d

# Stop grid
docker-compose down

Cross-Browser and Cross-Platform Testing Techniques

Java
// Grid connection with DesiredCapabilities
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setBrowserName("chrome");
capabilities.setPlatform(Platform.WINDOWS);

WebDriver driver = new RemoteWebDriver(
    new URL("http://hub-ip:4444/wd/hub"),
    capabilities
);

// With ChromeOptions
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless");
options.addArguments("--no-sandbox");

DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability(ChromeOptions.CAPABILITY, options);
capabilities.setBrowserName("chrome");

WebDriver driver = new RemoteWebDriver(
    new URL("http://hub-ip:4444/wd/hub"),
    capabilities
);
Python
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver

# Grid connection with capabilities
capabilities = {
    "browserName": "chrome",
    "platform": "WINDOWS",
    "version": "latest"
}

driver = RemoteWebDriver(
    command_executor="http://hub-ip:4444/wd/hub",
    desired_capabilities=capabilities
)

# With Chrome options
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument("--headless")
options.add_argument("--no-sandbox")

driver = RemoteWebDriver(
    command_executor="http://hub-ip:4444/wd/hub",
    options=options
)

Cloud-Based Automation Testing with Sauce Labs

Java
// Sauce Labs configuration
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability("browserName", "chrome");
capabilities.setCapability("browserVersion", "119");
capabilities.setCapability("platformName", "Windows 10");
capabilities.setCapability("sauce:options", new SauceOptions()
    .setUsername("your-username")
    .setAccessKey("your-access-key")
    .setBuild("Build-1")
    .setName("Login Test")
);

WebDriver driver = new RemoteWebDriver(
    new URL("https://ondemand.saucelabs.com/wd/hub"),
    capabilities
);

Scalable Execution Pipelines and Resource Optimisation


Week 6 — CI/CD Integration, Reporting & Capstone Project CI/CD

Introduction to Continuous Integration and Continuous Deployment (CI/CD)

CI/CD is the practice of automatically building, testing, and deploying software changes. In test automation, CI/CD ensures that tests are run on every code change, providing fast feedback to developers.

Integrating Selenium Frameworks with Jenkins Pipelines

// Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any

    tools {
        maven 'Maven-3'
        jdk 'JDK-17'
    }

    environment {
        BROWSER = 'chrome'
        HEADLESS = 'true'
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        stage('Install Dependencies') {
            steps {
                sh 'mvn clean install -DskipTests'
            }
        }

        stage('Run Tests') {
            steps {
                sh 'mvn test -Dbrowser=${BROWSER} -Dheadless=${HEADLESS}'
            }
            post {
                always {
                    // Archive test results
                    junit 'target/surefire-reports/*.xml'
                    // Archive HTML reports
                    publishHTML([
                        reportDir: 'target/surefire-reports',
                        reportFiles: 'index.html',
                        reportName: 'Test Report'
                    ])
                }
            }
        }

        stage('Report') {
            steps {
                // Generate Allure report
                allure([
                    includeProperties: false,
                    jdk: '',
                    properties: [],
                    reportBuildPolicy: 'ALWAYS',
                    results: [[path: 'allure-results']]
                ])
            }
        }
    }

    post {
        always {
            // Cleanup
            sh 'docker-compose down'
        }
        failure {
            // Notify on failure
            mail to: 'team@company.com',
                 subject: "Build failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
                 body: "Build failed. Check Jenkins for details."
        }
    }
}

Automated Build Triggering and Scheduled Test Execution

// Jenkins pipeline triggers
triggers {
    // Poll SCM every 5 minutes
    pollSCM 'H/5 * * * *'

    // Run daily at 2am
    cron 'H 2 * * *'

    // Run on specific days
    cron 'H 2 * * 1,3,5'  // Monday, Wednesday, Friday
}

// GitHub Actions scheduled trigger
# .github/workflows/playwright.yml
on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]
  schedule:
    - cron: '0 2 * * *'  # Daily at 2am
  workflow_dispatch:  # Manual trigger

Advanced Reporting Using Allure Reports

# Install Allure (macOS)
brew install allure

# Run tests with Allure reporting
# Java (TestNG)
mvn test
mvn allure:report

# Python (pytest)
pytest --alluredir=allure-results
allure serve allure-results

# Allure annotations
import io.qameta.allure.*;

@Severity(SeverityLevel.CRITICAL)
@Feature("Login")
@Story("User Authentication")
@Test
public void testLogin() {
    Allure.step("Navigate to login page", () -> {
        // Step logic
    });
    Allure.attachment("Screenshot", screenshot, "image/png");
}

Screenshot Capture, Logging, and Failure Analysis

Java
// Capture screenshot on failure
public void captureScreenshot(String fileName) {
    File screenshot = ((TakesScreenshot) driver)
        .getScreenshotAs(OutputType.FILE);
    String screenshotPath = "screenshots/" + fileName + ".png";
    try {
        FileUtils.copyFile(screenshot, new File(screenshotPath));
    } catch (IOException e) {
        e.printStackTrace();
    }
}

// TestNG Listener for automatic screenshot on failure
public class ScreenshotListener implements ITestListener {
    @Override
    public void onTestFailure(ITestResult result) {
        WebDriver driver = (WebDriver) result.getTestContext()
            .getAttribute("driver");
        File screenshot = ((TakesScreenshot) driver)
            .getScreenshotAs(OutputType.FILE);
        // Attach to report
        // Save to artifacts folder
    }
}
Python
# Capture screenshot on failure
def capture_screenshot(driver, name):
    driver.save_screenshot(f"screenshots/{name}.png")

# PyTest fixture for automatic screenshot on failure
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    report = outcome.get_result()
    if report.when == "call" and report.failed:
        driver = item.funcargs.get('driver')
        if driver:
            driver.save_screenshot(f"screenshots/{item.name}.png")

# Logging configuration
import logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('test.log'),
        logging.StreamHandler()
    ]
)

Version Control Integration with Git and GitHub

# Git workflow for test automation
# Initialize repository
git init

# Add remote
git remote add origin https://github.com/company/test-automation.git

# Create feature branch
git checkout -b feature/login-tests

# Add changes
git add .
git commit -m "Add login test cases"

# Push to remote
git push origin feature/login-tests

# Create pull request on GitHub

# GitHub Actions workflow
name: Test Automation

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        browser: [chrome, firefox, edge]
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'
      - name: Run tests
        run: mvn test -Dbrowser=${{ matrix.browser }}
      - name: Upload test results
        uses: actions/upload-artifact@v3
        if: always()
        with:
          name: test-results-${{ matrix.browser }}
          path: target/surefire-reports/

Automation Framework Optimisation and Maintenance

Capstone Project

Project Objective: Build and deploy an end-to-end Selenium automation framework integrated with CI/CD pipelines.

Deliverables:

🎯 Capstone Goal: Build a complete test automation framework that demonstrates mastery of Selenium WebDriver, Page Object Model, TestNG/PyTest, Selenium Grid, and CI/CD integration.

📚 References & Resources:

All references are provided for further study and research.

© Copyright Westminster College of AI and Quantum Computing Limited, 2026. | info@wcc.co.uk | https://wcc.co.uk