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.
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.
$ python3 selenium-form-fill-submit.py title: Selenium form submission demo submitted: true result: submitted|Aisha Rahman|aisha@example.test|Please send the invoice.
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.
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.
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
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.
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
$ rm selenium-form-fill-submit.py