A real test example
A complete login flow test with a page object, explicit waits and assertions that fail for a useful reason.
A page object
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:
URL = "https://app.example.com/login"
def __init__(self, driver, wait):
self.driver = driver
self.wait = wait
def open(self):
self.driver.get(self.URL)
return self
def login(self, email, password):
self.driver.find_element(By.ID, "email").send_keys(email)
self.driver.find_element(By.ID, "password").send_keys(password)
self.driver.find_element(By.CSS_SELECTOR, "[data-testid='login-submit']").click()
self.wait.until(EC.url_contains("/dashboard"))
return self
def error_message(self):
element = self.wait.until(
EC.visibility_of_element_located((By.CSS_SELECTOR, "[role='alert']")))
return element.textA page object keeps selectors and waits in one place, so a UI change is one edit instead of thirty. Keep assertions out of it: a page object describes what you can do, and the test decides what should be true.
The test itself
import pytest
from selenium.webdriver.support.ui import WebDriverWait
@pytest.fixture
def login_page(driver):
return LoginPage(driver, WebDriverWait(driver, 10)).open()
def test_valid_credentials_reach_dashboard(login_page):
login_page.login("[email protected]", "correct-horse-battery")
assert "/dashboard" in login_page.driver.current_url
def test_bad_password_shows_error(login_page):
login_page.login("[email protected]", "definitely-wrong")
assert "incorrect" in login_page.error_message().lower()- Assert on user-visible outcomes, not on implementation details such as a CSS class name.
- Create or reset the data the test needs; a test that depends on run order is not a test.
- Keep one reason to fail per test, so the test name tells you what broke.
⚠️
Do not automate third-party pages you do not control. A login form behind a bot check or CAPTCHA will fail intermittently and you cannot fix it. Automate your own application and stub the outside world.
FAQ
How do I handle data that persists between tests?
Create what the test needs and delete it afterwards, or give every run a fresh account. Shared state is the second most common cause of flakiness after a missing wait.
Selenium or Playwright?
Playwright has automatic waiting and a built-in runner, so new suites often start there. Selenium remains the safer choice when you must support a specific browser matrix or an existing Java or C# stack.
Related
Locators and waits Install and first test
Last refreshed 2026-09-18.