Testing Tool

Selenium

Overview

Selenium is a comprehensive testing framework established as the de facto standard for web application automation, featuring multi-language and multi-browser support, a long track record, and a rich ecosystem as the foundational technology of the WebDriver protocol. With broad language support including Java, Python, C#, JavaScript, Ruby, and Kotlin, it accommodates any development team's technology stack and enables automated testing across all major browsers including Chrome, Firefox, Safari, Edge, and IE (legacy). Through Selenium Grid for distributed test execution, extensive third-party tool integration, and over 15 years of development history, it provides reliable web application automation and integration/E2E testing solutions from small-scale scripts to large-scale enterprise environments.

Details

Selenium 2025 edition has established an unshakeable position as the industry standard in web application automation. By leading the specification and implementation of the WebDriver protocol and becoming the foundation of the W3C WebDriver standard, it has built the groundwork for all modern browser automation tools through technological innovation. With over 15 years of continuous development and support from the world's largest open-source community, it provides stability and reliability proven across organizations of all industries and scales. Through three core components - Selenium WebDriver, Selenium Grid, and Selenium IDE - it provides complete coverage from unit testing to enterprise-level distributed testing environments. Multi-language support (Java, Python, C#, JavaScript, Ruby, Kotlin, PHP) leverages existing development team skillsets and minimizes learning costs. With Docker integration, Kubernetes compatibility, CI/CD pipeline optimization, and cloud provider integration, it achieves complete integration with modern DevOps environments and functions as the foundational infrastructure for continuous test automation.

Key Features

  • Industry-Standard WebDriver Protocol: Implementation of W3C standard WebDriver specification with full browser support
  • Rich Multi-Language Support: Support for 7+ languages including Java, Python, C#, JavaScript, Ruby, and Kotlin
  • Selenium Grid Distributed Execution: Large-scale parallel testing and simultaneous execution across different OS and browser environments
  • Rich Ecosystem: Extensive plugins, extensions, and third-party tools accumulated over 15 years
  • Enterprise Ready: Proven stability, security, and scalability in large-scale organizations
  • Legacy Browser Support: Support for older browser versions including Internet Explorer

Pros and Cons

Pros

  • High stability and reliability through industry-standard technology with over 15 years of development history
  • Multi-language support for Java, Python, C#, JavaScript, Ruby, etc., enabling existing skill utilization
  • Complete support for all major browsers (Chrome, Firefox, Safari, Edge, IE)
  • Distributed test execution and large-scale test environment construction through Selenium Grid
  • World's largest open-source community with abundant learning resources
  • Extensive third-party tools, plugins, and cloud service integrations
  • Complete integration with CI/CD pipelines, Docker, Kubernetes, and other modern infrastructure
  • Rich implementation track record and accumulated best practices in enterprise environments

Cons

  • Complexity of setup and environment construction, overhead of browser driver management
  • Inferior execution speed compared to modern tools (Playwright, Cypress)
  • Need to write explicit wait processing with risk of flaky test occurrence
  • Limited debugging capabilities and difficulty of troubleshooting
  • Need for complex wait logic with modern web technologies (SPA, asynchronous processing)
  • Some constraints due to legacy architecture and delayed application of new features

Reference Links

Code Examples

Installation and Setup

Java Setup

<!-- Maven pom.xml -->
<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>
// Gradle build.gradle
dependencies {
    testImplementation 'org.seleniumhq.selenium:selenium-java:4.15.0'
    testImplementation 'io.github.bonigarcia:webdrivermanager:5.6.2'
    testImplementation 'junit:junit:4.13.2'
}

Python Setup

# pip installation
pip install selenium

# WebDriverManager for automatic driver management
pip install webdriver-manager

# Test frameworks
pip install pytest
pip install unittest2

JavaScript/Node.js Setup

# npm installation
npm install selenium-webdriver --save-dev

# WebDriverManager
npm install chromedriver geckodriver --save-dev

# Test frameworks
npm install mocha chai --save-dev
npm install jest --save-dev

C# Setup

# NuGet packages
dotnet add package Selenium.WebDriver
dotnet add package Selenium.WebDriver.ChromeDriver
dotnet add package Selenium.Support
dotnet add package DotNetSeleniumExtras.WaitHelpers

Basic Browser Operations (Python)

# test_basic_operations.py
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
import unittest

class BasicWebDriverTest(unittest.TestCase):
    
    def setUp(self):
        # ChromeDriver automatic management and setup
        service = Service(ChromeDriverManager().install())
        options = webdriver.ChromeOptions()
        options.add_argument('--headless')  # Headless mode
        options.add_argument('--no-sandbox')
        options.add_argument('--disable-dev-shm-usage')
        
        self.driver = webdriver.Chrome(service=service, options=options)
        self.driver.implicitly_wait(10)  # Implicit wait
        self.wait = WebDriverWait(self.driver, 10)  # Explicit wait
    
    def tearDown(self):
        self.driver.quit()
    
    def test_basic_navigation(self):
        """Basic page navigation test"""
        self.driver.get('https://example.com')
        
        # Check page title
        self.assertIn('Example Domain', self.driver.title)
        
        # Check element existence
        heading = self.driver.find_element(By.TAG_NAME, 'h1')
        self.assertEqual('Example Domain', heading.text)
        
        # Check URL
        self.assertEqual('https://example.com/', self.driver.current_url)
    
    def test_form_interaction(self):
        """Form interaction test"""
        self.driver.get('https://example.com/contact')
        
        # Form element operations
        name_field = self.wait.until(
            EC.presence_of_element_located((By.NAME, 'name'))
        )
        name_field.send_keys('John Doe')
        
        email_field = self.driver.find_element(By.NAME, 'email')
        email_field.send_keys('[email protected]')
        
        # Textarea operations
        message_field = self.driver.find_element(By.NAME, 'message')
        message_field.clear()
        message_field.send_keys('This is a contact message.')
        
        # Checkbox and radio button
        checkbox = self.driver.find_element(By.ID, 'agree-terms')
        if not checkbox.is_selected():
            checkbox.click()
        
        # Dropdown selection
        from selenium.webdriver.support.ui import Select
        dropdown = Select(self.driver.find_element(By.NAME, 'category'))
        dropdown.select_by_visible_text('Technical Support')
        
        # Submit button click
        submit_button = self.driver.find_element(By.CSS_SELECTOR, 'button[type="submit"]')
        submit_button.click()
        
        # Check success message
        success_message = self.wait.until(
            EC.presence_of_element_located((By.CLASS_NAME, 'success-message'))
        )
        self.assertIn('Message sent', success_message.text)

if __name__ == '__main__':
    unittest.main()

Java Example

// BasicSeleniumTest.java
import org.junit.jupiter.api.*;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.Select;
import io.github.bonigarcia.wdm.WebDriverManager;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.*;

public class BasicSeleniumTest {
    
    private WebDriver driver;
    private WebDriverWait wait;
    
    @BeforeAll
    static void setupClass() {
        WebDriverManager.chromedriver().setup();
    }
    
    @BeforeEach
    void setUp() {
        ChromeOptions options = new ChromeOptions();
        options.addArguments("--headless");
        options.addArguments("--no-sandbox");
        options.addArguments("--disable-dev-shm-usage");
        
        driver = new ChromeDriver(options);
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
        wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    }
    
    @AfterEach
    void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }
    
    @Test
    void testBasicNavigation() {
        driver.get("https://example.com");
        
        // Check page title
        assertTrue(driver.getTitle().contains("Example Domain"));
        
        // Check element existence
        WebElement heading = driver.findElement(By.tagName("h1"));
        assertEquals("Example Domain", heading.getText());
        
        // Check URL
        assertEquals("https://example.com/", driver.getCurrentUrl());
    }
    
    @Test
    void testFormInteraction() {
        driver.get("https://example.com/contact");
        
        // Form element operations
        WebElement nameField = wait.until(
            ExpectedConditions.presenceOfElementLocated(By.name("name"))
        );
        nameField.sendKeys("John Doe");
        
        WebElement emailField = driver.findElement(By.name("email"));
        emailField.sendKeys("[email protected]");
        
        // Textarea operations
        WebElement messageField = driver.findElement(By.name("message"));
        messageField.clear();
        messageField.sendKeys("This is a contact message.");
        
        // Dropdown selection
        Select dropdown = new Select(driver.findElement(By.name("category")));
        dropdown.selectByVisibleText("Technical Support");
        
        // Checkbox operations
        WebElement checkbox = driver.findElement(By.id("agree-terms"));
        if (!checkbox.isSelected()) {
            checkbox.click();
        }
        
        // Submit button click
        WebElement submitButton = driver.findElement(
            By.cssSelector("button[type='submit']")
        );
        submitButton.click();
        
        // Check success message
        WebElement successMessage = wait.until(
            ExpectedConditions.presenceOfElementLocated(By.className("success-message"))
        );
        assertTrue(successMessage.getText().contains("Message sent"));
    }
    
    @Test
    void testMultipleWindows() {
        driver.get("https://example.com");
        
        String originalWindow = driver.getWindowHandle();
        
        // Click link that opens new tab
        driver.findElement(By.linkText("Open in New Tab")).click();
        
        // Wait for new window to open
        wait.until(ExpectedConditions.numberOfWindowsToBe(2));
        
        // Switch to new window
        for (String windowHandle : driver.getWindowHandles()) {
            if (!originalWindow.equals(windowHandle)) {
                driver.switchTo().window(windowHandle);
                break;
            }
        }
        
        // Operations in new window
        assertTrue(driver.getTitle().contains("New Page"));
        
        // Return to original window
        driver.switchTo().window(originalWindow);
        assertTrue(driver.getTitle().contains("Example Domain"));
    }
}

JavaScript Example

// test_selenium_js.js
const { Builder, By, Key, until } = require('selenium-webdriver');
const { expect } = require('chai');

describe('Selenium WebDriver Tests', function() {
    let driver;
    
    before(async function() {
        // Driver setup
        driver = await new Builder()
            .forBrowser('chrome')
            .setChromeOptions(new (require('selenium-webdriver/chrome')).Options()
                .addArguments('--headless')
                .addArguments('--no-sandbox')
                .addArguments('--disable-dev-shm-usage')
            )
            .build();
    });
    
    after(async function() {
        await driver.quit();
    });
    
    it('basic page navigation', async function() {
        await driver.get('https://example.com');
        
        // Check page title
        const title = await driver.getTitle();
        expect(title).to.contain('Example Domain');
        
        // Check element existence
        const heading = await driver.findElement(By.tagName('h1'));
        const headingText = await heading.getText();
        expect(headingText).to.equal('Example Domain');
    });
    
    it('form interaction and input', async function() {
        await driver.get('https://example.com/contact');
        
        // Form input
        const nameField = await driver.wait(
            until.elementLocated(By.name('name')), 
            10000
        );
        await nameField.sendKeys('John Doe');
        
        const emailField = await driver.findElement(By.name('email'));
        await emailField.sendKeys('[email protected]');
        
        // Textarea operations
        const messageField = await driver.findElement(By.name('message'));
        await messageField.clear();
        await messageField.sendKeys('This is a contact message.');
        
        // Submit form with Enter key
        await messageField.sendKeys(Key.RETURN);
        
        // Check success message
        const successMessage = await driver.wait(
            until.elementLocated(By.className('success-message')),
            10000
        );
        const messageText = await successMessage.getText();
        expect(messageText).to.contain('Message sent');
    });
    
    it('wait conditions and timeout handling', async function() {
        await driver.get('https://example.com/dynamic');
        
        // Wait for element to be visible
        await driver.wait(until.elementIsVisible(
            driver.findElement(By.id('dynamic-content'))
        ), 15000);
        
        // Wait for element to be clickable
        const button = await driver.findElement(By.id('async-button'));
        await driver.wait(until.elementIsEnabled(button), 10000);
        await button.click();
        
        // Wait for text to change
        await driver.wait(until.elementTextContains(
            driver.findElement(By.id('result')), 
            'Processing complete'
        ), 20000);
    });
});

Page Object Model Pattern

# pages/base_page.py
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class BasePage:
    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)
    
    def find_element(self, locator):
        return self.wait.until(EC.presence_of_element_located(locator))
    
    def click_element(self, locator):
        element = self.wait.until(EC.element_to_be_clickable(locator))
        element.click()
    
    def enter_text(self, locator, text):
        element = self.find_element(locator)
        element.clear()
        element.send_keys(text)

# pages/login_page.py
from selenium.webdriver.common.by import By
from .base_page import BasePage

class LoginPage(BasePage):
    # Locator definitions
    USERNAME_FIELD = (By.NAME, 'username')
    PASSWORD_FIELD = (By.NAME, 'password')
    LOGIN_BUTTON = (By.CSS_SELECTOR, 'button[type="submit"]')
    ERROR_MESSAGE = (By.CLASS_NAME, 'error-message')
    
    def __init__(self, driver):
        super().__init__(driver)
        self.url = 'https://example.com/login'
    
    def load(self):
        self.driver.get(self.url)
        return self
    
    def login(self, username, password):
        self.enter_text(self.USERNAME_FIELD, username)
        self.enter_text(self.PASSWORD_FIELD, password)
        self.click_element(self.LOGIN_BUTTON)
        return self
    
    def get_error_message(self):
        error_element = self.find_element(self.ERROR_MESSAGE)
        return error_element.text
    
    def is_login_successful(self):
        return '/dashboard' in self.driver.current_url

# pages/dashboard_page.py
from selenium.webdriver.common.by import By
from .base_page import BasePage

class DashboardPage(BasePage):
    WELCOME_MESSAGE = (By.CLASS_NAME, 'welcome-message')
    LOGOUT_LINK = (By.LINK_TEXT, 'Logout')
    
    def get_welcome_message(self):
        element = self.find_element(self.WELCOME_MESSAGE)
        return element.text
    
    def logout(self):
        self.click_element(self.LOGOUT_LINK)

# tests/test_login.py
import unittest
from selenium import webdriver
from pages.login_page import LoginPage
from pages.dashboard_page import DashboardPage

class LoginTest(unittest.TestCase):
    
    def setUp(self):
        self.driver = webdriver.Chrome()
        self.login_page = LoginPage(self.driver)
        self.dashboard_page = DashboardPage(self.driver)
    
    def tearDown(self):
        self.driver.quit()
    
    def test_successful_login(self):
        """Successful login test"""
        self.login_page.load()
        self.login_page.login('validuser', 'validpass')
        
        # Check dashboard page navigation
        self.assertTrue(self.login_page.is_login_successful())
        
        # Check welcome message
        welcome_message = self.dashboard_page.get_welcome_message()
        self.assertIn('Welcome', welcome_message)
    
    def test_invalid_login(self):
        """Invalid credentials login test"""
        self.login_page.load()
        self.login_page.login('invaliduser', 'invalidpass')
        
        # Check error message
        error_message = self.login_page.get_error_message()
        self.assertIn('Authentication failed', error_message)
        
        # Check staying on login page
        self.assertFalse(self.login_page.is_login_successful())

Selenium Grid Configuration

# docker-compose.yml - Selenium Grid setup
version: '3.8'
services:
  selenium-hub:
    image: selenium/hub:4.15.0
    container_name: selenium-hub
    ports:
      - "4444:4444"
      - "4442:4442"
      - "4443:4443"
    environment:
      - GRID_MAX_SESSION=16
      - GRID_BROWSER_TIMEOUT=300
      - GRID_TIMEOUT=300

  chrome-node:
    image: selenium/node-chrome:4.15.0
    shm_size: 2gb
    depends_on:
      - selenium-hub
    environment:
      - HUB_HOST=selenium-hub
      - HUB_PORT=4444
      - NODE_MAX_INSTANCES=2
      - NODE_MAX_SESSION=2
    scale: 2

  firefox-node:
    image: selenium/node-firefox:4.15.0
    shm_size: 2gb
    depends_on:
      - selenium-hub
    environment:
      - HUB_HOST=selenium-hub
      - HUB_PORT=4444
      - NODE_MAX_INSTANCES=2
      - NODE_MAX_SESSION=2
    scale: 2

  edge-node:
    image: selenium/node-edge:4.15.0
    shm_size: 2gb
    depends_on:
      - selenium-hub
    environment:
      - HUB_HOST=selenium-hub
      - HUB_PORT=4444
      - NODE_MAX_INSTANCES=1
      - NODE_MAX_SESSION=1
# test_grid.py - Grid-compatible test
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
import unittest

class GridTest(unittest.TestCase):
    
    def setUp(self):
        # Grid Hub connection settings
        self.hub_url = 'http://localhost:4444/wd/hub'
    
    def test_chrome_grid(self):
        """Chrome on Grid test"""
        capabilities = DesiredCapabilities.CHROME
        capabilities['browserVersion'] = 'latest'
        capabilities['platformName'] = 'linux'
        
        driver = webdriver.Remote(
            command_executor=self.hub_url,
            desired_capabilities=capabilities
        )
        
        try:
            driver.get('https://example.com')
            self.assertIn('Example Domain', driver.title)
        finally:
            driver.quit()
    
    def test_firefox_grid(self):
        """Firefox on Grid test"""
        capabilities = DesiredCapabilities.FIREFOX
        capabilities['browserVersion'] = 'latest'
        capabilities['platformName'] = 'linux'
        
        driver = webdriver.Remote(
            command_executor=self.hub_url,
            desired_capabilities=capabilities
        )
        
        try:
            driver.get('https://example.com')
            self.assertIn('Example Domain', driver.title)
        finally:
            driver.quit()
    
    def test_parallel_execution(self):
        """Parallel execution test"""
        import concurrent.futures
        
        def run_test(browser):
            if browser == 'chrome':
                capabilities = DesiredCapabilities.CHROME
            else:
                capabilities = DesiredCapabilities.FIREFOX
            
            driver = webdriver.Remote(
                command_executor=self.hub_url,
                desired_capabilities=capabilities
            )
            
            try:
                driver.get('https://example.com')
                return driver.title
            finally:
                driver.quit()
        
        browsers = ['chrome', 'firefox', 'chrome', 'firefox']
        
        with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
            futures = [executor.submit(run_test, browser) for browser in browsers]
            results = [future.result() for future in futures]
        
        for result in results:
            self.assertIn('Example Domain', result)

CI/CD Integration (GitHub Actions)

# .github/workflows/selenium-tests.yml
name: Selenium Tests

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

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        browser: [chrome, firefox]
        python-version: [3.8, 3.9, '3.10']
    
    services:
      selenium:
        image: selenium/standalone-chrome:4.15.0
        ports:
          - 4444:4444
        options: --shm-size=2g
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v4
      with:
        python-version: ${{ matrix.python-version }}
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install selenium pytest webdriver-manager
        pip install -r requirements.txt
    
    - name: Install browser binaries
      run: |
        sudo apt-get update
        sudo apt-get install -y wget gnupg
        # Chrome
        wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
        sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'
        sudo apt-get update
        sudo apt-get install -y google-chrome-stable
        # Firefox
        sudo apt-get install -y firefox
    
    - name: Run Selenium tests
      run: |
        pytest tests/ -v --browser=${{ matrix.browser }}
      env:
        SELENIUM_GRID_URL: http://localhost:4444/wd/hub
    
    - name: Upload test results
      uses: actions/upload-artifact@v4
      if: failure()
      with:
        name: test-results-${{ matrix.browser }}-${{ matrix.python-version }}
        path: |
          screenshots/
          test-reports/

Debugging and Troubleshooting

# debug_helpers.py
import os
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class DebugHelper:
    
    def __init__(self, driver):
        self.driver = driver
        self.screenshot_dir = 'screenshots'
        os.makedirs(self.screenshot_dir, exist_ok=True)
    
    def take_screenshot(self, name):
        """Take screenshot"""
        timestamp = int(time.time())
        filename = f"{name}_{timestamp}.png"
        filepath = os.path.join(self.screenshot_dir, filename)
        self.driver.save_screenshot(filepath)
        print(f"Screenshot saved: {filepath}")
        return filepath
    
    def get_page_source(self, name):
        """Save page source"""
        timestamp = int(time.time())
        filename = f"{name}_{timestamp}.html"
        filepath = os.path.join(self.screenshot_dir, filename)
        with open(filepath, 'w', encoding='utf-8') as f:
            f.write(self.driver.page_source)
        print(f"Page source saved: {filepath}")
        return filepath
    
    def debug_element(self, locator):
        """Output element debug information"""
        try:
            element = self.driver.find_element(*locator)
            print(f"Element found: {locator}")
            print(f"  Tag: {element.tag_name}")
            print(f"  Text: {element.text}")
            print(f"  Displayed: {element.is_displayed()}")
            print(f"  Enabled: {element.is_enabled()}")
            print(f"  Location: {element.location}")
            print(f"  Size: {element.size}")
            return element
        except Exception as e:
            print(f"Element not found: {locator}")
            print(f"  Error: {e}")
            self.take_screenshot(f"error_{locator[1]}")
            return None
    
    def wait_and_debug(self, locator, timeout=10):
        """Combination of wait and debug"""
        wait = WebDriverWait(self.driver, timeout)
        try:
            element = wait.until(EC.presence_of_element_located(locator))
            print(f"Element appeared after waiting: {locator}")
            return element
        except Exception as e:
            print(f"Element did not appear within {timeout}s: {locator}")
            print(f"Current URL: {self.driver.current_url}")
            print(f"Page title: {self.driver.title}")
            self.take_screenshot(f"timeout_{locator[1]}")
            self.get_page_source(f"timeout_{locator[1]}")
            raise e

# Usage example
def test_with_debug(self):
    debug = DebugHelper(self.driver)
    
    self.driver.get('https://example.com/complex-page')
    
    # Find element with debug information
    button = debug.debug_element((By.ID, 'submit-button'))
    
    if button:
        button.click()
    else:
        # Alternative processing when element not found
        debug.take_screenshot('button_not_found')
        self.fail('Submit button not found')
    
    # Wait and debug
    result = debug.wait_and_debug((By.CLASS_NAME, 'result'), timeout=15)
    self.assertIsNotNone(result)