Reputation: 154
Given this simple form
<form action="/upload_file" method="POST" enctype="multipart/form-data" id="upload_file_form">
<input type="file" name="file" required>
<input type="text" placeholder="Select file" disabled>
<input type="submit" value="upload">
</form>
Since native events are not supported by cypress.io (can't select a file), I have to use a post-request.
The request structure in cypress.io looks like
cy.request({
method: 'POST',
url: '/some/url',
form: true,
body: { ... },
headers:{ ... }
})
I would like to know how I could send a simple *.txt
Any suggestions appreciated!
Upvotes: 10
Views: 20275
Reputation: 21
I spent most of the weekend on this so will post the solution that works for me.
I couldn't get the example in the NPM package cypress-upload-file-post-form
to work as it is written. Specifically I got a function not found error on Cypress.Blob.binaryStringToBlob
.
In support/commands.js
add this function
Cypress.Commands.add('multipartFormRequest', (method, url, formData, done) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.onload = function () {
done(xhr);
};
xhr.onerror = function () {
done(xhr);
};
xhr.send(formData);
});
Add a file called Base64TestCV.rtf
to the fixtures
folder.
I wanted a .rtf
file but obviously you can make a .txt
file - whatever you do it must be encoded in base64 by using an online converter or the base64
command in Linux.
For testing purposes, make sure it contains the text MyCypressTestCV
(encoded in base64 obviously).
Create and run your test. In this example my .rtf
file contains the text MyCypressTestCV
which is reflected back by httpbin.org so it proves it was decoded from base64 correctly and uploaded.
describe('Upload a file to a multipart form using cy.request', function () {
it('Performs a multipart post to httpbin.org', function () {
const baseUrl = 'http://httpbin.org';
const postUrl = `${baseUrl}/post`;
cy.visit(baseUrl); // Cypress will be very buggy if you don't do at least one cy.visit
cy.request(baseUrl).as('multipartForm'); // pretend we are doing the GET request for the multipart form
const base64FileName = 'Base64TestCV.rtf';
const postedFileName = 'TestCV.rtf';
const method = 'POST';
const mimeType = 'application/rtf';
cy.fixture(base64FileName).as('base64File'); // file content in base64
cy.get('@multipartForm').then((response) => {
const formData = new FormData();
formData.append('firstFormField', 'Hello');
formData.append('secondFormField', '25');
const blob = Cypress.Blob.base64StringToBlob(this.base64File, mimeType);
formData.append('uploadFile', blob, postedFileName);
cy.multipartFormRequest(method, postUrl, formData, function (response) {
expect(response.status).to.eq(200);
expect(response.response).to.match(/MyCypressTestCV/); // http://httpbin.org/post reflects what you post
});
});
});
});
Upvotes: 0
Reputation: 23793
I just needed from Cypress context to upload a file somewhere (to mock something out before the browser does request it).
The following is working just fine in my case:
cy.fixture('something.svg').then((file) => {
cy.request({
url: 'http://mock/uploads/some-id',
method: 'put',
body: file,
});
});
Upvotes: 2
Reputation: 1
While waiting for the built-in support, we can stop copy-pasting the hack and create a reusable package out of it. Feel free to open issues so we can make it suitable for the most popular use cases.
Note that's more of just uploading a file and not focused on a form specifically.
https://www.npmjs.com/package/cypress-file-upload
Upvotes: -3
Reputation: 4649
I ran into this issue today and developed a similar solution to the other answers, but it allows you to use the .then()
Cypress syntax.
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
});
Upvotes: 1
Reputation: 10665
Will leave this here in case it helps somebody https://github.com/javieraviles/cypress-upload-file-post-form
Second scenario (send_form_data_with_file_in_post_request_spec.js):
I want to build up the FormData myself( new FormData(), formData.append/formData.set ) and send it directly with a POST request to the backend or submit the form with the FormData I have created. For this case, the transmitted data must be in the same format as the form's submit(), which type is set to "multipart/form-data". Having a look at the MDN web docs to see how you can build a FormData: Using FormData Objects, and knowing that at this very moment (Cypress 2.1.0) cy.request doesn't support FormData (multipart/form-data) so we will need a XMLHttpRequest, the test can be performed as follows. Include the following code in your "commands.js" file within the cypress support folder, so the command cy.form_request() can be used from any test:
// Performs an XMLHttpRequest instead of a cy.request (able to send data as
// FormData - multipart/form-data)
Cypress.Commands.add('form_request', (method, url, formData, done) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.onload = function () {
done(xhr);
};
xhr.onerror = function () {
done(xhr);
};
xhr.send(formData);
})
Then, in case you want to send the same form as before (form containing an excel file and other plain inputs) but build it by yourself and send it directly to the server, the test would be something like this:
describe('Testing the API', function () {
it('Receives valid FormData and proccesses the information correctly',
function () {
/*
The reason why this test may look a bit tricky is because the backend endpoint is expecting the
submission of a web Form (multipart/form-data), not just data within a POST. The "cy.request()"
command doesn't support sending a web Form as a body in a POST request, so the test uses a support
command that has been created to perform a genuine XMLHttpRequest where a web Form can be placed.
*/
//Declarations
const fileName = 'your_excel_file.xlsx';
const method = 'POST';
const url = 'http://localhost:3000/api/excel_form';
const fileType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
const inputContent2 = 'input_content2';
const expectedAnswer = '{"msg":"X elements from the excel where successfully imported"}';
// Get file from fixtures as binary
cy.fixture(fileName, 'binary').then( (excelBin) => {
// File in binary format gets converted to blob so it can be sent as Form data
Cypress.Blob.binaryStringToBlob(excelBin, fileType).then((blob) => {
// Build up the form
const formData = new FormData();
formData.set('file', blob, fileName); //adding a file to the form
formData.set('input2', inputContent2); //adding a plain input to the form
.
.
.
// Perform the request
cy.form_request(method, url, formData, function (response) {
expect(response.status).to.eq(200);
expect(expectedAnswer).to.eq(response.response);
});
})
})
})
})
Upvotes: 8
Reputation: 602
bahmutov's cypress-form-data-with-file-upload makes reference to the github issues (311 and 170) where this problem is being considered and provides a rather dirty workaround for those that cannot accommodate Cypress' limitation on this front.
The workaround is to create a custom XHR
object and use that to submit the request with the file attached, instead of Cypress' cy.request
. The provided workaround is limited to functioning with an <input type="file" />
element's value. In my case, I needed to use a fixture in my request, so my solution was to create a custom cypress command
Cypress.Commands.add("xhr", (method, url, formdata) => { // inspired by https://github.com/bahmutov/cypress-form-data-with-file-upload/blob/8fe6106d28eef0634c78564c746746d1cc354e99/index.js // fixes lack of FormData (multipart/form-data) support in cy.request cy.window() .then(win => { return new Promise((resolve, reject) => { const XHR = new win.XMLHttpRequest() XHR.onload = resolve XHR.open(method, url) XHR.send(formdata) }) }) })
And attach Blobs or Files as needed to an instance of FormData.
I strongly recommend accommodating Cypress, for example, by accepting base64-encoded file data as plain text, if you can, due to the complexity associated with this workaround.
Upvotes: 1