Resource library

QA How-To

How to Upload a file in Cypress (2026)

Learn cypress how to upload a file with selectFile, fixtures, drag-and-drop, hidden inputs, multi-file uploads, intercepts, and reliable CI patterns.

23 min read | 2,629 words

TL;DR

Target the `<input type="file">` and call `.selectFile('cypress/fixtures/...')` or an in-memory file object. Use `force: true` for hidden inputs and `action: 'drag-drop'` for dropzones, then assert UI state and the upload network response.

Key Takeaways

  • Use `.selectFile()` on the file input; Cypress does not drive the OS file picker.
  • Pass fixture paths or `{ contents, fileName, mimeType }` objects for generated files.
  • Force-select intentionally hidden inputs and use `{ action: 'drag-drop' }` for dropzones.
  • Intercept upload requests to assert multipart contracts and status codes.
  • Cover type, size, auth, and unsafe filename negatives beyond the happy path.
  • Keep fixtures tiny and isolate uploaded test data in shared environments.
  • Separate UI wiring tests from broad API file-type matrices.

The direct answer to cypress how to upload a file is: select the target <input type="file"> and call .selectFile() with a fixture path, a buffer, or an object that includes file name and contents. Modern Cypress supports this without the old community upload plugins for ordinary file inputs, drag-and-drop targets, and hidden inputs when you pass the right options.

Upload bugs show up as silent no-ops, rejected MIME types, multi-file order mistakes, oversized payloads, and UI states that never leave "Uploading...". A strong test proves the browser selected the intended bytes, the application accepted them, and the server-side result matches the business rule. This guide covers selectFile modes, fixtures, drag-and-drop, multiple files, intercept-backed assertions, negative cases, and CI reliability.

TL;DR

Need Approach Assert
Standard file input cy.get('input[type=file]').selectFile('cypress/fixtures/sample.pdf') UI success + server contract
Hidden input .selectFile(path, { force: true }) Same as above
Drag and drop .selectFile(path, { action: 'drag-drop' }) Drop zone state + result
In-memory file .selectFile({ contents, fileName, mimeType }) Parsed contents server-side
Multi-file .selectFile([fileA, fileB]) Count and order if required
API-only upload cy.request with FormData/binary Status and response body

Prefer fixture files under cypress/fixtures for readability, and generate contents in memory when you need unique bytes per run.

1. Cypress How to Upload a File: Understand the Browser Surface

An upload path usually includes:

  1. A visible control (button, dropzone, or labeled input).
  2. An <input type="file">, sometimes visually hidden.
  3. Client validation for type, size, and count.
  4. A network request (multipart/form-data or direct-to-storage).
  5. UI progress and success or error states.

Cypress does not open the OS file chooser dialog. Instead, selectFile sets the input's files the way a user selection would, then triggers the events your framework expects for many common implementations. That is the supported path for end-to-end coverage.

Do not invent a fake cy.upload() helper that only stubs the network and never touches the input. Network stubs are valuable, but they answer a different question than "can the user attach this file through the UI?"

Also distinguish:

  • UI selection risk: wrong input, hidden input, drag-drop wiring, client validators.
  • Transport risk: headers, multipart boundaries, auth, size limits.
  • Processing risk: virus scan stubs, async jobs, thumbnail generation.

Your suite should assign each risk to the cheapest layer that can still fail meaningfully.

2. Upload From a Fixture Path With selectFile

Create a small fixture that is safe to commit:

cypress/fixtures/uploads/sample.pdf
cypress/fixtures/uploads/avatar.png
cypress/fixtures/uploads/employees.csv

Then select it:

it('uploads a PDF resume through the file input', () => {
  cy.visit('/candidates/new')

  cy.get('[data-cy=resume-input]').selectFile(
    'cypress/fixtures/uploads/sample.pdf',
  )

  cy.get('[data-cy=resume-name]').should('contain', 'sample.pdf')
  cy.get('[data-cy=submit-candidate]').click()
  cy.contains('[data-cy=toast]', 'Candidate saved').should('be.visible')
})

Paths are relative to the Cypress project root. Keep fixture names stable and descriptive. Prefer tiny files. A multi-megabyte demo video slows every run and rarely improves signal for form wiring tests.

If the product shows a client-side preview, assert the preview appears after selection and before submit. That catches broken URL.createObjectURL wiring even when the network call would still work from a programmatic API test.

3. Hidden Inputs, Force, and Custom File Buttons

Design systems often hide the native input and style a button. Cypress can still set files on the hidden input:

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

force: true is appropriate when the input is intentionally non-visible. Prefer selecting the input itself rather than the decorative button, because the input is the real file target. If you must click the button for coverage, still assert the input received files afterward when the DOM exposes that state.

Avoid brittle chains that depend on random sibling indexes:

// Fragile
cy.contains('button', 'Upload').parent().find('input').selectFile(...)

Give the input a stable data-cy or aria-label and target that.

4. Drag and Drop Uploads

Many products use a dropzone. selectFile supports a drag-drop action on the drop target:

it('accepts a CSV dropped on the import zone', () => {
  cy.visit('/imports')

  cy.get('[data-cy=import-dropzone]').selectFile(
    'cypress/fixtures/uploads/employees.csv',
    { action: 'drag-drop' },
  )

  cy.get('[data-cy=import-file-name]').should('contain', 'employees.csv')
  cy.get('[data-cy=start-import]').click()
  cy.contains('[data-cy=import-status]', 'Completed').should('be.visible')
})

Assert hover or active classes only if they are part of an accessibility or UX contract and are stable. Prefer business outcomes: filename shown, validation errors, import finished.

If the dropzone listens on a child node, target the element that actually handles drop events. When in doubt, inspect the application code for the drop listener rather than guessing in the test.

5. Build Files in Memory With Contents, Name, and MIME Type

Fixtures are not always enough. You may need unique content, a specific MIME type, or a file that should not live in Git:

it('uploads a generated text notes file', () => {
  const contents = `note-${Date.now()}\nline-2`
  cy.visit('/notes/upload')

  cy.get('[data-cy=notes-input]').selectFile({
    contents: Cypress.Buffer.from(contents),
    fileName: 'notes.txt',
    mimeType: 'text/plain',
    lastModified: Date.now(),
  })

  cy.get('[data-cy=submit-notes]').click()
  cy.contains('[data-cy=note-row]', 'line-2').should('be.visible')
})

Cypress.Buffer is the supported buffer type in Cypress tests. You can also pass a string for simple text files in many versions, but an explicit buffer and MIME type make intent obvious.

For JSON or CSV generation, build deterministic content from test data factories so assertions stay exact. For binary formats, prefer tiny real fixtures over hand-built invalid bytes unless invalid bytes are the subject of the test.

6. Multiple Files, Empty Selection, and Replacement

Multi-file inputs need explicit coverage:

it('uploads two images in one selection', () => {
  cy.visit('/gallery')

  cy.get('[data-cy=gallery-input]').selectFile([
    'cypress/fixtures/uploads/avatar.png',
    {
      contents: Cypress.Buffer.from('second-image-bytes'),
      fileName: 'second.png',
      mimeType: 'image/png',
    },
  ])

  cy.get('[data-cy=gallery-thumb]').should('have.length', 2)
})

If order matters to the product, assert order. If the product deduplicates by name, cover that rule with two same-named files when safe.

Also test replacement flows: selecting a second file after the first should update the UI according to product rules (replace vs append). Emptying a required file input, when the UI supports remove, should re-enable validation errors on submit.

7. Assert the Network Contract With cy.intercept

UI text can lie. Intercept the upload request to confirm method, URL, and payload characteristics:

it('sends multipart resume upload to the candidate API', () => {
  cy.intercept('POST', '/api/candidates/*/resume').as('resumeUpload')

  cy.visit('/candidates/new')
  cy.get('[data-cy=resume-input]').selectFile(
    'cypress/fixtures/uploads/sample.pdf',
    { force: true },
  )
  cy.get('[data-cy=submit-candidate]').click()

  cy.wait('@resumeUpload').then(({ request, response }) => {
    expect(request.headers['content-type']).to.match(/multipart\/form-data/i)
    expect(response?.statusCode).to.eq(201)
  })

  cy.contains('[data-cy=toast]', 'Candidate saved').should('be.visible')
})

For direct-to-object-storage flows, you may see a sign URL request followed by a PUT to storage. Intercept both when both are in scope, or split UI wiring tests from storage contract tests. Related network waiting patterns are covered in how to wait for an API response in Cypress.

Remember that cy.request does not go through cy.intercept routes. If you bypass the UI, do not expect intercept aliases to fire.

8. Negative Tests: Type, Size, Auth, and Malicious Names

Upload features are security-sensitive. Cover the client and server behaviors your product documents.

it('rejects an executable disguised by extension if client validation exists', () => {
  cy.visit('/avatars')
  cy.get('[data-cy=avatar-input]').selectFile({
    contents: Cypress.Buffer.from('MZ-fake'),
    fileName: 'not-an-image.png',
    mimeType: 'image/png',
  })
  cy.contains('[data-cy=field-error]', 'image').should('be.visible')
})

it('shows a clear error when the file exceeds the documented limit', () => {
  const big = Cypress.Buffer.alloc(3 * 1024 * 1024, 1) // illustrative 3 MiB
  cy.visit('/avatars')
  cy.get('[data-cy=avatar-input]').selectFile({
    contents: big,
    fileName: 'too-big.png',
    mimeType: 'image/png',
  })
  cy.contains('[data-cy=field-error]', '2 MB').should('be.visible')
})

Choose sizes from the product limit, not from this illustration. Server-side rejection needs API or full-stack tests with failOnStatusCode: false when you assert status codes. Client-only checks are not enough for security.

Also cover:

  • Unauthenticated upload endpoints.
  • Cross-tenant object IDs.
  • Path-like filenames (../../etc/passwd.png) handled safely.
  • Double extensions where policy requires blocking.
  • SVG uploads if scripted SVG is a threat model concern.

For a broader checklist, see API security testing basics.

9. Progress States, Retries, and Async Processing

Real uploads show intermediate UI. Assert the states that users rely on:

it('shows progress then success for a large-but-allowed PDF', () => {
  cy.intercept('POST', '/api/files', (req) => {
    req.on('response', (res) => {
      // optional: res.setDelay(500) when your Cypress version supports response delay helpers
    })
  }).as('fileUpload')

  cy.visit('/files')
  cy.get('[data-cy=file-input]').selectFile(
    'cypress/fixtures/uploads/sample.pdf',
    { force: true },
  )
  cy.get('[data-cy=upload-submit]').click()
  cy.get('[data-cy=upload-progress]').should('be.visible')
  cy.wait('@fileUpload')
  cy.contains('[data-cy=upload-status]', 'Ready').should('be.visible')
})

If processing continues after HTTP 201, wait on the status endpoint or UI polling result rather than a fixed sleep. If the product supports cancel, test cancel during progress and confirm no success toast appears.

Flake often comes from asserting success while a thumbnail job is still pending. Split "upload accepted" from "preview ready" when those are different backend stages.

10. API-Level Uploads vs UI Uploads

When the risk is the server contract, you can post multipart data without the browser input. Browser FormData can be used with cy.request in some setups, but many teams prefer a backend integration test outside Cypress for pure API matrices. If you stay in Cypress, keep one UI happy path and move combinations (100 file types, size boundaries, virus-scan responses) lower.

it('accepts resume bytes on the API with auth cookies', () => {
  cy.fixture('uploads/sample.pdf', 'binary').then((raw) => {
    const blob = Cypress.Blob.binaryStringToBlob(raw, 'application/pdf')
    const form = new FormData()
    form.append('file', blob, 'sample.pdf')

    cy.request({
      method: 'POST',
      url: '/api/candidates/can_123/resume',
      body: form,
      // Let the browser set multipart boundaries when supported by your stack
    }).then((response) => {
      expect(response.status).to.eq(201)
      expect(response.body).to.have.property('fileId')
    })
  })
})

Validate that this request style works in your Cypress and browser versions before standardizing it. If encoding becomes awkward, use a Node cy.task or a non-Cypress API suite for multipart matrices. The complementary download path is documented in how to download a file in Cypress.

11. Cypress How to Upload a File Reliably in CI

CI issues with uploads usually come from missing fixtures, wrong paths, oversized artifacts, or parallel pollution.

Checklist:

  • Commit tiny fixtures or generate bytes in the test.
  • Use project-relative fixture paths.
  • Do not depend on developer home-directory files.
  • Keep secrets out of fixtures.
  • Clean or isolate data created by uploads when tests share environments.
  • Avoid uploading huge binaries as CI artifacts on every run.

If multiple specs upload to the same user account, use unique filenames and unique parent records. Otherwise one test's cleanup deletes another test's evidence.

12. Full Example: Resume Upload Journey

describe('Candidate resume upload', () => {
  beforeEach(() => {
    cy.loginAs('recruiter@example.test')
  })

  it('attaches, submits, and shows the stored resume', () => {
    cy.intercept('POST', '/api/candidates').as('createCandidate')
    cy.intercept('POST', '/api/candidates/*/resume').as('uploadResume')

    cy.visit('/candidates/new')
    cy.get('[data-cy=full-name]').type('Asha Verma')
    cy.get('[data-cy=email]').type('asha.verma@example.test')

    cy.get('[data-cy=resume-input]').selectFile(
      'cypress/fixtures/uploads/sample.pdf',
      { force: true },
    )
    cy.get('[data-cy=resume-name]').should('contain', 'sample.pdf')
    cy.get('[data-cy=submit-candidate]').click()

    cy.wait('@createCandidate').its('response.statusCode').should('eq', 201)
    cy.wait('@uploadResume').its('response.statusCode').should('eq', 201)
    cy.contains('h1', 'Asha Verma').should('be.visible')
    cy.get('[data-cy=resume-link]').should('contain', 'sample.pdf')
  })
})

This journey proves selection, submit wiring, and visible persistence. Pair it with API tests for authorization and file-type matrices.

Interview Questions and Answers

Q: How do you upload a file in Cypress?

I target the file input and use .selectFile() with a fixture path or an in-memory file object. I assert UI feedback and, when useful, intercept the upload request.

Q: Do you still need cypress-file-upload?

For ordinary cases in current Cypress, no. selectFile covers path, drag-drop, and force on hidden inputs. I only evaluate extra plugins for unusual edge cases not supported by the built-in command.

Q: How do you handle a hidden file input?

I select the input with a stable test id and call selectFile with { force: true } when it is intentionally non-visible.

Q: How do you test drag and drop uploads?

I call selectFile on the dropzone with { action: 'drag-drop' }, then assert filename UI and the resulting network or business state.

Q: How do you validate the server received the file?

I intercept the upload request, assert status and content type, and confirm the UI shows the stored file metadata. Deep content scanning belongs in service tests when needed.

Q: What negative cases matter most?

Oversized files, disallowed types, unauthenticated calls, cross-tenant targets, and unsafe filenames. Client validation tests are necessary but not sufficient.

Q: How do you avoid flaky upload tests?

Use tiny fixtures, wait on intercepts or stable UI states instead of sleeps, isolate test data, and separate "accepted" from async "processed" stages.

Common Mistakes

  • Clicking a decorative Upload button and never setting the file input.
  • Depending on the OS file dialog, which Cypress cannot drive.
  • Committing huge binary fixtures that slow clones and CI.
  • Asserting only that a toast appeared without confirming storage metadata.
  • Ignoring hidden inputs and writing brittle parent/child selector chains.
  • Using fixed sleeps instead of intercepts for upload completion.
  • Testing every MIME type only through slow UI journeys.
  • Forgetting authorization and tenant isolation on upload endpoints.
  • Assuming drag-drop is covered by a standard input test.
  • Leaving uploaded personal data in shared environments.

Conclusion

For cypress how to upload a file, use .selectFile() on the real file input or dropzone, prefer small fixtures or generated buffers, and assert both UI outcomes and the upload network contract. Force-select hidden inputs, use action: 'drag-drop' for dropzones, and push broad type/size matrices to faster API layers.

Add one high-value happy path and one rejection path to your critical flow this week. That pair catches most release regressions without a large, slow upload suite.

13. Encoding, MIME Sniffing, and Content Assertions

MIME types supplied by the browser are advisory. Security-sensitive products re-check content on the server. Your tests should reflect that model.

When the client sends image/png but bytes are not a PNG, a good server rejects the object. Reproduce that with an in-memory file whose extension and MIME disagree with the payload. Assert the documented status code and error code. Do not only assert a generic failure toast if the API returns a stable machine-readable error.

For CSV imports, assert row-level results:

it('imports two valid employee rows and reports one invalid row', () => {
  const csv = [
    'email,role',
    'ok1@example.test,agent',
    'bad-email,agent',
    'ok2@example.test,admin',
  ].join('\n')

  cy.visit('/imports/employees')
  cy.get('[data-cy=import-input]').selectFile({
    contents: Cypress.Buffer.from(csv),
    fileName: 'employees.csv',
    mimeType: 'text/csv',
  })
  cy.get('[data-cy=start-import]').click()
  cy.contains('[data-cy=import-summary]', '2 imported').should('be.visible')
  cy.contains('[data-cy=import-summary]', '1 failed').should('be.visible')
})

Content assertions beat filename assertions. A test that only checks that employees.csv appears in a list has not proved import correctness.

If the product strips EXIF data from images or normalizes PDFs, decide whether that behavior belongs in UI tests. Usually it belongs in service tests with binary parsers. Keep Cypress focused on the user-visible contract unless the UI surfaces the normalization result.

14. Accessibility and Keyboard Paths Around Uploads

Uploads are often mouse-centric in demos and inaccessible in production. Where the product supports keyboard use, cover it:

  • The file input remains reachable by label association.
  • A dropzone that is also a button can be activated from the keyboard according to its role.
  • Error text is linked through aria-describedby or visible adjacent text.
  • Progress updates are not only color changes.

Cypress is not a full accessibility engine, but you can assert labels, roles, and that error text is present in the DOM for assistive tech. Pair with dedicated accessibility checks in CI for broader coverage. Layout and control reachability topics also appear in responsive layout testing with Cypress, especially for mobile upload forms with sticky action bars.

Team Conventions for Upload Tests

Agree on conventions so upload specs stay consistent:

  1. Fixtures live under cypress/fixtures/uploads/ and stay under a documented size budget.
  2. Every upload happy path waits on a named intercept or a deterministic UI status.
  3. Security negatives live next to the feature, not in an orphaned folder nobody runs.
  4. No production customer files in fixtures.
  5. Unique parent entities per test in shared environments.
  6. Document whether the test proves selection, transport, or processing.

Add these rules to the team testing README. Reviewers should reject new sleeps around uploads and reject fixtures that are multi-megabyte without a written reason.

When a flaky upload appears, ask: Did we assert the wrong stage? Did another test delete the object? Did the intercept route miss a redirect? Structured triage is faster than widening timeouts.

Troubleshooting SelectFile Failures

When selectFile appears to do nothing, work through a short list:

  1. Confirm the selector hits an input[type=file] or a drop target that listens for file drops.
  2. If the input is hidden, add { force: true }.
  3. Verify the fixture path is relative to the Cypress project root and the file exists in CI.
  4. Check whether client validation cleared the selection immediately due to type or size rules.
  5. Confirm your app listens for change or input events that selectFile triggers.
  6. Inspect whether a framework re-renders and remounts a fresh empty input after selection.

Framework remounts are a common source of confusion. If a parent state update recreates the input, the test may set files on a node that is about to be discarded. Adjust the app to keep the input mounted, or reselect after the state transition if that matches user reality. Prefer fixing unstable input identity over adding arbitrary waits.

If the network request never fires, the UI may require an extra confirm click, a filled sibling field, or acceptance of terms. Assert enabling rules explicitly instead of only the final upload.

Comparing Upload Testing Approaches

Approach Best for Weak at
UI selectFile happy path Wiring, labels, progressive disclosure Large type matrices
UI negative validation Client messages and blocked submits Server enforcement proof
cy.intercept assertions Request shape and status while using UI Deep binary parsing
Pure API multipart tests Authz, size limits, type matrices Browser event wiring
Contract tests against OpenAPI Schema stability UX states

A healthy portfolio uses at least one UI path per critical upload surface and API depth for policy. That mix is how you answer cypress how to upload a file in a professional codebase without turning Cypress into a binary fuzzing tool.

Migration Notes From Older Plugins

Teams migrating from older community upload helpers should:

  • Replace attach-style helpers with selectFile.
  • Keep fixtures, but shrink any oversized samples.
  • Revisit drag-drop tests because action options differ by helper.
  • Remove unused plugin registrations from cypress.config.
  • Re-run critical journeys on the browsers you support.

Migration is a good time to delete tests that only proved the plugin worked, not the product. Replace them with business assertions: candidate resume visible, avatar URL returned, import summary counts correct.

After migration, document the canonical example in your internal playbook so new engineers do not reinstall obsolete plugins from outdated blog posts.

Interview Questions and Answers

How do you automate file upload in Cypress?

I locate the file input, call `selectFile` with a fixture or generated file, assert client feedback, and wait on an intercept for the upload request. I keep broad validation matrices at the API layer.

How is selectFile different from attaching files through the OS dialog?

Cypress cannot operate the native OS dialog. `selectFile` sets the input files and dispatches the events the app needs, which is the supported automation path.

How do you test client-side file validation?

I select disallowed types or oversized contents and assert the inline error before or instead of a successful network call. I still add server-side rejection tests for security.

How do you handle direct uploads to cloud storage?

I map the sequence: sign request, storage PUT or POST, and app confirmation. I test UI wiring end to end once and validate storage auth rules in focused API tests.

What makes upload tests flaky?

Large fixtures, fixed sleeps, shared filenames in parallel runs, and asserting async processing as if it were synchronous acceptance. I wait on intercepts and unique data instead.

How do you test removing or replacing an uploaded file?

I upload an initial file, use the product remove or replace control, select a second file when needed, and assert UI metadata and subsequent submit behavior match the contract.

Where do upload security tests belong?

UI tests cover obvious client guards. API and security suites own authz, malware policy hooks, content sniffing, and tenant isolation because those are transport and server concerns.

Frequently Asked Questions

What Cypress command uploads a file?

Use `.selectFile()` on a file input or dropzone. Provide a fixture path, an array of files, or an object with contents, fileName, and mimeType.

How do I upload a fixture file in Cypress?

Place the file under `cypress/fixtures` and call `cy.get('input[type=file]').selectFile('cypress/fixtures/your-file.pdf')` with a project-root relative path.

How do I upload to a hidden file input?

Select the hidden input with a stable test id and call `selectFile(path, { force: true })` so Cypress can set files even when the input is not visible.

Can Cypress test drag and drop file uploads?

Yes. Call `selectFile` on the drop target with `{ action: 'drag-drop' }`, then assert the UI and network outcomes.

How do I upload multiple files at once?

Pass an array to `selectFile`, such as `.selectFile(['cypress/fixtures/a.png', 'cypress/fixtures/b.png'])`, then assert the product's multi-file UI and request contract.

How do I assert the upload API was called?

Register `cy.intercept` before the action, perform the upload, then `cy.wait('@alias')` and assert status codes and content type headers.

Do I still need the cypress-file-upload plugin?

Usually not for common inputs and dropzones. Current Cypress includes `selectFile`. Evaluate plugins only for unsupported edge cases.

Related Guides