Selenium WebDriver: Complete Guide to Browser Automation with Python
Selenium WebDriver is a browser automation framework that allows you to write code that controls a web browser — clicking buttons, filling forms, navigating pages — just like a human user would.
What You’ll Learn
- How to set up Selenium WebDriver with Python and Chrome
- The five main element locator strategies: ID, name, class, CSS, XPath
- Implicit vs explicit waits and when to use each
- The Page Object Model design pattern for maintainable tests
- Running Selenium tests in headless mode for CI/CD
Why Selenium Matters
Selenium is the most widely used browser automation framework with support for Chrome, Firefox, Safari, and Edge. Despite newer tools like Playwright and Cypress, Selenium remains the standard for cross-browser testing and has the largest community, most documentation, and widest language support. Understanding Selenium is a foundational skill for any test automation engineer.
Doda Browser uses Selenium for cross-platform compatibility testing — ensuring every feature works consistently across Chrome, Firefox, and Safari before each release.
Learning Path
flowchart LR
A[Testing Basics] --> B[Selenium Basics<br/>You are here]
B --> C[Page Object Model]
C --> D[Test Automation CI/CD]
D --> E[E2E Testing]
style B fill:#f90,color:#fff
Setup
pip install selenium webdriver-managerfrom selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
# Automatic driver management
driver = webdriver.Chrome(
service=Service(ChromeDriverManager().install())
)
driver.get("https://example.com")
print(driver.title) # Example Domain
driver.quit()Element Locators
Finding elements is the most fundamental skill in Selenium. You have five main strategies:
By ID
Fastest and most reliable — use it when available.
from selenium.webdriver.common.by import By
element = driver.find_element(By.ID, "email")
element.send_keys("user@example.com")By Name
driver.find_element(By.NAME, "password").send_keys("secret123")By Class Name
# Finds first element with class "error-message"
error = driver.find_element(By.CLASS_NAME, "error-message")
print(error.text)By CSS Selector
Most flexible for complex queries:
# CSS selector examples
driver.find_element(By.CSS_SELECTOR, "button[type='submit']")
driver.find_element(By.CSS_SELECTOR, ".nav-item.active")
driver.find_element(By.CSS_SELECTOR, "#login-form input:first-child")By XPath
Most powerful, but slower and more brittle:
# XPath examples
driver.find_element(By.XPATH, "//button[text()='Submit']")
driver.find_element(By.XPATH, "//div[@class='error']/span")
driver.find_element(By.XPATH, "//input[@type='email' and @required]")Preference order: ID > Name > CSS > XPath. Use XPath only when CSS selectors can’t express the relationship.
Waits
Web pages load asynchronously. Elements may not be available immediately. Waits solve this.
Implicit Waits
Poll the DOM for a set duration before raising an exception:
driver.implicitly_wait(10) # Wait up to 10 secondsCaveat: Applies to all find_element calls. Can slow down tests when elements are genuinely missing.
Explicit Waits
Wait for a specific condition on a specific element:
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 10)
# Wait for element to be visible
element = wait.until(
EC.visibility_of_element_located((By.ID, "success-message"))
)
# Wait for element to be clickable
button = wait.until(
EC.element_to_be_clickable((By.CSS_SELECTOR, "button[type='submit']"))
)
# Wait for URL to change
wait.until(EC.url_contains("/dashboard"))
# Wait for element to contain text
wait.until(
EC.text_to_be_present_in_element((By.ID, "status"), "Completed")
)When to Use Each
| Wait Type | When to Use |
|---|---|
| Implicit | Simple scripts, consistent behavior |
| Explicit | When you need precise conditions |
| Fixed sleep | Never — it’s a code smell. Use waits instead |
Full Example: Login Test
import pytest
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
class TestLogin:
def setup_method(self):
self.driver = webdriver.Chrome()
self.driver.get("https://example.com/login")
self.wait = WebDriverWait(self.driver, 10)
def teardown_method(self):
self.driver.quit()
def test_successful_login(self):
# Fill in credentials
self.driver.find_element(By.ID, "email").send_keys("user@example.com")
self.driver.find_element(By.ID, "password").send_keys("secret123")
# Submit
self.driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
# Wait for dashboard
self.wait.until(EC.url_contains("/dashboard"))
welcome = self.driver.find_element(By.CLASS_NAME, "welcome")
assert "Welcome" in welcome.text
def test_invalid_credentials(self):
self.driver.find_element(By.ID, "email").send_keys("wrong@example.com")
self.driver.find_element(By.ID, "password").send_keys("wrongpass")
self.driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
error = self.wait.until(
EC.visibility_of_element_located((By.CLASS_NAME, "error-message"))
)
assert "Invalid" in error.textPage Object Model (POM)
POM is the most important design pattern for Selenium tests. Each page becomes a class that encapsulates its elements and interactions:
# pages/login_page.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class LoginPage:
def __init__(self, driver):
self.driver = driver
self.wait = WebDriverWait(driver, 10)
# Locators
EMAIL_INPUT = (By.ID, "email")
PASSWORD_INPUT = (By.ID, "password")
SUBMIT_BUTTON = (By.CSS_SELECTOR, "button[type='submit']")
ERROR_MESSAGE = (By.CLASS_NAME, "error-message")
# Actions
def enter_email(self, email):
self.driver.find_element(*self.EMAIL_INPUT).send_keys(email)
def enter_password(self, password):
self.driver.find_element(*self.PASSWORD_INPUT).send_keys(password)
def click_submit(self):
self.driver.find_element(*self.SUBMIT_BUTTON).click()
def login(self, email, password):
self.enter_email(email)
self.enter_password(password)
self.click_submit()
def get_error_message(self):
return self.wait.until(
EC.visibility_of_element_located(self.ERROR_MESSAGE)
).text
# tests/test_login.py
from pages.login_page import LoginPage
from pages.dashboard_page import DashboardPage
class TestLogin:
def test_successful_login(self, driver):
login_page = LoginPage(driver)
login_page.login("user@example.com", "secret123")
dashboard = DashboardPage(driver)
assert dashboard.is_displayed()
assert "Welcome" in dashboard.get_welcome_text()Benefits of POM
- Reduced duplication: Selectors defined once
- Easier maintenance: When a page changes, update one class
- Readability: Tests read like user actions
- Reusability: Page objects can be used across multiple tests
Headless Mode for CI/CD
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
def create_driver():
options = Options()
options.add_argument("--headless") # No GUI
options.add_argument("--no-sandbox") # Required for Docker
options.add_argument("--disable-dev-shm-usage") # Shared memory fix
options.add_argument("--window-size=1920,1080")
return webdriver.Chrome(options=options)Common Selenium Mistakes
1. Using sleep() Instead of Waits
Fixed waits make tests slow and flaky. A page that loads in 2 seconds one day and 5 seconds the next will break your sleep-based tests.
Fix: Always use explicit waits with expected conditions.
2. Brittle XPath Selectors
XPaths like //div[3]/span[2]/input break when the DOM structure changes even slightly.
Fix: Use ID, name, or CSS selectors. If you must use XPath, use text-based or attribute-based expressions.
3. Not Cleaning Up Test Data
Tests that create users but don’t delete them fill up the database.
Fix: Use setup/teardown methods to clean up after each test.
4. Testing on the Wrong Environment
Running tests against production risks corrupting data.
Fix: Use dedicated staging or test environments. Use test accounts with limited permissions.
5. No Page Object Model
Tests with duplicated selectors are hard to maintain and painful to update.
Fix: Always use POM for test suites of more than 10 tests.
6. Not Handling StaleElementReferenceException
This exception occurs when a previously located element is no longer in the DOM (page refreshed, element re-rendered).
Fix: Re-locate the element before interacting with it after a page change.
7. Running Tests Sequentially When They Could Run in Parallel
Selenium Grid or parallel test execution can dramatically reduce test suite time.
Fix: Use pytest-xdist for parallel test execution or Selenium Grid for distributed testing.
Practice Questions
1. What are the five main element locator strategies in Selenium?
ID, name, class name, CSS selector, and XPath.
2. What is the difference between implicit and explicit waits?
Implicit waits apply to all find_element calls with a global timeout. Explicit waits apply to specific elements with specific conditions.
3. What is the Page Object Model design pattern?
A pattern where each web page is represented by a class that encapsulates its element locators and interaction methods.
4. Why should you avoid time.sleep() in Selenium tests?
Fixed sleeps make tests slow (worst-case wait time) and brittle (varying load times break them). Explicit waits adapt to actual page timing.
5. How do you run Selenium tests in headless mode?
Configure Chrome options with --headless argument. This is essential for running tests in CI/CD environments without a display.
Challenge: Create a Page Object Model test suite for a real website. Implement at least three page objects (e.g., Login, Dashboard, Profile) with interaction methods and verify end-to-end flow.
FAQ
What’s Next
| Tutorial | What You’ll Learn |
|---|---|
| Page Object Model Best Practices | Advanced POM patterns and utilities |
| Playwright Browser Testing | Modern browser automation alternatives |
| Test Automation Frameworks | Comparing test automation frameworks |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-20.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro