-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Drag & Drop upload #669
Comments
We actually have a way to do this in version Mostly you're looking to use the new describe('Drag and Drop', function() {
beforeEach(function() {
this.dropEvent = {
dataTransfer: {
files: [{ path: '/foo/bar' }]
}
}
})
it('shows project drop area with button to select project', function() {
cy.get('.project-drop p:first')
.should('contain', 'Drag your project here')
})
describe('dragging and dropping project', function() {
it('highlights/unhighlights drop area when dragging over it/leaving it', function() {
cy.get('.project-drop')
.trigger('dragover')
.should('have.class', 'is-dragging-over')
.trigger('dragleave')
.should('not.have.class', 'is-dragging-over')
})
it('unhighlights drop area when dropping a project on it', function() {
cy.get('.project-drop')
.trigger('dragover')
.should('have.class', 'is-dragging-over')
.trigger('drop', this.dropEvent)
.should('not.have.class', 'is-dragging-over')
})
it('adds project and opens it when dropped', function() {
cy.get('.project-drop')
.trigger('drop', this.dropEvent)
})
})
}) |
Wow thanks a lot @jennifer-shehane ! I'm going to try this out tomorrow and give you feedback about my success here. |
Hi @SMenigat, did this work out for you? |
Could also use some guidance on this approach, turns out I decide to start doing TDD when I'm building an upload screen w/ drag and drop existing third party |
Hey @egucciar, what about the test code above did not work exactly? Did you get an error? Did the assertions fail? I will add that some of the test code I pasted is particular to our implementation, so you will have to be familiar with how your drag and drop area is coded. Our implementation is open source here: https://github.com/cypress-io/cypress/blob/develop/packages/desktop-gui/src/app/intro.jsx#L30 With the tests for this implementation here: https://github.com/cypress-io/cypress/blob/develop/packages/desktop-gui/cypress/integration/global_mode_spec.coffee#L75 |
I guess the issue might be that I'm using angular-file-upload? Could that be it? |
@egucciar You have to look at the way the particular drag and drop you want to test is currently working. The way I do this is, within the Sources panel of the DevTools, you set some Event Listener Breakpoints for Drag/drop, then you use the drag and drop normally in your app by dragging an image in. Then you can see exactly what these events look like and the data that it uses in your application. Set a global Event Listener Breakpoint The dataTransfer object from me dragging in a photo manually to their site Doing this with the demo page of angular-file-upload, I can see that a From this, I can see that they have code around more data than my implementation above cared about in their describe('Drag and Drop', function() {
const fileName = "foobar.jpg"
beforeEach(function() {
this.dropEvent = {
dataTransfer: {
files: [{
name: fileName,
size: 32796,
type: "image/jpeg",
lastModified: 1510154936000,
webkitRelativePath: ""
}],
types: ["Files"]
}
}
})
it('drag and drop upload', function(){
cy.visit('http://nervgh.github.io/pages/angular-file-upload/examples/simple/')
cy.get('.my-drop-zone').first()
.trigger('dragover', this.dropEvent)
.should('have.class', 'nv-file-over')
.trigger('drop', this.dropEvent)
cy.get('table').contains(fileName)
})
}) |
@jennifer-shehane thats perfect and it works. Thanks so much for explaining your methodology as it will come in handy when we implement upload tests for other non angular code bases!!! |
@jennifer-shehane Thanks for the extremely helpful debugging post above. I learned a lot! I tried this, and I was able to get the drag-n-drop itself working, but the resulting POST has completely incorrect content somehow, and the server returns 500. It may relate to my // Drag and drop a file to upload it
const dropEvent = {
dataTransfer: {
dropEffect: "none",
effectAllowed: "all",
files: [],
items: [
{
kind: "file",
type: "text/plain",
// TODO: Mock getAsFile: https://www.w3.org/TR/html51/editing.html#a-drag-data-item-kind
getAsFile: function() {
return dropEvent.dataTransfer.files.length
? dropEvent.dataTransfer.files[0]
: null;
}
}
],
types: ["Files"]
}
};
cy.get('#upload-files').trigger('dragover', dropEvent)
.should('have.class', 'dragover')
.then(() => {
dropEvent.dataTransfer.files.push({
path: "/tmp/upload_cache/9a4611785ac01f967598d59ab536ee/2824-document.txt",
name: "2824-document.txt",
lastModified: 1522144523092,
size: 36,
type: "text/plain",
webkitRelativePath: ""
})
}).trigger('drop', dropEvent); |
Or perhaps I am misunderstanding here: I was expecting to upload a file from the harddrive. Perhaps I should restrict the scope of the test to actually testing the visuals drag'n'drop, without expecting end-to-end functionality? |
@jennifer-shehane thank you so much, this helped me a ton! Was a little confused at first, but ended up working through it. For our specific drag and drop, it didn't care about Been working on this all day, so pretty hyped right now. Example Code: describe('a user can change the order of specific action types', () => {
const { firstChip, secondChip, thirdChip } = rulesPageConstants
it.only('can reorder boost actions', () => {
setRuleActions(0)
cy
.get('#boosts-input-icon').click()
.get('#reorder-actions-dnd-chip-0').as('firstChip')
.should('have.text', firstChip)
.get('#reorder-actions-dnd-chip-1').as('secondChip')
.should('have.text', secondChip)
.get('#reorder-actions-dnd-chip-2').as('thirdChip')
.should('have.text', thirdChip)
.get('@firstChip')
.trigger("dragstart")
.get('@secondChip')
.trigger('dragenter')
.get('@firstChip')
.trigger('drop')
})
}) |
@jennifer-shehane dataTransfer object is little different: Do you have any idea why it is not "FileList" but "Array" thanks for advise |
Yes, the dataTransfer can definitely be different based on your implementation. Have you tried to walk through some of the debugging steps outlined here? #669 (comment) |
Does anyone have any tips on how one could simulate a drag and drop upload and be able to specify the file contents as well? Let's say for example, I could predefine define the file contents in a variable. The above example, at least from what I can see, doesn't say much about defining the actual file data. |
I've actually found a solution to my previous question, referring to this comment: To drag and drop upload file Cypress.Commands.add('upload_file', (selector, fileUrl, type = '') => {
return cy.fixture(fileUrl, 'base64')
.then(Cypress.Blob.base64StringToBlob)
.then(blob => {
const nameSegments = fileUrl.split('/')
const name = nameSegments[nameSegments.length - 1]
const testFile = new File([blob], name, { type })
const event = { dataTransfer: { files: [testFile] } }
return cy.get(selector).trigger('drop', event)
})
});
describe("drag and drop upload", () => {
it("uploads file from filesystem", () => {
cy.upload_file('.drop', 'myfile.csv');
});
}); |
unfurtunately it does not work for me. |
I ended up with something similar to @andygock. I made a gist here: https://gist.github.com/ZwaarContrast/00101934954980bcaa4ae70ac9930c60 This could be refactored to also make use of |
Hi @andygock , Any idea why? |
@DanielStoica85 same here. |
@DanielStoica85 and @andygock did you check my gist? https://gist.github.com/ZwaarContrast/00101934954980bcaa4ae70ac9930c60 Something important to make it work for me was using |
@ZwaarContrast your solution not works for me. Maybe this caused from Ember.js. |
Hi, I haven't had time to look at this, but regarding the framework with my solution I had working, I was working with React and interacting with a react-dropzone component. Andy |
Personally, I'd sleep much better if the test for this feature would depend directly on any particular implementation. Overall all the solutions still react to "drop" event and the only thing one has to do is to make sure that the provided event is as close to the real thing as possible (by avoiding any mocked objects/functions). Composed an example for a friend who had an issue with getting drag-and-drop working for his project. Used the same function names and argument formats proposed by @SMenigat. support/commands.jsconst resolveMediaType = (headerContents) => {
const header = (new Uint8Array(headerContents))
.subarray(0, 4)
.reduce((acc, item) => acc + item.toString(16), '');
switch (header) {
case '89504e47':
return 'image/png';
case '47494638':
return 'image/gif';
case 'ffd8ffe0':
case 'ffd8ffe1':
case 'ffd8ffe2':
case 'ffd8ffe3':
case 'ffd8ffe8':
return 'image/jpeg';
case '25504446':
return 'application/pdf';
case '504b0304':
return 'application/zip';
}
return 'application/octet-stream';
};
const configureBlob = (blob, name) => {
return new Cypress.Promise((resolve, reject) => Object.assign(
new FileReader(), {
'onloadend': (progress) => resolve(
Object.assign(
blob.slice(0, blob.size, resolveMediaType(progress.target.result)),
{name}
)
),
'onerror': () => reject(null)
})
.readAsArrayBuffer(blob.slice(0,4))
);
};
const createCustomEvent = (eventName, data, files) => {
const event = new CustomEvent(eventName, {
bubbles: true,
cancelable: true
});
const dataTransfer = Object.assign(new DataTransfer(), {
dropEffect: 'move'
});
(files || []).forEach(
file => dataTransfer.items.add(file)
);
Object.entries(data || {}).forEach(
entry => dataTransfer.setData(...entry)
);
return Object.assign(event, {dataTransfer});
};
/**
* The added command can either be absolute path to a file or a reference
* to Cypress fixture in which case the value should be provided as:
*
* fixture:some-file.png
*/
const dropFile = (subject, source) => {
let process = cy.then(() => source);
if (typeof source === 'string') {
const segments = source.split(':'),
type = segments.length > 1 ? segments.shift() : '',
path = segments.join(':'),
file = path.split('/').pop();
cy.log('Drop ' + type + ': ' + path);
switch (type) {
case 'fixture':
process = cy.fixture(file, 'base64')
.then(Cypress.Blob.base64StringToBlob)
.then(blob => configureBlob(blob, file))
.then(blob => new File([blob], file, {type: blob.type}));
}
}
return process
.then(file => createCustomEvent('drop', {}, [file]))
.then(event => subject[0].dispatchEvent(event));
}
/**
* Register the created command to be used on elements
*/
Cypress.Commands.add('dropFile', {prevSubject: 'element'}, dropFile); Usage examples/**
* Usage examples against publicly available uploaders.
* Requires a fixtures/img.png to be present
*/
context('TinyPNG: drag-and-drop upload', () => {
beforeEach(() => cy.visit('http://www.tinypng.com'));
it('converts and provides download link for dropped file', () => {
cy.get('.upload .target').dropFile('fixture:img.png');
cy.get('.upload .progress.success').should('be.visible');
cy.get('.upload .files a').contains('download');
});
});
context('Ember droplet: drag-and-drop upload', () => {
beforeEach(() => cy.visit('http://ember-droplet.herokuapp.com'));
it('displays, validates and uploads the dropped file', () => {
cy.get('.droppable').dropFile('fixture:img.png');
cy.get('.droppable .file').should('be.visible');
cy.get('.counts').contains('Valid: 1');
cy.get('button').contains('Upload All').click();
cy.get('.counts').contains('Uploaded: 1');
});
});
context('AngularJS: drag-and-drop upload #1', () => {
beforeEach(() => cy.visit('https://angular-file-upload.appspot.com'));
it('displays, validates and uploads the dropped file', () => {
cy.get('.drop-box').dropFile('fixture:img.png');
cy.get('.response').scrollIntoView();
cy.get('.preview img').should('be.visible');
cy.get('.response').contains('img.png');
cy.get('.response').contains('type: image/jpeg');
});
});
context('AngularJS: drag-and-drop upload #2', () => {
beforeEach(() => cy.visit('http://nervgh.github.io/pages/angular-file-upload/examples/simple'));
it('displays, validates and uploads the dropped file', () => {
cy.get('.my-drop-zone').first().dropFile('fixture:img.png');
cy.get('.my-drop-zone').last().dropFile('fixture:img.png');
cy.get('.container').contains('Queue length: 2');
});
}); |
@allanpaiste thank you for sharing |
Hmmm... I guess the problem that the code might have had was that I did not have MIME type set; Updated the code above with Ember droplet example. I guess ideally the MIME type resolver should be part of the dropFixture command. |
yes but i already tried it with type 'image/jpeg' and jpg file |
Do you have an equivalent public site with similar uploader that I could test the whole thing against? Figured out that the missing piece might have been the fact that created File object name was not properly configured; @kutlaykural - could you try the code again. |
I tried following @jennifer-shehane guide. Btw, huge thanks for posting this. Will be really helpful in the future :) This is my code snippet. Cypress.Commands.add('uploadFile', (fileName, selector) => {
let dataTransfer = new DataTransfer();
let files = [];
const dropEvent = {
dataTransfer: {
dropEffect: "none",
effectAllowed: "all",
files,
items: {
kind: "file",
type:"image/jpeg"
},
types: ["Files"]
}
};
cy.fixture(fileName).then((file) => {
cy.get(selector).first()
.trigger('dragover', dropEvent).should('have.class', 'dragover')
.then(() => {
const name = fileName.split('/').pop();
dropEvent.dataTransfer.files.push({
name: name,
size: 4478976,
type: "image/jpeg",
webkitRelativePath: ""
});
}).trigger('drop', dropEvent);
});
}; And that's how I'm using it after. I have this error that I can't really resolve. Any ideas?
|
Work with react-dropzone v10.1.4 Cypress.Commands.add(
'dropFiles',
{ prevSubject: true },
(subject, namesFiles) => {
const createFile = name => picture =>
Cypress.Blob.base64StringToBlob(picture, name.replace('.', '/')).then(
blob => new File([blob], name),
)
const createEvent = files => {
const dropEvent = { dataTransfer: new DataTransfer() }
files.forEach(file => dropEvent.dataTransfer.items.add(file))
return dropEvent
}
let process = cy
.fixture(namesFiles[0])
.then(createFile(namesFiles[0]))
.then(file => [file])
for (let i = 1; i < namesFiles.length; i++) {
process = process.then(files =>
cy
.fixture(namesFiles[i])
.then(createFile(namesFiles[i]))
.then(file => [...files, file]),
)
}
return process.then(files =>
cy.wrap(subject).trigger('drop', createEvent(files)),
)
},
) |
Hi @jennifer-shehane , just wondering if I can use Cypress with ng-file-upload? I've been working on my upload file testing, that uses ng-file-upload and the test always passes an error. Thanks! |
@khaldrago96 In short, cypress-file-upload works perfectly with ng-file-upload, see its API docs to get started 😈 |
hi @abramenal , thanks for your response! |
@khaldrago96 |
I spent several hours today trying to get Filestack to automate. The solution is simple but not obvious: the
|
Hello! @jennifer-shehane (or anyone else who knows and sees this) - could you please explain how to see the dataTransfer object, as shown in your screenshot here? I'm not sure which part of the console to click, and then I'm not sure how to drill down into the right object to find what's shown in your screenshot. Thanks!! |
It should be part of the event object that you'd be getting when observing the events that are also listed on the screenshot. So, register an observer for those events (or just one of them) for a the DOM object of your liking, throw in a break-point and the event payload should have that dataTransfer you are looking for :) |
@andygock By the way, I made your code working with vue-dropzone and nuxt-dropzone plugins. |
I'm trying to test the drag and drop of a folder recursively. I could create an implementation of a DataTransfer object with every folder/subfolder/file being defined, but it would be very ugly very fast. Does anyone know of an easy way to do it? My folder would simply be fixtures |
We are still looking for an option to test Drag & Drop file uploads.
Is there anything in the plans about this?
Would be nice if I could drop a fixture onto the selected subject. Something like this maybe:
Thanks in advance 😊
The text was updated successfully, but these errors were encountered: