Uploading file via POST request of formData doesn't work.
Being able to post formData using cypress POST request.
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',
},
});
[email protected]
ubuntu 16.04 LTS
Chrome 66.0.3359
You likely just want to read in a fixtures data and set it as the body. I'm not seeing a reason to construct an arbitrary formdata object in the browser when you can bypass these restrictions directly... I guess maybe it would change your server's implementation? Do I have that right?
Yes correct, the server is expecting multipart/form-data content type and using a fixture won't solve the issue.
@ahmehri Were you able to find a way to work around this? Please do let me know. Thanks
cy.fixture('FF_ViewAllActiveSites_20180503_102650').then((sites) => {
cy.request({
url: '/api/sites',
method: 'post',
form: true,
headers: {
'content-type': 'multipart/form-data',
},
files: {
file: sites
}
}).then((res) => {
console.log(res);
})
})
I have added the file to fixtures. I should be able to call my API which expects req.files.file to be set.
Is there any way to do this. I don't want to handle drag and drop event in my dropzone component.
files:
{ file:
{ name: 'FF_ViewAllActiveSites_20180503_102650.csv',
data: <Buffer ef bb bf 53 69 74 65 5f 49 44 2c 53 65 6c 65 63 74 65 64 20 50 72 6f 6a 65 63 74 2c 4d 61 69 6e 74 65 6e 61 6e 63 65 20 41 73 73 69 67 6e 65 64 20 74 ... >,
encoding: '7bit',
truncated: false,
mimetype: 'application/vnd.ms-excel',
md5: 'cb06f867b21209ae26616c3841a92ff0',
mv: [Function: mv] } },
This is how the files parameter looks when I do the request from a browser
@srinivasrk no I didn't.
Is there anyway we can get help on this ?
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
});
@drewbrend doesn't work fo me :(
Property 'XMLHttpRequest' does not exist on type 'Window'.
I googled your error, I'm guessing you're using TypeScript? Maybe this will work?
https://stackoverflow.com/questions/41474445/xmlhttprequest-does-not-exist-on-type-window
Has there been resolution to this? Ive tried your "form_request" function to no avail. taking the window() route and using fetch, no go.
this is what the command looks like currently, My server gives me an error saying the stream ended unexpectedly. The error from Cypress is:
Error: socket hang up
```js
let body = new FormData();
body.append("email", email || Cypress.env("validEmail"));
body.append("password", password || Cypress.env("validPassword"));
cy.request({
method: "POST",
url: ${Cypress.env("API_AUTH")}/login,
headers: {
"content-type":
"multipart/form-data; boundary=----WebKitFormBoundaryoh3GYY6WEwLKtV1k"
},
body
});
Followup, I worked around this by using fetch() it still seems to accomplish the goal of setting the cookie in the browser.
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 });
I tried this solution, this works on chrome browser but not in electron both in --headed and headless moe. Somehow form_request method do not wait till the response.
@drewbrend any suggestion on this?
@chit786
I've tried the above, but get a "ReferenceError: method is not defined" when .then(win =>{}) is called. Any advice?
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 });
@drewbrend doesn't work for me, either.
ReferenceError: method is not defined.
Looks like it happens somewhere in the cy.server......window().then(...) chain. Error right after THEN.
I even have
Cypress.on('uncaught:exception', (err, runnable) => {
Cy.log("Hi Mark Uncaught Exception");
debugger;
// returning false here prevents Cypress from
// failing the test
// return false;
throw error;
})
in index.js in cypress/support. It never gets hit.
Whoops... when I changed, xhr.open(method, url); to xhr.open('POST', url);, the error went away.
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
@ahmehri How did you end up working around this to POST with formData? I'm having the same issue.
based on this thread, i have ended up so far with:
```js
Cypress.Commands.add('createFile', (file, type) => {
const formData = new FormData();
formData.set('type', type);
return cy.fixture(file).then(fileData => {
formData.set('file', new File([JSON.stringify(fileData)], file), file);
return cy
.server().route('POST', '/api/files').as('formRequest').window()
.then(win => {
return new Promise((resolve, reject) => {
const xhr = new win.XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
resolve(xhr.response);
}
};
xhr.open('POST', '/api/files');
xhr.send(formData);
});
})
.wait('@formRequest').then((xhr) => {
return cy.wrap({ status: xhr.status, body: xhr.response.body });
});
});
});
````
... The unfortunate side of this, is that I have absolutely no idea what it's doing. On the bright side, though, at least it works.
Good day, the workaround described above does not work for me because I try to send a file to another subdomain, that why I got 404 error because I need to set correct Referrer( that is forbidden for xhr requests)
my code
Cypress.Commands.add('form_request', (method, url, formData, accountName, tokens, done) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url, false);
xhr.setRequestHeader('Access-Control-Allow-Origin',`https://${accountName}.condogenie-app.brocoders.xyz`);
xhr.setRequestHeader('Accept', 'application/json, text/plain, */*');
xhr.setRequestHeader('Access-Control-Request-Method', 'POST');
xhr.setRequestHeader('Access-Control-Allow-Credentials', 'true');
xhr.setRequestHeader('access-token', tokens['access-token']);
xhr.setRequestHeader('token-type', tokens['token-type']);
xhr.setRequestHeader('client', tokens['client']);
xhr.setRequestHeader('uid', tokens['uid']);
xhr.onload = function () {
done(xhr);
};
xhr.onerror = function () {
done(xhr);
};
xhr.send(formData);
});
I was able to do this by sending a form request using jQuery off of the window. I chose to use a synchronous request here just to keep the code simple.
cy.window().then(({$}) => {
const fd = new FormData();
fd.append('foo', 'bar');
$.ajax({
type: 'POST',
url: '/some/api/url',
data: fd,
dataType: 'json',
async: false, // send as synchronous request, to keep this simple
cache: false,
contentType: false,
processData: false,
});
});
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:
{
"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.
As far as I can see, all this code in here is way too complicated and uses too many "custom" XHR requests and whatsoever, I wrote this simple command:
Cypress.Commands.add('fileRequest', (filePath, requestOptions) => {
return cy
.fixture(filePath, 'binary')
.then(binary => Cypress.Blob.binaryStringToBlob(binary))
.then(blob => {
const formData = new FormData();
formData.set('file', blob);
return cy.request({ ...requestOptions, form: true, body: formData });
});
});
The typescript signature for this function is like this:
fileRequest(filePath: string, requestOptions: Partial<RequestOptions>): Chainable<Response>;
and it can be called like this:
cy.fileRequest(filePath, {
method: 'POST',
url: `/attachments/files/my-new-file`,
headers: {
authorization: `Bearer ${peterReaderAad.token}`
}
});
Since the second parameter is RequestOptions you can use the same object as is used by the official cy.request method.
@Nasicus your solution doesn't result in request with correct _multipart/form-data; boundary=---_ header
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);
});
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.
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.
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 });I tried this solution, this works on chrome browser but not in electron both in --headed and headless moe. Somehow
form_requestmethod do not wait till the response.@drewbrend any suggestion on this?
Hi,
I'm facing same issue on Electron Browser.. Any solution on this so far ?
Most helpful comment
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:
Then you'd be able to do the following in your tests: