Cypress can’t directly interact with file input elements like a human user would, but it provides a clever workaround by letting you programmatically set the file path.
Let’s see this in action. Imagine you have a form with a file upload field:
<input type="file" id="myFile" name="file">
<button type="submit">Upload</button>
Here’s how you’d test it with Cypress:
describe('File Upload Test', () => {
it('uploads a file successfully', () => {
cy.visit('/your-upload-page'); // Navigate to the page with the form
// Define the path to your test file (relative to your Cypress project root)
const filePath = 'cypress/fixtures/test_image.png';
// Select the file input element and use the 'change' command
cy.get('input[type="file"]').then(subject => {
cy.wrap(subject).invoke('show'); // Make sure the input is visible
cy.wrap(subject).trigger('drop', {
dataTransfer: {
files: [new File([Cypress.Blob.base64StringToBlob(Cypress.Blob.getFixtureBlob(filePath))], 'test_image.png', { type: 'image/png' })],
type: 'application/json', // This is often required by the browser
},
force: true, // Ensure the event is triggered even if element is not interactable
});
});
// If your form has a submit button, click it
cy.get('button[type="submit"]').click();
// Add assertions to verify the upload was successful
cy.contains('File uploaded successfully!'); // Example assertion
});
});
This test works by simulating the browser’s drop event on the file input. We create a File object, populate its dataTransfer property with the file details, and then trigger the event. The Cypress.Blob.base64StringToBlob and Cypress.Blob.getFixtureBlob are utility functions that help us read the fixture file into a format the browser understands.
The problem this solves is the inability of Cypress to directly interact with the operating system’s file picker dialog that normally opens when a user clicks a file input. Instead of trying to automate that native dialog (which is notoriously difficult and unreliable across different OSs), Cypress allows you to bypass it entirely by directly providing the file data. This makes your tests more robust and faster.
Internally, when you use trigger('drop', {...}), Cypress is essentially mimicking the browser’s native event handling. The browser’s JavaScript event listeners attached to the file input element will then receive this simulated event. If your application’s file upload logic is triggered by the change event (which drop often implicitly causes), your upload handler will fire as if a user had dragged and dropped the file. The files property within dataTransfer is the standard way for the browser to receive file information during drag-and-drop operations.
The force: true option is crucial here. It overrides Cypress’s default behavior of only interacting with visible and enabled elements. This is important because the file input element might be visually hidden or styled in a way that makes it seem non-interactive to Cypress, but it still needs to receive the drop event to trigger the file upload process.
When you’re setting up your file fixtures, ensure they are placed within your cypress/fixtures directory. For example, cypress/fixtures/test_image.png would be a valid path. The filename you provide when creating the File object (e.g., 'test_image.png') is what your server-side code will typically see as the uploaded filename. The type option in the File constructor (e.g., 'image/png') is also important as it tells the browser and potentially your server the MIME type of the file.
It’s important to understand that this method doesn’t actually open the file picker dialog. It directly injects the file data as if it had been dropped. This means your application’s file upload handler must be designed to accept files either via the traditional change event (which trigger('drop') can also simulate) or directly via drag-and-drop functionality.
A common pitfall is forgetting to explicitly set the type property on the dataTransfer object within the trigger command. While the files array contains the actual file data, the browser often relies on dataTransfer.type to understand the nature of the data being transferred, especially for drag-and-drop operations. Without it, the drop event might not be processed correctly by the browser’s event listeners.
After a successful file upload, you’ll likely want to assert that the file was processed correctly. This might involve checking for a success message on the page, verifying that an item appears in a list of uploaded files, or even making an API call to confirm the file’s presence on your server.
The next hurdle you’ll encounter is handling multiple file uploads or dealing with file input elements that have specific event listeners beyond the basic change or drop events.