20

I have a web page that opens a div when you click a button. This div allows you to drag a file from your desktop onto its area; the file then gets uploaded to the server. I'm working with the Ruby implementation of Selenium.

By using the JavaScript debugger in Firefox, I can see that an event called "drop" is being passed to some JavaScript code "handleFileDrop(event)". I presume that if I were to create a mock event and fire it somehow that I could trigger this code.

If found an interesting article that seemed to point me in a promising direction, but I'm still short of figuring it all out. I am able to pass JavaScript to the page using Selenium's get_eval method. Calling methods using this.browserbot is getting me the elements I need.

So:

  1. How do I build the file object that needs to be part of the mock drop event?
  2. How do I fire the drop event such that it gets picked up as if I had dropped a file in the div?
Ben Flynn
  • 18,524
  • 20
  • 97
  • 142

4 Answers4

29

I post an RSpec test that simulate files drag and drop using Selenium webdriver. It use jQuery to make and trigger a fake 'drop' event.

This code simulate drag and drop of a single file. For sake of simplicity I've stripped code that allow multiple files dropping. Tell me if you need it.

describe "when user drop files", :js => true do
  before do
    page.execute_script("seleniumUpload = window.$('<input/>').attr({id: 'seleniumUpload', type:'file'}).appendTo('body');")

    attach_file('seleniumUpload', Rails.root + 'spec/support/pdffile/pdfTest.pdf')

    # Trigger the drop event
    page.execute_script("e = $.Event('drop'); e.originalEvent = {dataTransfer : { files : seleniumUpload.get(0).files } }; $('#fileDropArea').trigger(e);")
  end

  it "should ..." do
     should have_content '...'
  end

P.S.: remember to replace #fileDropArea with ID of your drop area.

P.P.S: don't use evaluate_script in place of execute_script, otherwise selenium get stuck evaluating complex jQuery objects!

UPDATE: I've write a method you can reuse and do the stuff written above.

def drop_files files, drop_area_id
  js_script = "fileList = Array();"
  files.count.times do |i|
    # Generate a fake input selector
    page.execute_script("if ($('#seleniumUpload#{i}').length == 0) { seleniumUpload#{i} = window.$('<input/>').attr({id: 'seleniumUpload#{i}', type:'file'}).appendTo('body'); }")
    # Attach file to the fake input selector through Capybara
    attach_file("seleniumUpload#{i}", files[i])
    # Build up the fake js event
    js_script = "#{js_script} fileList.push(seleniumUpload#{i}.get(0).files[0]);"
  end

  # Trigger the fake drop event
  page.execute_script("#{js_script} e = $.Event('drop'); e.originalEvent = {dataTransfer : { files : fileList } }; $('##{drop_area_id}').trigger(e);")
end

Usage:

describe "when user drop files", :js => true do
  before do
     files = [ Rails.root + 'spec/support/pdffile/pdfTest1.pdf',
               Rails.root + 'spec/support/pdffile/pdfTest2.pdf',
               Rails.root + 'spec/support/pdffile/pdfTest3.pdf' ]
     drop_files files, 'fileDropArea'
  end

  it "should ..." do
     should have_content '...'
  end
end   
micred
  • 1,482
  • 1
  • 19
  • 19
  • This looks good -- I'm not working with RSpec on my current project so if someone else can confirm this works in the comments then I'll accept this answer. – Ben Flynn Jun 26 '12 at 16:23
  • Confirmed in rspec - works perfectly! Sir, you made my day! :) – Szymon Przybył Aug 17 '13 at 19:23
  • 1
    Thanks for this - I've translated the code successfully to work for my tests using C#. – Henry Wilson Jan 14 '14 at 10:10
  • @Shmoopy added as an answer below :) – Henry Wilson May 05 '14 at 09:54
  • Note here that `drop_area_id` is the html element that has the ondrop event handler attached to it. – C.J. Feb 20 '16 at 15:24
  • This is working perfectly fine but I had to upgrade PhantomJS from 1.9.8 to 2.1.1 to get it working. Also now I'm getting the following deprecation warning: `ArrayBuffer is deprecated in XMLHttpRequest.send(). Use ArrayBufferView instead.` – ZedTuX Feb 04 '17 at 10:55
3

As @Shmoopy asked for it, here's a C# translation of the code provided by @micred

private void DropImage(string dropBoxId, string filePath)
{
   var javascriptDriver = this.Driver as IJavaScriptExecutor;
   var inputId = dropBoxId + "FileUpload";

   // append input to HTML to add file path
   javascriptDriver.ExecuteScript(inputId + " = window.$('<input id=\"" + inputId + "\"/>').attr({type:'file'}).appendTo('body');");
   this.Driver.FindElement(By.Id(inputId)).SendKeys(filePath);

   // fire mock event pointing to inserted file path
   javascriptDriver.ExecuteScript("e = $.Event('drop'); e.originalEvent = {dataTransfer : { files : " + inputId + ".get(0).files } }; $('#" + dropBoxId + "').trigger(e);");
}
Henry Wilson
  • 3,281
  • 4
  • 31
  • 46
  • 2
    Thanks, this helped me. Although I got an error `selenium Unexpected error. type property can't be changed`. Fixed by changing `$('').attr({type:'file'})` to `$('')`. – Jay Sullivan Dec 10 '14 at 17:43
0

Note: you should also add

    e.originalEvent.dataTransfer.types = [ 'Files' ];
Gene
  • 46,253
  • 4
  • 58
  • 96
Michael Blake
  • 2,068
  • 2
  • 18
  • 31
0

You can use Blueduck Sda (http://sda.blueducktesting.com) Is an OSS that has implemented ALL selenium functions (It works with selenium RC) but it allows you to automate Windows actions. So you can test web, and interact with the OS. So you can make your test, and then, just tell the mouse to click on the element and drop it where you want!

Nice testing!

  • That looks like it could be an option, though I am working in OSX, not Windows. I am also technically curious about interacting with the browserbot and firing events in the JavaScript context. – Ben Flynn Mar 04 '11 at 03:46
  • As of this writing blueducktesting.com domain is for sale. – Stephan Dec 08 '19 at 22:34