14

I have to automate a web-application, which contains a drag and drop area for uploading files from the local file system. My test environment is developed using Python. For the automation tests I have used Selenium, but it is not possible to drag files from the file system, once the upload area is a div tag (No input tag - this way I know it would be easy).

I read a lot of different articles, but by the moment none worked for me. It's important to highlight that I'm not interested in using AutoIT, only native python with selenium.

I found this Selenium: Drag and Drop from file system to webdriver? what looks really promising, however I do not know to adapt to Python.

Thank you a lot in advance!

2
  • 1
    File managers are all desktop GUI apps, so it's impossible to automate them using Selenium if there is no special trick inside WebDriver. If you're on Windows, explorer.exe can be automated using pywinauto. My student wrote an example that drags-n-drops the file from explorer.exe to Chrome (Google Disk). Can it be helpful? Commented Apr 15, 2017 at 16:52
  • To be honest, I would like to know how to inject a new INPUT element in the page to receive the file through SendKeys. I know it is possible with c# and java, however I have no idea how can I manage it with python. Commented Apr 16, 2017 at 4:53

3 Answers 3

37

Here's the python version of the trick with input injection via script.

JS_DROP_FILE = """
    var target = arguments[0],
        offsetX = arguments[1],
        offsetY = arguments[2],
        document = target.ownerDocument || document,
        window = document.defaultView || window;

    var input = document.createElement('INPUT');
    input.type = 'file';
    input.onchange = function () {
      var rect = target.getBoundingClientRect(),
          x = rect.left + (offsetX || (rect.width >> 1)),
          y = rect.top + (offsetY || (rect.height >> 1)),
          dataTransfer = { files: this.files };

      ['dragenter', 'dragover', 'drop'].forEach(function (name) {
        var evt = document.createEvent('MouseEvent');
        evt.initMouseEvent(name, !0, !0, window, 0, 0, 0, x, y, !1, !1, !1, !1, 0, null);
        evt.dataTransfer = dataTransfer;
        target.dispatchEvent(evt);
      });

      setTimeout(function () { document.body.removeChild(input); }, 25);
    };
    document.body.appendChild(input);
    return input;
"""

def drag_and_drop_file(drop_target, path):
    driver = drop_target.parent
    file_input = driver.execute_script(JS_DROP_FILE, drop_target, 0, 0)
    file_input.send_keys(path)

As drop_target pass it some element visible on the page which you can get using any function from the family of driver.get_element_by_....

The approach is to invoke a javascript using selenium's execute_script function to emulate drag and drop events. The code works as following:

  1. selenium invokes javascript code
  2. javascript creates input element and attaches it to DOM
  3. javascript attaches a handler to the input which emulates mouse events that happens when user actually drops a file, namely dragenter, dragover, drop.
  4. selenium updates the input with the path to the file. At this point the handler from step 2 is invoked and it emulates drag and drop events.
Sign up to request clarification or add additional context in comments.

9 Comments

Excellent. Worked for me. Should be the accepted answer.
People who do this kind of things <3
when you call "drag_and_drop_file" what value do you pass for "drop_target"?
I know this is a late comment but I had a similar problem. I was stuck on my code for hours trying to find a solution until I found this answer. I tried it and it worked like a charm. Thanks for the answer.
This solution still rings true in 2023! Props to the author of this :clap:
|
1

I know this may be a late answer but just in case for people who find the answer!

If you are using Chrome please go to this site to download the Chrome driver. (Try to find your chrome version thru this and choose the suitable one)

There's still another thing you will need to do I'll show it right now


First: Download chrome driver and copy the Xpath

Step 1: Go to the site you want and copy fullXPath of your "drag and drop", by right click on the drag and drop area then hit the inspect.

Plz do this twice just in case it inspects the right place

Step 2: You will see the highlight color, again right-click on them

then you will find "copy" -> "copy fullXpath"

Finally, let's code

Wait!!! Just one more suggestion plz : If you see something goes wrong with pasting the "Xpath" or "link to the folder" for example

you might use ' ' instead of " "

from selenium import webdriver
driver = webdriver.Chrome('D:\Folder\chromedriver')
driver.get('https://exmaple.com')
drag_&_drop = driver.find_element_by_xpath('paste-the-full-xpath-here')
drag_&_drop.send_keys('D:\Folder\picture.png')
#python 3.9

1 Comment

The last tip about "/ ' has nothing to do with the question nor Selenium, this is a basic coding syntax knowledge. I suggest you delete it.
0

Its been a few years since the last response but I will add my own for those trying to use react dropzone with selenium, robot framework, or similar. The accepted answer may still work with pure selenium in your case, but as I am using robot framework with a non-standard library, my answer may not be universal. It is very slightly off topic but as I referenced this for creating my own keyword I hope it helps.

In my case I am using Qweb which required some additional setup such as getting the library instance and the webElement.

from robot.libraries.BuiltIn import BuiltIn

JS_DROP_FILE = """
    var target = arguments[0],
        offsetX = arguments[1],
        offsetY = arguments[2],
        
        // I did not need to fetch the document separately, it was already accessible in my case.
        window = document.defaultView;
    
    var input = document.createElement('INPUT');
    input.type = 'file';
    target.appendChild(input);

    input.onchange = function () {
      var rect = target.getBoundingClientRect(),
          x = rect.left + (offsetX || (rect.width / 2)),
          y = rect.top + (offsetY || (rect.height / 2));

      var dataTransfer = new DataTransfer();
      for (var i = 0; i < this.files.length; i++) {
          dataTransfer.items.add(this.files[i]);
      }

      ['dragenter', 'dragover', 'drop'].forEach(function (name) {
        var evt = new MouseEvent(name, {
          bubbles: true,
          cancelable: true,
          clientX: x,
          clientY: y,
          dataTransfer: dataTransfer
        });
        target.dispatchEvent(evt);
      });

      var fileDropEvent = new CustomEvent("file-drop", {
        bubbles: true,
        detail: { dataTransfer: dataTransfer }
      });
      target.dispatchEvent(fileDropEvent);

      setTimeout(function () { target.removeChild(input); }, 25);
    };
    
    return input; // Return the input element

   // # debugging: console.log(var) prints to chrome console

"""

def drag_and_drop_file(drop_target, path):
    qweb = BuiltIn().get_library_instance('Qweb')
    driver = qweb.return_browser() # debug: expect <selenium.webdriver.chrome.webdriver.WebDriver (session="alphabet-soup")>
    # for selenium library, you will use a something along these lines
    # selib = BuiltIn().get_library_instance("SeleniumLibrary")
    # driver = selib.driver
    # element = selib.get_webelement(drop_target)
    element = qweb.get_webelement(drop_target)
    if isinstance(element, list):
      element = element[0] # debug: expect <selenium.webdriver.remote.webelement.WebElement (session="alphabet-soup", element="longer-alphabet-soup")>
    file_input = driver.execute_script(JS_DROP_FILE, element, 0, 0)
    file_input.send_keys(path)
    # debugging: Builtin().log_to_console(var) prints to terminal

Some notable changes: new DataTransfer() and new MouseEvent() are more modern ways to handle these actions in chromium.

Other considerations: in my case the element is attached to the target vs the body, this makes debugging a little easier. You can still use body.appendChild and body.removeChild if you want to.

Working with react: You will preferably need some access to the react code in question, at least an ability to work with a developer, as the code will need to have an event listener. fileDropEvent is dispatched to the target, and thus the react code will need to have a useEffect() that will check for a target, and add an event listener for "file-drop" to it. This isn't the ideal case as you should not have to modify code much for QA purposes, but react-dropzone is a bit more difficult with automated testing.


  const handlePhotosUpload = useCallback(
    // Your photo upload logic here
  );

  useEffect(() => {
    const target = document.getElementById("upload-photos");
    const handleFileDrop = (event: any) => {
       // Prevent default browser handling
       event.preventDefault();
       if (event.detail?.dataTransfer?.files) {
          handlePhotosUpload(Array.from(event.detail.dataTransfer.files));
       }
    };
 
    if (target) {
      target.addEventListener("file-drop", handleFileDrop);
  }
 
    return () => {
       if (target) {
          target.removeEventListener("file-drop", handleFileDrop);
       }
    };
 }, [handlePhotosUpload]);

Usage in robot framework:

*** Settings ***
Library    QWeb  # Or SeleniumLibrary or Browser, whichever you use
Library   drag-n-drop.py

*** Keywords ***

Upload Photo
    [Arguments]    ${photo}
    [Documentation]    Executes a custom script to drag and drop a photo into the dropzone.
    Scroll to    //*[@id\="upload-photos"]. 
    # note: Qweb requires escape chars in xpath.
    # In selenium, xpath://div[@id="example"] or id:example may work.
    # I did not test this in selenium.
    Drag And Drop File    //*[@id\="upload-photos"]    ${EXECDIR}/files/images/${photo}
    Sleep    2s    # wait for photo to upload

*** Test Cases ***
Upload A Photo
    Upload Photo    my_picture.jpeg

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.