History | Log In     View a printable version of the current page.  
Issue Details (XML | Word | Printable)

Key: SRC-65
Type: New Feature New Feature
Status: Closed Closed
Resolution: Fixed
Priority: Minor Minor
Assignee: Nelson Sproul
Reporter: Nelson Sproul
Votes: 1
Watchers: 1
Operations

If you were logged in you would be able to see more operations.
Selenium Remote Control

selenium server should accept commands to control the position of the mouse pointer

Created: 25/May/06 02:06 PM   Updated: 02/Aug/06 03:35 PM
Component/s: None
Affects Version/s: None
Fix Version/s: None

Original Estimate: Unknown Remaining Estimate: Unknown Time Spent: Unknown
File Attachments: 1. File selenium_mouse_events.js (10 kb)



 Description  « Hide
perhaps

setMousePosition(int x, int y)
      getMousePositionX()
      getMousePositionY()

This would allow selenium to support working with images which overlay multiple links, where the link which gets followed is determined by the mouse pointer position at the time of the click.

Note that this feature is supported by QTP.

 All   Comments   Work Log   Change History      Sort Order:
Charlie - 11/Jul/06 03:07 PM
Hi - I've added this support to my version of selenium. I've attached my changes - note they are not in the form of a patch since I just overrode methods in my user-extensions.js file. But I can turn them into a patch if needed.

What the code does:

* Adds mouseOut, mouseUp, mouseMove events
* For each mouse event, and click, you can specify a parameter which is the x,y coordinates of the click. So something like this:

mouseMove | some_id | 17, 12

* Mouse coordinates are assumed to be in relation to the element of interest. So if "some_id" is a div tag at 10,10, then the mouse move event will be generated with coordinates of 27, 22. Its easy to change this to document coordinates, but this seemed easier.

* Update the browser bots to support mouse locations

* Updated the triggerMouseEvent to work with IE also. Note one big caveat to IE - user generated events do not bubble.

Hope this helps - feel free to ask questions. And if you'd prefer a patch let me know.


Charlie - 11/Jul/06 03:08 PM
Implementation of several new mouse events and support for mouse coordinates (for more info see comment above).

Harry - 12/Jul/06 10:36 AM
I would be good to have the ability of recording in selenium IDE.

Charlie - 12/Jul/06 01:08 PM
That would be nice. I assume it wouldn't be very hard. The extension assumedly has access to the mouse events which contain the x,y locations.

Nelson Sproul - 28/Jul/06 12:42 PM
Implemented as part of drag+drop support.

Thanks for the patch, Charles -- that gave me an excellent headstart on this task.

Charlie - 02/Aug/06 12:04 AM
Thanks Nelson - its great to have this in the core. I did notice a few bugs in my implementation which I later fixed, hopefully those didn't trip you up.

Nelson Sproul - 02/Aug/06 01:46 PM
I did end up making a number of changes, some related to bugs but probably more related to various re-factorings to share logic between this and other related tasks.

I think it could be worthwhile to review and compare the fixes you have made in your own code with what selenium has now. Would you mind attaching your latest to this JIRA issue so I could compare what you have with selenium's code?

Charlie - 02/Aug/06 02:19 PM
Sure, no problem. Except I can't figure out how to attach another file...so its inline below.

Any thoughts on when the next release will be? I'd be happy to download the latest and greatest to test these changes when they are ready.

Thanks,

Charlie

-----------------

/* Taken from prototype library */
function getElementOffset(element) {
  var valueT = 0, valueL = 0;
  do {
    valueT += element.offsetTop || 0;
    valueL += element.offsetLeft || 0;
    element = element.offsetParent;
  } while (element);
  return [valueL, valueT];
}

function getClientXY(element, coordString) {
   // Parse coordString
   var x = 0
   var y = 0
   if (coordString && coordString.trim() != '') {
      var coords = coordString.split(/,/)
      x = Number(coords[0])
      y = Number(coords[1])
   }
  
// Get position of element
   var offset = getElementOffset(element)
   
// Return 2 item array with clientX and clientY
   return [offset[0] + x, offset[1] + y]
}

function triggerMouseEvent(element, eventType, canBubble, clientX, clientY) {
    canBubble = (typeof(canBubble) == undefined) ? true : canBubble;

clientX = clientX || 0
    clientY = clientY || 0
    var screenX = 0
    var screenY = 0

if (window.screenX)
    {
      // Firefox
      screenX = window.screenX + clientX
      screenY = window.screenY + clientY
    }
    else if (window.screenLeft)
    {
      // IE
      screenX = window.screenLeft + clientX
      screenY = window.screenTop + clientY
    }

if (element.fireEvent) {
        var mouseEvent = document.createEventObject()
        mouseEvent.clientX = clientX
        mouseEvent.clientY = clientY
        mouseEvent.screenX = screenX
        mouseEvent.screenY = screenY
        element.fireEvent('on' + eventType);
    }
    else {
        var evt = document.createEvent('MouseEvents');
        if (evt.initMouseEvent)
        {
          

          
evt.initMouseEvent(eventType, canBubble, true, document.defaultView, 1,
                             screenX, screenY, clientX, clientY,
                             false, false, false, false, 0, null)
        }
        else
        {
            // Safari
            // TODO we should be initialising other mouse-event related attributes here
            evt.initEvent(eventType, canBubble, true);
        }
        element.dispatchEvent(evt);
    }
}

Selenium.prototype.doClick = function(locator, coordString) {
/**
   * Clicks on a link, button, checkbox or radio button. If the click action
   * causes a new page to load (like a link usually does), call
   * waitForPageToLoad.
   *
   * @param locator an element locator
   * @param coordString specifies the x,y position (i.e. - 10,20) of the mouse
   * event relative to the element returned by the locator.
   *
   */
    var element = this.page().findElement(locator);
    var clientXY = getClientXY(element, coordString)
    this.page().clickElement(element, clientXY[0], clientXY[1]);
};

Selenium.prototype.doMouseMove = function(locator, coordString) {
/**
   * Simulates a user pressing the mouse button (without releasing it yet) on
   * the specified element.
   *
   * @param locator an <a href="#locators">element locator</a>
   * @param coordString specifies the x,y position (i.e. - 10,20) of the mouse
   * event relative to the element returned by the locator.
   */
   
var element = this.page().findElement(locator);
    var clientXY = getClientXY(element, coordString)
   
triggerMouseEvent(element, 'mousemove', true, clientXY[0], clientXY[1]);
};

Selenium.prototype.doMouseDown = function(locator, coordString) {
/**
   * Simulates a user pressing the mouse button (without releasing it yet) on
   * the specified element.
   *
   * @param locator an <a href="#locators">element locator</a>
   * @param coordString specifies the x,y position (i.e. - 10,20) of the mouse
   * event relative to the element returned by the locator.
   */
    var element = this.page().findElement(locator);
    var clientXY = getClientXY(element, coordString)
   
triggerMouseEvent(element, 'mousedown', true, clientXY[0], clientXY[1]);
};

Selenium.prototype.doMouseUp = function(locator, coordString) {
/**
   * Simulates a user pressing the mouse button (without releasing it yet) on
   * the specified element.
   *
   * @param locator an <a href="#locators">element locator</a>
   * @param coordString specifies the x,y position (i.e. - 10,20) of the mouse
   * event relative to the element returned by the locator.
   */
    var element = this.page().findElement(locator);
    var clientXY = getClientXY(element, coordString)
   
triggerMouseEvent(element, 'mouseup', true, clientXY[0], clientXY[1]);
};



// TODO Opera uses this too - split out an Opera version so we don't need the isGecko check here
MozillaPageBot.prototype.clickElement = function(element, clientX, clientY) {

triggerEvent(element, 'focus', false);

// Add an event listener that detects if the default action has been prevented.
    // (This is caused by a javascript onclick handler returning false)
    var preventDefault = false;
    if (browserVersion.isGecko) {
        element.addEventListener("click", function(evt) {preventDefault = evt.getPreventDefault();}, false);
    }

// Trigger the click event.
    triggerMouseEvent(element, 'click', true, clientX, clientY);

// Perform the link action if preventDefault was set.
    if (browserVersion.isGecko && !preventDefault) {
        // Try the element itself, as well as it's parent - this handles clicking images inside links.
        if (element.href) {
            this.currentWindow.location.href = element.href;
        }
        else if (element.parentNode && element.parentNode.href) {
            this.currentWindow.location.href = element.parentNode.href;
        }
    }

if (this.windowClosed()) {
        return;
    }

triggerEvent(element, 'blur', false);
};

KonquerorPageBot.prototype.clickElement = function(element, clientX, clientY) {

triggerEvent(element, 'focus', false);

if (element.click) {
        element.click();
    }
    else {
        triggerMouseEvent(element, 'click', true, clientX, clientY);
    }

if (this.windowClosed()) {
        return;
    }

triggerEvent(element, 'blur', false);
};

SafariPageBot.prototype.clickElement = function(element, clientX, clientY) {

triggerEvent(element, 'focus', false);

var wasChecked = element.checked;

// For form element it is simple.
    if (element.click) {
        element.click();
    }
    // For links and other elements, event emulation is required.
    else {
        triggerMouseEvent(element, 'click', true, clientX, clientY);

// Unfortunately, triggering the event doesn't seem to activate onclick handlers.
        // We currently call onclick for the link, but I'm guessing that the onclick for containing
        // elements is not being called.
        var success = true;
        if (element.onclick) {
            var evt = document.createEvent('HTMLEvents');
            evt.initEvent('click', true, true);
            var onclickResult = element.onclick(evt);
            if (onclickResult === false) {
                success = false;
            }
        }

if (success) {
            // Try the element itself, as well as it's parent - this handles clicking images inside links.
            if (element.href) {
                this.currentWindow.location.href = element.href;
            }
            else if (element.parentNode.href) {
                this.currentWindow.location.href = element.parentNode.href;
            } else {
                // This is true for buttons outside of forms, and maybe others.
                LOG.warn("Ignoring 'click' call for button outside form, or link without href."
                        + "Using buttons without an enclosing form can cause wierd problems with URL resolution in Safari." );
                // I implemented special handling for window.open, but unfortunately this behaviour is also displayed
                // when we have a button without an enclosing form that sets document.location in the onclick handler.
                // The solution is to always use an enclosing form for a button.
            }
        }
    }

if (this.windowClosed()) {
        return;
    }

triggerEvent(element, 'blur', false);
};

IEPageBot.prototype.clickElement = function(element, clientX, clientY) {

triggerEvent(element, 'focus', false);

var wasChecked = element.checked;

// Set a flag that records if the page will unload - this isn't always accurate, because
    // <a href="javascript:alert('foo'):"> triggers the onbeforeunload event, even thought the page won't unload
    var pageUnloading = false;
    var pageUnloadDetector = function() {pageUnloading = true;};
    this.currentWindow.attachEvent("onbeforeunload", pageUnloadDetector);

element.click();

// If the page is going to unload - still attempt to fire any subsequent events.
    // However, we can't guarantee that the page won't unload half way through, so we need to handle exceptions.
    try {
        this.currentWindow.detachEvent("onbeforeunload", pageUnloadDetector);

if (this.windowClosed()) {
            return;
        }

// Onchange event is not triggered automatically in IE.
        if (isDefined(element.checked) && wasChecked != element.checked) {
            triggerEvent(element, 'change', true);
        }

triggerEvent(element, 'blur', false);
    }
    catch (e) {
        // If the page is unloading, we may get a "Permission denied" or "Unspecified error".
        // Just ignore it, because the document may have unloaded.
        if (pageUnloading) {
            LOG.warn("Caught exception when firing events on unloading page: " + e.message);
            return;
        }
        throw e;
    }
};


Nelson Sproul - 02/Aug/06 03:35 PM
The timing of the next release is currently being debated. My guess is that it will be within the next week or so.