Test Automation Lesson 2 of 6 ⏱️ 4 min read

Selenium WebDriver Basics

Your first Selenium test in Python — locators, actions, explicit waits and assertions against a real login form.

Updated Oct 30, 2026

What is Selenium WebDriver?

Selenium WebDriver drives a real browser through the W3C WebDriver protocol. Your test code sends commands ("open this URL", "click this button"), and the browser executes them exactly as a user would. It supports Chrome, Firefox, Edge and Safari, and has official bindings for Java, Python, C#, JavaScript and Ruby.

This lesson uses Python + pytest because it's the quickest to read and set up.

Setup

Bash
python -m venv .venv
source .venv/bin/activate      # Windows: .venv\Scripts\activate
pip install selenium pytest

Selenium 4.6+ includes Selenium Manager, which downloads the correct browser driver automatically — no manual chromedriver setup.

Your first test

We'll test the public practice login page at https://the-internet.herokuapp.com/login (username tomsmith, password SuperSecretPassword!).

Python
# test_login.py — run with: pytest -v
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

BASE_URL = "https://the-internet.herokuapp.com"


def test_valid_login_shows_secure_area():
    driver = webdriver.Chrome()
    try:
        driver.get(f"{BASE_URL}/login")

        driver.find_element(By.ID, "username").send_keys("tomsmith")
        driver.find_element(By.ID, "password").send_keys("SuperSecretPassword!")
        driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()

        flash = WebDriverWait(driver, 10).until(
            EC.visibility_of_element_located((By.ID, "flash"))
        )
        assert "You logged into a secure area!" in flash.text
        assert driver.current_url.endswith("/secure")
    finally:
        driver.quit()

Run pytest -v and you'll see Chrome open, log in and close.

Locators

Locators tell Selenium how to find elements. In order of preference:

Strategy Example Notes
By.ID (By.ID, "username") Fast and stable when IDs are unique
By.CSS_SELECTOR (By.CSS_SELECTOR, "[data-test='login-button']") Flexible; dedicated data-test attributes are the most stable
By.NAME (By.NAME, "email") Good for form fields
By.LINK_TEXT (By.LINK_TEXT, "Logout") For links with fixed text
By.XPATH (By.XPATH, "//button[text()='Log in']") Most powerful; use when nothing else works

Avoid brittle locators like /html/body/div[2]/div/form/button — any layout change breaks them. Ask developers to add data-test attributes to important elements.

Waits: the key to stable tests

Pages load asynchronously. Clicking an element before it exists throws NoSuchElementException. There are three ways to wait — only one is good:

Python
import time
time.sleep(5)                  # ✘ fixed sleep: slow when the page is fast, still fails when it's slower

driver.implicitly_wait(10)     # ~ implicit wait: global, can't wait for visibility or clickability

WebDriverWait(driver, 10).until(                                  # ✔ explicit wait
    EC.element_to_be_clickable((By.CSS_SELECTOR, "button[type='submit']"))
).click()

Useful expected conditions: visibility_of_element_located, element_to_be_clickable, text_to_be_present_in_element, url_contains, invisibility_of_element_located (for spinners).

Don't mix implicit and explicit waits — the timeouts can combine unpredictably.

Common actions

Python
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select

field = driver.find_element(By.ID, "username")
field.clear()
field.send_keys("tomsmith", Keys.TAB)                     # type, then press Tab

Select(driver.find_element(By.ID, "dropdown")).select_by_visible_text("Option 2")

checkbox = driver.find_element(By.CSS_SELECTOR, "input[type='checkbox']")
if not checkbox.is_selected():
    checkbox.click()

driver.save_screenshot("after-login.png")                  # evidence for reports

A negative test with a pytest fixture

Fixtures remove the repeated browser setup and guarantee the browser closes even when a test fails:

Python
# conftest.py
import pytest
from selenium import webdriver


@pytest.fixture
def driver():
    options = webdriver.ChromeOptions()
    options.add_argument("--headless=new")      # no visible window — needed in CI
    browser = webdriver.Chrome(options=options)
    yield browser
    browser.quit()
Python
# test_login_invalid.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait


def test_wrong_password_shows_error(driver):
    driver.get("https://the-internet.herokuapp.com/login")
    driver.find_element(By.ID, "username").send_keys("tomsmith")
    driver.find_element(By.ID, "password").send_keys("wrong-password")
    driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()

    flash = WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "flash")))
    assert "Your password is invalid!" in flash.text
    assert driver.current_url.endswith("/login")

These tests work, but every test repeats the selectors. The Page Object Model fixes that.

Finished this lesson?
Tick it off to track your Test Automation progress.
Next lesson