|
|
|
Implemented as part of drag+drop support.
Thanks for the patch, Charles -- that gave me an excellent headstart on this task. 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? 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; } }; The timing of the next release is currently being debated. My guess is that it will be within the next week or so.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
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.