A button click in Selenium has to happen after the browser exposes the element as visible and enabled. Clicking too early can produce no click, an intercepted click, or a stale element error after the page redraws, so the test should wait for the exact control and verify the page state caused by the click.
In Python, the usual flow is a locator tuple, WebDriverWait, EC.element_to_be_clickable(), and click() on the returned WebElement. A locator-based wait keeps the lookup close to the action, which matters when JavaScript enables or replaces the button after the initial page load.
Use this pattern for normal page buttons, links, and controls that a user could click. If an overlay covers the button, the button stays disabled, a native alert is open, or the button lives inside an iframe, fix that browser state or switch context before clicking instead of hiding the timing problem behind time.sleep() or a JavaScript click.
Steps to click a button with Selenium:
- Create selenium-click-button.py with a local button page and an explicit clickable wait.
- selenium-click-button.py
from pathlib import Path from tempfile import TemporaryDirectory import shutil from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait HTML = """<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Selenium button click demo</title> <script> window.addEventListener("DOMContentLoaded", () => { const button = document.querySelector("#place-order"); const status = document.querySelector("#status"); setTimeout(() => { button.disabled = false; status.textContent = "ready"; }, 300); button.addEventListener("click", () => { status.textContent = "clicked"; button.dataset.clicked = "true"; }); }); </script> </head> <body> <button id="place-order" disabled>Place order</button> <p id="status">waiting</p> </body> </html> """ options = Options() options.add_argument("--headless=new") options.add_argument("--window-size=1280,720") for browser_name in ("google-chrome", "chromium", "chromium-browser"): browser_path = shutil.which(browser_name) if browser_path: options.binary_location = browser_path break driver_path = shutil.which("chromedriver") service = Service(driver_path) if driver_path else Service() with TemporaryDirectory() as tmpdir: page = Path(tmpdir) / "button-click.html" page.write_text(HTML, encoding="utf-8") driver = webdriver.Chrome(service=service, options=options) try: driver.get(page.as_uri()) wait = WebDriverWait(driver, 10) button = wait.until(EC.element_to_be_clickable((By.ID, "place-order"))) button_text = button.text button.click() wait.until(EC.text_to_be_present_in_element((By.ID, "status"), "clicked")) status = driver.find_element(By.ID, "status").text print(f"title: {driver.title}") print(f"button_text: {button_text}") print(f"status: {status}") finally: driver.quit()
The demo waits for a disabled button to become clickable, clicks it, and checks the page text changed after the click.
- Run the script to confirm the button click changes the page state.
$ python3 selenium-click-button.py title: Selenium button click demo button_text: Place order status: clicked
- Use a stable locator for the real application button.
checkout_button = wait.until( EC.element_to_be_clickable((By.CSS_SELECTOR, "button[data-testid='checkout']")) ) checkout_button.click()
Prefer a stable id or test attribute when the application provides one. Button text and generated CSS class names often change before the click behavior changes.
- Verify the page state that the click should create.
wait.until( EC.visibility_of_element_located((By.CSS_SELECTOR, "[data-testid='order-confirmation']")) )
A successful click() call only proves Selenium sent the action. The follow-up wait proves the application reacted to it.
- Relocate the button before clicking again after a redraw or navigation.
button = wait.until(EC.element_to_be_clickable((By.ID, "place-order"))) button.click()
A saved WebElement can become stale after JavaScript replaces the node. Keep the locator and wait for a fresh element before the next click.
Related: How to troubleshoot stale element errors in Selenium - Remove the demo script after moving the pattern into the real test.
$ rm selenium-click-button.py
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.