Click "Browse" in a test and the operating system's file picker opens. It is not a web page, it has no DOM, and no browser automation tool can click it. This is a real limit, not a gap in Selenium.
The way around it is short enough to be surprising:
const path = require('path');
const input = await driver.findElement(By.css('input[type="file"]'));
await input.sendKeys(path.resolve('test-data/contacts.csv'));
You type the file path into the input element. That really is how it works. Browsers accept a path sent to a file input as if the user had chosen it, so the dialog never opens.
Two details that decide whether this works.
Use an absolute path. path.resolve turns a relative path into a full one. A relative path is interpreted against the wrong working directory often enough that it is not worth the risk.
Send keys to the input, not to the button. Apps usually hide the real <input type="file"> and show a styled button instead. Your selector must find the hidden input. Look in the Elements tab; it is nearly always there, just invisible.
If the input is hidden with display: none, Selenium may refuse to interact with it, because it enforces that a user could see the element. The usual fix is to make it visible for a moment with JavaScript:
await driver.executeScript(
"arguments[0].style.display = 'block'; arguments[0].style.visibility = 'visible';",
input,
);
await input.sendKeys(path.resolve('test-data/contacts.csv'));
executeScript runs JavaScript in the page. Use it sparingly. It bypasses the browser's own rules, so it can make a test pass on something a real user could never do. This is one of the few cases where it is the accepted answer.
Now the part that matters more than the mechanics: what do you assert?
Not that the filename appears on screen. That only proves the app read a name. Assert the effect:
await driver.findElement(By.css('[data-testid="confirm-import"]')).click();
await driver.wait(until.elementLocated(By.css('[data-testid="contact-row"]')), 5000);
const rows = await driver.findElements(By.css('[data-testid="contact-row"]'));
expect(rows.length).to.equal(12);
Twelve rows, matching the file's contents. That test fails if the parser breaks, which is the bug you actually care about.
And test the bad files: a missing column, a wrong file type, an empty file. Import bugs live in those paths and tests rarely go there.
Downloads are harder in Selenium and worth a warning. There is no download API. You configure the browser's download folder through its options, then check the filesystem yourself. It works, and it is fragile across browsers and headless modes. Many teams check the export endpoint with an HTTP request instead, which tests the same logic more reliably.
Remember this: send the absolute path to the file input, and assert on what the file did, not on its name.
Reference: File upload.