Uploading Files Directly Through the API with Cypress
Reaching a higher level of automated API testing
File uploads are a hit and miss with Cypress. There are no built-in commands that handle file uploads and you typically need to get creative with jquery events or install a node package such as cypress-file-upload to try and get your files uploaded through the frontend. In this post, I’ll be demonstrating how I’ve tackled this and what I use to test endpoints with file uploads.
Blobs
To send a file directly to the API, we need to process it a bit first, such as converting it into a Blob (Binary Large OBject); this can be accomplished through the usage of Cypress.blob. All you need to ensure is that you to pass the correct encoding type for that file before you convert it to a Blob. For example, binary string to blob or base 64 to blob, this depends on the data in the file and the file type that you want to upload. More on Cypress Blobs and examples for different file types here.
Multipart/Form-Data.
The command cy.request inherently works and is configured to work with generic HTTP request payloads and sends through Content type of application JSON. However, the endpoint that I want to upload a file to requires the body object to be sent as form-data (think of HTML forms and how they are sent using Ajax!), which isn’t discussed or documented as broadly in the Cypress world. Ultimately, with a bit of trial and error as well as scouring through the Git issues, I managed to find a solution that sends a XMLHttpRequest (a way of sending HTTP requests from the browser using JavaScript) to the endpoint with a form-data body object.
Solution
Combining what I learned about Blobs and how to send a request with form-data in Cypress, the following is what I managed to put together:
- Param-1: The file to upload, which is found and read from the fixtures folder
- Param-2: A uniqueName (this is just used as an example, your endpoint might need other keys and values appended)
- Param-3: An alias name that we can call back later in the test once this request has fired off
- The hasHeader option is set to true as I needed to inform the endpoint that I’ve set headers on this request
- Leveraging cy.server and cy.route to inform Cypress of the request that is about to be sent where the alias name is a reference to this request
- Using cy.fixture to read the file with a specific encoding type, in this case I’ve used binary
- Once read and resolved, the object that is now in binary is converted to a Blob using .binaryStringToBlob
- Once resolved, the blob is passed down to the XMLHttpRequest
- Using the formdata.set method, I’ve specified the key (user-file), value (blob) and name of the file (fileToUpload)
- Using the XMLHttpRequest .open method to open a channel of communication to the specified endpoint, here I’ve specified the type of request (“POST”) and the endpoint (“/fileToUpload”) — You don’t need to specify the full path as this is abstracted from the baseURL similar to cy.request
- Using the XMLHttpRequest .setRequestHeader method, I’ve set the auth token that I’ve got stored as a request header
- Finally, the XMLHttpRequest .send method is used to (as you’ve guessed) send the request
Usage
Using the custom command and inspecting it in the dev-console:
As we’ve given this request an alias, we can use all of Cypress` alias functionality to run assertions on the contents of that alias as well as wait for it, which might be useful if you’re uploading a large file. Here’s how this could look:
Conclusion
Hacky, but it works, and it works well! You’ve got access to the API response through the alias which you can pass down to other bits of assertions and comparisons should you need to do so, as well as set an in-line extended wait time if the file you are uploading is large. Obviously, you could test the endpoint by passing it files with invalid data, to assess how your backend handles that data. You could even make this custom command something you use on the frontend if you don’t have a reliable way of uploading files through clicking or drag & drop events.
Cy.route and server have been deprecated as of the latest Cypress versions, which doesn’t mean you cannot use this approach, but you just need to be mindful of the Cypress version you are running and whether you are planning to upgrade in the near-future. Alternatively, adjust the custom command code to use Cy.intercept !
Thanks for reading!