cypress: Posting formData using cypress doesn't work

Current behavior:

Uploading file via POST request of formData doesn’t work.

Desired behavior:

Being able to post formData using cypress POST request.

Steps to reproduce:

The following code doesn’t work.

  const formData = new FormData();
  formData.set('file', new File(['data'], 'upload.txt'), 'upload.txt');
  cy.request({
    method: 'POST',
    url: '/upload/end/point',
    body: formData,
    headers: {
      'content-type': 'multipart/form-data',
    },
  });

Versions

cypress@2.1.0 ubuntu 16.04 LTS Chrome 66.0.3359

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 26
  • Comments: 32 (5 by maintainers)

Most upvoted comments

cc: @srinivasrk @ahmehri

I was able to get a formData submitted with a combination of a mix of answers I found.

I hope this helps you.

Add this to support/commands.js:

Cypress.Commands.add("form_request", (url, formData) => {
    return cy
      .server()
      .route("POST", url)
      .as("formRequest")
      .window()
      .then(win => {
        var xhr = new win.XMLHttpRequest();
        xhr.open(method, url);
        xhr.send(formData);
      })
      .wait("@formRequest");
});

Then you’d be able to do the following in your tests:

cy
  .form_request(url, formData)
  .then(response => {
    // do stuff with your response
  });

If XHR request is part of your test setup and you need its response later on, here is the solution I worked out:

In a helper file you can define a function to execute the request:

export function formRequest(method, url, formData, headers) {
  const request = new XMLHttpRequest();
  request.open(method, url, false);
  if (headers) {
    headers.forEach(function(header) {
      request.setRequestHeader(header.name, header.value);
    });
  }
  request.send(formData);
  return JSON.parse(request.response);
}

Using helper function you can define a specific request to your API as:

export function customerUploadFile(accessToken, file, fileName, fileType) {
  return cy.fixture(file, 'binary').then(function(binary) {
    return Cypress.Blob.binaryStringToBlob(binary, fileType).then(function(blob) {
      const targetUrl = `${apiUrl}/${apiVersion}/customer/action/upload_file?license_id=${licenseId}`;
      const headers = [{ name: 'Authorization', value: `Bearer ${accessToken}` }];
      const formData = new FormData();
      formData.set('file', blob, fileName);
      return formRequest('POST', targetUrl, formData, headers);
    });
  });
}

And your custom command:

Cypress.Commands.add('customerUploadFile', function(customerToken, fixtureName) {
  return cy.fixture(fixtureName).then(function(fixture) {
    const { file, fileName, fileType } = fixture;
    return customerUploadFile(customerToken, file, fileName, fileType);
  });
});

Now in your fixtures you can prepare following files:

  • fixtures/image/image.jpg
  • fixture/image.json
{
  "file": "images/image.jpg",
  "fileName": "image.jpg",
  "fileType": "image/jpg"
}

And in your test you can easily handle such image upload:

    it('test', function() {
      const fixtureName = 'image';
      const constAuthToken = 'however you obtain it';
      cy.customerUploadFile(constAuthToken, fixtureName).then(function(uploadResponse) {
       cy.log(uploadResponse);
        });
      });
    });

This will get necessary information about image from the json fixture and then get the binary and upload it.

Thanks @shwarcu this is pretty much everything you need. There are a couple of little things you need to make sure you do with this.

  • cy.fixture() in combination with “Cypress.Blob.binaryStringToBlob()” does not appear to parse the file correctly, unless you specify ‘binary’ in cy.fixture, i.e. cy.fixture(‘filepath’, ‘binary’). If you exclude this the FormData object is not created correctly.
  • Make sure you do not specify the content type when using multipart/form-data. The boundary needs to be added to that header in the request, so we need to let the request add that header on it’s own.
  • logging anything about the FormData or request in this situation is inaccurate. Cypress does not capture the actual request being sent, so you would need to capture the request with a different tool to see what is actually being sent.
  • with Cypress 5 and up, the “Cypress.Blob.binaryStringToBlob” can be assigned directly to a variable as it is no longer a promise.

It would really be ideal for Cypress to add support for multipart/form-data requests using their cy.request() command, but this is a reasonable workaround for the moment. This is very picky on how everything is set up and executed, so it is likely to break with future Cypress updates.

lmfao is all I can say.

If XHR request is part of your test setup and you need its response later on, here is the solution I worked out:

In a helper file you can define a function to execute the request:

export function formRequest(method, url, formData, headers) {
  const request = new XMLHttpRequest();
  request.open(method, url, false);
  if (headers) {
    headers.forEach(function(header) {
      request.setRequestHeader(header.name, header.value);
    });
  }
  request.send(formData);
  return JSON.parse(request.response);
}

Using helper function you can define a specific request to your API as:

export function customerUploadFile(accessToken, file, fileName, fileType) {
  return cy.fixture(file, 'binary').then(function(binary) {
    return Cypress.Blob.binaryStringToBlob(binary, fileType).then(function(blob) {
      const targetUrl = `${apiUrl}/${apiVersion}/customer/action/upload_file?license_id=${licenseId}`;
      const headers = [{ name: 'Authorization', value: `Bearer ${accessToken}` }];
      const formData = new FormData();
      formData.set('file', blob, fileName);
      return formRequest('POST', targetUrl, formData, headers);
    });
  });
}

And your custom command:

Cypress.Commands.add('customerUploadFile', function(customerToken, fixtureName) {
  return cy.fixture(fixtureName).then(function(fixture) {
    const { file, fileName, fileType } = fixture;
    return customerUploadFile(customerToken, file, fileName, fileType);
  });
});

Now in your fixtures you can prepare following files:

  • fixtures/image/image.jpg
  • fixture/image.json
{
  "file": "images/image.jpg",
  "fileName": "image.jpg",
  "fileType": "image/jpg"
}

And in your test you can easily handle such image upload:

    it('test', function() {
      const fixtureName = 'image';
      const constAuthToken = 'however you obtain it';
      cy.customerUploadFile(constAuthToken, fixtureName).then(function(uploadResponse) {
       cy.log(uploadResponse);
        });
      });
    });

This will get necessary information about image from the json fixture and then get the binary and upload it.

This worked for me with cypress 4.8.0

   cy.request({
      url: "https:/....",
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      form: true,
      body:{ some body}
    }).then(response => {
      console.log("response", response);
    });

I would hope this issue can get fixed the right way. For us, the workaround does not work due to CORS since we are doing direct request to api in testing instead of of web server