UI Automation
Selenium WebDriver
Selenium with Python

Selenium with Python Tutorial

Parul Dhingra - Senior Quality Analyst
Parul Dhingra13+ Years ExperienceHire Me

Senior Quality Analyst

Updated: 1/23/2026

Python's simplicity makes it ideal for test automation. With readable syntax, a gentle learning curve, and powerful libraries like pytest, Python enables rapid development of maintainable test frameworks. Whether you're new to automation or experienced with other languages, this guide covers everything you need to build professional Selenium tests with Python.

Python's "batteries included" philosophy extends to testing - you'll be writing effective browser automation in minutes.

Prerequisites and Setup

Required Software

SoftwareVersionPurpose
Python3.8+Runtime
pipLatestPackage manager
BrowserChrome, Firefox, EdgeTest execution
IDEVS Code, PyCharmDevelopment

Verify Python Installation

# Check Python version
python --version
# or
python3 --version
 
# Check pip
pip --version
# or
pip3 --version

If not installed, download from python.org (opens in a new tab). During installation on Windows, check "Add Python to PATH".

Create Virtual Environment (Recommended)

# Create virtual environment
python -m venv venv
 
# Activate (Windows)
venv\Scripts\activate
 
# Activate (macOS/Linux)
source venv/bin/activate

Installation

Install Selenium

pip install selenium

Install WebDriver Manager

WebDriver Manager automatically handles browser driver downloads:

pip install webdriver-manager

Install pytest

pip install pytest
pip install pytest-html  # For HTML reports

Full Requirements File

Create requirements.txt:

selenium==4.21.0
webdriver-manager==4.0.1
pytest==8.1.1
pytest-html==4.1.1
pytest-xdist==3.5.0  # For parallel execution

Install all dependencies:

pip install -r requirements.txt

Project Structure

selenium-tests/
├── pages/                # Page Objects
│   ├── __init__.py
│   ├── base_page.py
│   └── login_page.py
├── tests/                # Test files
│   ├── __init__.py
│   ├── conftest.py       # pytest fixtures
│   └── test_login.py
├── utils/                # Utilities
│   ├── __init__.py
│   └── config.py
├── requirements.txt
├── pytest.ini
└── README.md

Your First Selenium Test

Simple Script

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
 
# Setup Chrome driver
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
 
try:
    # Navigate to URL
    driver.get("https://www.google.com")
 
    # Find search box and enter text
    search_box = driver.find_element(By.NAME, "q")
    search_box.send_keys("Selenium Python")
    search_box.submit()
 
    # Print page title
    print(f"Page title: {driver.title}")
 
finally:
    # Close browser
    driver.quit()

Test with pytest

# test_google.py
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
 
 
@pytest.fixture
def driver():
    """Setup and teardown for each test."""
    driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
    driver.maximize_window()
    yield driver
    driver.quit()
 
 
def test_google_search(driver):
    """Test Google search functionality."""
    driver.get("https://www.google.com")
 
    search_box = driver.find_element(By.NAME, "q")
    search_box.send_keys("Selenium Python")
    search_box.submit()
 
    assert "Selenium Python" in driver.title

Run the test:

pytest test_google.py -v

WebDriver Basics

Creating Driver Instances

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.firefox import GeckoDriverManager
from webdriver_manager.microsoft import EdgeChromiumDriverManager
 
# Chrome
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
 
# Firefox
driver = webdriver.Firefox(service=Service(GeckoDriverManager().install()))
 
# Edge
driver = webdriver.Edge(service=Service(EdgeChromiumDriverManager().install()))

Browser Options

from selenium.webdriver.chrome.options import Options
 
options = Options()
options.add_argument("--headless")              # Headless mode
options.add_argument("--disable-gpu")
options.add_argument("--window-size=1920,1080")
options.add_argument("--incognito")             # Incognito mode
options.add_argument("--disable-notifications")
options.add_argument("--disable-extensions")
 
driver = webdriver.Chrome(
    service=Service(ChromeDriverManager().install()),
    options=options
)

Navigation Commands

# Navigate to URL
driver.get("https://example.com")
 
# Browser navigation
driver.back()
driver.forward()
driver.refresh()
 
# Get page info
title = driver.title
url = driver.current_url
source = driver.page_source

Window Management

# Maximize window
driver.maximize_window()
 
# Set specific size
driver.set_window_size(1920, 1080)
 
# Full screen
driver.fullscreen_window()
 
# Get window size
size = driver.get_window_size()
print(f"Width: {size['width']}, Height: {size['height']}")

Locator Strategies

The By Class

from selenium.webdriver.common.by import By
 
# By ID
driver.find_element(By.ID, "username")
 
# By Name
driver.find_element(By.NAME, "email")
 
# By Class Name (single class)
driver.find_element(By.CLASS_NAME, "btn-primary")
 
# By Tag Name
driver.find_elements(By.TAG_NAME, "input")
 
# By Link Text (exact match)
driver.find_element(By.LINK_TEXT, "Sign In")
 
# By Partial Link Text
driver.find_element(By.PARTIAL_LINK_TEXT, "Sign")
 
# By CSS Selector (recommended)
driver.find_element(By.CSS_SELECTOR, "#login-form input[type='email']")
driver.find_element(By.CSS_SELECTOR, ".btn.btn-primary")
driver.find_element(By.CSS_SELECTOR, "[data-testid='submit-btn']")
 
# By XPath (when CSS can't do it)
driver.find_element(By.XPATH, "//button[text()='Submit']")
driver.find_element(By.XPATH, "//input[@id='email']/../label")

CSS Selector Examples

# ID
By.CSS_SELECTOR, "#elementId"
 
# Class
By.CSS_SELECTOR, ".className"
 
# Attribute
By.CSS_SELECTOR, "[name='email']"
By.CSS_SELECTOR, "input[type='text']"
 
# Partial attribute match
By.CSS_SELECTOR, "[id*='partial']"     # Contains
By.CSS_SELECTOR, "[id^='start']"       # Starts with
By.CSS_SELECTOR, "[id$='end']"         # Ends with
 
# Hierarchy
By.CSS_SELECTOR, "form > input"        # Direct child
By.CSS_SELECTOR, "form input"          # Any descendant
By.CSS_SELECTOR, "label + input"       # Adjacent sibling
 
# Pseudo-classes
By.CSS_SELECTOR, "tr:nth-child(2)"     # Second row
By.CSS_SELECTOR, "li:first-child"
By.CSS_SELECTOR, "li:last-child"

Finding Multiple Elements

# Find all links
links = driver.find_elements(By.TAG_NAME, "a")
print(f"Found {len(links)} links")
 
# Iterate through elements
for link in links:
    print(f"{link.text} -> {link.get_attribute('href')}")
 
# Find elements within element
container = driver.find_element(By.ID, "nav")
nav_links = container.find_elements(By.TAG_NAME, "a")

Element Interactions

Basic Interactions

element = driver.find_element(By.ID, "username")
 
# Click
element.click()
 
# Type text
element.send_keys("test@example.com")
 
# Clear field
element.clear()
 
# Submit form
element.submit()
 
# Get text content
text = element.text
 
# Get attribute value
value = element.get_attribute("value")
placeholder = element.get_attribute("placeholder")
 
# Check element state
is_displayed = element.is_displayed()
is_enabled = element.is_enabled()
is_selected = element.is_selected()

Keyboard Actions

from selenium.webdriver.common.keys import Keys
 
input_field = driver.find_element(By.ID, "search")
 
# Special keys
input_field.send_keys(Keys.ENTER)
input_field.send_keys(Keys.TAB)
input_field.send_keys(Keys.ESCAPE)
 
# Key combinations
input_field.send_keys(Keys.CONTROL + "a")  # Select all
input_field.send_keys(Keys.CONTROL + "c")  # Copy
 
# Clear and type
input_field.send_keys(Keys.CONTROL + "a", Keys.DELETE)
input_field.send_keys("new text")

Dropdown Selection

from selenium.webdriver.support.ui import Select
 
dropdown = driver.find_element(By.ID, "country")
select = Select(dropdown)
 
# Select by visible text
select.select_by_visible_text("United States")
 
# Select by value attribute
select.select_by_value("US")
 
# Select by index (0-based)
select.select_by_index(2)
 
# Get selected option
selected = select.first_selected_option
print(f"Selected: {selected.text}")
 
# Get all options
options = select.options
for option in options:
    print(option.text)
 
# Multi-select dropdowns
if select.is_multiple:
    select.deselect_all()
    select.select_by_visible_text("Option 1")
    select.select_by_visible_text("Option 2")

Checkboxes and Radio Buttons

# Checkbox
checkbox = driver.find_element(By.ID, "remember-me")
if not checkbox.is_selected():
    checkbox.click()
 
# Radio button
radio = driver.find_element(By.CSS_SELECTOR, "input[value='option1']")
radio.click()
 
# Verify selection
assert checkbox.is_selected()

Handling Waits

Explicit Wait (Recommended)

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, "result"))
)
 
# Wait for element to be clickable
button = wait.until(
    EC.element_to_be_clickable((By.ID, "submit"))
)
button.click()
 
# Wait for text to appear
wait.until(
    EC.text_to_be_present_in_element((By.ID, "status"), "Complete")
)
 
# Wait for element to disappear
wait.until(
    EC.invisibility_of_element_located((By.ID, "loader"))
)
 
# Wait for URL to change
wait.until(EC.url_contains("/dashboard"))

Common Expected Conditions

from selenium.webdriver.support import expected_conditions as EC
 
# Presence (in DOM, may be hidden)
EC.presence_of_element_located((By.ID, "element"))
 
# Visibility (in DOM and visible)
EC.visibility_of_element_located((By.ID, "element"))
 
# Clickable (visible and enabled)
EC.element_to_be_clickable((By.ID, "element"))
 
# Text present
EC.text_to_be_present_in_element((By.ID, "element"), "expected text")
 
# Attribute value
EC.element_attribute_to_include((By.ID, "element"), "class")
 
# Frame available
EC.frame_to_be_available_and_switch_to_it((By.ID, "frame"))
 
# Alert present
EC.alert_is_present()
 
# Title
EC.title_contains("Dashboard")
 
# URL
EC.url_contains("/login")

Custom Wait Condition

# Using lambda
wait.until(lambda driver: driver.find_element(By.ID, "status").text == "Ready")
 
# Custom condition function
def element_has_class(locator, class_name):
    def _predicate(driver):
        element = driver.find_element(*locator)
        classes = element.get_attribute("class")
        return class_name in classes if classes else False
    return _predicate
 
wait.until(element_has_class((By.ID, "btn"), "active"))

Implicit Wait (Use Sparingly)

# Set once - applies to all find_element calls
driver.implicitly_wait(10)
 
# Note: Don't mix implicit and explicit waits

pytest Integration

Fixtures in conftest.py

# conftest.py
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
 
 
@pytest.fixture(scope="function")
def driver():
    """Create a new browser instance for each test."""
    driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
    driver.maximize_window()
    driver.implicitly_wait(5)
    yield driver
    driver.quit()
 
 
@pytest.fixture(scope="session")
def base_url():
    """Base URL for all tests."""
    return "https://example.com"
 
 
@pytest.fixture
def logged_in_driver(driver, base_url):
    """Driver with user already logged in."""
    driver.get(f"{base_url}/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()
    yield driver

pytest Configuration

# pytest.ini
[pytest]
testpaths = tests
python_files = test_*.py
python_functions = test_*
addopts = -v --html=reports/report.html --self-contained-html
markers =
    smoke: Quick smoke tests
    regression: Full regression tests
    slow: Tests that take longer to run

Test File Structure

# tests/test_login.py
import pytest
from selenium.webdriver.common.by import By
 
 
class TestLogin:
    """Test suite for login functionality."""
 
    def test_valid_login(self, driver, base_url):
        """Test login with valid credentials."""
        driver.get(f"{base_url}/login")
 
        driver.find_element(By.ID, "username").send_keys("valid@test.com")
        driver.find_element(By.ID, "password").send_keys("password123")
        driver.find_element(By.ID, "login-btn").click()
 
        assert "Dashboard" in driver.title
 
    def test_invalid_login(self, driver, base_url):
        """Test login with invalid credentials."""
        driver.get(f"{base_url}/login")
 
        driver.find_element(By.ID, "username").send_keys("invalid@test.com")
        driver.find_element(By.ID, "password").send_keys("wrongpassword")
        driver.find_element(By.ID, "login-btn").click()
 
        error = driver.find_element(By.CLASS_NAME, "error-message")
        assert error.is_displayed()
        assert "Invalid credentials" in error.text
 
    @pytest.mark.smoke
    def test_login_page_elements(self, driver, base_url):
        """Smoke test: verify login page elements exist."""
        driver.get(f"{base_url}/login")
 
        assert driver.find_element(By.ID, "username").is_displayed()
        assert driver.find_element(By.ID, "password").is_displayed()
        assert driver.find_element(By.ID, "login-btn").is_displayed()

Parametrized Tests

import pytest
 
@pytest.mark.parametrize("username,password,expected", [
    ("valid@test.com", "password123", True),
    ("invalid@test.com", "wrong", False),
    ("", "password", False),
    ("user@test.com", "", False),
])
def test_login_scenarios(driver, base_url, username, password, expected):
    """Test various login scenarios."""
    driver.get(f"{base_url}/login")
 
    if username:
        driver.find_element(By.ID, "username").send_keys(username)
    if password:
        driver.find_element(By.ID, "password").send_keys(password)
 
    driver.find_element(By.ID, "login-btn").click()
 
    if expected:
        assert "Dashboard" in driver.title
    else:
        assert driver.find_element(By.CLASS_NAME, "error-message").is_displayed()

Running Tests

# Run all tests
pytest
 
# Run specific file
pytest tests/test_login.py
 
# Run specific test
pytest tests/test_login.py::TestLogin::test_valid_login
 
# Run by marker
pytest -m smoke
pytest -m "not slow"
 
# Run with verbose output
pytest -v
 
# Run in parallel (requires pytest-xdist)
pytest -n 4
 
# Generate HTML report
pytest --html=report.html

Page Object Model

Base Page Class

# pages/base_page.py
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
 
 
class BasePage:
    """Base class for all page objects."""
 
    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)
 
    def find_element(self, locator):
        """Find element with explicit wait."""
        return self.wait.until(EC.visibility_of_element_located(locator))
 
    def find_elements(self, locator):
        """Find multiple elements."""
        return self.driver.find_elements(*locator)
 
    def click(self, locator):
        """Click element after waiting for it to be clickable."""
        element = self.wait.until(EC.element_to_be_clickable(locator))
        element.click()
 
    def type_text(self, locator, text):
        """Clear and type text into element."""
        element = self.find_element(locator)
        element.clear()
        element.send_keys(text)
 
    def get_text(self, locator):
        """Get text from element."""
        return self.find_element(locator).text
 
    def is_element_visible(self, locator, timeout=5):
        """Check if element is visible."""
        try:
            WebDriverWait(self.driver, timeout).until(
                EC.visibility_of_element_located(locator)
            )
            return True
        except:
            return False
 
    def wait_for_url_contains(self, text):
        """Wait for URL to contain text."""
        self.wait.until(EC.url_contains(text))

Login Page Object

# pages/login_page.py
from selenium.webdriver.common.by import By
from pages.base_page import BasePage
 
 
class LoginPage(BasePage):
    """Page object for login page."""
 
    # Locators
    USERNAME_INPUT = (By.ID, "username")
    PASSWORD_INPUT = (By.ID, "password")
    LOGIN_BUTTON = (By.ID, "login-btn")
    ERROR_MESSAGE = (By.CLASS_NAME, "error-message")
    REMEMBER_ME = (By.ID, "remember-me")
 
    def __init__(self, driver):
        super().__init__(driver)
        self.url = "/login"
 
    def open(self, base_url):
        """Navigate to login page."""
        self.driver.get(f"{base_url}{self.url}")
        return self
 
    def enter_username(self, username):
        """Enter username."""
        self.type_text(self.USERNAME_INPUT, username)
        return self
 
    def enter_password(self, password):
        """Enter password."""
        self.type_text(self.PASSWORD_INPUT, password)
        return self
 
    def click_login(self):
        """Click login button."""
        self.click(self.LOGIN_BUTTON)
        return self
 
    def login(self, username, password):
        """Perform complete login."""
        self.enter_username(username)
        self.enter_password(password)
        self.click_login()
        return self
 
    def get_error_message(self):
        """Get error message text."""
        return self.get_text(self.ERROR_MESSAGE)
 
    def is_error_displayed(self):
        """Check if error message is visible."""
        return self.is_element_visible(self.ERROR_MESSAGE)
 
    def check_remember_me(self):
        """Check remember me checkbox."""
        checkbox = self.find_element(self.REMEMBER_ME)
        if not checkbox.is_selected():
            checkbox.click()
        return self

Tests Using Page Objects

# tests/test_login.py
import pytest
from pages.login_page import LoginPage
 
 
class TestLogin:
    """Test suite for login functionality."""
 
    @pytest.fixture(autouse=True)
    def setup(self, driver, base_url):
        """Setup for each test."""
        self.login_page = LoginPage(driver)
        self.login_page.open(base_url)
 
    def test_valid_login(self, driver):
        """Test login with valid credentials."""
        self.login_page.login("valid@test.com", "password123")
 
        assert "Dashboard" in driver.title
 
    def test_invalid_login(self):
        """Test login with invalid credentials."""
        self.login_page.login("invalid@test.com", "wrongpassword")
 
        assert self.login_page.is_error_displayed()
        assert "Invalid credentials" in self.login_page.get_error_message()
 
    def test_empty_username(self):
        """Test login with empty username."""
        self.login_page.enter_password("password123")
        self.login_page.click_login()
 
        assert self.login_page.is_error_displayed()
 
    def test_remember_me(self, driver):
        """Test remember me functionality."""
        self.login_page.check_remember_me()
        self.login_page.login("valid@test.com", "password123")
 
        # Verify login succeeded
        assert "Dashboard" in driver.title

Advanced Techniques

Handling Alerts

from selenium.webdriver.common.alert import Alert
 
# Wait for alert
wait = WebDriverWait(driver, 5)
wait.until(EC.alert_is_present())
 
# Get alert
alert = Alert(driver)
# or: alert = driver.switch_to.alert
 
# Get alert text
alert_text = alert.text
 
# Accept (OK)
alert.accept()
 
# Dismiss (Cancel)
alert.dismiss()
 
# Send text to prompt
alert.send_keys("Input text")
alert.accept()

Handling Frames

# Switch to frame by name or ID
driver.switch_to.frame("frameName")
 
# Switch to frame by index
driver.switch_to.frame(0)
 
# Switch to frame by WebElement
frame = driver.find_element(By.CSS_SELECTOR, "iframe.main")
driver.switch_to.frame(frame)
 
# Switch back to main content
driver.switch_to.default_content()
 
# Switch to parent frame
driver.switch_to.parent_frame()

Handling Multiple Windows

# Get current window handle
main_window = driver.current_window_handle
 
# Click link that opens new window
driver.find_element(By.LINK_TEXT, "Open New Window").click()
 
# Get all window handles
handles = driver.window_handles
 
# Switch to new window
for handle in handles:
    if handle != main_window:
        driver.switch_to.window(handle)
        break
 
# Do something in new window
print(f"New window title: {driver.title}")
 
# Close new window and return to main
driver.close()
driver.switch_to.window(main_window)

JavaScript Execution

# Scroll to element
element = driver.find_element(By.ID, "footer")
driver.execute_script("arguments[0].scrollIntoView(true);", element)
 
# Scroll by pixels
driver.execute_script("window.scrollBy(0, 500)")
 
# Click via JavaScript (bypasses overlays)
driver.execute_script("arguments[0].click();", element)
 
# Get computed style
color = driver.execute_script(
    "return window.getComputedStyle(arguments[0]).color;", element
)
 
# Wait for page load
driver.execute_script("return document.readyState") == "complete"

Taking Screenshots

# Save screenshot to file
driver.save_screenshot("screenshot.png")
 
# Get screenshot as base64
base64_image = driver.get_screenshot_as_base64()
 
# Get screenshot as PNG bytes
png_bytes = driver.get_screenshot_as_png()
 
# Screenshot of specific element
element = driver.find_element(By.ID, "chart")
element.screenshot("element_screenshot.png")

Action Chains

from selenium.webdriver.common.action_chains import ActionChains
 
actions = ActionChains(driver)
 
# Hover over element
element = driver.find_element(By.ID, "menu")
actions.move_to_element(element).perform()
 
# Double click
actions.double_click(element).perform()
 
# Right click (context menu)
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()
 
# Chain multiple actions
actions.move_to_element(menu)\
       .click()\
       .move_to_element(submenu)\
       .click()\
       .perform()

Best Practices

1. Always Use Context Managers or Try/Finally

# Option 1: Try/finally
driver = webdriver.Chrome()
try:
    # Test code
finally:
    driver.quit()
 
# Option 2: pytest fixtures (recommended)
@pytest.fixture
def driver():
    driver = webdriver.Chrome()
    yield driver
    driver.quit()

2. Use Explicit Waits

# Avoid
import time
time.sleep(5)  # Bad!
 
# Prefer
wait = WebDriverWait(driver, 10)
wait.until(EC.element_to_be_clickable((By.ID, "submit")))

3. Keep Locators in Page Objects

# Bad: Locators scattered in tests
def test_login():
    driver.find_element(By.ID, "username").send_keys("user")
 
# Good: Locators in page object
class LoginPage:
    USERNAME = (By.ID, "username")
 
    def enter_username(self, username):
        self.find_element(self.USERNAME).send_keys(username)

4. Use Meaningful Assert Messages

# Bad
assert element.is_displayed()
 
# Good
assert element.is_displayed(), "Login button should be visible on the page"

5. Create Reusable Fixtures

# conftest.py
@pytest.fixture
def authenticated_driver(driver, base_url):
    """Return driver with logged-in user."""
    login_page = LoginPage(driver)
    login_page.open(base_url).login("testuser", "password")
    return driver

6. Use Markers for Test Organization

@pytest.mark.smoke
def test_homepage_loads():
    pass
 
@pytest.mark.regression
def test_full_checkout_flow():
    pass
 
# Run specific markers
# pytest -m smoke

Test Your Knowledge

Quiz on Selenium with Python

Your Score: 0/10

Question: What is the correct way to install Selenium for Python?


Continue Your Selenium Journey


Frequently Asked Questions

Frequently Asked Questions (FAQs) / People Also Ask (PAA)

Should I use pytest or unittest for Selenium tests in Python?

How do I run Selenium tests in headless mode with Python?

What is webdriver-manager and why should I use it?

Why do I get NoSuchElementException even though the element is on the page?

How do I share fixtures across multiple test files?

What is the Page Object Model and why use it with Python Selenium?

How do I take screenshots on test failure in pytest?

How do I handle StaleElementReferenceException in Python Selenium?