Updated in July 2026 to combine the original two-part tutorial and use the current Selenium API.
Selenium controls a real browser from code. That makes it useful when a page depends on JavaScript or when you need to reproduce a user action such as clicking a button.
This tutorial builds the smallest useful example in two stages:
- open a page, wait for a button, and click it;
- move that behavior into a reusable browser wrapper.
Setup
Install the Selenium Python bindings:
python -m pip install --upgrade selenium
Modern Selenium can usually find or download a compatible browser driver
through Selenium Manager,
so a recent Chrome installation and webdriver.Chrome() are enough for this
example.
Open a page and click a button
We will use a tiny page embedded in the script. Unlike a selector copied from a news site, this example will not break when somebody redesigns a page.
from urllib.parse import quote
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
PAGE = """
<!doctype html>
<html lang="en">
<body>
<button id="demo-button" onclick="this.textContent = 'Clicked!'">
Click me
</button>
</body>
</html>
"""
driver = webdriver.Chrome()
wait = WebDriverWait(driver, 5)
try:
driver.get(f"data:text/html;charset=utf-8,{quote(PAGE)}")
button = wait.until(
EC.element_to_be_clickable((By.ID, "demo-button"))
)
button.click()
assert button.text == "Clicked!"
finally:
driver.quit()
The important part is the explicit wait. Pages do not always become interactive
the moment they appear. WebDriverWait repeatedly checks the condition until
the button is clickable or the five-second timeout expires.
Locators are pairs such as (By.ID, "demo-button"). IDs are usually easier to
read and less brittle than long XPath expressions. Selenium also supports CSS
selectors, link text, names, and other locator strategies.
Handle a missing element
Waiting for an element that never appears raises TimeoutException. Catch that
specific exception when a timeout is an expected outcome:
try:
missing_button = wait.until(
EC.element_to_be_clickable((By.ID, "missing-button"))
)
except TimeoutException:
print("The button did not become clickable before the timeout.")
Do not catch every exception here. A broad except could hide a broken
selector, a browser crash, or a programming error.
Extract a small browser wrapper
Once several scripts need the same waiting and cleanup behavior, a small class
can keep that behavior in one place. The decorator below turns an expected
timeout into a False result. If decorators are new to you, see this short
decorator example.
from functools import wraps
from urllib.parse import quote
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
def report_timeout(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except TimeoutException:
print(f"{func.__name__} timed out")
return False
return wrapper
class Browser:
def __init__(self, timeout=5):
self.driver = webdriver.Chrome()
self.wait = WebDriverWait(self.driver, timeout)
def load_html(self, html):
url = f"data:text/html;charset=utf-8,{quote(html)}"
self.driver.get(url)
def load_page(self, address):
self.driver.get(address)
@report_timeout
def click(self, locator):
element = self.wait.until(EC.element_to_be_clickable(locator))
element.click()
return True
def close(self):
self.driver.quit()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
with Browser(timeout=5) as browser:
browser.load_html(PAGE)
clicked = browser.click((By.ID, "demo-button"))
print(f"Clicked: {clicked}")
The context manager guarantees that quit() runs when the block ends, even if
an assertion or browser action fails. quit() closes the entire WebDriver
session; close() on the driver would close only the current window.
This wrapper is intentionally small. In a larger test suite, keep page-specific selectors and actions in separate page objects rather than letting one browser class collect every possible interaction.