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
- Selenium Official Site
- Selenium WebDriver Official Documentation
- Selenium GitHub Repository
- Selenium Grid 4 Documentation
- WebDriver W3C Specification
- Selenium Best Practices
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)