Skip to main content
SeleniumDecoded

Upload a File Locally and Through a Grid

Send a file path to an input[type=file], make it work on a remote Grid or cloud with LocalFileDetector, and handle hidden file inputs behind styled buttons.

Stable Verified against Selenium 4.48.0

Problem

The “Upload” button opens a native file dialog Selenium cannot drive. On a Grid, even the right approach fails because the file exists on the test runner, not on the browser machine.

Solution

Never click the button. Send the absolute file path to the <input type="file"> element. For remote drivers, set LocalFileDetector so Selenium uploads the file to the browser’s host first. If the input is hidden, make it visible with a small style change.

sendKeys to the file input, with remote support
Selenium 4 Stable
Path file = Path.of("src/test/resources/avatar.png").toAbsolutePath();
// Remote (Grid / cloud): transfer the file to the browser host automatically
if (driver instanceof RemoteWebDriver remote) {
remote.setFileDetector(new LocalFileDetector());
}
WebElement input = driver.findElement(By.cssSelector("input[type=file]"));
// Styled uploaders often hide the input; unhide it (does not affect the app's handler)
((JavascriptExecutor) driver).executeScript(
"arguments[0].style.display='block'; arguments[0].style.visibility='visible'; arguments[0].style.opacity=1;", input);
input.sendKeys(file.toString());
new WebDriverWait(driver, Duration.ofSeconds(10))
.until(ExpectedConditions.textToBePresentInElementLocated(By.cssSelector(".upload-status"), "avatar.png"));
from pathlib import Path
from selenium.webdriver.remote.file_detector import LocalFileDetector
file = Path("tests/resources/avatar.png").resolve()
# Remote (Grid / cloud)
driver.file_detector = LocalFileDetector()
inp = driver.find_element(By.CSS_SELECTOR, "input[type=file]")
driver.execute_script(
"arguments[0].style.display='block'; arguments[0].style.visibility='visible'; arguments[0].style.opacity=1;", inp)
inp.send_keys(str(file))
WebDriverWait(driver, 10).until(
EC.text_to_be_present_in_element((By.CSS_SELECTOR, ".upload-status"), "avatar.png"))
const path = require('path');
const remote = require('selenium-webdriver/remote');
const file = path.resolve('test/resources/avatar.png');
// Remote (Grid / cloud)
driver.setFileDetector(new remote.FileDetector());
const input = await driver.findElement(By.css('input[type=file]'));
await driver.executeScript(
"arguments[0].style.display='block'; arguments[0].style.visibility='visible'; arguments[0].style.opacity=1;", input);
await input.sendKeys(file);
await driver.wait(until.elementTextContains(driver.findElement(By.css('.upload-status')), 'avatar.png'), 10000);
var file = Path.GetFullPath("Resources/avatar.png");
// Remote (Grid / cloud)
if (driver is RemoteWebDriver remote) remote.FileDetector = new LocalFileDetector();
var input = driver.FindElement(By.CssSelector("input[type=file]"));
((IJavaScriptExecutor)driver).ExecuteScript(
"arguments[0].style.display='block'; arguments[0].style.visibility='visible'; arguments[0].style.opacity=1;", input);
input.SendKeys(file);
new WebDriverWait(driver, TimeSpan.FromSeconds(10))
.Until(d => d.FindElement(By.CssSelector(".upload-status")).Text.Contains("avatar.png"));

Why It Works

Browsers treat sendKeys on a file input as the user choosing that path, and fire change so the app’s upload handler runs. LocalFileDetector intercepts the sendKeys path, POSTs the file to the remote session’s /file endpoint, and substitutes the remote path. Unhiding the input is only necessary because WebDriver refuses to interact with elements that are not displayed.

Gotchas

  • Multiple files: join paths with \n for multiple inputs.
  • Drag-and-drop-only uploaders without a file input need a JavaScript DataTransfer shim; see the File Uploads lesson.
  • Do not use LocalFileDetector on a local driver; it is harmless but wastes time. The Java check above only sets it on RemoteWebDriver.
  • Some cloud vendors restrict upload size and file types; check their documentation.

Learn the theory