File upload tests fail when browser automation clicks the native file picker instead of assigning the file to the page's file input. Selenium avoids that dialog by sending an absolute path to an input[type=file] element, which lets the test run in headless browsers and CI jobs without desktop interaction.

A passing upload check should observe the file input accepting the path, the form being submitted, and a page state that names the expected file. Checking only that the input exists can miss broken submit handlers, server-side validation failures, or frontend status text that never updates after the upload.

Remote WebDriver and Selenium Grid add a filesystem boundary because the test code and browser node may run on different machines. Keep the uploaded file on the test runner, send an absolute path, and use a local file detector for remote sessions when the browser node needs Selenium to transfer the file before submission.

Steps to upload a file with Selenium:

  1. Create upload_file_test.py with a local upload fixture and Selenium assertion.
    upload_file_test.py
    from pathlib import Path
    import shutil
    import textwrap
     
    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
     
     
    fixture_dir = Path("upload-fixture")
    fixture_dir.mkdir(exist_ok=True)
     
    sample_file = fixture_dir / "sample-upload.txt"
    sample_file.write_text("Selenium upload fixture\n", encoding="utf-8")
     
    html_file = fixture_dir / "upload.html"
    html_file.write_text(
        textwrap.dedent(
            """
            <!doctype html>
            <html lang="en">
            <head>
              <meta charset="utf-8">
              <title>Selenium upload fixture</title>
            </head>
            <body>
              <form id="upload-form">
                <label for="file-upload">Upload file</label>
                <input id="file-upload" name="file" type="file">
                <button id="submit" type="submit">Upload</button>
              </form>
              <p id="result" aria-live="polite"></p>
              <script>
                document.getElementById("upload-form").addEventListener("submit", event => {
                  event.preventDefault();
                  const file = document.getElementById("file-upload").files[0];
                  document.getElementById("result").textContent = file
                    ? `uploaded: ${file.name} (${file.size} bytes)`
                    : "no file selected";
                });
              </script>
            </body>
            </html>
            """
        ).strip(),
        encoding="utf-8",
    )
     
    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 None
     
    driver = webdriver.Chrome(service=service, options=options)
    try:
        driver.get(html_file.resolve().as_uri())
        file_input = driver.find_element(By.CSS_SELECTOR, "input[type='file']")
        file_input.send_keys(str(sample_file.resolve()))
        driver.find_element(By.ID, "submit").click()
        WebDriverWait(driver, 5).until(
            EC.text_to_be_present_in_element((By.ID, "result"), "uploaded: sample-upload.txt")
        )
     
        selected_value = file_input.get_attribute("value")
        selected_name = selected_value.replace("\\", "/").split("/")[-1]
        result_text = driver.find_element(By.ID, "result").text
        print(f"selected: {selected_name}")
        print(f"result: {result_text}")
    finally:
        driver.quit()

    The fixture writes its own HTML page and sample file so the upload path can be checked without depending on a live application.

  2. Run the script to prove the file input and submit flow.
    $ python3 upload_file_test.py
    selected: sample-upload.txt
    result: uploaded: sample-upload.txt (24 bytes)

    If Chrome does not launch, install Selenium for Python or configure the browser driver first.
    Related: How to install Selenium WebDriver for Python
    Related: How to configure ChromeDriver for Selenium

  3. Replace the fixture URL and selectors with the application under test.
    driver.get("https://app.example.net/uploads")
    file_input = driver.find_element(By.CSS_SELECTOR, "input[type='file']")
    file_input.send_keys(str(Path("sample-upload.txt").resolve()))
    driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()

    Use the real input[type=file] element. Selenium sends the path to the file input instead of typing into the operating system file picker.

  4. Wait for the application upload confirmation.
    WebDriverWait(driver, 10).until(
        EC.text_to_be_present_in_element((By.CSS_SELECTOR, ".upload-status"), "sample-upload.txt")
    )

    For Remote WebDriver or Selenium Grid, set driver.file_detector = LocalFileDetector() before send_keys() when the local test runner must transfer the file to the browser node.
    Related: How to use explicit waits in Selenium
    Related: How to connect to a remote Selenium WebDriver

  5. Remove the local fixture after the smoke test.
    $ rm -r upload-fixture