I am using Cypress to use my application and encounter a problem by sending an uploaded file to the backend. It sends an empty FormData.
I am using the code found here https://github.com/cypress-io/cypress/issues/170 to handle file upload which is:
return cy.get('input[type=file]').then(subject => {
return cy
.fixture('blueprint.xlsx', 'base64')
.then(Cypress.Blob.base64StringToBlob)
.then(blob => {
const el = <HTMLInputElement>subject[0]
if (el != null) {
const testFile = new File([blob], 'blueprint.xlsx')
const dataTransfer = new DataTransfer()
dataTransfer.items.add(testFile)
el.files = dataTransfer.files
}
return subject
})
})
When I debug the API call, the file is set, it is in the fixtures folder and everything seems fine but the call doesn't have any formdata (which should be the file) and ends in a 400 Bad request error.
Why is the formdata empty? Is this a Cypress problem? Is there a way to send my fixture file to the backend?
Your code seems to run ok on the ng-file-upload demo page.
I also tested with an 'xlsx' file, no problem found.
describe('Angular file upload Demo', () => {
/*
To run these tests, add a file 'logo.png' to /cypress/fixtures
*/
it('uploads the fixture file', () => {
cy.visit('https://angular-file-upload.appspot.com/')
cy.get('[name=userName]').type('myLogo')
cy.get('[name=file]').then(subject => {
return cy.fixture('logo.png', 'base64')
.then(Cypress.Blob.base64StringToBlob)
.then(blob => {
console.log('blob', blob)
const el = subject[0]
if (el != null) {
const testFile = new File([blob], 'logo.png')
const dataTransfer = new DataTransfer()
dataTransfer.items.add(testFile)
el.files = dataTransfer.files
}
return subject
})
})
cy.contains('button', 'Submit').click()
cy.contains('.progress', '100%')
cy.contains('body', 'Upload Successful')
})
Cypress.Commands.add('uploadFile', { prevSubject: 'element' }, (subject, fileName) => {
console.log('subject', subject)
return cy.fixture(fileName, 'base64')
.then(Cypress.Blob.base64StringToBlob)
.then(blob => {
console.log('blob', blob)
const el = subject[0]
if (el != null) {
const testFile = new File([blob], fileName)
const dataTransfer = new DataTransfer()
dataTransfer.items.add(testFile)
el.files = dataTransfer.files
}
return subject
})
}
)
it('uploads the file via custom command', () => {
cy.visit('https://angular-file-upload.appspot.com/')
cy.get('[name=userName]').type('myLogo')
cy.get('[name=file]').uploadFile('logo.png')
cy.contains('button', 'Submit').click()
cy.contains('.progress', '100%')
cy.contains('body', 'Upload Successful')
})
})
I use "cypress": "3.3.1"
The following codes work for me,
const fixturePath = 'test.png';
const mimeType = 'application/png';
const filename = 'test.png';
cy.getTestElement('testUploadFrontID')
.get('input[type=file')
.eq(0)
.then(subject => {
cy.fixture(fixturePath, 'base64').then(front => {
Cypress.Blob.base64StringToBlob(front, mimeType).then(function(blob) {
var testfile = new File([blob], filename, { type: mimeType });
var dataTransfer = new DataTransfer();
var fileInput = subject[0];
dataTransfer.items.add(testfile);
fileInput.files = dataTransfer.files;
cy.wrap(subject).trigger('change', { force: true });
});
});
});
getTestElement is a command added by myself,
Cypress.Commands.add(`getTestElement`, selector =>
cy.get(`[data-testid="${selector}"]`)
);
after many hours of trying, i figured out a workaround to make ng-file-upload works.
At least my problem was about the File that was not passed as an instance of Blob, i guess.
I've used the same snippet as Jonas one on cypress side.
The workaround is to add a check into the upload function that manages changes in select and drop directives.
function upload() {
if (!Upload.isFile(file)) {
file = new File([file], file.name, { type: file.type })
}
Upload.upload({
url: "/api/upload",
data: {
file: file
}
})
.then(/* ... */)
/* ... */
}
This is just a workaround and i don't really like it.
I don't know why this happens, it happens for me only when i test it using cypress, so i don't like to add that in my production code.
Could someone please help me understanding why this happens?
Does anyone know why the file instance passed into the upload function seems to be a File instance but then it's not?
Related
'filepond' is being used in vue. But there's a problem.
In "process",
The ID value was returned after the file was transferred to the server. (response.id) If you register this as 'serverId' of file and check 'file' as console, it is registered normally.
mounted() {
setOptions({
server: {
process: (fieldName, file, metadata, load, error, progress, abort) => {
const formData = new FormData();
formData.append('mainData', file);
createmainData(formData, {
onUploadProgress: (event) => {
progress(event.lengthComputable, event.loaded, event.total);
}
})
.then(response => {
const serverId = response.id
file.serverId = serverId
load(response, serverId)
})
.catch(error => {
console.log(error);
error('Error uploading file');
})
console.log (file)
},
},
});
}
But if you run 'updatefiles' methods and check 'files' as a console,
It says 'serverId: undefined'.
methods: {
updatefiles(files) {
this.files = files.map(files => files.setMetadata);
console.log('files',files )
},
},
If you look at the comments left by the producers,
I tried, but I keep failing, maybe I'm misunderstanding.
Can you tell me the solution?
You have to push serverId to separate array (i.e. uploadedFiles) in the process after the file was successfully transfered to the server. I can't see this in your code. With this array you will be able to manage FilePond content and order of uploaded files.
handleFilePondInit() {
this.uploadedFiles = [];
},
handleFilePondLoad(response) {
this.uploadedFiles.push(response);
return response;
},
handleFilePondUpdate(files, origin, target) {
this.uploadedFiles = files.map(files => files.serverId);
this.uploadedFiles = this.item.uploadedFiles.filter(element => { return element !== null; });
},
I'm trying to decode an .avro file loaded from a web server.
Since the string version of the uInt8Array starts with
"buffer from S3 Objavro.schema�{"type":"record","name":"Destination",..."
I assume it's avro Container File
I found 'avro.js' and 'avsc' as tools for working with the .avro format and javascript but reading the documentation it sound's like the decoding of a Container File is only possible in Node.js, not in the browser.
(The FileDecoder/Encoder methods are taking a path to a file as string, not an uInt8Array)
Do I get this wrong or is there an alternative way to decode an .avro Container File in the browser with javascript?
Luckily I found a way using avsc with broserify
avro.createBlobDecoder(blob, [{options}])
[avro.js - before browserifying]
var avro = require('avsc');
const AVRO = {
decodeBlob(blob) {
let schema, columnTitles, columnTypes, records = []
return new Promise((resolve) => {
avro.createBlobDecoder(blob, {
// noDecode: true
})
.on('metadata', (s) => {
schema = s
columnTitles = schema.fields.map(f => f.name)
columnTypes = schema.fields.map(f => f.type)
})
.on('data', (data) => {
records.push(data)
})
.on('finish', () => {
resolve(
{
columnTitles: columnTitles,
columnTypes: columnTypes,
records: records
}
)
})
})
}
}
module.exports = AVRO
[package.json]
"scripts": {
"avro": "browserify public/avro.js --s AVRO > public/build/avro.js"
}
[someOtherFile.js]
//s3.getObject => uInt8array
const blob = new Blob([uInt8array]) //arr in brackets !important
const avroDataObj = await AVRO.decodeBlob(blob)
Thanks for posting!
Below is my integration of avro/binary with axios, in case it helps anyone else trying to implement browser side decoding:
[before browserifying]
const axios = require('axios')
const avro = require('avsc')
const config = {
responseType: 'blob'
};
const url = 'https://some-url.com'
axios.get(url, config)
.then(res => {
avro.createBlobDecoder(res.data)
.on('metadata', (type) => console.log(type))
.on('data', (record) => console.log(record))
})
.catch(e => console.error(e))
I'm using google API and I want to download files from UI to my google drive.
As I found in google drive API documentation here, I want to use simple import.
For the moment I have such code for my onChange input event.
const onLoadFile = async (e: { target: { files: any } }) => {
const fileData = e.target.files[0];
//gapi request
uploadFile(body);
return null;
};
uploadFile:
const uploadFile = async (body: string) => {
const result = await gapiRequest({
path: `${ENDPOINT}/upload/drive/v3/files`,
method: 'POST',
body,
});
setUploadFileData(result);
};
gapiRequest:
const gapiRequest = async (options: gapi.client.RequestOptions): Promise<any> =>
new Promise<any>((resolve, reject) =>
gapi.client.request(options).execute((res) => {
resolve(res);
if (!res) {
reject(res);
}
})
);
I need to know which request body I need to create for such a request.
The request body should consist of a form that contains both metadata and the file, like so:
const metadata = {
"name": "yourFilename",
"mimeType": "text/plain", // whatever is appropriate in your case
"parents": ["folder id or 'root'"], // Google Drive folder id
};
const form = new FormData();
form.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
form.append('file', file); // file could be a blob or similar
You might also need to add an uploadType parameter to your path property. The multipart value works even for simple uploads.
See also here: https://stackoverflow.com/a/68595887/7821823
I am trying to upload a file from mobile to google bucket using ionic 4. Although a file can upload into the could. I am struggling to get the file properties out of file object.
Here is my method,
async selectAFile() {
const uploadFileDetails = {
name: '',
contentLength: '',
size: '',
type: '',
path: '',
};
this.fileChooser.open().then(uri => {
this.file.resolveLocalFilesystemUrl(uri).then(newUrl => {
let dirPath = newUrl.nativeURL;
const dirPathSegments = dirPath.split('/');
dirPathSegments.pop();
dirPath = dirPathSegments.join('/');
(<any>window).resolveLocalFileSystemURL(
newUrl.nativeURL,
function(fileEntry) {
uploadFileDetails.path = newUrl.nativeURL;
const file: any = getFileFromFileEntry(fileEntry);
//log 01
console.log({ file });
uploadFileDetails.size = file.size;
uploadFileDetails.name = `${newUrl.name
.split(':')
.pop()}.${file.type.split('/').pop()}`;
uploadFileDetails.type = file.type;
async function getFileFromFileEntry(fileEntry) {
try {
return await new Promise((resolve, reject) =>
fileEntry.file(resolve, reject)
);
} catch (err) {
console.log(err);
}
}
},
function(e) {
console.error(e);
}
);
});
});
// here uploadFileDetails is simller to what I declared at the top ;)
// I wan't this to be populated with file properties
// console.log(uploadFileDetails.name) --> //''
const uploadUrl = await this.getUploadUrl(uploadFileDetails);
const response: any = this.uploadFile(
uploadFileDetails,
uploadUrl
);
response
.then(function(success) {
console.log({ success });
this.presentToast('File uploaded successfully.');
this.loadFiles();
})
.catch(function(error) {
console.log({ error });
});
}
even though I can console.log the file in log 01. I am unable to get file properties like, size, name, type out of the resolveLocalFileSystemURL function. basically, I am unable to populate uploadFileDetails object. What am I doing wrong? Thank you in advance.
you actually need 4 Ionic Cordova plugins to upload a file after getting all the metadata of a file.
FileChooser
Opens the file picker on Android for the user to select a file, returns a file URI.
FilePath
This plugin allows you to resolve the native filesystem path for Android content URIs and is based on code in the aFileChooser library.
File
This plugin implements a File API allowing read/write access to files residing on the device.
File Trnafer
This plugin allows you to upload and download files.
getting the file's metadata.
file.resolveLocalFilesystemUrl with fileEntry.file give you all the metadata you need, except the file name. There is a property called name in the metadata but it always contains value content.
To get the human readable file name you need filePath. But remember you can't use returning file path to retrieve metadata. For that, you need the original url from fileChooser.
filePathUrl.substring(filePathUrl.lastIndexOf('/') + 1) is used to get only file name from filePath.
You need nativeURL of the file in order to upload it. Using file path returning from filePath is not going to work.
getFileInfo(): Promise<any> {
return this.fileChooser.open().then(fileURI => {
return this.filePath.resolveNativePath(fileURI).then(filePathUrl => {
return this.file
.resolveLocalFilesystemUrl(fileURI)
.then((fileEntry: any) => {
return new Promise((resolve, reject) => {
fileEntry.file(
meta =>
resolve({
nativeURL: fileEntry.nativeURL,
fileNameFromPath: filePathUrl.substring(filePathUrl.lastIndexOf('/') + 1),
...meta,
}),
error => reject(error)
);
});
});
});
});
}
select a file from the file system of the mobile.
async selectAFile() {
this.getFileInfo()
.then(async fileMeta => {
//get the upload
const uploadUrl = await this.getUploadUrl(fileMeta);
const response: Promise < any > = this.uploadFile(
fileMeta,
uploadUrl
);
response
.then(function(success) {
//upload success message
})
.catch(function(error) {
//upload error message
});
})
.catch(error => {
//something wrong with getting file infomation
});
}
uploading selected file.
This depends on your backend implementation. This is how to use File Transfer to upload a file.
uploadFile(fileMeta, uploadUrl) {
const options: FileUploadOptions = {
fileKey: 'file',
fileName: fileMeta.fileNameFromPath,
headers: {
'Content-Length': fileMeta.size,
'Content-Type': fileMeta.type,
},
httpMethod: 'PUT',
mimeType: fileMeta.type,
};
const fileTransfer: FileTransferObject = this.transfer.create();
return fileTransfer.upload(file.path, uploadUrl, options);
}
hope it helps. :)
In the application I'm currently working on, there are a couple of file forms that are submitted via superagent to an Express API endpoint. For example, image data is posted like so:
handleSubmit: function(evt) {
var imageData = new FormData();
if ( this.state.image ) {
imageData.append('image', this.state.image);
AwsAPI.uploadImage(imageData, 'user', user.id).then(function(uploadedImage) {
console.log('image uploaded:', uploadedImage);
}).catch(function(err) {
this.setState({ error: err });
}.bind(this));
}
}
and this.state.image is set like this from a file input:
updateImage: function(evt) {
this.setState({
image: evt.target.files[0]
}, function() {
console.log('image:', this.state.image);
});
},
AWSAPI.uploadImage looks like this:
uploadImage: function(imageData, type, id) {
var deferred = when.defer();
request.put(APIUtils.API_ROOT + 'upload/' + type + '/' + id)
.type('form')
.send(imageData)
.end(function(res) {
if ( !res.ok ) {
deferred.reject(res.text);
} else {
deferred.resolve(APIUtils.normalizeResponse(res));
}
});
return deferred.promise;
}
And lastly, the file receiving endpoint looks like this:
exports.upload = function(req, res) {
req.pipe(req.busboy);
req.busboy.on('file', function(fieldname, file) {
console.log('file:', fieldname, file);
res.status(200).send('Got a file!');
});
};
Currently, the receiving endpoint's on('file') function never gets called and so nothing happens. Previously, I've tried similar approaches with multer instead of Busboy with no more success (req.body contained the decoded image file, req.files was empty).
Am I missing something here? What is the best approach to upload files from a (ReactJS) Javascript app to an Express API endpoint?
I think superAgent is setting the wrong content-type of application/x-form-www-encoded instead of multipart/form-data you can fix this by using the attach method like so:
request.put(APIUtils.API_ROOT + 'upload/' + type + '/' + id)
.attach("image-file", this.state.image, this.state.image.name)
.end(function(res){
console.log(res);
});
for more information about the attach method, read the documentation here: http://visionmedia.github.io/superagent/#multipart-requests
since this involves a nodejs server script I decided to make a GitHub repo instead of a fiddle: https://github.com/furqanZafar/reactjs-image-upload
From experience, uploading a file using ajax works when you use FormData, however the file must be the only form field / data. If you try and combine it with other data (like username, password or pretty much anything at all) it does not work. (Possibly there are work arounds to get around that issue, but I am not aware of any)
If you need to send the username/password you should be sending those as headers if you can instead.
Another approach I took was first do the user registration with the normal data, then on success I upload the file with the FormData separately as an update.
The react file upload iamges component:
class ImageUpload extends React.Component {
constructor(props) {
super(props);
this.state = {file: '',imagePreviewUrl: ''};
}
_handleSubmit(e) {
e.preventDefault();
// this.uploadImage()
// TODO: do something with -> this.state.file
console.log('handle uploading-', this.state.file); }
_handleImageChange(e) {
e.preventDefault();
let reader = new FileReader();
let file = e.target.files[0];
reader.onloadend = () => {
this.setState({
file: file,
imagePreviewUrl: reader.result
});
}
reader.readAsDataURL(file) }
// XHR/Ajax file upload uploadImage(imageFile) {
return new Promise((resolve, reject) => {
let imageFormData = new FormData();
imageFormData.append('imageFile', imageFile);
var xhr = new XMLHttpRequest();
xhr.open('post', '/upload', true);
xhr.onload = function () {
if (this.status == 200) {
resolve(this.response);
} else {
reject(this.statusText);
}
};
xhr.send(imageFormData);
}); }
render() {
let {imagePreviewUrl} = this.state;
let $imagePreview = null;
if (imagePreviewUrl) {
$imagePreview = (<img src={imagePreviewUrl} />);
} else {
$imagePreview = (<div className="previewText">Please select an Image for Preview</div>);
}
return (
<div className="previewComponent">
<form onSubmit={(e)=>this._handleSubmit(e)}>
<input className="fileInput" type="file" onChange={(e)=>this._handleImageChange(e)} />
<button className="submitButton" type="submit" onClick={(e)=>this._handleSubmit(e)}>Upload Image</button>
</form>
<div className="imgPreview">
{$imagePreview}
</div>
</div>
) } } React.render(<ImageUpload/>, document.getElementById("mainApp"));
The Server Side Image Save and Copy:
Along with express You needed to npm install 'multiparty'. This example uses multiparty to parse the form data and extract the image file information. Then 'fs' to copy the temporarily upload image to a more permanent location.
let multiparty = require('multiparty');
let fs = require('fs');
function saveImage(req, res) {
let form = new multiparty.Form();
form.parse(req, (err, fields, files) => {
let {path: tempPath, originalFilename} = files.imageFile[0];
let newPath = "./images/" + originalFilename;
fs.readFile(tempPath, (err, data) => {
// make copy of image to new location
fs.writeFile(newPath, data, (err) => {
// delete temp image
fs.unlink(tempPath, () => {
res.send("File uploaded to: " + newPath);
});
});
});
})
}