Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

To upload a file with Python Selenium, locate the page’s real <input type="file">, resolve the file to a dependable path, pass that path to send_keys(), activate the page’s upload or submit control, and assert the result. Selenium sets the input through WebDriver; it does not need to operate the operating-system file chooser.

The standard Selenium upload workflow

A browser upload normally begins with an HTML file input. Selenium’s documented approach is to bypass the native chooser and set that input directly. The page may still require a separate button click, JavaScript validation, or an application-specific confirmation after the input is populated.

  1. Put a known fixture file in your project.
  2. Resolve its path independently of the process’s current working directory.
  3. Find the actual input[type='file'] element.
  4. Call send_keys() with the path.
  5. Click the site’s upload or submit control.
  6. Assert a visible result, such as the uploaded filename or success message.

Complete local WebDriver example

This example follows the usual test-page pattern. Change the URL and selectors to match your application.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import os
from pathlib import Path

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options


def test_upload():
    options = Options()
    # options.add_argument("--headless=new")  # Enable when appropriate.
    driver = webdriver.Chrome(options=options)

    try:
        driver.get("https://example.com/upload")

        # Anchor the fixture to this test file, not to the shell's current directory.
        fixture = Path(__file__).parent / "fixtures" / "sample.png"
        upload_file = os.path.abspath(fixture)

        if not os.path.isfile(upload_file):
            raise FileNotFoundError(f"Upload fixture does not exist: {upload_file}")

        file_input = driver.find_element(By.CSS_SELECTOR, "input[type='file']")
        file_input.send_keys(upload_file)

        driver.find_element(By.ID, "file-submit").click()

        uploaded_name = driver.find_element(By.ID, "uploaded-files").text
        assert uploaded_name == Path(upload_file).name
    finally:
        driver.quit()


test_upload()

The path is made absolute before it is sent. The assertion checks what the application reports, rather than assuming that setting the input alone completed the upload.

Finding the correct file input

Use the actual HTML input

Inspect the page and identify an element similar to <input type="file" name="attachment">. Select it by a stable ID, name, or CSS selector when possible:

file_input = driver.find_element(By.ID, "attachment")
# or:
file_input = driver.find_element(By.NAME, "attachment")
# or:
file_input = driver.find_element(By.CSS_SELECTOR, "input[type='file']")

A visible “Choose file” button may be styled markup that activates a hidden input. The element that receives send_keys() is still the file input, not the decorative button.

Multiple files

If the input has the multiple attribute, provide several paths separated by the platform’s line separator:

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import os

paths = [
    os.path.abspath("tests/fixtures/one.txt"),
    os.path.abspath("tests/fixtures/two.txt"),
]
file_input.send_keys(os.linesep.join(paths))

Whether multiple files are accepted, validated, or displayed is controlled by the page. Assert the application’s actual result.

Custom upload widgets

Drag-and-drop components, third-party upload libraries, shadow DOM controls, and widgets that create inputs dynamically do not share one universal Selenium recipe. Inspect the DOM at the moment the widget is ready, locate the underlying file input if one exists, and follow the application’s own validation and submission flow. If no file input is exposed, the component may require a site-specific integration rather than a generic click sequence.

Building reliable file paths

Relative paths depend on where the test process was launched. A test that passes from an IDE can fail in CI when the working directory changes. Anchor fixtures to a stable directory:

from pathlib import Path

fixture = Path(__file__).resolve().parent / "fixtures" / "sample.png"
upload_file = str(fixture)
file_input.send_keys(upload_file)

Using Path.resolve() or os.path.abspath() also makes failures easier to diagnose. Check that the path points to a regular file and that the test account can read it. On platforms using different path syntax, pass the path produced by Python on that platform; environments such as Cygwin may require a Windows-form path when the browser driver runs on Windows.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why Selenium does not need the operating-system chooser

Selenium cannot interact with the native file-upload dialog in the normal WebDriver model. Its file-input mechanism exists specifically so the test can assign a local path without opening that dialog. Do not add coordinate clicks, keyboard automation, or desktop-control libraries for the standard case. Those approaches are slower, more fragile, and tied to a particular desktop session.

Submit and verify the application result

Calling send_keys() populates the control; it does not guarantee that the site has uploaded anything. Common completion actions include a form submit button, an “Upload” button, or a JavaScript request triggered after the input’s change event.

file_input.send_keys(upload_file)
driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()

success = driver.find_element(By.CSS_SELECTOR, "[role='status']").text
assert "uploaded" in success.lower()

Prefer an application-visible assertion: returned filename, success status, progress completion, attachment row, or server-generated ID. Avoid asserting only that the input’s value is nonempty, because that proves the browser control changed, not that the server accepted the file.

Waiting for dynamic upload controls

If the page creates the input after navigation or after opening a modal, wait for the element before sending the path. A concise explicit-wait pattern is:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 20)
file_input = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR, "input[type='file']"))
)
file_input.send_keys(upload_file)

submit = wait.until(
    EC.element_to_be_clickable((By.ID, "file-submit"))
)
submit.click()

result = wait.until(
    EC.visibility_of_element_located((By.ID, "uploaded-files"))
)
assert result.text == Path(upload_file).name

Use presence when the input may be hidden but attached to the DOM. Use visibility or clickability for the result and submit control when the interface requires those states.

Remote WebDriver and Selenium Grid

Remote execution introduces two machines: the Python test client and the browser node. A path that exists on the client may not exist on the node. Remote WebDriver uses a local file detector to transfer the local file so the remote browser can use it. Python Remote WebDriver provides a local detector by default; setting it explicitly makes the intent clear.

import os
from pathlib import Path

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.file_detector import LocalFileDetector

fixture = Path(__file__).resolve().parent / "fixtures" / "sample.png"
upload_file = os.path.abspath(fixture)

options = webdriver.ChromeOptions()
driver = webdriver.Remote(
    command_executor="http://grid-host:4444/wd/hub",
    options=options,
)

try:
    driver.file_detector = LocalFileDetector()
    driver.get("https://example.com/upload")
    driver.find_element(By.CSS_SELECTOR, "input[type='file']").send_keys(upload_file)
    driver.find_element(By.ID, "file-submit").click()
    uploaded_name = driver.find_element(By.ID, "uploaded-files").text
    assert uploaded_name == fixture.name
finally:
    driver.quit()

The detector solves the client-to-node file-location boundary; it does not bypass server-side size, type, authentication, or content validation.

Execution context Where the fixture starts What to do
Local WebDriver The test and browser generally share one machine. Resolve the fixture path and send it to the file input.
Remote WebDriver or Grid The fixture may exist only on the Python client. Use the local file detector behavior so the file is transferred to the browser node.

Troubleshooting common failures

send_keys() reports that the file does not exist

  • Print the resolved path and check it with Path(path).is_file().
  • Stop relying on the shell’s current directory; derive the path from __file__ or a known project root.
  • Confirm the fixture is included in the CI checkout and has readable permissions.
  • For remote runs, verify that a local file detector is active.

The file chooser opens or the test hangs

You are probably clicking the visible chooser rather than assigning the path to the input. Locate input[type='file'] and call send_keys() directly. Remove desktop automation from the standard flow.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

No file input is found

  • Wait for the upload modal or component to render.
  • Check whether the input is inside an iframe and switch to that frame first.
  • Inspect whether a shadow-root component or dynamically generated input changes the selector.
  • Confirm that the page really uses a file input; a custom widget may need application-specific handling.

The input is populated but nothing uploads

Click the site’s submit control or wait for its change-triggered request. Then assert the server-side result. Also check required metadata, file type and size restrictions, authentication, and any validation message rendered by the page.

The result assertion runs too early

Replace an immediate lookup with an explicit wait for the success element, uploaded filename, or completed progress state. Do not use a long fixed sleep as the primary synchronization method.

Remote upload fails while local upload works

Confirm the file exists on the Python client, the remote session has a local file detector, and the Grid node can receive temporary files. Log the client path and the remote session details without exposing credentials.

Reliability and test-design checklist

  • Use small deterministic fixtures with names that make assertions unambiguous.
  • Resolve paths from the test or fixture directory.
  • Select the real file input with a stable locator.
  • Use explicit waits for dynamically rendered controls and results.
  • Submit through the application’s intended control.
  • Assert a user-visible, application-level outcome.
  • Clean up the driver in a finally block.
  • Keep separate tests for local and remote execution when their file-transfer behavior differs.
  • Never print access tokens, private upload URLs, or sensitive file contents in CI logs.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your goal is a rendered page image rather than exercising an upload form, ScreenshotNeo provides a one-call website screenshot API. It accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets before capture. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and each response identifies the page verdict and billing status in headers.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use the API documentation at https://screenshotneo.com/docs/ for all options. A basic cURL request is:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The same request in Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

And Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo also has an MCP server with take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients. Features include full-page and element capture, device presets, retina scale, PDF controls, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, timezone and geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, a usage API and an OpenAPI specification. Its parameter names are compatible with those used by other screenshot APIs.

The Free plan includes 1,000 screenshots each month with no card. Paid plans start at $5 for 3,000 shots; every feature is on every plan. Sign up for the free ScreenshotNeo plan.

FAQ

Can Selenium upload a file without opening a dialog?

Yes. Send the path to the page’s file input with send_keys(); do not automate the native chooser.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Does send_keys() submit the form?

No. It populates the input. Your test must perform the page’s upload or submit action unless the application submits automatically.

Does a remote browser need the file on its own disk?

The client path is transferred through the remote file-detector mechanism, which Python Remote WebDriver provides by default.

Frequently Asked Questions

Can I upload a file by clicking the visible “Choose file” button?

For the standard workflow, no desktop chooser automation is needed. Locate the underlying input[type='file'] and pass its path to send_keys().

How should I test several files?

Use a file input with the multiple attribute and send platform-separated absolute paths, then verify that the application accepted each file.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.