Cypress has had built-in file upload support since version 9.3 through the selectFile command, so no plugin is needed. This guide shows the patterns that cover nearly every upload feature.
Where to put fixtures
Cypress looks for fixtures in cypress/fixtures. Download a few small samples there: the 10 KB PDF, the 1 KB PNG and the 10 KB CSV are a good start.
cypress/
fixtures/
sample-pdf-10kb.pdf
sample-png-1kb.png
sample-csv-10kb.csv
e2e/
upload.cy.jsSelect a file
Paths passed to selectFile are relative to the project root.
describe('document upload', () => {
it('uploads a PDF', () => {
cy.visit('/documents');
cy.get('input[type=file]').selectFile('cypress/fixtures/sample-pdf-10kb.pdf');
cy.contains('button', 'Upload').click();
cy.contains('li', 'sample-pdf-10kb.pdf').should('be.visible');
});
});For several files, pass an array. The input must have the multiple attribute.
cy.get('input[type=file]').selectFile([
'cypress/fixtures/sample-pdf-10kb.pdf',
'cypress/fixtures/sample-png-1kb.png',
]);Hidden inputs
When the real input is hidden behind a styled button, Cypress refuses to act on it by default. Pass force: true:
cy.get('input[type=file]').selectFile('cypress/fixtures/sample-pdf-10kb.pdf', { force: true });You can also call selectFile on the label that wraps the input, which is closer to what a user does.
Drag and drop
To test a drop zone, call selectFile on the drop target with the drag-drop action:
cy.get('[data-cy=drop-zone]').selectFile('cypress/fixtures/sample-png-1kb.png', {
action: 'drag-drop',
});Cypress dispatches the drag events with a DataTransfer object containing the file.
Files created in the test
A file can be built in memory by passing an object with its contents. This is useful for size limits and odd cases.
it('rejects files over 5 MB', () => {
cy.visit('/documents');
cy.get('input[type=file]').selectFile({
contents: Cypress.Buffer.alloc(5 * 1024 * 1024 + 1),
fileName: 'too-large.bin',
mimeType: 'application/octet-stream',
lastModified: Date.now(),
});
cy.get('[role=alert]').should('contain', '5 MB');
});A buffer of zeros is fine for a size check. If the application validates content, use a real sample file of the right size instead.
A zero-byte file and a mislabelled file take one line each:
cy.get('input[type=file]').selectFile({ contents: Cypress.Buffer.alloc(0), fileName: 'empty.txt' });
cy.get('input[type=file]').selectFile({
contents: 'cypress/fixtures/sample-png-1kb.png',
fileName: 'not-really.pdf',
mimeType: 'application/pdf',
});The second example renames a PNG to look like a PDF, which tests whether the server checks content or trusts the name.
Assert on the request
The most reliable upload assertion is on the network request itself. Intercept it, perform the upload, then check the response.
it('sends the file to the API', () => {
cy.intercept('POST', '/api/documents').as('upload');
cy.visit('/documents');
cy.get('input[type=file]').selectFile('cypress/fixtures/sample-pdf-10kb.pdf');
cy.contains('button', 'Upload').click();
cy.wait('@upload').then(({ request, response }) => {
expect(request.headers['content-type']).to.include('multipart/form-data');
expect(response.statusCode).to.eq(201);
expect(response.body.size).to.eq(10240);
});
});The last assertion assumes your API returns the stored size. Comparing it with the sample's published byte count confirms that the whole file arrived.
Upload without the user interface
To seed data before a test, send the file with cy.request. Read it as binary and wrap it in FormData:
cy.fixture('sample-csv-10kb.csv', 'binary').then((content) => {
const blob = Cypress.Blob.binaryStringToBlob(content, 'text/csv');
const form = new FormData();
form.append('file', blob, 'sample-csv-10kb.csv');
cy.request({ method: 'POST', url: '/api/imports', body: form }).its('status').should('eq', 201);
});Verify a download
Cypress saves downloads to cypress/downloads by default. Click the link, then read the file. cy.readFile retries until the file exists.
it('downloads the original file', () => {
cy.visit('/documents');
cy.contains('a', 'Download').click();
cy.readFile('cypress/downloads/sample-pdf-10kb.pdf', null).then((buffer) => {
expect(buffer.length).to.eq(10240);
});
});Passing null as the encoding returns a raw buffer, so the length is the exact file size in bytes.
A data-driven validation spec
const cases = [
{ file: 'sample-pdf-10kb.pdf', accepted: true },
{ file: 'sample-png-1kb.png', accepted: false },
{ file: 'empty-file.txt', accepted: false },
];
cases.forEach(({ file, accepted }) => {
it(`${file} is ${accepted ? 'accepted' : 'rejected'}`, () => {
cy.visit('/documents');
cy.get('input[type=file]').selectFile(`cypress/fixtures/${file}`);
cy.contains('button', 'Upload').click();
cy.get(accepted ? '[role=status]' : '[role=alert]').should('be.visible');
});
});Extend the list with the zero-byte file and a file with a non-ASCII name. Both are quick to add and regularly find bugs.
Things that go wrong
- An error saying that selectFile needs a file input, a label or the drag-drop action. The selector matched a button or a wrapper. Target the input, its label, or use the
drag-dropaction. - The file path is not found. Paths are relative to the project root, not to the spec file.
- Old downloads cause false passes. Cypress clears the downloads folder before a run by default; keep
trashAssetsBeforeRunsenabled so that a stale file cannot satisfy an assertion.