Selenium Python Tutorial: Setup, Examples, Best Practices

Learn Selenium with Python using clear examples, waits, locators, and best practices for stable cross-browser automation.
March 2, 2026 13 min read
Feature Image
Home Blog Selenium Python Tutorial: Setup, Examples, Best Practices

Selenium Python Tutorial: Setup, Examples, Best Practices

Selenium is a powerful tool for automating web applications for testing purposes, and when combined with Python, it makes web automation even more accessible and efficient. Whether you’re a beginner or looking to level up your skills, this guide will walk you through everything you need to know.

In this article, we’ll cover the essential setup steps, provide real-world examples, and share best practices for writing clean, efficient Selenium scripts with Python. From installing Selenium and setting up your environment to automating tasks like form submissions, web scraping, and more

Why Do Developers Prefer Python for Writing Selenium Test Scripts in 2026?

Python’s popularity in Selenium automation is not just about syntax simplicity; it is about how effectively Python supports test system design at scale. Modern Selenium Python projects are no longer collections of scripts—they are structured automation systems integrated into CI pipelines, release gates, and monitoring workflows.

Python enables this because it treats test code as first-class software. Features like dynamic typing, decorators, and first-class functions make it easier to build reusable utilities for waits, retries, environment configuration, and reporting. Compared to more verbose languages, Python reduces boilerplate in page objects, fixtures, and test data handling, which directly lowers maintenance cost as test suites grow.

Another major reason is ecosystem maturity. Python integrates cleanly with:

  • pytest for fixture-driven test orchestration
  • Allure and HTML reporting tools
  • Parallel execution frameworks
  • API, database, and backend validation libraries

This allows Selenium Python tests to validate workflows end-to-end, not just UI interactions. As applications become more distributed and asynchronous in 2026, this flexibility is a decisive advantage.

Getting Started with Selenium Python in 2026

Setting up Selenium Python today is less about installation and more about choosing the right execution model. Local execution is useful for development, but production-grade automation requires consistency across machines and environments.

After installing Selenium via pip, the most important setup decision is driver management. Hardcoding driver binaries leads to version drift and CI failures. Modern Selenium Python setups rely on:

  • Automated driver management
  • Explicit browser version targeting
  • Environment-based configuration for headless vs headed runs

A typical Selenium Python setup initializes the driver using configuration rather than inline values, allowing the same test suite to run locally, in CI, or on cloud infrastructure without code changes.

This separation between test logic and execution environment is essential for long-term stability.

What Is Selenium WebDriver?

Selenium WebDriver is the core component responsible for controlling browsers programmatically. It follows the W3C WebDriver specification, ensuring consistent behavior across browsers.

WebDriver enables automation scripts to simulate real user actions such as clicking buttons, entering text, navigating pages, and executing JavaScript. Because it interacts with real browsers, it provides high-fidelity test results.

What Are Selenium Python Bindings?

Selenium Python bindings act as a bridge between Python code and the WebDriver protocol. They allow Python developers to interact with browsers using familiar language constructs.

Bindings expose APIs for locating elements, managing waits, handling browser sessions, and performing advanced actions, while hiding protocol-level complexity.

Selenium Python Example: How to Run Your First Test

A basic Selenium Python test demonstrates browser control, element interaction, and validation.

from selenium import webdriver

from selenium.webdriver.common.by import By

driver = webdriver.Chrome()

driver.get("https://example.com")

heading = driver.find_element(By.TAG_NAME, "h1")

assert "Example" in heading.text

driver.quit()

This example launches a browser, verifies page content, and closes the session, illustrating the basic Selenium workflow.

Selenium Components Overview

Selenium is composed of multiple components that support different automation scenarios.

WebDriver handles browser automation, while distributed execution can be achieved using grid-based or cloud-based execution models. Python projects primarily rely on WebDriver, with execution scaling handled externally.

Applications of Selenium in 2026

Selenium is used beyond traditional UI regression testing. Modern teams apply it across multiple quality assurance workflows.

Applications include cross-browser compatibility testing, regression validation in CI pipelines, accessibility checks, and monitoring critical user journeys in production-like environments.

Interacting with Common Elements in Selenium

Most Selenium tests involve interacting with standard web elements such as buttons, inputs, and dropdowns. Understanding these interactions is foundational.

Selenium provides direct methods for clicking, typing, and reading values, as long as elements are located reliably.

Navigating Through HTML DOM Elements

DOM navigation becomes important when elements are nested or repeated across the page.

Locate and Interact with Navigation Links

Navigation links are typically accessed using attribute-based selectors.

driver.find_element(By.CSS_SELECTOR, "nav a[href='/pricing']").click()

This approach remains stable even if layout changes.

Access and Interact with Header Sections

Scoping element searches to a container avoids ambiguity.

header = driver.find_element(By.TAG_NAME, "header")

header.find_element(By.CSS_SELECTOR, "img[alt='Company logo']")

Interact with Forms and Input Fields

Forms are commonly automated using name, id, or type attributes.

driver.find_element(By.NAME, "email").send_keys("[email protected]")

driver.find_element(By.NAME, "password").send_keys("password123")

driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()

Handling Waits in Selenium Python

Web applications load content asynchronously, making timing control essential. Selenium does not wait automatically for elements to appear or become interactive.

Explicit waits synchronize test execution with application state.

from selenium.webdriver.support.ui import WebDriverWait

from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 10)

wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "button[type='submit']"))).click()

Assertions and Validations

Assertions confirm that the application behaves as expected from a user perspective.

assert driver.find_element(By.ID, "status").text == "Completed"

Assertions should focus on visible outcomes rather than internal implementation details.

Handling Alerts and Pop-ups

JavaScript alerts interrupt normal browser flow and must be handled explicitly.

alert = driver.switch_to.alert

alert.accept()

Failing to handle alerts can block further interactions.

Handling Checkboxes in Selenium Python

Checkbox state should always be verified before interaction to avoid unintended toggling.

checkbox = driver.find_element(By.ID, "terms")

if not checkbox.is_selected():

    checkbox.click()

Handling Cookies in Selenium Python

Cookies help manage sessions and authentication states during tests.

Adding Cookies

driver.add_cookie({"name": "session", "value": "abc123"})

Retrieving Cookies

driver.get_cookies()

Deleting Cookies

driver.delete_all_cookies()

Exception Handling in Selenium Python

Exception handling improves test resilience and diagnostics.

from selenium.common.exceptions import NoSuchElementException

try:

    driver.find_element(By.ID, "promo")

except NoSuchElementException:

    print("Element not found")

Cleanup and Teardown

Proper cleanup ensures browsers are closed and resources are released.

driver.quit()

Teardown logic is typically handled by test frameworks.

Handling Mouse Actions in Selenium Python

Advanced interactions such as hover menus and drag-and-drop require action chains.

from selenium.webdriver.common.action_chains import ActionChains

ActionChains(driver).move_to_element(menu).click(submenu).perform()

Locating WebElements in Selenium Python

Locating web elements is the foundation of every Selenium Python test. If elements are not located reliably, even well-written test logic will fail under real conditions such as UI refactors, dynamic rendering, or cross-browser differences. Effective element location is less about knowing all locator types and more about choosing locators that remain stable as the application evolves.

Before diving into locator strategies, it is important to understand that Selenium Python does not “search intelligently.” It executes the locator exactly as written against the current DOM snapshot. This makes locator choice a design decision, not a syntax detail.

Locator Strategies Supported in Selenium Python

Selenium Python provides multiple locator strategies through the By class. Each has specific use cases and trade-offs.

from selenium.webdriver.common.by import By

The most commonly used strategies are ID, NAME, CSS_SELECTOR, and XPATH. Among these, CSS selectors are generally preferred for clarity, performance, and browser-native execution.

Locating Elements by ID in Selenium Python

Using id is the fastest and most reliable approach only if the ID is stable and unique. Unfortunately, many modern applications generate dynamic IDs, making this strategy unreliable in practice.

driver.find_element(By.ID, "login-button")

Use ID-based locators only when:

  • IDs are explicitly defined by developers
  • IDs do not change across sessions or deployments

Locating Elements by Name Attribute in Selenium Python

The name attribute is commonly used for form inputs and is often more stable than IDs.

driver.find_element(By.NAME, "email")

driver.find_element(By.NAME, "password")

This approach works well for authentication forms, search fields, and input-heavy pages.

Locating WebElements Using CSS Selectors in Selenium Python

CSS selectors are the most widely used locator strategy in Selenium Python because they are expressive, readable, and evaluated directly by the browser.

driver.find_element(By.CSS_SELECTOR, "button[type='submit']")

driver.find_element(By.CSS_SELECTOR, "input[name='email']")

CSS selectors are ideal when:

  • Selecting by custom attributes such as data-testid
  • Scoping elements within specific containers
  • Avoiding brittle DOM traversal logic

Example using a test-specific attribute:

driver.find_element(By.CSS_SELECTOR, "[data-testid='checkout-submit']")

This approach remains stable even when layout or styling changes.

Scoping CSS Selectors to Reduce Ambiguity

When multiple similar elements exist on a page, selectors must be scoped to a stable parent container.

checkout_section = driver.find_element(

    By.CSS_SELECTOR, "section[data-testid='checkout']"

)

checkout_section.find_element(

    By.CSS_SELECTOR, "button[data-testid='place-order']"

).click()

Scoping prevents accidental matches and improves test reliability across responsive layouts.

Locating Elements Using XPath in Selenium Python

XPath allows complex DOM traversal but should be used selectively. Overuse often leads to brittle tests tightly coupled to DOM structure.

driver.find_element(By.XPATH, "//button[@type='submit']")

XPath is most appropriate when:

  • Traversing upward in the DOM is unavoidable
  • No stable attributes are available
  • Working with legacy markup

Avoid deeply nested XPath expressions that depend on element position.

Handling Multiple Matching Elements in Selenium Python

When multiple elements match a locator, Selenium returns the first match when using find_element. To handle collections, use find_elements.

items = driver.find_elements(By.CSS_SELECTOR, "ul.products li")

for item in items:

    print(item.text)

When interacting with a specific item, always refine the selector using attributes or content rather than relying on index position.

Avoiding Common Locator Anti-Patterns

Some locator patterns are inherently fragile and should be avoided in Selenium Python tests.

Avoid:

div:nth-child(3) > button

Prefer:

div[data-product-id='sku-123'] button

Position-based locators break when content is reordered, filtered, or personalized.

Re-locating Elements to Prevent Stale Element Errors

Modern frameworks frequently re-render the DOM, replacing elements without warning. Holding references to elements across UI transitions often causes stale element exceptions.

Instead of storing elements, re-locate them immediately before interaction.

driver.find_element(By.CSS_SELECTOR, "button[data-testid='save']").click()

This approach improves resilience across browsers and slower devices.

Verifying Locator Reliability Before Using It

Before finalizing a locator, it should be validated directly in the browser.

Best practices include:

  • Testing the selector in browser DevTools
  • Ensuring it matches a single element when uniqueness is required
  • Verifying it works across desktop and mobile layouts

Locators should express intent, not DOM structure. A good Selenium Python locator explains why an element is selected, not where it happens to sit in the markup today.

Page Object Model (POM) in Selenium Python

Page Object Model improves test maintainability by separating page logic from test logic.

class LoginPage:

    def __init__(self, driver):

        self.driver = driver

    def login(self, email, password):

        self.driver.find_element(By.NAME, "email").send_keys(email)

        self.driver.find_element(By.NAME, "password").send_keys(password)

        self.driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()

Handling Waits in Selenium Python (In-Depth)

Wait handling is the single most important skill in Selenium Python automation. Most flaky tests are not caused by selectors—they are caused by incorrect waiting strategies.

Explicit waits should be tied to application state, not element existence. Waiting for an element to be present does not mean it is usable.

Bad waiting strategy:

time.sleep(5)

driver.find_element(By.ID, "submit").click()

Correct waiting strategy:

wait.until(EC.element_to_be_clickable(

    (By.CSS_SELECTOR, "button[data-testid='submit']")

))

Even better is waiting for state transitions, not UI artifacts:

  • aria-expanded=”true” for menus
  • aria-busy=”false” for loaders
  • URL changes for navigation
  • Text updates for async operations

In mature Selenium Python suites, waits are centralized into utility functions so test logic never directly handles timing. This ensures consistency across browsers and environments.

Locating WebElements in Selenium Python (Beyond Basics)

Element location is not about choosing By.CSS_SELECTOR over XPath—it is about choosing selectors that survive change.

Stable Selenium Python projects follow these rules:

  • Never rely on DOM position
  • Never rely on visual styling classes
  • Always scope selectors to functional containers
  • Prefer attributes that reflect user intent

Example of a fragile selector:

div.card:nth-child(2) button

Resilient selector:

section[data-testid="pricing"] button[data-plan="pro"]

Selectors should answer why an element is being selected, not where it happens to be today. This approach dramatically reduces breakage during UI refactors.

Page Object Model (POM) in Selenium Python (Practical Use)

Page Object Model is often explained poorly. Its real value is controlling change, not organizing files.

A good Selenium Python page object:

  • Exposes user actions, not elements
  • Contains no assertions
  • Handles waits internally
  • Hides selector complexity

Bad page object:

self.login_button = driver.find_element(...)

Good page object:

def submit_login(self, email, password):

    self.wait_for_form()

    self.enter_credentials(email, password)

    self.click_submit()

Tests should read like workflows, not DOM scripts. When implemented correctly, POM allows UI changes to be absorbed in one place without breaking dozens of tests.

Conclusion

Locating WebElements in Selenium Python is a critical factor in test stability and maintainability. Using intent-based, attribute-driven selectors and avoiding DOM-dependent patterns helps tests remain reliable as UIs evolve. Thoughtful locator design reduces flakiness, simplifies debugging, and ensures Selenium Python automation scales effectively across browsers and devices.

FAQs

Selenium in Python is a framework that allows automation of web browsers using Python scripts.

Selenium can be installed using pip, Python’s package manager, by installing the Selenium library and the required browser driver.

Selenium Python supports major browsers such as Chrome, Firefox, Edge, and Safari.

Python offers simple syntax and strong library support, making it suitable for writing readable and maintainable automation scripts.