How to click a button with Selenium

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:

  1. 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.

  2. 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
  3. 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.

  4. 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.

  5. 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

  6. Remove the demo script after moving the pattern into the real test.
    $ rm selenium-click-button.py