How to fill and submit a form with Selenium

Form automation can pass too early when the script types into fields before they are visible or clicks submit without checking the page reaction. Selenium should fill the same controls a user can edit, submit through the real button, and wait for the browser state that proves the form was accepted.

In Python, a maintainable form flow combines stable locators, WebDriverWait, clear(), send_keys(), and a submit-button click(). The click only proves that Selenium sent an action, so the test should also inspect confirmation text, a URL change, a success banner, or another page state that belongs to the submitted form.

Use id, name, or dedicated test attributes for form fields whenever the application exposes them. Custom widgets, iframe-hosted forms, file uploads, and native <select> menus may need their own locator or helper pattern, but ordinary text inputs and textareas should stay close to the user path instead of using JavaScript assignment or time.sleep() delays.

Steps to fill and submit a form with Selenium:

  1. Create selenium-form-fill-submit.py with a local form page and explicit waits.
    selenium-form-fill-submit.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 form submission demo</title>
        <script>
          window.addEventListener("DOMContentLoaded", () => {
            const form = document.querySelector("#contact-form");
            const result = document.querySelector("#result");
     
            form.addEventListener("submit", (event) => {
              event.preventDefault();
              const data = new FormData(form);
              result.textContent = [
                "submitted",
                data.get("full_name"),
                data.get("email"),
                data.get("message")
              ].join("|");
              document.body.dataset.submitted = "true";
            });
          });
        </script>
      </head>
      <body>
        <form id="contact-form">
          <label>
            Full name
            <input name="full_name" autocomplete="name">
          </label>
          <label>
            Email
            <input name="email" type="email" autocomplete="email">
          </label>
          <label>
            Message
            <textarea name="message"></textarea>
          </label>
          <button type="submit">Send request</button>
        </form>
        <p id="result" aria-live="polite">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) / "form-submit.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)
     
            full_name = wait.until(
                EC.visibility_of_element_located((By.NAME, "full_name"))
            )
            email = driver.find_element(By.NAME, "email")
            message = driver.find_element(By.NAME, "message")
            submit = wait.until(
                EC.element_to_be_clickable((By.CSS_SELECTOR, "button[type='submit']"))
            )
     
            full_name.clear()
            full_name.send_keys("Aisha Rahman")
            email.clear()
            email.send_keys("aisha@example.test")
            message.clear()
            message.send_keys("Please send the invoice.")
            submit.click()
     
            wait.until(
                EC.text_to_be_present_in_element((By.ID, "result"), "submitted")
            )
     
            result = driver.find_element(By.ID, "result").text
            submitted = driver.find_element(By.TAG_NAME, "body").get_attribute(
                "data-submitted"
            )
     
            print(f"title: {driver.title}")
            print(f"submitted: {submitted}")
            print(f"result: {result}")
        finally:
            driver.quit()

    The local page keeps the smoke test independent from an external website while exercising the same send_keys(), click(), and wait pattern used against a real form.

  2. Run the script to confirm the form is filled and submitted.
    $ python3 selenium-form-fill-submit.py
    title: Selenium form submission demo
    submitted: true
    result: submitted|Aisha Rahman|aisha@example.test|Please send the invoice.
  3. Open the real form page in the test.
    driver.get("https://app.example.test/contact")
    wait = WebDriverWait(driver, 10)

    Replace the URL with the application route that owns the form. Keep credentials and private customer data out of saved examples and screenshots.

  4. Fill each editable field with stable locators.
    full_name = wait.until(
        EC.visibility_of_element_located((By.NAME, "full_name"))
    )
    full_name.clear()
    full_name.send_keys("Aisha Rahman")
     
    email = driver.find_element(By.NAME, "email")
    email.clear()
    email.send_keys("aisha@example.test")
     
    message = driver.find_element(By.NAME, "message")
    message.clear()
    message.send_keys("Please send the invoice.")

    Use clear() before send_keys() when the field can contain remembered, default, or server-rendered text.

  5. Submit the form through its submit button.
    submit = wait.until(
        EC.element_to_be_clickable((By.CSS_SELECTOR, "button[type='submit']"))
    )
    submit.click()

    Click the same button a user would use so client-side validation and submit handlers run.
    Related: How to click a button with Selenium

  6. Verify the post-submit browser state.
    wait.until(
        EC.text_to_be_present_in_element(
            (By.CSS_SELECTOR, "[data-testid='form-status']"),
            "Received"
        )
    )

    A submitted HTTP request is not enough by itself. Check the confirmation banner, redirected URL, saved record, or page text that the application shows after accepting the form.

  7. Relocate fields before filling the form again after a redraw or navigation.
    full_name = wait.until(
        EC.visibility_of_element_located((By.NAME, "full_name"))
    )

    A saved WebElement can become stale when JavaScript replaces the form. Wait for a fresh element before reusing the fill sequence.
    Related: How to troubleshoot stale element errors in Selenium

  8. Remove the local smoke-test script after moving the pattern into the real test suite.
    $ rm selenium-form-fill-submit.py