cypress-file-upload
cypress-file-upload copied to clipboard
[Feature] adding webkitdirectory to support directory uploads
I would like to test a directory upload input, for that it should be enough to set the webkitRelativePath
property in the created File
object(s), eg:
File {
name: "PANO_20190103_141758.jpg",
lastModified: 1546521489000,
webkitRelativePath: "images/2017/01/PANO_20190103_141758.jpg",
size: 11591640,
type: "image/jpeg" }
The File constructor does not support that, but you can force set it afterwards via Object.set
. I'm using that approach already in my app if the user selects a single file instead of a directory.
Could that be implemented?
Hi @endymonium Thanks for submitting the issue! It was requested a while ago from @artemgurzhii in #69 but there was not much users so I decided not to do that.
Right now I feel it might be a good thing to implement, but not sure if I can help with coding that. Can you please look through the source code and make a PR for that? We can review it together with @artemgurzhii and finally make it happen 😄
Thanks!
@abramenal Any timeline on when this will happen? We would love to see this feature happen!
Hey @MingruiZhang Thanks for feedback!
I am not sure which value is expected for webkitRelativePath
– please suggest. Otherwise it can roughly be taken from the command arguments.
For this simple solution I would expect having it by the end of this week. Are you using v4?
cc @endymonium @artemgurzhii
I’m eagerly waiting to run our pending test case using cypress for folder upload functionality.
Hey @MingruiZhang Thanks for feedback!
I am not sure which value is expected for
webkitRelativePath
– please suggest. Otherwise it can roughly be taken from the command arguments. For this simple solution I would expect having it by the end of this week. Are you using v4?cc @endymonium @artemgurzhii
Hey @abramenal, any update on this?
I would like to test a directory upload input, for that it should be enough to set the
webkitRelativePath
property in the createdFile
object(s), eg:File { name: "PANO_20190103_141758.jpg", lastModified: 1546521489000, webkitRelativePath: "images/2017/01/PANO_20190103_141758.jpg", size: 11591640, type: "image/jpeg" }
The File constructor does not support that, but you can force set it afterwards via
Object.set
. I'm using that approach already in my app if the user selects a single file instead of a directory.Could that be implemented?
Hey @endymonium can you share pseudocode on how you achieve this?
Hello ... is there any update on this? We would also need to upload a directory structure (folder with files and subfolders). @endymonium Could you please write an example how your workaround looks like for this.
I couldn't find anywhere example how to test folder structure upload with cypress.
@abramenal Can you maybe use this implementation https://github.com/knotthere/cypress-file-upload/blob/21e16fbbc0f9d1f8b842eab390a3beebbe53ce1f/src/attachDirectory.js ?
Hey @Donaab, this looks good – just wondering why @knotthere didn't submit a PR with this. I can pick it up early next week
@abramenal Only thing with that command is it doesn’t recognize a subfolder as item to add to the dataTransfer. It just reads the files from that folder for me.
@abramenal Maybe it would be good idea to add an option to add some items in the dataTransfer .
This is how my dataTransfer looks like:
dataTransfer: {
items: [
{
webkitGetAsEntry: () => ({
isDirectory: true,
isFile: false,
fullPath: '/foo',
createReader() {
return {
sentEntries: false,
readEntries(callback) {
if (!this.sentEntries) {
this.sentEntries = true;
callback([
{
isDirectory: false,
isFile: true,
file: callback => callback(testFile),
fullPath: '/foo/alpha.txt',
},
]);
} else {
callback([]);
}
},
};
},
}),
},
{
webkitGetAsEntry: () => ({
isDirectory: true,
isFile: false,
fullPath: '/bar',
createReader() {
return {
sentEntries: false,
readEntries(callback) {
if (!this.sentEntries) {
this.sentEntries = true;
callback([]);
} else {
callback([]);
}
},
};
},
}),
},
],
types: ['Files'],
}
Would be nice if someone can join code review – I am not sure I fully understand use cases, so might have done some garbage instead of solution 😉
I have an use case, It like uploading a folder with multiple files. Then we list all the files in UI. After reviewing, it will be submitted in the application.
It’s almost like google drive folder upload behaviour.
Can you please help me, when can I use this feature? @abramenal
Please suggest workaround for scripts in cypress.
I checked with this link https://github.com/knotthere/cypress-file-upload/blob/21e16fbbc0f9d1f8b842eab390a3beebbe53ce1f/src/attachDirectory.js
Didn’t help me…
Please provide this feature soon. Waiting for six months. @abramenal
I'm trying to help with this.
It seems the simplest solution would be to fix the upload fixtures so they return the correct information on when webkitGetAsEntry
(https://wicg.github.io/entries-api/#dom-datatransferitem-webkitgetasentry) is called on the dataTransfer items.
For some reason when uploading files via cypress-file-upload webkitGetAsEntry()
always returns null. If we could fix this to give the correct FileEntry object the folder uploading should be pretty trivial
Any help on what we need to do to make webkitGetAsEntry()
work with the files selected via cypress-file-upload would be appreciate as I am stuck as to what needs to change to get that data.
I believe the reason that webkitGetAsEntry
is null is because the drag item is stuck in protected
mode. This data is only accessible on the drop event(not drag or drag over). https://stackoverflow.com/a/31922258.
As we are using a custom event to call drop it appears this will always be null. Not sure what the best approach would be here. Ideally we would be able to get the real data that would come from calling item.webkitGetAsEntry()
however as that appears to be protected and I cannot find a way to simulate an actual drop with real files we may need to stub this? Any recommendations on an approach here would be appreciated.
Same problem. We're using webkitGetAsEntry together with react-dnd to distinguish between files and directories. Any workaround?
I went for a solution without cypress-file-upload mocking the DataTranfer class and all the other classes involved. Maybe for other cases these mock will need a more detailed implementation but this worked fine for me:
export class DataTransferMock {
constructor () {
this.data = { dragX: '', dragY: '' }
this.dropEffect = 'none'
this.effectAllowed = 'all'
this.files = []
this.img = ''
this.items = new DataTransferItemListMock()
this.types = ['Files']
this.xOffset = 0
this.yOffset = 0
}
clearData () {
this.data = {}
}
getData (format) {
return this.data[format]
}
setData (format, data) {
this.data[format] = data
}
setDragImage (img, xOffset, yOffset) {
this.img = img
this.xOffset = xOffset
this.yOffset = yOffset
}
}
export class DataTransferItemListMock extends Array {
clear () {
this.splice(0, this.length - 1)
}
add (data, options) {
this.push(data)
}
remove (index) {
if (index > -1) {
this.splice(index, 1)
}
}
}
export class DataTransferItemMock {
constructor (entry) {
this.kind = 'file'
this.type = entry.data.type
this.file = entry.data
this.entry = entry
}
getAsFile () {
return this.file
}
getAsString () {
return ''
}
webkitGetAsEntry () {
return this.entry
}
}
export class FileSystemEntryMock {
constructor (file, options) {
this.filesystem = { name: file.name, root: '' }
this.isFile = options.isFile
this.isDirectory = !options.isFile
this.fullPath = options.fullPath ?? ''
this.name = file.name
this.data = file
}
file (callback) {
return callback(this.data)
}
getParent () {
}
}
export class FileSystemDirectoryEntryMock extends FileSystemEntryMock {
constructor (data, options) {
super(data, options)
this.entries = []
}
setEntries (entries) {
this.entries = entries
}
getDirectory (path, options, callback) {
callback(this.data)
}
getFile (path, options, callback) {
callback(options.fullPath)
}
createReader () {
return new FileSystemDirectoryReaderMock(this.entries)
}
}
export class FileSystemDirectoryReaderMock {
constructor (entries) {
this.entries = entries
this.read = false
}
readEntries (callback) {
if (this.read === false) {
this.read = true
callback(this.entries)
} else {
this.entries = []
callback(this.entries)
}
}
}
then created the following command:
const mapEntries = (blob, files, fullPath = '') => {
return files.map((fileData) => getEntry(blob, fileData, fullPath))
}
const getEntry = (blob, fileData, fullPath = '') => {
let transferItem
if (fileData.type) {
const systemEntry = new FileSystemEntryMock(new File([blob], fileData.name, { type: fileData.type }), { isFile: true, fullPath: fullPath ? `/${fileData.name}` : fileData.name })
transferItem = systemEntry
} else if (fileData.entries) {
const systemDirectoryEntry = new FileSystemDirectoryEntryMock(new File([], fileData.name), { isFile: false, fullPath: fullPath ? `/${fileData.name}` : fileData.name })
const entries = mapEntries(blob, fileData.entries, fullPath)
systemDirectoryEntry.setEntries(entries)
transferItem = systemDirectoryEntry
}
return transferItem
}
Cypress.Commands.add('dropFiles', (fixture, files) => {
const dataTransfer = new DataTransferMock()
files.forEach((fileData, i) => {
const blob = Cypress.Blob.base64StringToBlob(fixture, fileData.type)
const testFile = new File([blob], fileData.name, { type: fileData.type })
dataTransfer.files.push(testFile)
dataTransfer.items.add(new DataTransferItemMock(getEntry(blob, fileData)))
})
cy.get('.your_selector').trigger('dragenter', { dataTransfer: dataTransfer })
.trigger('dragover', { dataTransfer: dataTransfer })
.trigger('drop', { dataTransfer: dataTransfer })
})
can be used like this:
cy.dropFiles(fixture, [
{ name: `${name}0`, type: 'image/jpeg' },
{
name: 'directory1',
entries: [
{ name: `${name}1`, type: 'image/jpeg' },
{
name: 'directory2',
entries: [
{ name: `${name}2`, type: 'image/jpeg' },
{ name: 'directory3', entries: [] },
{
name: 'directory4',
entries: [
{ name: `${name}3`, type: 'image/jpeg' },
{ name: `${name}4`, type: 'image/jpeg' }
]
}
]
}
]
}
])
Hey @C3PablO , This is great, but it would be so awesome if we could extract your mocks into this plugin in some way. I've setup an example and gotten it working with uppy at https://github.com/abramenal/cypress-file-upload/pull/329, however I would really appreciate help in trying to make it possible to select a folder instead or in addition to files in this case.