Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cypress Error making HTTP POST with File in request body

I upgraded from Cypress 6.8.0 to 7.0.1. After the upgrade, when this function is called by one of the Cypress tests

async saveTask (task, file) {
  const requestBody = new FormData()
  requestBody.append('file', file)

  return await http.post('/api/endpoint', requestBody, {
    headers: {
      'Content-Type': 'multipart/form-data'
    }
  })
},

I get the following error

TypeError [ERR_INVALID_ARG_TYPE] [ERR_INVALID_ARG_TYPE]: 
The first argument must be of type string or an instance of Buffer or Uint8Array. Received type number (45)
    at write_ (_http_outgoing.js:696:11)
    at ClientRequest.write (_http_outgoing.js:661:15)
    at Request.write (/Users/donal/Library/Caches/Cypress/7.0.1/Cypress.app/Contents/Resources/app/packages/server/node_modules/@cypress/request/request.js:1496:27)
    at /Users/donal/Library/Caches/Cypress/7.0.1/Cypress.app/Contents/Resources/app/packages/server/node_modules/@cypress/request/request.js:546:20
    at Array.forEach (<anonymous>:null:null)
    at end (/Users/donal/Library/Caches/Cypress/7.0.1/Cypress.app/Contents/Resources/app/packages/server/node_modules/@cypress/request/request.js:545:23)
    at Immediate._onImmediate (/Users/donal/Library/Caches/Cypress/7.0.1/Cypress.app/Contents/Resources/app/packages/server/node_modules/@cypress/request/request.js:578:7)
    at processImmediate (internal/timers.js:461:21)
 {
  code: 'ERR_INVALID_ARG_TYPE'
}

The http object that I use to make the POST request is an Axios instance and the file object that I append to the request body is a File. The file object is the cause of the problem, because if I don't append it to the request body, the error doesn't occur.

The error only occurs when the function is run by a Cypress test. Cypress uses Node.js, and judging by the error message above it seems the File type is not allowed. Furthermore, the Axios request config docs indicate that when Axios runs under Node, a File is not allowed.

  // `data` is the data to be sent as the request body
  // Only applicable for request methods 'PUT', 'POST', 'DELETE , and 'PATCH'
  // When no `transformRequest` is set, must be of one of the following types:
  // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
  // - Browser only: FormData, File, Blob
  // - Node only: Stream, Buffer
  data: {
    firstName: 'Fred'
  },

So I guess I need to convert the File objects to something else, so that this function will work both within the app itself, and when run by Cypress.

On the server-side (a Spring Boot app), this file is bound to a MultipartFile

public void handlePost(
    RequestPart(value = "file") MultipartFile file) {

    // controller action body       
}

If my theory that the File type is the problem, what should I use instead and how should I do the conversion?

like image 506
Dónal Avatar asked Apr 09 '21 14:04

Dónal


People also ask

How to handle exception in Cypress?

Handling Error in Cypress Unlike other Javascript-Based Frameworks, Cypress doesn't allow you to use the try and catch block to handle the exception. Cypress provides a special mechanism for handling exceptions in your code. Handling different types of Exceptions such as: Exception from Webpage Under Test.


Video Answer


1 Answers

The error only occurs when the function is run by a Cypress test. Cypress uses Node.js, and judging by the error message above it seems the File type is not allowed. Furthermore, the Axios request config docs indicate that when Axios runs under Node, a File is not allowed.

By default Cypress starts Electron in headless mode, i.e. the tests would always have access to browser APIs. You can control on which browser you want to run the tests. More details here.

Now let's look at the solution for file upload

Assuming the file you are referring in the function saveTask is an input field of type=file and name=file.

And let's say, you need to upload a png image named email.png which is at cypress/fixtures/images/email.png.

Use the following block of code for uploading the file.

 cy.fixture('images/email.png').as('emailImage')
   cy.get('input[name=file]').then(function (el) {
     const blob = Cypress.Blob.base64StringToBlob(this.emailImage, 'image/png')
     const file = new File([blob], 'email.png', { type: 'image/png' })
     const list = new DataTransfer()
     list.items.add(file)
     el[0].files = list.files
     el[0].dispatchEvent(new Event('change', { bubbles: true }))
 })
 

Now you can trigger the action based on which your upload api (/api/endpoint) is invoked and finally verify the upload is successful.

 // Trigger the action which causes the upload api to be invoked e.g. clicking on a button
 cy.get('input[type=button]').click();
 
 // Verify the api invocation is successful (The below code checks a div with id result to have the String 'Success')
 cy.get('#result').should('contain', 'Success');

You can upload any type of file and it doesn't have to be an image. For more information look at fixture and Blob. This answer is based on this example on Blob documentation.

This repo runs a test on CI using GitHub Actions https://github.com/GSSwain/file-upload-cypress-test and the server is a Spring boot app (Run as a Docker container on CI server) code is at https://github.com/GSSwain/file-upload-ajax-sample

like image 53
GSSwain Avatar answered Oct 22 '22 08:10

GSSwain