ScreenshotNeo

BlogHow-to

How to Handle Local Files in Browser Automation

Upload fixtures, test custom file pickers and drag-and-drop controls, and verify downloads in Playwright, Selenium, and Cypress—including remote-browser file paths.

By the ScreenshotNeo team4 October 202611 min read

For a normal file upload, locate the page’s input[type="file"] and use your browser automation framework’s file API. Don’t try to automate the operating system’s native file picker. For custom controls, determine whether the page opens a file chooser or accepts drag-and-drop, then use the framework API for that interaction. For remote browsers, make sure the file is available to the browser host or is transferred there. For downloads, capture the framework’s download event or configured output and wait for the download to finish before checking the file.

This guide covers uploads, multiple files, in-memory fixtures, custom controls, remote sessions, downloads, common failures, and practical reliability considerations in Playwright, Selenium, and Cypress.

1. Choose the right file-handling approach

Situation Use Watch for
Visible or hidden standard file input Framework file-input API Use the input element, even if a visible button opens it.
Button opens a file chooser dynamically Register for the chooser event before clicking, then assign files Registering after the click can miss the event.
Custom drop zone Framework drag-and-drop file action, if supported A file input and a drop zone are different interfaces; test the behavior your users use.
Remote browser or Grid Framework-specific file transfer or remote file support A path on the test runner may not exist on the browser host.
Download verification Download event or configured downloads folder Wait for completion before asserting on existence or contents.

Keep fixtures in a known project directory and build paths from that location. This makes tests portable across developer machines and CI runners. Also distinguish the test runner’s filesystem from the browser machine’s filesystem: a path string alone does not move a file between them.

2. Playwright: upload files and capture downloads

Playwright’s Locator.setInputFiles handles a regular file input. It supports single or multiple paths, directory selection where applicable, clearing a selection with an empty array, and file payloads constructed in memory. Relative paths resolve from the current working directory. See the Playwright locator API and download documentation.

Runnable Node.js example

const { chromium } = require('playwright');
const path = require('node:path');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  try {
    await page.goto('https://example.com/upload');

    const input = page.locator('input[type="file"]');
    await input.setInputFiles(path.resolve('fixtures/sample.pdf'));
    await page.getByRole('button', { name: 'Upload' }).click();
    await page.getByText('Upload complete').waitFor();

    // Multiple files:
    // await input.setInputFiles([
    //   path.resolve('fixtures/first.pdf'),
    //   path.resolve('fixtures/second.png'),
    // ]);

    // In-memory file payload (no fixture path required):
    // await input.setInputFiles({
    //   name: 'hello.txt',
    //   mimeType: 'text/plain',
    //   buffer: Buffer.from('hello from the test'),
    // });

    // Clear a previously selected file:
    // await input.setInputFiles([]);
  } finally {
    await browser.close();
  }
})();

Replace the example URL, selector, button name, and completion assertion with those from the application under test. The completion assertion should reflect an application signal—such as a success message or completed upload state—not merely the click.

When clicking opens a file chooser

Set up the event wait before clicking so the listener is active when the chooser opens:

const chooserPromise = page.waitForEvent('filechooser');
await page.getByRole('button', { name: 'Choose file' }).click();
const chooser = await chooserPromise;
await chooser.setFiles('fixtures/sample.pdf');

This is useful when the input is created only after a user action. If the control is simply a styled label connected to an existing input, setting files on the input is usually more direct.

Wait for a download before asserting

const downloadPromise = page.waitForEvent('download');
await page.getByRole('link', { name: 'Download report' }).click();
const download = await downloadPromise;
await download.saveAs('artifacts/report.csv');

// Optional: inspect the saved file after saveAs completes.
const fs = require('node:fs/promises');
const contents = await fs.readFile('artifacts/report.csv', 'utf8');
if (!contents.includes('expected heading')) {
  throw new Error('Downloaded report did not contain the expected heading');
}

Make sure the destination directory exists before saving. Playwright emits the download event when a download starts; saving it and then inspecting the saved path gives the test a clear completion point.

3. Selenium: use the file input, not the native picker

Selenium WebDriver does not interact with the native operating-system upload dialog. Its documented approach is to locate the page’s file input and send the full file path to it, then submit or activate the page’s upload control. See Selenium’s file upload documentation.

Runnable Python example

from pathlib import Path
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

fixture = (Path(__file__).parent / 'fixtures' / 'sample.pdf').resolve()
if not fixture.is_file():
    raise FileNotFoundError(f'Upload fixture not found: {fixture}')

driver = webdriver.Chrome()
try:
    driver.get('https://example.com/upload')
    file_input = driver.find_element(By.CSS_SELECTOR, 'input[type="file"]')
    file_input.send_keys(str(fixture))
    driver.find_element(By.CSS_SELECTOR, 'button[type="submit"]').click()
    WebDriverWait(driver, 20).until(
        EC.visibility_of_element_located((By.CSS_SELECTOR, '.upload-complete'))
    )
finally:
    driver.quit()

The path is resolved from the test file’s directory, rather than being tied to one developer’s machine. Adapt the URL, selectors, and success condition to the application.

Remote WebDriver and Selenium Grid

In a remote session, the test code may run on one machine while the browser runs on another. The browser driver cannot necessarily read a local client path directly. Selenium documents a Local File Detector for transferring files to the remote end. Java requires configuring the detector explicitly; other bindings may supply one by default. Confirm the behavior for the binding and Grid version in use.

For remote downloads, enable Selenium Grid’s managed-download support and the session capability, then retrieve the file to the test client using the documented Grid workflow. Selenium notes that the remote downloadable-file listing is an immediate snapshot; it does not wait for an active download to finish. Polling or waiting for the application’s download-complete signal before retrieving the file avoids treating an incomplete listing as proof of completion. See Remote WebDriver documentation.

4. Cypress: select files and test drag-and-drop

Cypress provides selectFile for file inputs. A path resolves relative to the project root. The default selection mode targets a file input or connected label; action: 'drag-drop' simulates dropping a file onto a DOM element. Cypress also documents using force: true when a hidden input’s actionability checks prevent selection. See Cypress selectFile.

Runnable JavaScript example

describe('file upload', () => {
  it('uploads a fixture', () => {
    cy.visit('/upload');
    cy.get('input[type="file"]').selectFile('cypress/fixtures/sample.pdf');
    cy.get('button[type="submit"]').click();
    cy.get('.upload-complete').should('be.visible');
  });

  it('drops a fixture on a drop zone', () => {
    cy.visit('/upload');
    cy.get('[data-testid="drop-zone"]')
      .selectFile('cypress/fixtures/sample.pdf', { action: 'drag-drop' });
    cy.get('.upload-complete').should('be.visible');
  });
});

If a visible button activates a hidden input, target the input with force: true only when Cypress’s normal actionability checks block the intended selection:

cy.get('input[type="file"]').selectFile('cypress/fixtures/sample.pdf', { force: true });

For multiple files, pass an array of paths supported by selectFile. Use the form expected by the installed Cypress version, and assert that the application received each file.

Verify a Cypress download

Cypress writes application downloads to its configured downloadsFolder, which defaults to cypress/downloads. Trigger the download, then read or assert on the file from disk after it has been written. See Cypress readFile and the downloadsFolder configuration.

it('downloads a report', () => {
  cy.visit('/reports');
  cy.contains('Download report').click();
  cy.readFile('cypress/downloads/report.csv', { timeout: 20000 })
    .should('contain', 'expected heading');
});

Use the application’s actual output filename and expected contents. If a test can reuse a previous file, clear the destination or choose an assertion that distinguishes the new result.

5. Handle custom upload controls and file boundaries

Choose between file input, chooser, and drop zone

  1. Inspect the DOM. Find whether the page has an input[type="file"], even if it is visually hidden.
  2. Use direct assignment when possible. Set the input’s files through the framework API and activate the page’s upload action if required.
  3. Use chooser events for action-created controls. Start listening before clicking the button that opens the chooser.
  4. Use drag-and-drop for a real drop-zone workflow. Target the zone and use the framework’s file-drop action. Verify the app’s resulting state.
  5. Assert application behavior. Check the selected filename, upload progress/result, or server-confirmed state; a selected file alone does not prove that upload succeeded.

Make fixtures predictable

  • Commit small, non-sensitive fixtures with the test project, or generate in-memory data where the framework supports it.
  • Build paths from a known project or test-file directory and resolve them before use.
  • Use representative extensions and MIME types; some applications validate both.
  • For multiple-file inputs, confirm the page allows multiple selection and that the test supplies a list in the API’s expected form.
  • Keep fixtures below application size limits unless the test specifically covers limit behavior.
  • Avoid relying on files in a developer’s Downloads folder or other machine-specific locations.

Understand filesystem ownership

For local browser runs, the browser and test runner commonly share access to the same machine, but containers and hosted runners can still have separate mounts. In remote WebDriver, explicitly transfer the file when required. Playwright MCP’s file chooser tool accepts absolute paths, but file access is restricted to workspace roots by default; its browser_drop operation supports paths or MIME-typed data. See the Playwright MCP documentation. Check the access rules of the specific automation environment rather than assuming any host path is visible.

6. Troubleshooting uploads and downloads

Symptom Likely cause Fix
“File not found” or path error Relative path resolves from a different working directory, fixture is missing, or path belongs to another machine. Resolve from a known project/test directory, check the file exists, and transfer it for remote execution.
Native picker appears but automation stalls The test tries to operate the OS dialog directly. Set the page’s file input using the framework API, or register for the framework’s chooser event before clicking.
Chooser event times out Listener was registered after the click, or the button did not open a chooser. Register the event wait first; inspect whether the control uses an input, custom drop zone, or another interaction.
Hidden input is rejected Framework actionability rules disallow interaction with the hidden element. Use the framework’s supported file assignment method; in Cypress, use force: true where appropriate.
File is selected but not uploaded The application requires a separate submit action, validation passes asynchronously, or the wrong input was targeted. Activate the upload control and wait for an application-level success or failure state.
Drop-zone test does not behave like a user drop The test selected the input instead of simulating the drop interaction, or targeted the wrong zone. Use the framework’s drag-and-drop file action on the actual drop zone and assert the resulting UI state.
Download assertion races or sees partial output The test checks before download completion. Wait for Playwright’s download event and save the file, wait for Cypress’s file read to succeed, or use the remote Grid download workflow with an explicit completion condition.
Remote file list is empty or stale Selenium’s listing is only an immediate snapshot. Wait for completion before listing/retrieving; do not use the list itself as a wait.
Wrong or old downloaded file is asserted A prior run left the same filename in the destination. Use a clean output directory or remove stale files before triggering the download.
Upload fails only in CI Fixture path, mount, permissions, size limits, or timing differ in CI. Log the resolved path, verify existence and readability in the job, and wait for the app’s upload result rather than a fixed short delay.

7. Performance, reliability, and cost

File handling is usually a small part of browser-test runtime; transfer size, application processing, and waiting for a real completion state can dominate. Prefer small fixtures for routine tests, and keep large-file cases isolated to tests that need to cover size limits or streaming behavior. Avoid arbitrary long sleeps: wait for chooser, download, or application-state events that correspond to the operation.

For reliability, use deterministic fixture paths, unique or clean download destinations, explicit remote transfer, and assertions on the resulting application state and file contents. Test a representative case for empty files, invalid formats, multiple files, and size boundaries when those conditions matter to the product. Framework APIs remove dependence on native dialog coordinates and OS-specific dialog behavior.

These workflows do not require a physical accessory or a separate screenshot service. They use the automation framework and the test runner’s accessible storage. ScreenshotNeo is useful for a related task—capturing a webpage as an image or PDF—but it does not replace file upload or download automation.

8. Or skip the browser setup

For a clean screenshot or PDF of a webpage, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. It does not automate local file upload workflows; use the framework methods above for those. One GET request captures a URL as PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents—including Claude, Cursor, and any MCP client—take screenshots. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, no card required.

9. Frequently asked questions

Can browser automation upload a file without opening the native picker?

Yes. Assign a path or supported file payload directly to the page’s file input using the framework API. Selenium documents this approach because WebDriver does not interact with the native upload dialog.

Can a test upload a file that exists only on my laptop to a remote browser?

Only if the automation environment can access or transfer it. Selenium Remote WebDriver provides file transfer support through a Local File Detector; other environments have their own access boundaries.

Should I test a drop zone by setting the file input?

Set the input for input-based behavior. Use a drag-and-drop action when the behavior under test is specifically the drop-zone interaction.

How do I know a download has finished?

Use the framework’s download event or file-read/completion mechanism and inspect the resulting file. A Selenium Grid remote file listing alone is not a completion signal.

Can ScreenshotNeo take a screenshot of a local file?

ScreenshotNeo captures web pages by URL. It is not a browser automation file transfer tool; use Playwright, Selenium, or Cypress for local-file handling.