How to Upload Files in Cypress With selectFile()
Use Cypress selectFile() to test file inputs, hidden upload controls, drag-and-drop zones, and multipart endpoints. Runnable examples cover paths, buffers, multiple files, and common errors.
Use Cypress’s built-in selectFile() command to select a file in an HTML file input or simulate dropping files onto a page element. For a file already in your project, the basic test is:
cy.get('input[type="file"]').selectFile('cypress/fixtures/example.pdf')
The path is relative to the project root, which is the directory containing your Cypress configuration file. Use { action: 'drag-drop' } for a drop zone, and use a direct multipart cy.request() only when you want to test the upload endpoint without exercising browser file selection. Cypress added selectFile() in version 9.3.0. See the Cypress selectFile() API and its fixtures guidance.
1. Select a file in a standard input
Query the actual input[type="file"] and pass a project-relative file path. Cypress reads the file from disk and attaches it as the selected file.
describe('document upload', () => {
it('selects a fixture and submits it', () => {
cy.visit('/documents/new')
cy.get('input[type="file"]').selectFile('cypress/fixtures/example.pdf')
cy.get('button[type="submit"]').click()
cy.contains('Upload complete').should('be.visible')
})
})
Use a real representative fixture when possible. Cypress documents a path as its preferred approach for files on disk, and it avoids converting binary data through a text encoding. The selection step fires the browser’s file-input interaction; your test should still assert the application behavior that matters, such as a displayed file name, validation message, upload request, or success state.
2. Upload through a hidden input
Many interfaces visually hide the file input and expose a styled button or drop area. If Cypress cannot interact with the hidden input under normal actionability rules, select it with { force: true }:
cy.get('input[type="file"]').selectFile(
'cypress/fixtures/example.pdf',
{ force: true }
)
cy.get('[data-cy="selected-file"]').should('contain', 'example.pdf')
This sets the input’s selected file directly. If the behavior you need to cover is specifically that the visible button opens the operating system’s file chooser, that is a separate user interaction from assigning a file to the input. Keep the test intent clear: use the input selection for upload flow behavior, and assert the resulting application state.
3. Simulate drag and drop
For a drop zone, set the action to drag-drop. In this mode the target can be any DOM element, not just a file input:
cy.get('[data-cy="drop-zone"]').selectFile(
'cypress/fixtures/example.pdf',
{ action: 'drag-drop' }
)
cy.get('[data-cy="upload-status"]').should('contain', 'Uploaded')
If the app listens for drop events on the document rather than on the visible zone, target body; the event bubbles to document-level listeners:
cy.get('body').selectFile('cypress/fixtures/example.pdf', {
action: 'drag-drop',
})
Choose the element that receives the application’s drop handling. A test that drops on the wrong target can pass the Cypress command while failing to exercise the expected application flow.
4. Choose how to provide file contents
Use a path for an existing fixture
A path is the simplest option for a file already stored in the repository:
cy.get('input[type="file"]').selectFile('cypress/fixtures/file.json')
Paths are resolved from the project root. Confirm that the file exists there, including exact capitalization, especially when the test runs on a case-sensitive filesystem.
Use a fixture alias
For an existing fixture you have already loaded, pass a Cypress alias. For binary fixtures, specify null encoding so Cypress returns bytes in a Buffer rather than decoding them as text:
cy.fixture('image.png', null).as('imageBytes')
cy.get('input[type="file"]').selectFile('@imageBytes')
Cypress recommends using a path for large upload simulation instead of loading the whole file through cy.fixture(). That can avoid keeping a second in-memory copy of a large fixture.
Read a file as bytes
cy.readFile(path, null) returns a Cypress.Buffer, regardless of file extension:
cy.readFile('cypress/fixtures/image.png', null).then((bytes) => {
cy.get('input[type="file"]').selectFile(bytes)
})
cy.readFile() became a query in Cypress 13.0.0. Check your installed Cypress version if your test relies on behavior introduced in newer releases.
Build the file in the test
Pass an object when you need generated content or explicit file metadata. The supported fields include contents, fileName, mimeType, and lastModified. The timestamp is milliseconds since the Unix epoch; if omitted, Cypress uses the current time. Cypress infers MIME type from the extension when possible. If it cannot infer one, it defaults to an empty string unless you provide a type.
cy.get('input[type="file"]').selectFile({
contents: Cypress.Buffer.from('test file contents'),
fileName: 'sample.txt',
mimeType: 'text/plain',
lastModified: new Date('2024-01-01T00:00:00Z').valueOf(),
})
Structured objects are also the right way to provide literal file contents. A plain string in the path form is treated as a disk path, so do not pass a string expecting Cypress to interpret it as file text.
5. Select multiple files and check metadata
Pass an array of paths to select more than one file. The input must support multiple files; otherwise Cypress reports an error.
cy.get('input[type="file"][multiple]').selectFile([
'cypress/fixtures/first.json',
'cypress/fixtures/second.json',
])
You can also mix file objects and paths when each file needs specific metadata. Assert against the input’s FileList when names or counts are part of the contract:
cy.get('input[type="file"][multiple]')
.selectFile([
{
contents: Cypress.Buffer.from('{"kind":"first"}'),
fileName: 'first.json',
mimeType: 'application/json',
},
'cypress/fixtures/second.json',
])
.then(($input) => {
expect($input[0].files).to.have.length(2)
expect($input[0].files[0].name).to.equal('first.json')
})
The command yields the same subject it received, but Cypress cautions that chaining commands that depend on that subject after selectFile() is unsafe. A fresh query for follow-up assertions is often clearer.
6. Test the upload API directly when UI selection is out of scope
If the test only needs to exercise a server endpoint, Cypress documents sending multipart form data with cy.request(). This skips the browser input and drop-zone behavior, so it does not replace a UI upload test.
cy.fixture('example.pdf', null).then((fileBytes) => {
const form = new FormData()
form.append('file', new Blob([fileBytes]), 'example.pdf')
cy.request({
method: 'POST',
url: '/api/uploads',
body: form,
headers: { 'content-type': 'multipart/form-data' },
}).its('status').should('be.oneOf', [200, 201])
})
Use the actual endpoint’s field name, authentication, and expected response for your application. Keep endpoint-only tests and browser interaction tests distinct so a passing API request is not mistaken for proof that the page’s upload control works.
7. Cypress options and version notes
| Input or option | Use | Key detail |
|---|---|---|
| Path | Existing file in the project | Relative to the project root; preferred for on-disk files |
| Alias | Previously loaded fixture or bytes | Use @alias; aliased value cannot be null or undefined |
| Buffer or TypedArray | Generated or loaded bytes | Use null encoding when reading binary fixtures |
| File object | Control contents and metadata | Supports contents, fileName, mimeType, lastModified |
action: 'select' |
File input selection | Default action; subject must be one file input or an associated label |
action: 'drag-drop' |
Drop zone interaction | Subject can be any DOM element; use body for document-level listeners |
force: true |
Hidden file input | Bypasses actionability for the hidden input |
| Multiple paths | Multi-file selection | The input must have its multiple property |
selectFile() was introduced in Cypress 9.3.0. The API history lists TypedArray and mimeType support in 9.4.0, and a scrollBehavior update in 15.20.0. Check your installed version before relying on newer options. The command waits for the target to become actionable and retries reading a disk path until it appears or the command times out. Its timeout can therefore reflect an actionability wait, a missing file, or an unavailable alias.
8. Troubleshoot common upload test failures
| Symptom | Likely cause | Fix |
|---|---|---|
| “Subject must be a file input” | Default select action targets an ordinary element |
Target the actual input or its associated label; use drag-drop for a drop zone. |
| Element is not actionable or visible | The real input is hidden behind a styled control | Use { force: true } on the input when directly setting its selected file matches the test intent. |
| File does not exist or command times out | Path is wrong, relative to the wrong directory, or has different capitalization | Check the path from the Cypress project root and verify the fixture is present in the test environment. |
| Binary upload is corrupted or rejected | Bytes were decoded as text | Load with cy.fixture(name, null) or cy.readFile(path, null), or pass the on-disk path. |
| Several files fail to select | The input does not allow multiple files | Use an input with the multiple property or test one file at a time. |
| Drop command runs but app does not react | Target misses the listener or the app handles drops at document level | Drop on the correct zone; try body when the listener is on document. |
| Generated text is treated as a path | A plain string was supplied where a file path is expected | Use a file object with contents or a Buffer and set the name and MIME type as needed. |
| Alias has no usable value | The alias was not set or resolves to null/undefined | Set the alias before the command and confirm it contains a Buffer or supported file value. |
| Assertion after selection behaves inconsistently | It relies on the yielded subject after the action | Run a fresh query for the input or assert on a stable application status element. |
9. Performance, reliability, and cost
For stable and efficient tests, keep representative fixtures in the project and pass their paths directly, especially for large files. Loading bytes into a fixture alias or buffer is useful when the test needs to inspect or construct contents, but it can consume additional memory. Use deterministic file names, MIME types, and modified times when the application validates metadata. Avoid relying on a changing current timestamp if time itself is not under test.
Cypress’s automatic waiting helps with target actionability and paths that appear shortly after a test starts, but it cannot correct a wrong selector, an incorrect project-relative path, or a mismatch between the selected file and the app’s validation rules. Assert on the outcome that matters and use a fresh query after the action. A UI test exercises file selection behavior; a multipart request test exercises the server endpoint. The former may involve more application work, while the latter isolates endpoint behavior. Cypress pricing or execution cost is not specified in the cited API documentation; resource use depends on the files and test environment.
10. Or skip the browser setup
If your task is to capture a page after an upload flow or document its resulting state, ScreenshotNeo is a website screenshot API and MCP server. This does not replace testing Cypress’s file input or upload endpoint; it gives you a direct way to capture a page without managing browser screenshot setup. 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
It also accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each of those steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response reports the page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The free plan includes 1,000 screenshots a month without a card; paid plans start at $5 for 3,000 screenshots. Every feature is on every plan. Sign up for 1,000 free screenshots a month, with no card required.
11. FAQ
Can I use selectFile() for a file input without opening a file chooser?
Yes. It sets the selected file for the test directly. Use it to cover the application’s input-driven upload behavior, then assert on the page’s response.
Can I upload a file that is not stored as a fixture?
Yes. Provide a Buffer, TypedArray, or file object with generated contents. Set fileName and mimeType when the app depends on them.
Which should I use: selectFile() or cy.request()?
Use selectFile() when browser selection or drag-and-drop is part of the behavior under test. Use a multipart cy.request() when you only need to exercise the server endpoint.
What does Cypress support for drag-and-drop?
Pass { action: 'drag-drop' } and target the drop zone. Target body when the page handles bubbled drop events at the document level.


