minkphp / MinkSelenium2Driver

Selenium2 (webdriver) driver for Mink framework
MIT License
507 stars 163 forks source link

Use webdriver JSONprotocol instead of current dragTo implementation #51

Open tarjei opened 11 years ago

tarjei commented 11 years ago

I could not get my test to work when using the dragTo implementation in the Selenium2Driver, but this worked:

    private function performDragAndDrop(Element $element, Element $dropZone)
    {
        $session = $this->session->getDriver()->getWebDriverSession();

        //this requires a sequence of steps as follows:
//1st find the source and target/destination elements to use as reference to do the drag and drop
        $from = $session->element('xpath',$element->getXpath());
        $to = $session->element('xpath',$dropZone->getXpath());
//now perform drag and drop
        $session->moveto(array('element' => $from->getID())); //move to source location, using reference to source element
        $session->buttondown(""); //click mouse to start drag, defaults to left mouse button
        $session->moveto(array('element' => $to->getID())); //move to target location, using reference to target element
        $session->buttonup(""); //release mouse to complete drag and drop operation
//it may be worthwhile to encapsulate these steps into a function called draganddrop($src,$target), etc.
    }

Most of the code is just a cut and paste from an example I found in a comment on the internet(I cannot remember where), but it is fairly straight forward.

stof commented 11 years ago

Well, the only difference between your code and the driver is that your code does not trigger the events: https://github.com/Behat/MinkSelenium2Driver/blob/master/src/Behat/Mink/Driver/Selenium2Driver.php#L829

tarjei commented 11 years ago

Interesting. Then the question becomes, why did my code work and the code that also triggers the events not?

stof commented 11 years ago

@everzet do we really need to emulate the D&D events ? We are using the selenium API to trigger all actions there, so it should handle them, no ?

everzet commented 11 years ago

@stof Mink has and always had 2 responsibilities:

  1. Provide single API to all emulators
  2. Eliminate differences between emulators as much as possible

If all of the emulators emulate events, but Selenium doesn't, then it's a good idea for Mink to do it.

sp0tteh commented 11 years ago

The default behaviour of this function doesn't work with already binded drag/drop elements. (using jquery ui droppable / draggable). Replacing the function with the one mentioned above works fine.

If this function can't be changed then are we able to get another function added with the following parts removed?:

 $script = <<<JS
(function (element) {
    var event = document.createEvent("HTMLEvents");

    event.initEvent("dragstart", true, true);
    event.dataTransfer = {};

    element.dispatchEvent(event);
}({{ELEMENT}}));
JS;
$this->withSyn()->executeJsOnXpath($sourceXpath, $script);

and:

 $script = <<<JS
(function (element) {
    var event = document.createEvent("HTMLEvents");

    event.initEvent("drop", true, true);
    event.dataTransfer = {};

    element.dispatchEvent(event);
}({{ELEMENT}}));
JS;
        $this->withSyn()->executeJsOnXpath($destinationXpath, $script);

Let me know if this would be accepted and I'll submit a pull request.

Thanks.

aik099 commented 11 years ago

@allmyitjason , can you please send PR anyway? Without it I need to jump back and forth in the code just to see what has been changed compared to current implementation.

aik099 commented 11 years ago

@tarjei: Just to clarify: with current drag-n-drop implementation the drag works fine, but respective events aren't fired at all? Drag-n-drop tests pass right now, because they are using jQueryUI draggable/droppable components to detect if a drag-n-drop operation succeeded. jQuery does pretty well in overcoming browser incompatibilities and firing correct events at correct times. There might be an issue however in cross-browser even firing code, that is used.

Improved code version is here: https://github.com/Behat/MinkSelenium2Driver/blob/master/src/Behat/Mink/Driver/Selenium2Driver.php#L684-L701 Original code version is here: https://github.com/Behat/MinkSelenium2Driver/blob/master/src/Behat/Mink/Driver/Selenium2Driver.php#L895-L902

As you can see if document.createEvent is missing in used browser, then no events are fired. Can you please try adding missing parts of cross-browser event triggering code to dragTo method and see if it helps you. And if it does I would be glad to see an PR from you.

@allmyitjason: In your PR you provide alternative implementation of dragTo function. What is not working in current dragTo implementation for you? Are it events not being fired? If so, then please read beginning of this message, for more info.

sp0tteh commented 11 years ago

hi @aik099

Yea drop and drop events aren't being fired. I'll try with the updated js and will let you know if it resolves my issue.

aik099 commented 11 years ago

@allmyitjason , please also update your PR if it does help you.

sp0tteh commented 11 years ago

@aik099:

I have just been testing the changes with the new js code and I still see the same result. The events aren't fired and text between the two elements are simply being highlighted. Same result is experienced in IE 9,10 / Firefox 23.

Using the above pull request code, the drag and drop works as expected in IE and FF.

This was the JS I added to https://github.com/Behat/MinkSelenium2Driver/blob/master/src/Behat/Mink/Driver/Selenium2Driver.php#L894

$script = <<<JS
(function (element, eventName) {
    var event;
    if (document.createEvent) {
        event = document.createEvent("HTMLEvents");
        event.initEvent(eventName, true, true);
    } else {
        event = document.createEventObject();
        event.eventType = eventName;
    }

    event.eventName = eventName;

    if (document.createEvent) {
        element.dispatchEvent(event);
    } else {
        element.fireEvent("on" + event.eventType, event);
    }
}({{ELEMENT}}, 'dragstart'));
JS;

and https://github.com/Behat/MinkSelenium2Driver/blob/master/src/Behat/Mink/Driver/Selenium2Driver.php#L912

 $script = <<<JS
(function (element, eventName) {
    var event;
    if (document.createEvent) {
        event = document.createEvent("HTMLEvents");
        event.initEvent(eventName, true, true);
    } else {
        event = document.createEventObject();
        event.eventType = eventName;
    }

    event.eventName = eventName;

    if (document.createEvent) {
        element.dispatchEvent(event);
    } else {
        element.fireEvent("on" + event.eventType, event);
    }
}({{ELEMENT}}, 'drop'));
JS;
aik099 commented 11 years ago

Are you using jQuery to bind event listeners to drag events? If nope, then I'm strongly recommending that.

Also can you run tests locally for MinkSelenium2Driver? These tests are using jQueryUI/jQuery (as I've mentioned before), to verify, that drop has happened. They pass for me on Firefox 23 without problems.

sp0tteh commented 11 years ago

I'm using the jQuery UI draggable for the drag element. And jQuery Fullcalendar plugin for the drop event.

Drag

$('#external-events div.external-event').each(function() {
        // it doesn't need to have a start or end
        var eventObject = {
            title: $.trim($(this).text()) // use the element's text as the event title
        };
        // store the Event Object in the DOM element so we can get to it later
        $(this).data('eventObject', eventObject);

        // make the event draggable using jQuery UI
        $(this).draggable({
            zIndex: 999,
            revert: true,      // will cause the event to go back to its
            revertDuration: 0  //  original position after the drag
        });

    });

Drop

$('#cal').fullCalendar({
    droppable: true,
    drop: function(date, allDay) {
        alert('drop');
    }
});

I'll have a look at the tests, but I not sure how to run them? So far been using Mink simply through codeception.

aik099 commented 11 years ago

To run tests do the following:

  1. clone the repo on disk, where you have both Selenium and WebServer installed from https://github.com/Behat/MinkSelenium2Driver url
  2. create phpunit.xml file in cloned directory based on phpunit.xml.dist
  3. put correct paths/urls in it

About your code: And what is test code, that you're performing? I guess it's clicking on the input to open a calendar, but I can't guess what exactly are you dragging there.

sp0tteh commented 11 years ago

I have ran the tests and they did pass.

However I still feel this function should be added, as this is all an end user would be doing. Click on an element, drag and un-click. This is the exact function that I wan't to be able to test without having extra javascript running and possibly conflicting with any listening methods.

My current tests are simply clicking on an element and dropping on a day in the calendar. This is working fine in the browser if I do it myself, and is also working fine with the code in the above pull request.

I'm not too familiar with JavaScript listening methods, but somehow that extra JavaScript is conflicting.

The drag and drop code is all from the jQuery fullcalendar plugin (http://arshaw.com/fullcalendar/docs/dropping/drop/)

aik099 commented 11 years ago

I have ran the tests and they did pass.

That is very strange, because tests are testing, that something is dropped onto designed area. That's what you're doing too.

However I still feel this function should be added, as this is all an end user would be doing.

Existing function obviously don't work in your case and we need to find out why, rather than adding another function that partially duplicates current dragTo method.

I'm not too familiar with JavaScript listening methods, but somehow that extra JavaScript is conflicting.

Fully agree on that. Here are full info about drag-n-drop events: https://developer.mozilla.org/en-US/docs/DragDrop/Drag_and_Drop . Now we need to:

  1. figure out what events on on which elements need to be called during drag-n-drop operation
  2. run your proposed dragTo implementation and see which events are fined
  3. run current dragTo implementation and see which events are fined

In worst case Selenium would fire events on it's own and manual event firing done in current dragTo implementation might confuse other JS code (that fullcalendar in this case).

aik099 commented 11 years ago

@tarjei or @allmyitjason , ready for a PR creation as discussed in last comment?

aik099 commented 11 years ago

@tarjei, @allmyitjason, based on @stof comment (see https://github.com/Behat/MinkSelenium2Driver/issues/51#issuecomment-18337179) I can conclude, that removing manual JavaScript event triggering can solve the problem. Then the only thing that needs to be tested is whatever Selenium does trigger proper JavaScript events in correct order on it's own.

Preferably we need a test in JavaScriptDriverTest class to verify that as well. Then we can safely remove Syn usage from dragTo method.

robocoder commented 10 years ago

It would be helpful to know what OS people are testing on.

As a bit of background: Firefox+Webdriver on Windows defaults to using native events. Firefox+Webdriver on Linux defaults to using emulation. (On Linux, we found it didn't generate dragstart/drop events, hence 274ecf6c9028f642766ec734d8ecfce07343a5b6.)

aik099 commented 10 years ago

@robocoder , is there any way to force native event usage?

robocoder commented 10 years ago

Yes, it can be forced, but I believe we should leave it "as is" because native events is not fully supported across all window managers:

That said, I agree with determining the sequence of events to be triggered and adding tests.

aik099 commented 10 years ago

Does these native events mean, that even onclick won't be triggered natively when ->click() is called on the WebElement, if Selenium server is launched on Linux?

Or native events are only HTML5 events?

robocoder commented 10 years ago

I haven't looked at the code, so I don't know which events are emulated vs native. For the time being, I think we'll have to find a way to support both:

As we develop features in the Firefox Driver, we expose the ability to use them. For example, until we feel native events are stable on Firefox for Linux, they are disabled by default.

aik099 commented 10 years ago

Without a way to detecting which events are fired natively we can't safely use Syn, because there is a risk, that event can be fired twice (native and via syn), which indeed may result in some weird results.

robocoder commented 10 years ago

We typically run Firefox+Linux with xvfb and without a window manager. Running Selenium 2.40, even if I force nativeEvents=true in the desired/required capabilities request, the driver response says nativeEvents=false. So while we can specify that we want native events, the driver appears to have the discretion to ignore it -- which is disconcerting given that I also tried with "required" capabilities.

aik099 commented 10 years ago

@robocoder, how do you get the nativeEvents setting back from WebDriver?

So we can get that setting and based on actual native event support decide whatever to invoke Syn code or not.

robocoder commented 10 years ago

@aik099 See https://github.com/Behat/MinkSelenium2Driver/pull/123#issuecomment-38308430