cypress-io / cypress

Fast, easy and reliable testing for anything that runs in a browser.
https://cypress.io
MIT License
47.23k stars 3.19k forks source link

Multipart form-data for API Post Request Issue #21173

Open jenglishPS opened 2 years ago

jenglishPS commented 2 years ago

Current behavior

I've tried many different formats/variations to post an API request which includes a multipart form-data request and a csv file to the body of an API request. The from data versions I've tried do not attach any data to the body, and when trying to us a binary to blob conversion, the request does not work. I've also tried custom commands and those do not submit my request. XMLHttpRequest does not work for me either.

I've gotten 400 errors because the file wasn't attached/uploaded and in different versions, "Failed to read the request form. Unexpected end of Stream, the content may have already been read by another component. "

BlobIssue FailedRead

image image

Desired behavior

I'm expecting to receive a 200 status with a returned id from the api.

Test code to reproduce

To reproduce aws access would be needed and the call contains private data. Here is layout of the code trying to be run:

        describe("POST import", () => {
            it("should returns the new import run.", () => {

                // //Attempt 
                cy.fixture('TestCsvSpreadsheet.csv', 'binary')
                    .then((file) => {
                        const blob = Cypress.Blob.binaryStringToBlob(file,fileType);
                        const formData = new FormData();
                        formData.set('modelJson', jsonModel);                        
                        formData.set("file", blob);

                        cy.request({
                            method: 'POST',
                            url: urlEndpoint,
                            body: formData
                        }).then((response) => {
                            expect(response.status).to.equal(200);
                            importRunId = response.body.id.toString();
                        })
                    });

                // // **** Another Attempt 
                // cy.fixture(fileName, 'binary')
                //     .then(file => {
                //         const blob = Cypress.Blob.binaryStringToBlob(file, 'text/csv');
                //         cy.request({
                //             method: 'POST',
                //             url: urlEndpoint,
                //             headers: {
                //                 "content-type": "multipart/form-data; boundary=----CypressFormDataBoundary",
                //             },
                //             body: {
                //                 file: blob,
                //                 modelJson: jsonModel
                //             }
                //         }).then(response => {
                //             expect(response.status).to.equal(200);
                //             importRunId = response.body.id.toString();
                //         })
                //     });

            })
        })

Cypress Version

9.5.2

Other

No response

davidmunechika commented 2 years ago

Thanks for opening an issue! There is a related discussion here about improvements we wish to make to handle requests with multipart/form-data. This has been identified as an issue but the work hasn't been prioritized yet

jenglishPS commented 2 years ago

Has the work on multipart/form-data been prioritized or completed in the latest version?

github-actions[bot] commented 1 year ago

This issue has not had any activity in 180 days. Cypress evolves quickly and the reported behavior should be tested on the latest version of Cypress to verify the behavior is still occurring. It will be closed in 14 days if no updates are provided.

liambutler commented 1 year ago

Can confirm that this is still happening as of 12.9.0

Harshithkumar commented 1 year ago

Still the Issue persist in 12.17.1V as well. Can somebody help and fix this issue ?

justine-santini commented 1 year ago

Do we have a work around for this problem? I am using cypress v13.4.0 and I still have the issue.

sramshek commented 12 months ago

Do we have a work around for this problem? I am using cypress v13.4.0 and I still have the issue.

++

BednarOG commented 8 months ago

"Hi! If anyone still has a problem with this type of form, I invite you to discuss. I have a solution that has been working for me for quite some time now."

damian-sketch commented 7 months ago

@BednarOG could you share the solution that has been working for you ?

BednarOG commented 7 months ago

In the example of what @jenglishPS wrote.

  1. Load fixture file (remember that with cy.fixture, you can only load one file) - cy.fixture(fileName, 'binary').then(file =>{})

  2. Create FormData object

    • const payload = new FormData()
  3. Append data with file name

    • payload.append("userName", "jenglishPS")
  4. Create a request :)

My example:

addSomething() {
  cy.fixture("attachFiles/example.png", "binary").then((image) => {
    const payload = new FormData();
    payload.append("userName", "random user name");
    payload.append("email", "random email");
    payload.append("file", new Blob([image], { type: "image/png" }), "example.png");
  });
  cy.request({
    method: "POST",
    url: endpointURL,
    headers: {
      "Content-Type": "multipart/form-data",
    },
    body: payload,
  })
}

If there is something not entirely clear, let me know.

sairudayaraj commented 3 months ago

@BednarOG cypress version v13+

I have tried with PDF attachment still getting error I've gotten 400 errors in response , response body getting empty

   uploadRL(filePath: string): any {
        const accessToken = Cypress.env('accessToken');

        return cy.fixture(filePath, "binary").then((fileContent) => {
            const payload = new FormData();
            payload.append("file", new Blob([fileContent], { type: "application/pdf" }), filePath);

            return cy.request({
                method: 'POST',
                url: `${apiconfig.endpoints.postUploadRL}`,
                headers: {
                    'Authorization': `Bearer ${accessToken}`,
                     'Content-Type': 'multipart/form-data'
                },
                body: payload,
                failOnStatusCode: false
            }).as('uploadRL');
        });
    } 
Response :
    {
    "method": "POST",
    "url": "xyz",
    "headers": {
        "Authorization": "Bearer xxxxx
    },
    "body": {},
    "failOnStatusCode": false
}
BednarOG commented 3 months ago
  1. You don't need to type 'content-type' header - cypress will do it automatically.
  2. Ensure that the file is correctly read as binary and converted to a Blob before appending it to the FormData.
  3. Add encoding: 'binary': This ensures the file is transmitted in binary format, which is necessary for file uploads.

Try this:

uploadRL(filePath: string): any {
    const accessToken = Cypress.env('accessToken');

    return cy.fixture(filePath, "binary").then((fileContent) => {
        const payload = new FormData();
        const blob = new Blob([fileContent], { type: "application/pdf" });
        payload.append("file", blob, filePath);

        return cy.request({
            method: 'POST',
            url: `${apiconfig.endpoints.postUploadRL}`,
            headers: {
                'Authorization': `Bearer ${accessToken}`,
            },
            body: payload,
            encoding: 'binary'
        }).as('uploadRL');
    });
} 
sairudayaraj commented 3 months ago

@BednarOG Still getting 400 error with same response like empty in body

i have given the file in this way and converted to a Blob before appending it to the FormData

When(`uploadltr`, () => {
        const filePath = 'xyz.pdf';
        cy.wait(2000);
        API.uploadRL(filePath).then((resp: any) => {
            response = resp;
            cy.log(JSON.stringify(response.body));  // Log response for debugging
            expect(response.status).to.eq(200);
        });
    });
BednarOG commented 3 months ago

First of all, if you only need to upload a .pdf file, you don't need to format it as a Blob. Make sure you have this file in the 'cypress/fixtures/attachFiles' folder, and in the body, provide the path to this file.

cy.request({
  method: "POST",
  url: url.path,
  body: {
    filePath: "attachFiles/yourFile.pdf",
  },
  headers: your headers,
})
sairudayaraj commented 3 months ago

@jennifer-shehane @BednarOG As per the above input tried i have placed my Pdf file in fixture folder still getting 400 , kindly check the below

    When(` upload file `, () => {
    const filePath = 'xyz.pdf';
    cy.wait(2000);
    API.uploadRL(filePath).then((resp: any) => {
        response = resp;
        cy.log(JSON.stringify(response.body));  // Log response for debugging
        expect(response.status).to.eq(200);
    });
});

            uploadRL(filePath: string): any {
                const accessToken = Cypress.env('accessToken');

                cy.request({
                    method: 'POST',
                    url: `${apiconfig.endpoints.postUploadRL}`,
                    body: {
                        filePath: `${filePath}`
                    },
                    headers: {
                        'Authorization': `Bearer ${accessToken}`,
                        'Content-Type': 'application/pdf',
                    },
                    failOnStatusCode: false
                }).as('uploadRL');

                return cy.get('@uploadRL');
            }

Here is my request & response :

            {
"method": "POST",
"url": "path",
"body": {
    "filePath": "xyz.pdf"
},
"headers": {
    "Authorization": "Bearer XXX",
    "Content-Type": "application/pdf"
},
"failOnStatusCode": false

}

Response: 400 150ms null

BednarOG commented 3 months ago

Try something like this:

uploadRL(filePath: string): any {
    const accessToken = Cypress.env('accessToken');

    cy.fixture(filePath, 'binary').then((fileContent) => {
        // Convert binary content to a Blob object
        const blob = Cypress.Blob.binaryStringToBlob(fileContent, 'application/pdf');

        // Create a FormData object to include the file
        const formData = new FormData();
        formData.append('file', blob, filePath);

        cy.request({
            method: 'POST',
            url: `${apiconfig.endpoints.postUploadRL}`,
            body: formData,
            headers: {
                'Authorization': `Bearer ${accessToken}`,
            },
            // Important: Do not set Content-Type here, let the browser set it for the form-data
            failOnStatusCode: false
        }).as('uploadRL');
    });

    return cy.get('@uploadRL');
}
sairudayaraj commented 3 months ago

@BednarOG I have tried this earlier , not working getting response 400 and on request still the body is empty {}

BednarOG commented 3 months ago

send me your test file, data file (body) and the API methods that you using. Try to do this manually and send all of data from payload tab.

sairudayaraj commented 3 months ago

@BednarOG I have tried same in Postman tool upload the PDF it is works (simply I'm attaching the PDF file in form data) image

API i tried this

Adding the xyz.pdf in fixture folder and call the UploadRL

    When(` upload file `, () => {
    const filePath = 'xyz.pdf';
    cy.wait(2000);
    API.uploadRL(filePath).then((resp: any) => {
        response = resp;
        cy.log(JSON.stringify(response.body));  // Log response for debugging
        expect(response.status).to.eq(200);
    });
});

uploadRL(filePath: string): any {
    const accessToken = Cypress.env('accessToken');

    cy.fixture(filePath, 'binary').then((fileContent) => {
        // Convert binary content to a Blob object
        const blob = Cypress.Blob.binaryStringToBlob(fileContent, 'application/pdf');

        // Create a FormData object to include the file
        const formData = new FormData();
        formData.append('file', blob, filePath);

        cy.request({
            method: 'POST',
            url: `${apiconfig.endpoints.postUploadRL}`,
            body: formData,
            headers: {
                'Authorization': `Bearer ${accessToken}`,
            },
            // Important: Do not set Content-Type here, let the browser set it for the form-data
            failOnStatusCode: false
        }).as('uploadRL');
    });

    return cy.get('@uploadRL');
}

printing in console image