Uploading a JSON File using API - Cypress.io [shorts]

Uploading a JSON File using API - Cypress.io [shorts]
In this news series called #shorts I'm taking a dig at few small but peculiar problems and their solution.

Hello!

We all know that cypress.io is a really cool automation tool where one can easily automate API and UI part of an web application easily. Cypress has everything working out for its success, as it provides most of the thing out of the box; except for few things which one has to tackle in not so easy fashion.

In this #short we are gonna explore how to tackle a simple JSON File Upload using cypress custom commands.

PROBLEM:

The problem that we're solving is to upload a JSON file passed as a form-data to an API end-point with a multipart/form-data request.

SOLUTION:

We are gonna start with writing a custom cypress command called "uploadFile()" inside /cypress/support/command.js

uploadFile() takes in 2 arguments;
url = the endpoint to where we have to upload a file,
formData = form-data object we have to pass along to the endpoint
/**
 * This Command uploads a file to a given POST endpoint
 * 
 * @param {String} url - full URL of the upload endpoint
 * @param {String} formData - formaData to be passed as body to endpoint
 */
 Cypress.Commands.add("uploadFile", (url, formData) => {
  return cy
    .server()
    .route("POST", url)
    .as("uploadFileRequest")
    .window()
    .then(window => {
      var xhr = new window.XMLHttpRequest();
      xhr.open("POST", url);
      xhr.send(formData);
    })
    .wait("@uploadFileRequest");
});
cy.uploadFile() code inside /support/command.js

the above command is implemented using cy.server() (which at the time of writing this post cypress v.7.7.0 is now deprecated and will be moved to a plugin at a later version). In this command, we upload this file using the Javascript window method XMLHttpRequest()

We can reuse this command any number of times we'd like without repeating this code again and again.

Writing the code:

In the following code, we are fetching the user data from user.json present inside fixtures.

Once we have the user data we convert it to a blob using Cypress.blob base64StringToBlob() method (which converts a base64 string to a blob). We then use this created blob to construct a file by creating a new File object using a new File().

After that just create a simple form-data object and pass this newly formed File object into "content" form-data param.

The last thing is just to call cy.uploadFile() and pass the requestURL and form-data. The response to this request is stored in the "@createANewUser" alias.

  cy.fixture('/test/requestPayloads/user.json',"binary")
  .then(data => {
          const fileName = "user.json";
          const blob =   Cypress.Blob.base64StringToBlob(btoa(JSON.stringify(data)), 'application/json');
          const file =  new File([blob], fileName);
          const formData = new FormData();
          formData.set('name', "John");
          formData.set('description', "A new User named John");
          formData.set('id', generateRandomUUID());
          formData.set('citizenship', country.toUpperCase());
          formData.set('content', file);

          cy.uploadFile('/api/v2/user', formData).as('createANewUser');
      });
writing code for creating form-data and passing it to cy.uploadFile()

Note:
btoa(JSON.stringify(data)) is reuired to convert the binary user.json data string to base64 encoding first. Check this link  for more information about base64 encode/decode


EDIT [2022]:

In the above article, we did the uploading of JSON file using now deprecated cy.server() and cy.route() commands; though they will keep working as a plugin in Cypress in the future until we will keep seeing a deprecation message in cypress console.

So there is an alternate implementation of cy.uploadFile() command

/**
 * This Command uploads a file to a given POST endpoint
 *
 * @param {String} url - full URL of the upload endpoint
 * @param {String} formData - formaData to be passed as body to endpoint
 */
 Cypress.Commands.add("uploadFile", (url, formData) => {
	return cy.request({
	   method: 'POST',
	   url: url,
	   headers: {
		 'Content-Type': 'application/json'  
	   },
	   body: formData
	 }).as('uploadFileRequest')
	 
})
Different implementation of cy.uploadFile() code inside /support/command.js

Now it can happen that sometimes the response that we get from this comment is not in plain JSON i.e it may contain Buffer object.

So we can Sanitize this using the below code:

cy.get('@uploadFileRequest').then(async (res) => {
   
   //convert res.body ArrayBuffer to JSON object
   var jsonResponse = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(res.body)))
   return jsonResponse;

});
converting Javascript ArrayBuffer to JSON object

That's all folks!!