diff options
Diffstat (limited to 'tests/test_tools/selenium/core')
14 files changed, 2747 insertions, 1704 deletions
diff --git a/tests/test_tools/selenium/core/SeleniumLog.html b/tests/test_tools/selenium/core/SeleniumLog.html index dfa0080a..dffa184f 100644 --- a/tests/test_tools/selenium/core/SeleniumLog.html +++ b/tests/test_tools/selenium/core/SeleniumLog.html @@ -2,6 +2,7 @@ <head> <title>Selenium Log Console</title> +<link id="cssLink" rel="stylesheet" href="selenium.css" /> </head> <body id="logging-console"> @@ -15,8 +16,6 @@ var logLevels = { error: 3 }; -var logLevelThreshold = null; - function getThresholdLevel() { var buttons = document.getElementById('logLevelChooser').level; for (var i = 0; i < buttons.length; i++) { @@ -27,10 +26,9 @@ function getThresholdLevel() { } function setThresholdLevel(logLevel) { - logLevelThreshold = logLevel; var buttons = document.getElementById('logLevelChooser').level; for (var i = 0; i < buttons.length; i++) { - if (buttons[i].value==logLevel) { + if (buttons[i].value==logLevel) { buttons[i].checked = true; } else { @@ -40,9 +38,7 @@ function setThresholdLevel(logLevel) { } function append(message, logLevel) { - if (logLevelThreshold==null) { - logLevelThreshold = getThresholdLevel(); - } + var logLevelThreshold = getThresholdLevel(); if (logLevels[logLevel] < logLevels[logLevelThreshold]) { return; } @@ -64,9 +60,9 @@ function append(message, logLevel) { value="error" /><label for="level-error">Error</label> <input id="level-warn" type="radio" name="level" value="warn" /><label for="level-warn">Warn</label> - <input id="level-info" type="radio" name="level" checked="yes" + <input id="level-info" type="radio" name="level" value="info" /><label for="level-info">Info</label> - <input id="level-debug" type="radio" name="level" + <input id="level-debug" type="radio" name="level" checked="yes" value="debug" /><label for="level-debug">Debug</label> </form> <h1>Selenium Log Console</h1> diff --git a/tests/test_tools/selenium/core/TestRunner-splash.html b/tests/test_tools/selenium/core/TestRunner-splash.html index 205bb8ef..1c32dd79 100644 --- a/tests/test_tools/selenium/core/TestRunner-splash.html +++ b/tests/test_tools/selenium/core/TestRunner-splash.html @@ -15,6 +15,7 @@ Copyright 2005 ThoughtWorks, Inc --> <html> +<link rel="stylesheet" type="text/css" href="selenium.css" /> <body> <table width="100%"> diff --git a/tests/test_tools/selenium/core/scripts/find_matching_child.js b/tests/test_tools/selenium/core/scripts/find_matching_child.js index 197d1032..fbf35b75 100644 --- a/tests/test_tools/selenium/core/scripts/find_matching_child.js +++ b/tests/test_tools/selenium/core/scripts/find_matching_child.js @@ -15,8 +15,8 @@ * */ -Element.findMatchingChildren = function(element, selector) { - var matches = $A([]); +elementFindMatchingChildren = function(element, selector) { + var matches = []; var childCount = element.childNodes.length; for (var i=0; i<childCount; i++) { @@ -24,7 +24,7 @@ Element.findMatchingChildren = function(element, selector) { if (selector(child)) { matches.push(child); } else { - childMatches = Element.findMatchingChildren(child, selector); + childMatches = elementFindMatchingChildren(child, selector); matches.push(childMatches); } } @@ -34,7 +34,7 @@ Element.findMatchingChildren = function(element, selector) { ELEMENT_NODE_TYPE = 1; -Element.findFirstMatchingChild = function(element, selector) { +elementFindFirstMatchingChild = function(element, selector) { var childCount = element.childNodes.length; for (var i=0; i<childCount; i++) { @@ -43,7 +43,7 @@ Element.findFirstMatchingChild = function(element, selector) { if (selector(child)) { return child; } - result = Element.findFirstMatchingChild(child, selector); + result = elementFindFirstMatchingChild(child, selector); if (result) { return result; } @@ -52,7 +52,7 @@ Element.findFirstMatchingChild = function(element, selector) { return null; } -Element.findFirstMatchingParent = function(element, selector) { +elementFindFirstMatchingParent = function(element, selector) { var current = element.parentNode; while (current != null) { if (selector(current)) { @@ -63,7 +63,7 @@ Element.findFirstMatchingParent = function(element, selector) { return current; } -Element.findMatchingChildById = function(element, id) { - return Element.findFirstMatchingChild(element, function(element){return element.id==id} ); +elementFindMatchingChildById = function(element, id) { + return elementFindFirstMatchingChild(element, function(element){return element.id==id} ); } diff --git a/tests/test_tools/selenium/core/scripts/htmlutils.js b/tests/test_tools/selenium/core/scripts/htmlutils.js index 4d78e1a6..a3cd3dd9 100644 --- a/tests/test_tools/selenium/core/scripts/htmlutils.js +++ b/tests/test_tools/selenium/core/scripts/htmlutils.js @@ -18,6 +18,93 @@ // This script contains a badly-organised collection of miscellaneous // functions that really better homes. +function classCreate() { + return function() { + this.initialize.apply(this, arguments); + } +} + +function objectExtend(destination, source) { + for (var property in source) { + destination[property] = source[property]; + } + return destination; +} + +function $() { + var results = [], element; + for (var i = 0; i < arguments.length; i++) { + element = arguments[i]; + if (typeof element == 'string') + element = document.getElementById(element); + results[results.length] = element; + } + return results.length < 2 ? results[0] : results; +} + +function $A(iterable) { + if (!iterable) return []; + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0; i < iterable.length; i++) + results.push(iterable[i]); + return results; + } +} + +function fnBind() { + var args = $A(arguments), __method = args.shift(), object = args.shift(); + var retval = function() { + return __method.apply(object, args.concat($A(arguments))); + } + retval.__method = __method; + return retval; +} + +function fnBindAsEventListener(fn, object) { + var __method = fn; + return function(event) { + return __method.call(object, event || window.event); + } +} + +function removeClassName(element, name) { + var re = new RegExp("\\b" + name + "\\b", "g"); + element.className = element.className.replace(re, ""); +} + +function addClassName(element, name) { + element.className = element.className + ' ' + name; +} + +function elementSetStyle(element, style) { + for (var name in style) { + var value = style[name]; + if (value == null) value = ""; + element.style[name] = value; + } +} + +function elementGetStyle(element, style) { + var value = element.style[style]; + if (!value) { + if (document.defaultView && document.defaultView.getComputedStyle) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css.getPropertyValue(style) : null; + } else if (element.currentStyle) { + value = element.currentStyle[style]; + } + } + + /** DGF necessary? + if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) + if (Element.getStyle(element, 'position') == 'static') value = 'auto'; */ + + return value == 'auto' ? null : value; + } + String.prototype.trim = function() { var result = this.replace(/^\s+/g, ""); // strip leading @@ -134,31 +221,49 @@ function xmlDecode(text) { // Sets the text in this element function setText(element, text) { - if (element.textContent) { + if (element.textContent != null) { element.textContent = text; - } else if (element.innerText) { + } else if (element.innerText != null) { element.innerText = text; } } // Get the value of an <input> element function getInputValue(inputElement) { - if (inputElement.type.toUpperCase() == 'CHECKBOX' || - inputElement.type.toUpperCase() == 'RADIO') - { - return (inputElement.checked ? 'on' : 'off'); + if (inputElement.type) { + if (inputElement.type.toUpperCase() == 'CHECKBOX' || + inputElement.type.toUpperCase() == 'RADIO') + { + return (inputElement.checked ? 'on' : 'off'); + } + } + if (inputElement.value == null) { + throw new SeleniumError("This element has no value; is it really a form field?"); } return inputElement.value; } /* Fire an event in a browser-compatible manner */ -function triggerEvent(element, eventType, canBubble) { +function triggerEvent(element, eventType, canBubble, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown) { canBubble = (typeof(canBubble) == undefined) ? true : canBubble; if (element.fireEvent) { - element.fireEvent('on' + eventType); + var evt = createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown); + element.fireEvent('on' + eventType, evt); } else { var evt = document.createEvent('HTMLEvents'); + + try { + evt.shiftKey = shiftKeyDown; + evt.metaKey = metaKeyDown; + evt.altKey = altKeyDown; + evt.ctrlKey = controlKeyDown; + } catch (e) { + // On Firefox 1.0, you can only set these during initMouseEvent or initKeyEvent + // we'll have to ignore them here + LOG.exception(e); + } + evt.initEvent(eventType, canBubble, true); element.dispatchEvent(evt); } @@ -179,14 +284,23 @@ function getKeyCodeFromKeySequence(keySequence) { if (match != null) { return match[0]; } - throw SeleniumError("invalid keySequence"); + throw new SeleniumError("invalid keySequence"); } -function triggerKeyEvent(element, eventType, keySequence, canBubble) { +function createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown) { + var evt = element.ownerDocument.createEventObject(); + evt.shiftKey = shiftKeyDown; + evt.metaKey = metaKeyDown; + evt.altKey = altKeyDown; + evt.ctrlKey = controlKeyDown; + return evt; +} + +function triggerKeyEvent(element, eventType, keySequence, canBubble, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown) { var keycode = getKeyCodeFromKeySequence(keySequence); canBubble = (typeof(canBubble) == undefined) ? true : canBubble; if (element.fireEvent) { - keyEvent = element.ownerDocument.createEventObject(); + var keyEvent = createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown); keyEvent.keyCode = keycode; element.fireEvent('on' + eventType, keyEvent); } @@ -194,82 +308,24 @@ function triggerKeyEvent(element, eventType, keySequence, canBubble) { var evt; if (window.KeyEvent) { evt = document.createEvent('KeyEvents'); - evt.initKeyEvent(eventType, true, true, window, false, false, false, false, keycode, keycode); + evt.initKeyEvent(eventType, true, true, window, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown, keycode, keycode); } else { evt = document.createEvent('UIEvents'); + + evt.shiftKey = shiftKeyDown; + evt.metaKey = metaKeyDown; + evt.altKey = altKeyDown; + evt.ctrlKey = controlKeyDown; + evt.initUIEvent(eventType, true, true, window, 1); evt.keyCode = keycode; + evt.which = keycode; } element.dispatchEvent(evt); } } -/* Fire a mouse event in a browser-compatible manner */ -function triggerMouseEvent(element, eventType, canBubble, clientX, clientY) { - clientX = clientX ? clientX : 0; - clientY = clientY ? clientY : 0; - - // TODO: set these attributes -- they don't seem to be needed by the initial test cases, but that could change... - var screenX = 0; - var screenY = 0; - - canBubble = (typeof(canBubble) == undefined) ? true : canBubble; - if (element.fireEvent) { - LOG.error("element has fireEvent"); - if (!screenX && !screenY && !clientX && !clientY) { - element.fireEvent('on' + eventType); - } - else { - var ieEvent = element.ownerDocument.createEventObject(); - ieEvent.detail = 0; - ieEvent.screenX = screenX; - ieEvent.screenY = screenY; - ieEvent.clientX = clientX; - ieEvent.clientY = clientY; - ieEvent.ctrlKey = false; - ieEvent.altKey = false; - ieEvent.shiftKey = false; - ieEvent.metaKey = false; - ieEvent.button = 1; - ieEvent.relatedTarget = null; - - // when we go this route, window.event is never set to contain the event we have just created. - // ideally we could just slide it in as follows in the try-block below, but this normally - // doesn't work. This is why I try to avoid this code path, which is only required if we need to - // set attributes on the event (e.g., clientX). - try { - window.event = ieEvent; - } - catch(e) { - // getting an "Object does not support this action or property" error. Save the event away - // for future reference. - // TODO: is there a way to update window.event? - - // work around for http://jira.openqa.org/browse/SEL-280 -- make the event available somewhere: - selenium.browserbot.getCurrentWindow().selenium_event = ieEvent; - } - element.fireEvent('on' + eventType, ieEvent); - } - } - else { - LOG.error("element doesn't have fireEvent"); - var evt = document.createEvent('MouseEvents'); - if (evt.initMouseEvent) - { - LOG.error("element has initMouseEvent"); - //Safari - evt.initMouseEvent(eventType, canBubble, true, document.defaultView, 1, screenX, screenY, clientX, clientY, false, false, false, false, 0, null) - } - else { - LOG.error("element doesen't has initMouseEvent"); - // TODO we should be initialising other mouse-event related attributes here - evt.initEvent(eventType, canBubble, true); - } - element.dispatchEvent(evt); - } -} - function removeLoadListener(element, command) { LOG.info('Removing loadListenter for ' + element + ', ' + command); if (window.removeEventListener) @@ -280,10 +336,13 @@ function removeLoadListener(element, command) { function addLoadListener(element, command) { LOG.info('Adding loadListenter for ' + element + ', ' + command); + var augmentedCommand = function() { + command.call(this, element); + } if (window.addEventListener && !browserVersion.isOpera) - element.addEventListener("load", command, true); + element.addEventListener("load", augmentedCommand, true); else if (window.attachEvent) - element.attachEvent("onload", command); + element.attachEvent("onload", augmentedCommand); } /** @@ -306,10 +365,160 @@ function getDocumentBase(doc) { return ""; } +function getTagName(element) { + var tagName; + if (element && element.tagName && element.tagName.toLowerCase) { + tagName = element.tagName.toLowerCase(); + } + return tagName; +} + +function absolutify(url, baseUrl) { + /** returns a relative url in its absolute form, given by baseUrl. + * + * This function is a little odd, because it can take baseUrls that + * aren't necessarily directories. It uses the same rules as the HTML + * <base> tag; if the baseUrl doesn't end with "/", we'll assume + * that it points to a file, and strip the filename off to find its + * base directory. + * + * So absolutify("foo", "http://x/bar") will return "http://x/foo" (stripping off bar), + * whereas absolutify("foo", "http://x/bar/") will return "http://x/bar/foo" (preserving bar). + * Naturally absolutify("foo", "http://x") will return "http://x/foo", appropriately. + * + * @param url the url to make absolute; if this url is already absolute, we'll just return that, unchanged + * @param baseUrl the baseUrl from which we'll absolutify, following the rules above. + * @return 'url' if it was already absolute, or the absolutized version of url if it was not absolute. + */ + + // DGF isn't there some library we could use for this? + + if (/^\w+:/.test(url)) { + // it's already absolute + return url; + } + + var loc; + try { + loc = parseUrl(baseUrl); + } catch (e) { + // is it an absolute windows file path? let's play the hero in that case + if (/^\w:\\/.test(baseUrl)) { + baseUrl = "file:///" + baseUrl.replace(/\\/g, "/"); + loc = parseUrl(baseUrl); + } else { + throw new SeleniumError("baseUrl wasn't absolute: " + baseUrl); + } + } + loc.search = null; + loc.hash = null; + + // if url begins with /, then that's the whole pathname + if (/^\//.test(url)) { + loc.pathname = url; + var result = reassembleLocation(loc); + return result; + } + + // if pathname is null, then we'll just append "/" + the url + if (!loc.pathname) { + loc.pathname = "/" + url; + var result = reassembleLocation(loc); + return result; + } + + // if pathname ends with /, just append url + if (/\/$/.test(loc.pathname)) { + loc.pathname += url; + var result = reassembleLocation(loc); + return result; + } + + // if we're here, then the baseUrl has a pathname, but it doesn't end with / + // in that case, we replace everything after the final / with the relative url + loc.pathname = loc.pathname.replace(/[^\/\\]+$/, url); + var result = reassembleLocation(loc); + return result; + +} + +var URL_REGEX = /^((\w+):\/\/)(([^:]+):?([^@]+)?@)?([^\/\?:]*):?(\d+)?(\/?[^\?#]+)?\??([^#]+)?#?(.+)?/; + +function parseUrl(url) { + var fields = ['url', null, 'protocol', null, 'username', 'password', 'host', 'port', 'pathname', 'search', 'hash']; + var result = URL_REGEX.exec(url); + if (!result) { + throw new SeleniumError("Invalid URL: " + url); + } + var loc = new Object(); + for (var i = 0; i < fields.length; i++) { + var field = fields[i]; + if (field == null) { + continue; + } + loc[field] = result[i]; + } + return loc; +} + +function reassembleLocation(loc) { + if (!loc.protocol) { + throw new Error("Not a valid location object: " + o2s(loc)); + } + var protocol = loc.protocol; + protocol = protocol.replace(/:$/, ""); + var url = protocol + "://"; + if (loc.username) { + url += loc.username; + if (loc.password) { + url += ":" + loc.password; + } + url += "@"; + } + if (loc.host) { + url += loc.host; + } + + if (loc.port) { + url += ":" + loc.port; + } + + if (loc.pathname) { + url += loc.pathname; + } + + if (loc.search) { + url += "?" + loc.search; + } + if (loc.hash) { + var hash = loc.hash; + hash = loc.hash.replace(/^#/, ""); + url += "#" + hash; + } + return url; +} + +function canonicalize(url) { + var tempLink = window.document.createElement("link"); + tempLink.href = url; // this will canonicalize the href + return tempLink.href; +} + +function extractExceptionMessage(ex) { + if (ex == null) return "null exception"; + if (ex.message != null) return ex.message; + if (ex.toString && ex.toString() != null) return ex.toString(); +} + + function describe(object, delimiter) { var props = new Array(); for (var prop in object) { - props.push(prop + " -> " + object[prop]); + try { + props.push(prop + " -> " + object[prop]); + } catch (e) { + props.push(prop + " -> [htmlutils: ack! couldn't read this property! (Permission Denied?)]"); + } } return props.join(delimiter || '\n'); } @@ -502,28 +711,27 @@ function SeleniumError(message) { return error; } -var Effect = new Object(); - -Object.extend(Effect, { - highlight : function(element) { - var highLightColor = "yellow"; - if (element.originalColor == undefined) { // avoid picking up highlight - element.originalColor = Element.getStyle(element, "background-color"); - } - Element.setStyle(element, {"background-color" : highLightColor}); - window.setTimeout(function() { +function highlight(element) { + var highLightColor = "yellow"; + if (element.originalColor == undefined) { // avoid picking up highlight + element.originalColor = elementGetStyle(element, "background-color"); + } + elementSetStyle(element, {"backgroundColor" : highLightColor}); + window.setTimeout(function() { + try { //if element is orphan, probably page of it has already gone, so ignore if (!element.parentNode) { return; } - Element.setStyle(element, {"background-color" : element.originalColor}); - }, 200); - } -}); + elementSetStyle(element, {"backgroundColor" : element.originalColor}); + } catch (e) {} // DGF unhighlighting is very dangerous and low priority + }, 200); +} + // for use from vs.2003 debugger -function objToString(obj) { +function o2s(obj) { var s = ""; for (key in obj) { var line = key + "->" + obj[key]; @@ -535,7 +743,7 @@ function objToString(obj) { var seenReadyStateWarning = false; -function openSeparateApplicationWindow(url) { +function openSeparateApplicationWindow(url, suppressMozillaWarning) { // resize the Selenium window itself window.resizeTo(1200, 500); window.moveTo(window.screenX, 0); @@ -560,7 +768,7 @@ function openSeparateApplicationWindow(url) { } - if (window.document.readyState == null && !seenReadyStateWarning) { + if (!suppressMozillaWarning && window.document.readyState == null && !seenReadyStateWarning) { alert("Beware! Mozilla bug 300992 means that we can't always reliably detect when a new page has loaded. Install the Selenium IDE extension or the readyState extension available from selenium.openqa.org to make page load detection more reliable."); seenReadyStateWarning = true; } @@ -568,8 +776,8 @@ function openSeparateApplicationWindow(url) { return appWindow; } -var URLConfiguration = Class.create(); -Object.extend(URLConfiguration.prototype, { +var URLConfiguration = classCreate(); +objectExtend(URLConfiguration.prototype, { initialize: function() { }, _isQueryParameterTrue: function (name) { @@ -615,6 +823,11 @@ Object.extend(URLConfiguration.prototype, { isMultiWindowMode:function() { return this._isQueryParameterTrue('multiWindow'); + }, + + getBaseUrl:function() { + return this._getQueryParameter('baseUrl'); + } }); diff --git a/tests/test_tools/selenium/core/scripts/injection.html b/tests/test_tools/selenium/core/scripts/injection.html index d41fbe69..a75c7211 100644 --- a/tests/test_tools/selenium/core/scripts/injection.html +++ b/tests/test_tools/selenium/core/scripts/injection.html @@ -1,21 +1,22 @@ <script language="JavaScript"> if (window["selenium_has_been_loaded_into_this_window"]==null) { -__SELENIUM_JS__ + + __SELENIUM_JS__ // Some background on the code below: broadly speaking, where we are relative to other windows // when running in proxy injection mode depends on whether we are in a frame set file or not. -// +// // In regular HTML files, the selenium JavaScript is injected into an iframe called "selenium" // in order to reduce its impact on the JavaScript environment (through namespace pollution, // etc.). So in regular HTML files, we need to look at the parent of the current window when we want // a handle to, e.g., the application window. -// +// // In frame set files, we can't use an iframe, so we put the JavaScript in the head element and share // the window with the frame set. So in this case, we need to look at the current window, not the -// parent when looking for, e.g., the application window. (TODO: Perhaps I should have just +// parent when looking for, e.g., the application window. (TODO: Perhaps I should have just // assigned a regular frame for selenium?) -// +// BrowserBot.prototype.getContentWindow = function() { if (window["seleniumInSameWindow"] != null) return window; return window.parent; @@ -38,7 +39,7 @@ LOG.openLogWindow = function(message, className) { BrowserBot.prototype.relayToRC = function(name) { var object = eval(name); var s = 'state:' + serializeObject(name, object) + "\n"; - sendToRC(s); + sendToRC(s,"state=true"); } BrowserBot.prototype.relayBotToRC = function(s) { @@ -58,10 +59,16 @@ function seleniumOnLoad() { runSeleniumTest(); } +function seleniumOnUnload() { + sendToRC("OK"); // just in case some poor PI server thread is waiting for a response +} + if (window.addEventListener) { window.addEventListener("load", seleniumOnLoad, false); // firefox + window.addEventListener("unload", seleniumOnUnload, false); // firefox } else if (window.attachEvent){ window.attachEvent("onload", seleniumOnLoad); // IE + window.attachEvent("onunload", seleniumOnUnload); // IE } else { throw "causing a JavaScript error to tell the world that I did not arrange to be run on load"; diff --git a/tests/test_tools/selenium/core/scripts/selenium-api.js b/tests/test_tools/selenium/core/scripts/selenium-api.js index e8e587f7..1646236f 100644 --- a/tests/test_tools/selenium/core/scripts/selenium-api.js +++ b/tests/test_tools/selenium/core/scripts/selenium-api.js @@ -15,157 +15,179 @@ * */ -// TODO: stop navigating this.page().document() ... it breaks encapsulation +// TODO: stop navigating this.browserbot.document() ... it breaks encapsulation var storedVars = new Object(); function Selenium(browserbot) { - /** - * Defines an object that runs Selenium commands. - * - * <h3><a name="locators"></a>Element Locators</h3> - * <p> - * Element Locators tell Selenium which HTML element a command refers to. - * The format of a locator is:</p> - * <blockquote> - * <em>locatorType</em><strong>=</strong><em>argument</em> - * </blockquote> - * - * <p> - * We support the following strategies for locating elements: - * </p> - * <blockquote> - * <dl> - * <dt><strong>identifier</strong>=<em>id</em></dt> - * <dd>Select the element with the specified @id attribute. If no match is - * found, select the first element whose @name attribute is <em>id</em>. - * (This is normally the default; see below.)</dd> - * <dt><strong>id</strong>=<em>id</em></dt> - * <dd>Select the element with the specified @id attribute.</dd> - * - * <dt><strong>name</strong>=<em>name</em></dt> - * <dd>Select the first element with the specified @name attribute.</dd> - * <dd><ul class="first last simple"> - * <li>username</li> - * <li>name=username</li> - * </ul> - * </dd> - * <dd>The name may optionally be followed by one or more <em>element-filters</em>, separated from the name by whitespace. If the <em>filterType</em> is not specified, <strong>value</strong> is assumed.</dd> - * - * <dd><ul class="first last simple"> - * <li>name=flavour value=chocolate</li> - * </ul> - * </dd> - * <dt><strong>dom</strong>=<em>javascriptExpression</em></dt> - * - * <dd> - * - * <dd>Find an element using JavaScript traversal of the HTML Document Object - * Model. DOM locators <em>must</em> begin with "document.". - * <ul class="first last simple"> - * <li>dom=document.forms['myForm'].myDropdown</li> - * <li>dom=document.images[56]</li> - * </ul> - * </dd> - * - * </dd> - * - * <dt><strong>xpath</strong>=<em>xpathExpression</em></dt> - * <dd>Locate an element using an XPath expression. - * <ul class="first last simple"> - * <li>xpath=//img[@alt='The image alt text']</li> - * <li>xpath=//table[@id='table1']//tr[4]/td[2]</li> - * - * </ul> - * </dd> - * <dt><strong>link</strong>=<em>textPattern</em></dt> - * <dd>Select the link (anchor) element which contains text matching the - * specified <em>pattern</em>. - * <ul class="first last simple"> - * <li>link=The link text</li> - * </ul> - * - * </dd> - * - * <dt><strong>css</strong>=<em>cssSelectorSyntax</em></dt> - * <dd>Select the element using css selectors. Please refer to <a href="http://www.w3.org/TR/REC-CSS2/selector.html">CSS2 selectors</a>, <a href="http://www.w3.org/TR/2001/CR-css3-selectors-20011113/">CSS3 selectors</a> for more information. You can also check the TestCssLocators test in the selenium test suite for an example of usage, which is included in the downloaded selenium core package. - * <ul class="first last simple"> - * <li>css=a[href="#id3"]</li> - * <li>css=span#firstChild + span</li> - * </ul> - * </dd> - * <dd>Currently the css selector locator supports all css1, css2 and css3 selectors except namespace in css3, some pseudo classes(:nth-of-type, :nth-last-of-type, :first-of-type, :last-of-type, :only-of-type, :visited, :hover, :active, :focus, :indeterminate) and pseudo elements(::first-line, ::first-letter, ::selection, ::before, ::after). </dd> - * </dl> - * </blockquote> - * <p> - * Without an explicit locator prefix, Selenium uses the following default - * strategies: - * </p> - * - * <ul class="simple"> - * <li><strong>dom</strong>, for locators starting with "document."</li> - * <li><strong>xpath</strong>, for locators starting with "//"</li> - * <li><strong>identifier</strong>, otherwise</li> - * </ul> - * - * <h3><a name="element-filters">Element Filters</a></h3> - * <blockquote> - * <p>Element filters can be used with a locator to refine a list of candidate elements. They are currently used only in the 'name' element-locator.</p> - * <p>Filters look much like locators, ie.</p> - * <blockquote> - * <em>filterType</em><strong>=</strong><em>argument</em></blockquote> - * - * <p>Supported element-filters are:</p> - * <p><strong>value=</strong><em>valuePattern</em></p> - * <blockquote> - * Matches elements based on their values. This is particularly useful for refining a list of similarly-named toggle-buttons.</blockquote> - * <p><strong>index=</strong><em>index</em></p> - * <blockquote> - * Selects a single element based on its position in the list (offset from zero).</blockquote> - * </blockquote> - * - * <h3><a name="patterns"></a>String-match Patterns</h3> - * - * <p> - * Various Pattern syntaxes are available for matching string values: - * </p> - * <blockquote> - * <dl> - * <dt><strong>glob:</strong><em>pattern</em></dt> - * <dd>Match a string against a "glob" (aka "wildmat") pattern. "Glob" is a - * kind of limited regular-expression syntax typically used in command-line - * shells. In a glob pattern, "*" represents any sequence of characters, and "?" - * represents any single character. Glob patterns match against the entire - * string.</dd> - * <dt><strong>regexp:</strong><em>regexp</em></dt> - * <dd>Match a string using a regular-expression. The full power of JavaScript - * regular-expressions is available.</dd> - * <dt><strong>exact:</strong><em>string</em></dt> - * - * <dd>Match a string exactly, verbatim, without any of that fancy wildcard - * stuff.</dd> - * </dl> - * </blockquote> - * <p> - * If no pattern prefix is specified, Selenium assumes that it's a "glob" - * pattern. - * </p> - */ + /** + * Defines an object that runs Selenium commands. + * + * <h3><a name="locators"></a>Element Locators</h3> + * <p> + * Element Locators tell Selenium which HTML element a command refers to. + * The format of a locator is:</p> + * <blockquote> + * <em>locatorType</em><strong>=</strong><em>argument</em> + * </blockquote> + * + * <p> + * We support the following strategies for locating elements: + * </p> + * + * <ul> + * <li><strong>identifier</strong>=<em>id</em>: + * Select the element with the specified @id attribute. If no match is + * found, select the first element whose @name attribute is <em>id</em>. + * (This is normally the default; see below.)</li> + * <li><strong>id</strong>=<em>id</em>: + * Select the element with the specified @id attribute.</li> + * + * <li><strong>name</strong>=<em>name</em>: + * Select the first element with the specified @name attribute. + * <ul class="first last simple"> + * <li>username</li> + * <li>name=username</li> + * </ul> + * + * <p>The name may optionally be followed by one or more <em>element-filters</em>, separated from the name by whitespace. If the <em>filterType</em> is not specified, <strong>value</strong> is assumed.</p> + * + * <ul class="first last simple"> + * <li>name=flavour value=chocolate</li> + * </ul> + * </li> + * <li><strong>dom</strong>=<em>javascriptExpression</em>: + * + * Find an element by evaluating the specified string. This allows you to traverse the HTML Document Object + * Model using JavaScript. Note that you must not return a value in this string; simply make it the last expression in the block. + * <ul class="first last simple"> + * <li>dom=document.forms['myForm'].myDropdown</li> + * <li>dom=document.images[56]</li> + * <li>dom=function foo() { return document.links[1]; }; foo();</li> + * </ul> + * + * </li> + * + * <li><strong>xpath</strong>=<em>xpathExpression</em>: + * Locate an element using an XPath expression. + * <ul class="first last simple"> + * <li>xpath=//img[@alt='The image alt text']</li> + * <li>xpath=//table[@id='table1']//tr[4]/td[2]</li> + * <li>xpath=//a[contains(@href,'#id1')]</li> + * <li>xpath=//a[contains(@href,'#id1')]/@class</li> + * <li>xpath=(//table[@class='stylee'])//th[text()='theHeaderText']/../td</li> + * <li>xpath=//input[@name='name2' and @value='yes']</li> + * <li>xpath=//*[text()="right"]</li> + * + * </ul> + * </li> + * <li><strong>link</strong>=<em>textPattern</em>: + * Select the link (anchor) element which contains text matching the + * specified <em>pattern</em>. + * <ul class="first last simple"> + * <li>link=The link text</li> + * </ul> + * + * </li> + * + * <li><strong>css</strong>=<em>cssSelectorSyntax</em>: + * Select the element using css selectors. Please refer to <a href="http://www.w3.org/TR/REC-CSS2/selector.html">CSS2 selectors</a>, <a href="http://www.w3.org/TR/2001/CR-css3-selectors-20011113/">CSS3 selectors</a> for more information. You can also check the TestCssLocators test in the selenium test suite for an example of usage, which is included in the downloaded selenium core package. + * <ul class="first last simple"> + * <li>css=a[href="#id3"]</li> + * <li>css=span#firstChild + span</li> + * </ul> + * <p>Currently the css selector locator supports all css1, css2 and css3 selectors except namespace in css3, some pseudo classes(:nth-of-type, :nth-last-of-type, :first-of-type, :last-of-type, :only-of-type, :visited, :hover, :active, :focus, :indeterminate) and pseudo elements(::first-line, ::first-letter, ::selection, ::before, ::after). </p> + * </li> + * </ul> + * + * <p> + * Without an explicit locator prefix, Selenium uses the following default + * strategies: + * </p> + * + * <ul class="simple"> + * <li><strong>dom</strong>, for locators starting with "document."</li> + * <li><strong>xpath</strong>, for locators starting with "//"</li> + * <li><strong>identifier</strong>, otherwise</li> + * </ul> + * + * <h3><a name="element-filters">Element Filters</a></h3> + * <blockquote> + * <p>Element filters can be used with a locator to refine a list of candidate elements. They are currently used only in the 'name' element-locator.</p> + * <p>Filters look much like locators, ie.</p> + * <blockquote> + * <em>filterType</em><strong>=</strong><em>argument</em></blockquote> + * + * <p>Supported element-filters are:</p> + * <p><strong>value=</strong><em>valuePattern</em></p> + * <blockquote> + * Matches elements based on their values. This is particularly useful for refining a list of similarly-named toggle-buttons.</blockquote> + * <p><strong>index=</strong><em>index</em></p> + * <blockquote> + * Selects a single element based on its position in the list (offset from zero).</blockquote> + * </blockquote> + * + * <h3><a name="patterns"></a>String-match Patterns</h3> + * + * <p> + * Various Pattern syntaxes are available for matching string values: + * </p> + * <ul> + * <li><strong>glob:</strong><em>pattern</em>: + * Match a string against a "glob" (aka "wildmat") pattern. "Glob" is a + * kind of limited regular-expression syntax typically used in command-line + * shells. In a glob pattern, "*" represents any sequence of characters, and "?" + * represents any single character. Glob patterns match against the entire + * string.</li> + * <li><strong>regexp:</strong><em>regexp</em>: + * Match a string using a regular-expression. The full power of JavaScript + * regular-expressions is available.</li> + * <li><strong>exact:</strong><em>string</em>: + * + * Match a string exactly, verbatim, without any of that fancy wildcard + * stuff.</li> + * </ul> + * <p> + * If no pattern prefix is specified, Selenium assumes that it's a "glob" + * pattern. + * </p> + */ this.browserbot = browserbot; this.optionLocatorFactory = new OptionLocatorFactory(); + // DGF for backwards compatibility this.page = function() { - return browserbot.getCurrentPage(); + return browserbot; }; this.defaultTimeout = Selenium.DEFAULT_TIMEOUT; + this.mouseSpeed = 10; } Selenium.DEFAULT_TIMEOUT = 30 * 1000; +Selenium.DEFAULT_MOUSE_SPEED = 10; + +Selenium.decorateFunctionWithTimeout = function(f, timeout) { + if (f == null) { + return null; + } + var timeoutValue = parseInt(timeout); + if (isNaN(timeoutValue)) { + throw new SeleniumError("Timeout is not a number: '" + timeout + "'"); + } + var now = new Date().getTime(); + var timeoutTime = now + timeoutValue; + return function() { + if (new Date().getTime() > timeoutTime) { + throw new SeleniumError("Timed out after " + timeoutValue + "ms"); + } + return f(); + }; +} -Selenium.createForWindow = function(window) { +Selenium.createForWindow = function(window, proxyInjectionMode) { if (!window.location) { throw "error: not a window!"; } - return new Selenium(BrowserBot.createForWindow(window)); + return new Selenium(BrowserBot.createForWindow(window, proxyInjectionMode)); }; Selenium.prototype.reset = function() { @@ -176,7 +198,7 @@ Selenium.prototype.reset = function() { }; Selenium.prototype.doClick = function(locator) { - /** + /** * 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. @@ -184,43 +206,69 @@ Selenium.prototype.doClick = function(locator) { * @param locator an element locator * */ - var element = this.page().findElement(locator); - this.page().clickElement(element); + var element = this.browserbot.findElement(locator); + this.browserbot.clickElement(element); +}; + +Selenium.prototype.doDoubleClick = function(locator) { + /** + * Double clicks on a link, button, checkbox or radio button. If the double click action + * causes a new page to load (like a link usually does), call + * waitForPageToLoad. + * + * @param locator an element locator + * + */ + var element = this.browserbot.findElement(locator); + this.browserbot.doubleClickElement(element); }; Selenium.prototype.doClickAt = 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. * - * Beware of http://jira.openqa.org/browse/SEL-280, which will lead some event handlers to - * get null event arguments. Read the bug for more details, including a workaround. + * @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.browserbot.findElement(locator); + var clientXY = getClientXY(element, coordString) + this.browserbot.clickElement(element, clientXY[0], clientXY[1]); +}; + +Selenium.prototype.doDoubleClickAt = function(locator, coordString) { + /** + * Doubleclicks on a link, button, checkbox or radio button. If the 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 element = this.browserbot.findElement(locator); var clientXY = getClientXY(element, coordString) - this.page().clickElement(element, clientXY[0], clientXY[1]); + this.browserbot.doubleClickElement(element, clientXY[0], clientXY[1]); }; Selenium.prototype.doFireEvent = function(locator, eventName) { - /** + /** * Explicitly simulate an event, to trigger the corresponding "on<em>event</em>" * handler. * * @param locator an <a href="#locators">element locator</a> * @param eventName the event name, e.g. "focus" or "blur" */ - var element = this.page().findElement(locator); + var element = this.browserbot.findElement(locator); triggerEvent(element, eventName, false); }; Selenium.prototype.doKeyPress = function(locator, keySequence) { - /** + /** * Simulates a user pressing and releasing a key. * * @param locator an <a href="#locators">element locator</a> @@ -228,12 +276,80 @@ Selenium.prototype.doKeyPress = function(locator, keySequence) { * of the key to be pressed, normally the ASCII value of that key), or a single * character. For example: "w", "\119". */ - var element = this.page().findElement(locator); - triggerKeyEvent(element, 'keypress', keySequence, true); + var element = this.browserbot.findElement(locator); + triggerKeyEvent(element, 'keypress', keySequence, true, + this.browserbot.controlKeyDown, + this.browserbot.altKeyDown, + this.browserbot.shiftKeyDown, + this.browserbot.metaKeyDown); +}; + +Selenium.prototype.doShiftKeyDown = function() { + /** + * Press the shift key and hold it down until doShiftUp() is called or a new page is loaded. + * + */ + this.browserbot.shiftKeyDown = true; +}; + +Selenium.prototype.doShiftKeyUp = function() { + /** + * Release the shift key. + * + */ + this.browserbot.shiftKeyDown = false; +}; + +Selenium.prototype.doMetaKeyDown = function() { + /** + * Press the meta key and hold it down until doMetaUp() is called or a new page is loaded. + * + */ + this.browserbot.metaKeyDown = true; +}; + +Selenium.prototype.doMetaKeyUp = function() { + /** + * Release the meta key. + * + */ + this.browserbot.metaKeyDown = false; +}; + +Selenium.prototype.doAltKeyDown = function() { + /** + * Press the alt key and hold it down until doAltUp() is called or a new page is loaded. + * + */ + this.browserbot.altKeyDown = true; +}; + +Selenium.prototype.doAltKeyUp = function() { + /** + * Release the alt key. + * + */ + this.browserbot.altKeyDown = false; +}; + +Selenium.prototype.doControlKeyDown = function() { + /** + * Press the control key and hold it down until doControlUp() is called or a new page is loaded. + * + */ + this.browserbot.controlKeyDown = true; +}; + +Selenium.prototype.doControlKeyUp = function() { + /** + * Release the control key. + * + */ + this.browserbot.controlKeyDown = false; }; Selenium.prototype.doKeyDown = function(locator, keySequence) { - /** + /** * Simulates a user pressing a key (without releasing it yet). * * @param locator an <a href="#locators">element locator</a> @@ -241,12 +357,16 @@ Selenium.prototype.doKeyDown = function(locator, keySequence) { * of the key to be pressed, normally the ASCII value of that key), or a single * character. For example: "w", "\119". */ - var element = this.page().findElement(locator); - triggerKeyEvent(element, 'keydown', keySequence, true); + var element = this.browserbot.findElement(locator); + triggerKeyEvent(element, 'keydown', keySequence, true, + this.browserbot.controlKeyDown, + this.browserbot.altKeyDown, + this.browserbot.shiftKeyDown, + this.browserbot.metaKeyDown); }; Selenium.prototype.doKeyUp = function(locator, keySequence) { - /** + /** * Simulates a user releasing a key. * * @param locator an <a href="#locators">element locator</a> @@ -254,8 +374,12 @@ Selenium.prototype.doKeyUp = function(locator, keySequence) { * of the key to be pressed, normally the ASCII value of that key), or a single * character. For example: "w", "\119". */ - var element = this.page().findElement(locator); - triggerKeyEvent(element, 'keyup', keySequence, true); + var element = this.browserbot.findElement(locator); + triggerKeyEvent(element, 'keyup', keySequence, true, + this.browserbot.controlKeyDown, + this.browserbot.altKeyDown, + this.browserbot.shiftKeyDown, + this.browserbot.metaKeyDown); }; function getClientXY(element, coordString) { @@ -278,13 +402,13 @@ function getClientXY(element, coordString) { } Selenium.prototype.doMouseOver = function(locator) { - /** + /** * Simulates a user hovering a mouse over the specified element. * * @param locator an <a href="#locators">element locator</a> */ - var element = this.page().findElement(locator); - triggerMouseEvent(element, 'mouseover', true); + var element = this.browserbot.findElement(locator); + this.browserbot.triggerMouseEvent(element, 'mouseover', true); }; Selenium.prototype.doMouseOut = function(locator) { @@ -293,100 +417,91 @@ Selenium.prototype.doMouseOut = function(locator) { * * @param locator an <a href="#locators">element locator</a> */ - var element = this.page().findElement(locator); - triggerMouseEvent(element, 'mouseout', true); + var element = this.browserbot.findElement(locator); + this.browserbot.triggerMouseEvent(element, 'mouseout', true); }; Selenium.prototype.doMouseDown = function(locator) { - /** + /** * Simulates a user pressing the mouse button (without releasing it yet) on * the specified element. * * @param locator an <a href="#locators">element locator</a> */ - var element = this.page().findElement(locator); - triggerMouseEvent(element, 'mousedown', true); + var element = this.browserbot.findElement(locator); + this.browserbot.triggerMouseEvent(element, 'mousedown', true); }; Selenium.prototype.doMouseDownAt = function(locator, coordString) { - /** + /** * Simulates a user pressing the mouse button (without releasing it yet) on * the specified element. * - * Beware of http://jira.openqa.org/browse/SEL-280, which will lead some event handlers to - * get null event arguments. Read the bug for more details, including a workaround. - * * @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 element = this.browserbot.findElement(locator); var clientXY = getClientXY(element, coordString) - triggerMouseEvent(element, 'mousedown', true, clientXY[0], clientXY[1]); + this.browserbot.triggerMouseEvent(element, 'mousedown', true, clientXY[0], clientXY[1]); }; Selenium.prototype.doMouseUp = function(locator) { - /** + /** * Simulates a user pressing the mouse button (without releasing it yet) on * the specified element. * * @param locator an <a href="#locators">element locator</a> */ - var element = this.page().findElement(locator); - triggerMouseEvent(element, 'mouseup', true); + var element = this.browserbot.findElement(locator); + this.browserbot.triggerMouseEvent(element, 'mouseup', true); }; Selenium.prototype.doMouseUpAt = function(locator, coordString) { - /** + /** * Simulates a user pressing the mouse button (without releasing it yet) on * the specified element. * - * Beware of http://jira.openqa.org/browse/SEL-280, which will lead some event handlers to - * get null event arguments. Read the bug for more details, including a workaround. - * * @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 element = this.browserbot.findElement(locator); var clientXY = getClientXY(element, coordString) - triggerMouseEvent(element, 'mouseup', true, clientXY[0], clientXY[1]); + this.browserbot.triggerMouseEvent(element, 'mouseup', true, clientXY[0], clientXY[1]); }; Selenium.prototype.doMouseMove = function(locator) { - /** + /** * Simulates a user pressing the mouse button (without releasing it yet) on * the specified element. * * @param locator an <a href="#locators">element locator</a> */ - var element = this.page().findElement(locator); - triggerMouseEvent(element, 'mousemove', true); + var element = this.browserbot.findElement(locator); + this.browserbot.triggerMouseEvent(element, 'mousemove', true); }; Selenium.prototype.doMouseMoveAt = function(locator, coordString) { - /** + /** * Simulates a user pressing the mouse button (without releasing it yet) on * the specified element. * - * Beware of http://jira.openqa.org/browse/SEL-280, which will lead some event handlers to - * get null event arguments. Read the bug for more details, including a workaround. - * * @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 element = this.browserbot.findElement(locator); var clientXY = getClientXY(element, coordString) - triggerMouseEvent(element, 'mousemove', true, clientXY[0], clientXY[1]); + this.browserbot.triggerMouseEvent(element, 'mousemove', true, clientXY[0], clientXY[1]); }; Selenium.prototype.doType = function(locator, value) { - /** + /** * Sets the value of an input field, as though you typed it in. * * <p>Can also be used to set the value of combo boxes, check boxes, etc. In these cases, @@ -395,13 +510,65 @@ Selenium.prototype.doType = function(locator, value) { * @param locator an <a href="#locators">element locator</a> * @param value the value to type */ - // TODO fail if it can't be typed into. - var element = this.page().findElement(locator); - this.page().replaceText(element, value); + if (this.browserbot.controlKeyDown || this.browserbot.altKeyDown || this.browserbot.metaKeyDown) { + throw new SeleniumError("type not supported immediately after call to controlKeyDown() or altKeyDown() or metaKeyDown()"); + } + // TODO fail if it can't be typed into. + var element = this.browserbot.findElement(locator); + if (this.browserbot.shiftKeyDown) { + value = new String(value).toUpperCase(); + } + this.browserbot.replaceText(element, value); +}; + +Selenium.prototype.doTypeKeys = function(locator, value) { + /** + * Simulates keystroke events on the specified element, as though you typed the value key-by-key. + * + * <p>This is a convenience method for calling keyDown, keyUp, keyPress for every character in the specified string; + * this is useful for dynamic UI widgets (like auto-completing combo boxes) that require explicit key events.</p> + * + * <p>Unlike the simple "type" command, which forces the specified value into the page directly, this command + * may or may not have any visible effect, even in cases where typing keys would normally have a visible effect. + * For example, if you use "typeKeys" on a form element, you may or may not see the results of what you typed in + * the field.</p> + * <p>In some cases, you may need to use the simple "type" command to set the value of the field and then the "typeKeys" command to + * send the keystroke events corresponding to what you just typed.</p> + * + * @param locator an <a href="#locators">element locator</a> + * @param value the value to type + */ + var keys = new String(value).split(""); + for (var i = 0; i < keys.length; i++) { + var c = keys[i]; + this.doKeyDown(locator, c); + this.doKeyUp(locator, c); + this.doKeyPress(locator, c); + } +}; + +Selenium.prototype.doSetSpeed = function(value) { + /** + * Set execution speed (i.e., set the millisecond length of a delay which will follow each selenium operation). By default, there is no such delay, i.e., + * the delay is 0 milliseconds. + * + * @param value the number of milliseconds to pause after operation + */ + throw new SeleniumError("this operation is only implemented in selenium-rc, and should never result in a request making it across the wire"); +}; + +Selenium.prototype.doGetSpeed = function() { + /** + * Get execution speed (i.e., get the millisecond length of the delay following each selenium operation). By default, there is no such delay, i.e., + * the delay is 0 milliseconds. + * + * See also setSpeed. + */ + throw new SeleniumError("this operation is only implemented in selenium-rc, and should never result in a request making it across the wire"); }; Selenium.prototype.findToggleButton = function(locator) { - var element = this.page().findElement(locator); + var element = this.browserbot.findElement(locator); if (element.checked == null) { Assert.fail("Element " + locator + " is not a toggle-button."); } @@ -409,7 +576,7 @@ Selenium.prototype.findToggleButton = function(locator) { } Selenium.prototype.doCheck = function(locator) { - /** + /** * Check a toggle-button (checkbox/radio) * * @param locator an <a href="#locators">element locator</a> @@ -418,7 +585,7 @@ Selenium.prototype.doCheck = function(locator) { }; Selenium.prototype.doUncheck = function(locator) { - /** + /** * Uncheck a toggle-button (checkbox/radio) * * @param locator an <a href="#locators">element locator</a> @@ -427,7 +594,7 @@ Selenium.prototype.doUncheck = function(locator) { }; Selenium.prototype.doSelect = function(selectLocator, optionLocator) { - /** + /** * Select an option from a drop-down using an option locator. * * <p> @@ -436,37 +603,37 @@ Selenium.prototype.doSelect = function(selectLocator, optionLocator) { * that the selected option satisfies a specification). There are several * forms of Select Option Locator. * </p> - * <dl> - * <dt><strong>label</strong>=<em>labelPattern</em></dt> - * <dd>matches options based on their labels, i.e. the visible text. (This + * <ul> + * <li><strong>label</strong>=<em>labelPattern</em>: + * matches options based on their labels, i.e. the visible text. (This * is the default.) * <ul class="first last simple"> * <li>label=regexp:^[Oo]ther</li> * </ul> - * </dd> - * <dt><strong>value</strong>=<em>valuePattern</em></dt> - * <dd>matches options based on their values. + * </li> + * <li><strong>value</strong>=<em>valuePattern</em>: + * matches options based on their values. * <ul class="first last simple"> * <li>value=other</li> * </ul> * * - * </dd> - * <dt><strong>id</strong>=<em>id</em></dt> + * </li> + * <li><strong>id</strong>=<em>id</em>: * - * <dd>matches options based on their ids. + * matches options based on their ids. * <ul class="first last simple"> * <li>id=option1</li> * </ul> - * </dd> - * <dt><strong>index</strong>=<em>index</em></dt> - * <dd>matches an option based on its index (offset from zero). + * </li> + * <li><strong>index</strong>=<em>index</em>: + * matches an option based on its index (offset from zero). * <ul class="first last simple"> * * <li>index=2</li> * </ul> - * </dd> - * </dl> + * </li> + * </ul> * <p> * If no option locator prefix is provided, the default behaviour is to match on <strong>label</strong>. * </p> @@ -475,15 +642,17 @@ Selenium.prototype.doSelect = function(selectLocator, optionLocator) { * @param selectLocator an <a href="#locators">element locator</a> identifying a drop-down menu * @param optionLocator an option locator (a label by default) */ - var element = this.page().findElement(selectLocator); + var element = this.browserbot.findElement(selectLocator); if (!("options" in element)) { throw new SeleniumError("Specified element is not a Select (has no options)"); } var locator = this.optionLocatorFactory.fromLocatorString(optionLocator); var option = locator.findOption(element); - this.page().selectOption(element, option); + this.browserbot.selectOption(element, option); }; + + Selenium.prototype.doAddSelection = function(locator, optionLocator) { /** * Add a selection to the set of selected options in a multi-select element using an option locator. @@ -493,13 +662,13 @@ Selenium.prototype.doAddSelection = function(locator, optionLocator) { * @param locator an <a href="#locators">element locator</a> identifying a multi-select box * @param optionLocator an option locator (a label by default) */ - var element = this.page().findElement(locator); + var element = this.browserbot.findElement(locator); if (!("options" in element)) { throw new SeleniumError("Specified element is not a Select (has no options)"); } var locator = this.optionLocatorFactory.fromLocatorString(optionLocator); var option = locator.findOption(element); - this.page().addSelection(element, option); + this.browserbot.addSelection(element, option); }; Selenium.prototype.doRemoveSelection = function(locator, optionLocator) { @@ -512,45 +681,39 @@ Selenium.prototype.doRemoveSelection = function(locator, optionLocator) { * @param optionLocator an option locator (a label by default) */ - var element = this.page().findElement(locator); + var element = this.browserbot.findElement(locator); if (!("options" in element)) { throw new SeleniumError("Specified element is not a Select (has no options)"); } var locator = this.optionLocatorFactory.fromLocatorString(optionLocator); var option = locator.findOption(element); - this.page().removeSelection(element, option); + this.browserbot.removeSelection(element, option); }; +Selenium.prototype.doRemoveAllSelections = function(locator) { + /** + * Unselects all of the selected options in a multi-select element. + * + * @param locator an <a href="#locators">element locator</a> identifying a multi-select box + */ + var element = this.browserbot.findElement(locator); + if (!("options" in element)) { + throw new SeleniumError("Specified element is not a Select (has no options)"); + } + for (var i = 0; i < element.options.length; i++) { + this.browserbot.removeSelection(element, element.options[i]); + } +} + Selenium.prototype.doSubmit = function(formLocator) { - /** + /** * Submit the specified form. This is particularly useful for forms without * submit buttons, e.g. single-input "Search" forms. * * @param formLocator an <a href="#locators">element locator</a> for the form you want to submit */ - var form = this.page().findElement(formLocator); - var actuallySubmit = true; - if (form.onsubmit) { - if (browserVersion.isHTA) { - // run the code in the correct window so alerts are handled correctly even in HTA mode - var win = this.browserbot.getCurrentWindow(); - var now = new Date().getTime(); - var marker = 'marker' + now; - win[marker] = form; - win.setTimeout("var actuallySubmit = "+marker+".onsubmit(); if (actuallySubmit) { "+marker+".submit(); };", 0); - // pause for at least 20ms for this command to run - testLoop.waitForCondition = function () { - return new Date().getTime() > (now + 20); - } - } else { - actuallySubmit = form.onsubmit(); - if (actuallySubmit) { - form.submit(); - } - } - } else { - form.submit(); - } + var form = this.browserbot.findElement(formLocator); + return this.browserbot.submit(form); }; @@ -558,11 +721,11 @@ Selenium.prototype.makePageLoadCondition = function(timeout) { if (timeout == null) { timeout = this.defaultTimeout; } - return decorateFunctionWithTimeout(this._isNewPageLoaded.bind(this), timeout); + return Selenium.decorateFunctionWithTimeout(fnBind(this._isNewPageLoaded, this), timeout); }; Selenium.prototype.doOpen = function(url) { - /** + /** * Opens an URL in the test frame. This accepts both relative and absolute * URLs. * @@ -580,35 +743,70 @@ Selenium.prototype.doOpen = function(url) { return this.makePageLoadCondition(); }; +Selenium.prototype.doOpenWindow = function(url, windowID) { + /** + * Opens a popup window (if a window with that ID isn't already open). + * After opening the window, you'll need to select it using the selectWindow + * command. + * + * <p>This command can also be a useful workaround for bug SEL-339. In some cases, Selenium will be unable to intercept a call to window.open (if the call occurs during or before the "onLoad" event, for example). + * In those cases, you can force Selenium to notice the open window's name by using the Selenium openWindow command, using + * an empty (blank) url, like this: openWindow("", "myFunnyWindow").</p> + * + * @param url the URL to open, which can be blank + * @param windowID the JavaScript window ID of the window to select + */ + this.browserbot.openWindow(url, windowID); +}; + Selenium.prototype.doSelectWindow = function(windowID) { - /** + /** * Selects a popup window; once a popup window has been selected, all - * commands go to that window. To select the main window again, use "null" + * commands go to that window. To select the main window again, use null * as the target. * + * <p>Selenium has several strategies for finding the window object referred to by the "windowID" parameter.</p> + * + * <p>1.) if windowID is null, then it is assumed the user is referring to the original window instantiated by the browser).</p> + * <p>2.) if the value of the "windowID" parameter is a JavaScript variable name in the current application window, then it is assumed + * that this variable contains the return value from a call to the JavaScript window.open() method.</p> + * <p>3.) Otherwise, selenium looks in a hash it maintains that maps string names to window objects. Each of these string + * names matches the second parameter "windowName" past to the JavaScript method window.open(url, windowName, windowFeatures, replaceFlag) + * (which selenium intercepts).</p> + * + * <p>If you're having trouble figuring out what is the name of a window that you want to manipulate, look at the selenium log messages + * which identify the names of windows created via window.open (and therefore intercepted by selenium). You will see messages + * like the following for each window as it is opened:</p> + * + * <p><code>debug: window.open call intercepted; window ID (which you can use with selectWindow()) is "myNewWindow"</code></p> + * + * <p>In some cases, Selenium will be unable to intercept a call to window.open (if the call occurs during or before the "onLoad" event, for example). + * (This is bug SEL-339.) In those cases, you can force Selenium to notice the open window's name by using the Selenium openWindow command, using + * an empty (blank) url, like this: openWindow("", "myFunnyWindow").</p> + * * @param windowID the JavaScript window ID of the window to select */ this.browserbot.selectWindow(windowID); }; Selenium.prototype.doSelectFrame = function(locator) { - /** - * Selects a frame within the current window. (You may invoke this command - * multiple times to select nested frames.) To select the parent frame, use - * "relative=parent" as a locator; to select the top frame, use "relative=top". - * - * <p>You may also use a DOM expression to identify the frame you want directly, - * like this: <code>dom=frames["main"].frames["subframe"]</code></p> - * - * @param locator an <a href="#locators">element locator</a> identifying a frame or iframe - */ + /** + * Selects a frame within the current window. (You may invoke this command + * multiple times to select nested frames.) To select the parent frame, use + * "relative=parent" as a locator; to select the top frame, use "relative=top". + * + * <p>You may also use a DOM expression to identify the frame you want directly, + * like this: <code>dom=frames["main"].frames["subframe"]</code></p> + * + * @param locator an <a href="#locators">element locator</a> identifying a frame or iframe + */ this.browserbot.selectFrame(locator); }; Selenium.prototype.getLogMessages = function() { - /** + /** * Return the contents of the log. - * + * * <p>This is a placeholder intended to make the code generator make this API * available to clients. The selenium server will intercept this call, however, * and return its recordkeeping of log messages since the last call to this API. @@ -617,9 +815,9 @@ Selenium.prototype.getLogMessages = function() { * <p>The reason I opted for a servercentric solution is to be able to support * multiple frames served from different domains, which would break a * centralized JavaScript logging mechanism under some conditions.</p> - * + * * @return string all log messages seen since the last call to this API - */ + */ return "getLogMessages should be implemented in the selenium server"; }; @@ -678,52 +876,68 @@ Selenium.prototype.getWhetherThisFrameMatchFrameExpression = function(currentFra return false; }; -Selenium.prototype.doWaitForPopUp = function(windowID, timeout) { - /** - * Waits for a popup window to appear and load up. - * - * @param windowID the JavaScript window ID of the window that will appear - * @param timeout a timeout in milliseconds, after which the action will return with an error - */ - if (isNaN(timeout)) { - throw new SeleniumError("Timeout is not a number: " + timeout); - } +Selenium.prototype.getWhetherThisWindowMatchWindowExpression = function(currentWindowString, target) { + /** + * Determine whether currentWindowString plus target identify the window containing this running code. + * + * <p>This is useful in proxy injection mode, where this code runs in every + * browser frame and window, and sometimes the selenium server needs to identify + * the "current" window. In this case, when the test calls selectWindow, this + * routine is called for each window to figure out which one has been selected. + * The selected window will return true, while all others will return false.</p> + * + * @param currentWindowString starting window + * @param target new window (which might be relative to the current one, e.g., "_parent") + * @return boolean true if the new window is this code's window + */ + if (window.opener!=null && window.opener[target]!=null && window.opener[target]==window) { + return true; + } + return false; +}; +Selenium.prototype.doWaitForPopUp = function(windowID, timeout) { + /** + * Waits for a popup window to appear and load up. + * + * @param windowID the JavaScript window ID of the window that will appear + * @param timeout a timeout in milliseconds, after which the action will return with an error + */ var popupLoadedPredicate = function () { var targetWindow = selenium.browserbot.getWindowByName(windowID, true); if (!targetWindow) return false; if (!targetWindow.location) return false; if ("about:blank" == targetWindow.location) return false; if (browserVersion.isKonqueror) { - if ("/" == targetWindow.location.href) { - // apparently Konqueror uses this as the temporary location, instead of about:blank - return false; - } + if ("/" == targetWindow.location.href) { + // apparently Konqueror uses this as the temporary location, instead of about:blank + return false; + } } if (browserVersion.isSafari) { - if(targetWindow.location.href == selenium.browserbot.buttonWindow.location.href) { - // Apparently Safari uses this as the temporary location, instead of about:blank - // what a world! - LOG.debug("DGF what a world!"); - return false; - } + if(targetWindow.location.href == selenium.browserbot.buttonWindow.location.href) { + // Apparently Safari uses this as the temporary location, instead of about:blank + // what a world! + LOG.debug("DGF what a world!"); + return false; + } } if (!targetWindow.document) return false; if (!selenium.browserbot.getCurrentWindow().document.readyState) { - // This is Firefox, with no readyState extension - return true; - } + // This is Firefox, with no readyState extension + return true; + } if ('complete' != targetWindow.document.readyState) return false; return true; }; - return decorateFunctionWithTimeout(popupLoadedPredicate, timeout); + return Selenium.decorateFunctionWithTimeout(popupLoadedPredicate, timeout); } Selenium.prototype.doWaitForPopUp.dontCheckAlertsAndConfirms = true; Selenium.prototype.doChooseCancelOnNextConfirmation = function() { - /** + /** * By default, Selenium's overridden window.confirm() function will * return true, as if the user had manually clicked OK. After running * this command, the next call to confirm() will return false, as if @@ -735,7 +949,7 @@ Selenium.prototype.doChooseCancelOnNextConfirmation = function() { Selenium.prototype.doAnswerOnNextPrompt = function(answer) { - /** + /** * Instructs Selenium to return the specified answer string in response to * the next JavaScript prompt [window.prompt()]. * @@ -750,7 +964,7 @@ Selenium.prototype.doGoBack = function() { * Simulates the user clicking the "back" button on their browser. * */ - this.page().goBack(); + this.browserbot.goBack(); }; Selenium.prototype.doRefresh = function() { @@ -758,7 +972,7 @@ Selenium.prototype.doRefresh = function() { * Simulates the user clicking the "Refresh" button on their browser. * */ - this.page().refresh(); + this.browserbot.refresh(); }; Selenium.prototype.doClose = function() { @@ -766,7 +980,7 @@ Selenium.prototype.doClose = function() { * Simulates the user clicking the "close" button in the titlebar of a popup * window or tab. */ - this.page().close(); + this.browserbot.close(); }; Selenium.prototype.ensureNoUnhandledPopups = function() { @@ -814,7 +1028,7 @@ Selenium.prototype.isConfirmationPresent = function() { return this.browserbot.hasConfirmations(); }; Selenium.prototype.getAlert = function() { - /** + /** * Retrieves the message of a JavaScript alert generated during the previous action, or fail if there were no alerts. * * <p>Getting an alert has the same effect as manually clicking OK. If an @@ -837,7 +1051,7 @@ Selenium.prototype.getAlert = function() { Selenium.prototype.getAlert.dontCheckAlertsAndConfirms = true; Selenium.prototype.getConfirmation = function() { - /** + /** * Retrieves the message of a JavaScript confirmation dialog generated during * the previous action. * @@ -870,7 +1084,7 @@ Selenium.prototype.getConfirmation = function() { Selenium.prototype.getConfirmation.dontCheckAlertsAndConfirms = true; Selenium.prototype.getPrompt = function() { - /** + /** * Retrieves the message of a JavaScript question prompt dialog generated during * the previous action. * @@ -893,28 +1107,28 @@ Selenium.prototype.getPrompt = function() { }; Selenium.prototype.getLocation = function() { - /** Gets the absolute URL of the current page. + /** Gets the absolute URL of the current page. * * @return string the absolute URL of the current page */ - return this.page().getCurrentWindow().location; + return this.browserbot.getCurrentWindow().location; }; Selenium.prototype.getTitle = function() { - /** Gets the title of the current page. + /** Gets the title of the current page. * * @return string the title of the current page */ - return this.page().getTitle(); + return this.browserbot.getTitle(); }; Selenium.prototype.getBodyText = function() { - /** - * Gets the entire text of the page. - * @return string the entire text of the page - */ - return this.page().bodyText(); + /** + * Gets the entire text of the page. + * @return string the entire text of the page + */ + return this.browserbot.bodyText(); }; @@ -927,12 +1141,12 @@ Selenium.prototype.getValue = function(locator) { * @param locator an <a href="#locators">element locator</a> * @return string the element value, or "on/off" for checkbox/radio elements */ - var element = this.page().findElement(locator) + var element = this.browserbot.findElement(locator) return getInputValue(element).trim(); } Selenium.prototype.getText = function(locator) { - /** + /** * Gets the text of an element. This works for any element that contains * text. This command uses either the textContent (Mozilla-like browsers) or * the innerText (IE-like browsers) of the element, which is the rendered @@ -941,12 +1155,22 @@ Selenium.prototype.getText = function(locator) { * @param locator an <a href="#locators">element locator</a> * @return string the text of the element */ - var element = this.page().findElement(locator); + var element = this.browserbot.findElement(locator); return getText(element).trim(); }; +Selenium.prototype.doHighlight = function(locator) { + /** + * Briefly changes the backgroundColor of the specified element yellow. Useful for debugging. + * + * @param locator an <a href="#locators">element locator</a> + */ + var element = this.browserbot.findElement(locator); + this.browserbot.highlight(element, true); +}; + Selenium.prototype.getEval = function(script) { - /** Gets the result of evaluating the specified JavaScript snippet. The snippet may + /** Gets the result of evaluating the specified JavaScript snippet. The snippet may * have multiple lines, but only the result of the last line will be returned. * * <p>Note that, by default, the snippet will run in the context of the "selenium" @@ -956,28 +1180,28 @@ Selenium.prototype.getEval = function(script) { * <p>If you need a reference to the window of your application, you can refer * to <code>this.browserbot.getCurrentWindow()</code> and if you need to use * a locator to refer to a single element in your application page, you can - * use <code>this.page().findElement("foo")</code> where "foo" is your locator.</p> + * use <code>this.browserbot.findElement("foo")</code> where "foo" is your locator.</p> * * @param script the JavaScript snippet to run * @return string the results of evaluating the snippet */ try { - var result = eval(script); - // Selenium RC doesn't allow returning null - if (null == result) return "null"; - return result; + var result = eval(script); + // Selenium RC doesn't allow returning null + if (null == result) return "null"; + return result; } catch (e) { - throw new SeleniumError("Threw an exception: " + e.message); + throw new SeleniumError("Threw an exception: " + e.message); } }; Selenium.prototype.isChecked = function(locator) { - /** + /** * Gets whether a toggle-button (checkbox/radio) is checked. Fails if the specified element doesn't exist or isn't a toggle-button. * @param locator an <a href="#locators">element locator</a> pointing to a checkbox or radio button * @return boolean true if the checkbox is checked, false otherwise */ - var element = this.page().findElement(locator); + var element = this.browserbot.findElement(locator); if (element.checked == null) { throw new SeleniumError("Element " + locator + " is not a toggle-button."); } @@ -985,7 +1209,7 @@ Selenium.prototype.isChecked = function(locator) { }; Selenium.prototype.getTable = function(tableCellAddress) { - /** + /** * Gets the text from a cell of a table. The cellAddress syntax * tableLocator.row.column, where row and column start at 0. * @@ -1006,7 +1230,7 @@ Selenium.prototype.getTable = function(tableCellAddress) { row = pieces[2]; col = pieces[3]; - var table = this.page().findElement(tableName); + var table = this.browserbot.findElement(tableName); if (row > table.rows.length) { Assert.fail("Cannot access row " + row + " - table has " + table.rows.length + " rows"); } @@ -1017,7 +1241,7 @@ Selenium.prototype.getTable = function(tableCellAddress) { actualContent = getText(table.rows[row].cells[col]); return actualContent.trim(); } - return null; + return null; }; Selenium.prototype.getSelectedLabels = function(selectLocator) { @@ -1098,7 +1322,7 @@ Selenium.prototype.isSomethingSelected = function(selectLocator) { * @param selectLocator an <a href="#locators">element locator</a> identifying a drop-down menu * @return boolean true if some option has been selected, false otherwise */ - var element = this.page().findElement(selectLocator); + var element = this.browserbot.findElement(selectLocator); if (!("options" in element)) { throw new SeleniumError("Specified element is not a Select (has no options)"); } @@ -1115,12 +1339,12 @@ Selenium.prototype.isSomethingSelected = function(selectLocator) { } Selenium.prototype.findSelectedOptionProperties = function(locator, property) { - var element = this.page().findElement(locator); + var element = this.browserbot.findElement(locator); if (!("options" in element)) { throw new SeleniumError("Specified element is not a Select (has no options)"); } - var selectedOptions = []; + var selectedOptions = []; for (var i = 0; i < element.options.length; i++) { if (element.options[i].selected) @@ -1145,17 +1369,17 @@ Selenium.prototype.findSelectedOptionProperty = function(locator, property) { } Selenium.prototype.getSelectOptions = function(selectLocator) { - /** Gets all option labels in the specified select drop-down. + /** Gets all option labels in the specified select drop-down. * * @param selectLocator an <a href="#locators">element locator</a> identifying a drop-down menu * @return string[] an array of all option labels in the specified select drop-down */ - var element = this.page().findElement(selectLocator); + var element = this.browserbot.findElement(selectLocator); var selectOptions = []; for (var i = 0; i < element.options.length; i++) { - var option = element.options[i].text.replace(/,/g, "\\,"); + var option = element.options[i].text.replace(/,/g, "\\,"); selectOptions.push(option); } @@ -1164,49 +1388,49 @@ Selenium.prototype.getSelectOptions = function(selectLocator) { Selenium.prototype.getAttribute = function(attributeLocator) { - /** + /** * Gets the value of an element attribute. * - * Beware of http://jira.openqa.org/browse/SEL-280, which will lead some event handlers to - * get null event arguments. Read the bug for more details, including a workaround. - * * @param attributeLocator an element locator followed by an @ sign and then the name of the attribute, e.g. "foo@bar" * @return string the value of the specified attribute */ - var result = this.page().findAttribute(attributeLocator); + var result = this.browserbot.findAttribute(attributeLocator); if (result == null) { - throw new SeleniumError("Could not find element attribute: " + attributeLocator); - } + throw new SeleniumError("Could not find element attribute: " + attributeLocator); + } return result; }; Selenium.prototype.isTextPresent = function(pattern) { - /** + /** * Verifies that the specified text pattern appears somewhere on the rendered page shown to the user. * @param pattern a <a href="#patterns">pattern</a> to match with the text of the page * @return boolean true if the pattern matches the text, false otherwise */ - var allText = this.page().bodyText(); + var allText = this.browserbot.bodyText(); var patternMatcher = new PatternMatcher(pattern); if (patternMatcher.strategy == PatternMatcher.strategies.glob) { - patternMatcher.matcher = new PatternMatcher.strategies.globContains(pattern); + if (pattern.indexOf("glob:")==0) { + pattern = pattern.substring("glob:".length); // strip off "glob:" + } + patternMatcher.matcher = new PatternMatcher.strategies.globContains(pattern); } else if (patternMatcher.strategy == PatternMatcher.strategies.exact) { - pattern = pattern.substring("exact:".length); // strip off "exact:" - return allText.indexOf(pattern) != -1; + pattern = pattern.substring("exact:".length); // strip off "exact:" + return allText.indexOf(pattern) != -1; } return patternMatcher.matches(allText); }; Selenium.prototype.isElementPresent = function(locator) { - /** + /** * Verifies that the specified element is somewhere on the page. * @param locator an <a href="#locators">element locator</a> * @return boolean true if the element is present, false otherwise */ try { - this.page().findElement(locator); + this.browserbot.findElement(locator); } catch (e) { return false; } @@ -1214,7 +1438,7 @@ Selenium.prototype.isElementPresent = function(locator) { }; Selenium.prototype.isVisible = function(locator) { - /** + /** * Determines if the specified element is visible. An * element can be rendered invisible by setting the CSS "visibility" * property to "hidden", or the "display" property to "none", either for the @@ -1225,7 +1449,7 @@ Selenium.prototype.isVisible = function(locator) { * @return boolean true if the specified element is visible, false otherwise */ var element; - element = this.page().findElement(locator); + element = this.browserbot.findElement(locator); var visibility = this.findEffectiveStyleProperty(element, "visibility"); var _isDisplayed = this._isDisplayed(element); return (visibility != "hidden" && _isDisplayed); @@ -1269,14 +1493,14 @@ Selenium.prototype.findEffectiveStyle = function(element) { }; Selenium.prototype.isEditable = function(locator) { - /** + /** * Determines whether the specified input element is editable, ie hasn't been disabled. * This method will fail if the specified element isn't an input element. * * @param locator an <a href="#locators">element locator</a> * @return boolean true if the input element is editable, false otherwise */ - var element = this.page().findElement(locator); + var element = this.browserbot.findElement(locator); if (element.value == undefined) { Assert.fail("Element " + locator + " is not an input."); } @@ -1284,134 +1508,184 @@ Selenium.prototype.isEditable = function(locator) { }; Selenium.prototype.getAllButtons = function() { - /** Returns the IDs of all buttons on the page. + /** Returns the IDs of all buttons on the page. * * <p>If a given button has no ID, it will appear as "" in this array.</p> * * @return string[] the IDs of all buttons on the page */ - return this.page().getAllButtons(); + return this.browserbot.getAllButtons(); }; Selenium.prototype.getAllLinks = function() { - /** Returns the IDs of all links on the page. + /** Returns the IDs of all links on the page. * * <p>If a given link has no ID, it will appear as "" in this array.</p> * * @return string[] the IDs of all links on the page */ - return this.page().getAllLinks(); + return this.browserbot.getAllLinks(); }; Selenium.prototype.getAllFields = function() { - /** Returns the IDs of all input fields on the page. + /** Returns the IDs of all input fields on the page. * * <p>If a given field has no ID, it will appear as "" in this array.</p> * * @return string[] the IDs of all field on the page */ - return this.page().getAllFields(); -}; - -Selenium.prototype._getTestAppParentOfAllWindows = function() { - /** Returns the IDs of all input fields on the page. - * - * <p>If a given field has no ID, it will appear as "" in this array.</p> - * - * @return string[] the IDs of all field on the page - */ - if (this.browserbot.getCurrentWindow().opener!=null) { - return this.browserbot.getCurrentWindow().opener; - } - if (this.browserbot.buttonWindow!=null) { - return this.browserbot.buttonWindow; - } - return top; // apparently we are in proxy injection mode + return this.browserbot.getAllFields(); }; Selenium.prototype.getAttributeFromAllWindows = function(attributeName) { - /** Returns every instance of some attribute from all known windows. - * - * @param attributeName name of an attribute on the windows - * @return string[] the set of values of this attribute from all known windows. - */ - var attributes = new Array(); - var testAppParentOfAllWindows = this._getTestAppParentOfAllWindows(); - attributes.push(eval("testAppParentOfAllWindows." + attributeName)); - var selenium = testAppParentOfAllWindows.selenium==null ? testAppParentOfAllWindows.parent.selenium : testAppParentOfAllWindows.selenium; - for (windowName in selenium.browserbot.openedWindows) - { - attributes.push(eval("selenium.browserbot.openedWindows[windowName]." + attributeName)); - } - return attributes; + /** Returns every instance of some attribute from all known windows. + * + * @param attributeName name of an attribute on the windows + * @return string[] the set of values of this attribute from all known windows. + */ + var attributes = new Array(); + + var win = selenium.browserbot.topWindow; + + // DGF normally you should use []s instead of eval "win."+attributeName + // but in this case, attributeName may contain dots (e.g. document.title) + // in that case, we have no choice but to use eval... + attributes.push(eval("win."+attributeName)); + for (var windowName in this.browserbot.openedWindows) + { + try { + win = selenium.browserbot.openedWindows[windowName]; + attributes.push(eval("win."+attributeName)); + } catch (e) {} // DGF If we miss one... meh. It's probably closed or inaccessible anyway. + } + return attributes; }; Selenium.prototype.findWindow = function(soughtAfterWindowPropertyValue) { - var testAppParentOfAllWindows = this._getTestAppParentOfAllWindows(); var targetPropertyName = "name"; if (soughtAfterWindowPropertyValue.match("^title=")) { - targetPropertyName = "document.title"; + targetPropertyName = "document.title"; soughtAfterWindowPropertyValue = soughtAfterWindowPropertyValue.replace(/^title=/, ""); } else { - // matching "name": - // If we are not in proxy injection mode, then the top-level test window will be named myiframe. + // matching "name": + // If we are not in proxy injection mode, then the top-level test window will be named myiframe. // But as far as the interface goes, we are expected to match a blank string to this window, if // we are searching with respect to the widow name. // So make a special case so that this logic will work: if (PatternMatcher.matches(soughtAfterWindowPropertyValue, "")) { - return this.browserbot.getCurrentWindow(); + return this.browserbot.getCurrentWindow(); } } - if (PatternMatcher.matches(soughtAfterWindowPropertyValue, eval("testAppParentOfAllWindows." + targetPropertyName))) { - return testAppParentOfAllWindows; + // DGF normally you should use []s instead of eval "win."+attributeName + // but in this case, attributeName may contain dots (e.g. document.title) + // in that case, we have no choice but to use eval... + if (PatternMatcher.matches(soughtAfterWindowPropertyValue, eval("this.browserbot.topWindow." + targetPropertyName))) { + return this.browserbot.topWindow; } for (windowName in selenium.browserbot.openedWindows) { - var openedWindow = selenium.browserbot.openedWindows[windowName]; - if (PatternMatcher.matches(soughtAfterWindowPropertyValue, eval("openedWindow." + targetPropertyName))) { - return openedWindow; + var openedWindow = selenium.browserbot.openedWindows[windowName]; + if (PatternMatcher.matches(soughtAfterWindowPropertyValue, eval("openedWindow." + targetPropertyName))) { + return openedWindow; } } throw new SeleniumError("could not find window with property " + targetPropertyName + " matching " + soughtAfterWindowPropertyValue); }; Selenium.prototype.doDragdrop = function(locator, movementsString) { - /** Drags an element a certain distance and then drops it - * Beware of http://jira.openqa.org/browse/SEL-280, which will lead some event handlers to - * get null event arguments. Read the bug for more details, including a workaround. +/** deprecated - use dragAndDrop instead * - * @param movementsString offset in pixels from the current location to which the element should be moved, e.g., "+70,-300" * @param locator an element locator + * @param movementsString offset in pixels from the current location to which the element should be moved, e.g., "+70,-300" */ - var element = this.page().findElement(locator); - var clientStartXY = getClientXY(element) - var clientStartX = clientStartXY[0]; - var clientStartY = clientStartXY[1]; - - var movements = movementsString.split(/,/); - var movementX = Number(movements[0]); - var movementY = Number(movements[1]); + this.doDragAndDrop(locator, movementsString); +}; - var clientFinishX = ((clientStartX + movementX) < 0) ? 0 : (clientStartX + movementX); - var clientFinishY = ((clientStartY + movementY) < 0) ? 0 : (clientStartY + movementY); +Selenium.prototype.doSetMouseSpeed = function(pixels) { + /** Configure the number of pixels between "mousemove" events during dragAndDrop commands (default=10). + * <p>Setting this value to 0 means that we'll send a "mousemove" event to every single pixel + * in between the start location and the end location; that can be very slow, and may + * cause some browsers to force the JavaScript to timeout.</p> + * + * <p>If the mouse speed is greater than the distance between the two dragged objects, we'll + * just send one "mousemove" at the start location and then one final one at the end location.</p> + * @param pixels the number of pixels between "mousemove" events + */ + this.mouseSpeed = pixels; +} + +Selenium.prototype.getMouseSpeed = function() { + /** Returns the number of pixels between "mousemove" events during dragAndDrop commands (default=10). + * + * @return number the number of pixels between "mousemove" events during dragAndDrop commands (default=10) + */ + this.mouseSpeed = pixels; +} - var movementXincrement = (movementX > 0) ? 1 : -1; - var movementYincrement = (movementY > 0) ? 1 : -1; - triggerMouseEvent(element, 'mousedown', true, clientStartX, clientStartY); - var clientX = clientStartX; - var clientY = clientStartY; - while ((clientX != clientFinishX) || (clientY != clientFinishY)) { - if (clientX != clientFinishX) { - clientX += movementXincrement; - } - if (clientY != clientFinishY) { - clientY += movementYincrement; - } - triggerMouseEvent(element, 'mousemove', true, clientX, clientY); +Selenium.prototype.doDragAndDrop = function(locator, movementsString) { + /** Drags an element a certain distance and then drops it + * @param locator an element locator + * @param movementsString offset in pixels from the current location to which the element should be moved, e.g., "+70,-300" + */ + var element = this.browserbot.findElement(locator); + var clientStartXY = getClientXY(element) + var clientStartX = clientStartXY[0]; + var clientStartY = clientStartXY[1]; + + var movements = movementsString.split(/,/); + var movementX = Number(movements[0]); + var movementY = Number(movements[1]); + + var clientFinishX = ((clientStartX + movementX) < 0) ? 0 : (clientStartX + movementX); + var clientFinishY = ((clientStartY + movementY) < 0) ? 0 : (clientStartY + movementY); + + var mouseSpeed = this.mouseSpeed; + var move = function(current, dest) { + if (current == dest) return current; + if (Math.abs(current - dest) < mouseSpeed) return dest; + return (current < dest) ? current + mouseSpeed : current - mouseSpeed; + } + + this.browserbot.triggerMouseEvent(element, 'mousedown', true, clientStartX, clientStartY); + this.browserbot.triggerMouseEvent(element, 'mousemove', true, clientStartX, clientStartY); + var clientX = clientStartX; + var clientY = clientStartY; + + while ((clientX != clientFinishX) || (clientY != clientFinishY)) { + clientX = move(clientX, clientFinishX); + clientY = move(clientY, clientFinishY); + this.browserbot.triggerMouseEvent(element, 'mousemove', true, clientX, clientY); } - triggerMouseEvent(element, 'mouseup', true, clientFinishX, clientFinishY); + + this.browserbot.triggerMouseEvent(element, 'mousemove', true, clientFinishX, clientFinishY); + this.browserbot.triggerMouseEvent(element, 'mouseup', true, clientFinishX, clientFinishY); +}; + +Selenium.prototype.doDragAndDropToObject = function(locatorOfObjectToBeDragged, locatorOfDragDestinationObject) { +/** Drags an element and drops it on another element + * + * @param locatorOfObjectToBeDragged an element to be dragged + * @param locatorOfDragDestinationObject an element whose location (i.e., whose center-most pixel) will be the point where locatorOfObjectToBeDragged is dropped + */ + var startX = this.getElementPositionLeft(locatorOfObjectToBeDragged); + var startY = this.getElementPositionTop(locatorOfObjectToBeDragged); + + var destinationLeftX = this.getElementPositionLeft(locatorOfDragDestinationObject); + var destinationTopY = this.getElementPositionTop(locatorOfDragDestinationObject); + var destinationWidth = this.getElementWidth(locatorOfDragDestinationObject); + var destinationHeight = this.getElementHeight(locatorOfDragDestinationObject); + + var endX = Math.round(destinationLeftX + (destinationWidth / 2)); + var endY = Math.round(destinationTopY + (destinationHeight / 2)); + + var deltaX = endX - startX; + var deltaY = endY - startY; + + var movementsString = "" + deltaX + "," + deltaY; + + this.doDragAndDrop(locatorOfObjectToBeDragged, movementsString); }; Selenium.prototype.doWindowFocus = function(windowName) { @@ -1430,7 +1704,7 @@ Selenium.prototype.doWindowMaximize = function(windowName) { */ var window = this.findWindow(windowName); if (window!=null && window.screen) { - window.moveTo(0,0); + window.moveTo(0,0); window.outerHeight = screen.availHeight; window.outerWidth = screen.availWidth; } @@ -1461,32 +1735,32 @@ Selenium.prototype.getAllWindowTitles = function() { }; Selenium.prototype.getHtmlSource = function() { - /** Returns the entire HTML source between the opening and + /** Returns the entire HTML source between the opening and * closing "html" tags. * * @return string the entire HTML source */ - return this.page().document().getElementsByTagName("html")[0].innerHTML; + return this.browserbot.getDocument().getElementsByTagName("html")[0].innerHTML; }; Selenium.prototype.doSetCursorPosition = function(locator, position) { - /** + /** * Moves the text cursor to the specified position in the given input element or textarea. * This method will fail if the specified element isn't an input element or textarea. * * @param locator an <a href="#locators">element locator</a> pointing to an input element or textarea * @param position the numerical position of the cursor in the field; position should be 0 to move the position to the beginning of the field. You can also set the cursor to -1 to move it to the end of the field. */ - var element = this.page().findElement(locator); + var element = this.browserbot.findElement(locator); if (element.value == undefined) { Assert.fail("Element " + locator + " is not an input."); } if (position == -1) { - position = element.value.length; + position = element.value.length; } if( element.setSelectionRange && !browserVersion.isOpera) { - element.focus(); + element.focus(); element.setSelectionRange(/*start*/position,/*end*/position); } else if( element.createTextRange ) { @@ -1507,7 +1781,7 @@ Selenium.prototype.getElementIndex = function(locator) { * @param locator an <a href="#locators">element locator</a> pointing to an element * @return number of relative index of the element to its parent (starting from 0) */ - var element = this.page().findElement(locator); + var element = this.browserbot.findElement(locator); var previousSibling; var index = 0; while ((previousSibling = element.previousSibling) != null) { @@ -1528,8 +1802,8 @@ Selenium.prototype.isOrdered = function(locator1, locator2) { * @param locator2 an <a href="#locators">element locator</a> pointing to the second element * @return boolean true if two elements are ordered and have same parent, false otherwise */ - var element1 = this.page().findElement(locator1); - var element2 = this.page().findElement(locator2); + var element1 = this.browserbot.findElement(locator1); + var element2 = this.browserbot.findElement(locator2); if (element1 === element2) return false; var previousSibling; @@ -1553,48 +1827,48 @@ Selenium.prototype.getElementPositionLeft = function(locator) { * @param locator an <a href="#locators">element locator</a> pointing to an element OR an element itself * @return number of pixels from the edge of the frame. */ - var element; + var element; if ("string"==typeof locator) { - element = this.page().findElement(locator); + element = this.browserbot.findElement(locator); } else { - element = locator; + element = locator; + } + var x = element.offsetLeft; + var elementParent = element.offsetParent; + + while (elementParent != null) + { + if(document.all) + { + if( (elementParent.tagName != "TABLE") && (elementParent.tagName != "BODY") ) + { + x += elementParent.clientLeft; + } + } + else // Netscape/DOM + { + if(elementParent.tagName == "TABLE") + { + var parentBorder = parseInt(elementParent.border); + if(isNaN(parentBorder)) + { + var parentFrame = elementParent.getAttribute('frame'); + if(parentFrame != null) + { + x += 1; + } + } + else if(parentBorder > 0) + { + x += parentBorder; + } + } } - var x = element.offsetLeft; - var elementParent = element.offsetParent; - - while (elementParent != null) - { - if(document.all) - { - if( (elementParent.tagName != "TABLE") && (elementParent.tagName != "BODY") ) - { - x += elementParent.clientLeft; - } - } - else // Netscape/DOM - { - if(elementParent.tagName == "TABLE") - { - var parentBorder = parseInt(elementParent.border); - if(isNaN(parentBorder)) - { - var parentFrame = elementParent.getAttribute('frame'); - if(parentFrame != null) - { - x += 1; - } - } - else if(parentBorder > 0) - { - x += parentBorder; - } - } - } - x += elementParent.offsetLeft; - elementParent = elementParent.offsetParent; - } - return x; + x += elementParent.offsetLeft; + elementParent = elementParent.offsetParent; + } + return x; }; Selenium.prototype.getElementPositionTop = function(locator) { @@ -1604,61 +1878,61 @@ Selenium.prototype.getElementPositionTop = function(locator) { * @param locator an <a href="#locators">element locator</a> pointing to an element OR an element itself * @return number of pixels from the edge of the frame. */ - var element; + var element; if ("string"==typeof locator) { - element = this.page().findElement(locator); + element = this.browserbot.findElement(locator); } else { - element = locator; + element = locator; } - var y = 0; - - while (element != null) - { - if(document.all) - { - if( (element.tagName != "TABLE") && (element.tagName != "BODY") ) - { - y += element.clientTop; - } - } - else // Netscape/DOM - { - if(element.tagName == "TABLE") - { - var parentBorder = parseInt(element.border); - if(isNaN(parentBorder)) - { - var parentFrame = element.getAttribute('frame'); - if(parentFrame != null) - { - y += 1; - } - } - else if(parentBorder > 0) - { - y += parentBorder; - } - } - } - y += element.offsetTop; - - // Netscape can get confused in some cases, such that the height of the parent is smaller - // than that of the element (which it shouldn't really be). If this is the case, we need to - // exclude this element, since it will result in too large a 'top' return value. - if (element.offsetParent && element.offsetParent.offsetHeight && element.offsetParent.offsetHeight < element.offsetHeight) - { - // skip the parent that's too small - element = element.offsetParent.offsetParent; - } - else - { - // Next up... - element = element.offsetParent; - } - } - return y; + var y = 0; + + while (element != null) + { + if(document.all) + { + if( (element.tagName != "TABLE") && (element.tagName != "BODY") ) + { + y += element.clientTop; + } + } + else // Netscape/DOM + { + if(element.tagName == "TABLE") + { + var parentBorder = parseInt(element.border); + if(isNaN(parentBorder)) + { + var parentFrame = element.getAttribute('frame'); + if(parentFrame != null) + { + y += 1; + } + } + else if(parentBorder > 0) + { + y += parentBorder; + } + } + } + y += element.offsetTop; + + // Netscape can get confused in some cases, such that the height of the parent is smaller + // than that of the element (which it shouldn't really be). If this is the case, we need to + // exclude this element, since it will result in too large a 'top' return value. + if (element.offsetParent && element.offsetParent.offsetHeight && element.offsetParent.offsetHeight < element.offsetHeight) + { + // skip the parent that's too small + element = element.offsetParent.offsetParent; + } + else + { + // Next up... + element = element.offsetParent; + } + } + return y; }; Selenium.prototype.getElementWidth = function(locator) { @@ -1668,7 +1942,7 @@ Selenium.prototype.getElementWidth = function(locator) { * @param locator an <a href="#locators">element locator</a> pointing to an element * @return number width of an element in pixels */ - var element = this.page().findElement(locator); + var element = this.browserbot.findElement(locator); return element.offsetWidth; }; @@ -1679,12 +1953,12 @@ Selenium.prototype.getElementHeight = function(locator) { * @param locator an <a href="#locators">element locator</a> pointing to an element * @return number height of an element in pixels */ - var element = this.page().findElement(locator); + var element = this.browserbot.findElement(locator); return element.offsetHeight; }; Selenium.prototype.getCursorPosition = function(locator) { - /** + /** * Retrieves the text cursor position in the given input element or textarea; beware, this may not work perfectly on all browsers. * * <p>Specifically, if the cursor/selection has been cleared by JavaScript, this command will tend to @@ -1694,37 +1968,37 @@ Selenium.prototype.getCursorPosition = function(locator) { * @param locator an <a href="#locators">element locator</a> pointing to an input element or textarea * @return number the numerical position of the cursor in the field */ - var element = this.page().findElement(locator); - var doc = this.page().getDocument(); - var win = this.browserbot.getCurrentWindow(); - if( doc.selection && !browserVersion.isOpera){ - var selectRange = doc.selection.createRange().duplicate(); - var elementRange = element.createTextRange(); - selectRange.move("character",0); - elementRange.move("character",0); - var inRange1 = selectRange.inRange(elementRange); - var inRange2 = elementRange.inRange(selectRange); - try { - elementRange.setEndPoint("EndToEnd", selectRange); - } catch (e) { - Assert.fail("There is no cursor on this page!"); - } - var answer = String(elementRange.text).replace(/\r/g,"").length; - return answer; - } else { - if (typeof(element.selectionStart) != "undefined") { - if (win.getSelection && typeof(win.getSelection().rangeCount) != undefined && win.getSelection().rangeCount == 0) { - Assert.fail("There is no cursor on this page!"); - } - return element.selectionStart; - } - } - throw new Error("Couldn't detect cursor position on this browser!"); + var element = this.browserbot.findElement(locator); + var doc = this.browserbot.getDocument(); + var win = this.browserbot.getCurrentWindow(); + if( doc.selection && !browserVersion.isOpera){ + try { + var selectRange = doc.selection.createRange().duplicate(); + var elementRange = element.createTextRange(); + selectRange.move("character",0); + elementRange.move("character",0); + var inRange1 = selectRange.inRange(elementRange); + var inRange2 = elementRange.inRange(selectRange); + elementRange.setEndPoint("EndToEnd", selectRange); + } catch (e) { + Assert.fail("There is no cursor on this page!"); + } + var answer = String(elementRange.text).replace(/\r/g,"").length; + return answer; + } else { + if (typeof(element.selectionStart) != "undefined") { + if (win.getSelection && typeof(win.getSelection().rangeCount) != undefined && win.getSelection().rangeCount == 0) { + Assert.fail("There is no cursor on this page!"); + } + return element.selectionStart; + } + } + throw new Error("Couldn't detect cursor position on this browser!"); } Selenium.prototype.doSetContext = function(context, logLevelThreshold) { - /** + /** * Writes a message to the status bar and adds a note to the browser-side * log. * @@ -1739,26 +2013,26 @@ Selenium.prototype.doSetContext = function(context, logLevelThreshold) { * @param logLevelThreshold one of "debug", "info", "warn", "error", sets the threshold for browser-side logging */ if (logLevelThreshold==null || logLevelThreshold=="") { - return this.page().setContext(context); + return this.browserbot.setContext(context); } - return this.page().setContext(context, logLevelThreshold); + return this.browserbot.setContext(context, logLevelThreshold); }; Selenium.prototype.getExpression = function(expression) { - /** - * Returns the specified expression. - * - * <p>This is useful because of JavaScript preprocessing. - * It is used to generate commands like assertExpression and waitForExpression.</p> - * - * @param expression the value to return - * @return string the value passed in - */ - return expression; + /** + * Returns the specified expression. + * + * <p>This is useful because of JavaScript preprocessing. + * It is used to generate commands like assertExpression and waitForExpression.</p> + * + * @param expression the value to return + * @return string the value passed in + */ + return expression; } Selenium.prototype.doWaitForCondition = function(script, timeout) { - /** + /** * Runs the specified JavaScript snippet repeatedly until it evaluates to "true". * The snippet may have multiple lines, but only the result of the last line * will be considered. @@ -1770,10 +2044,7 @@ Selenium.prototype.doWaitForCondition = function(script, timeout) { * @param script the JavaScript snippet to run * @param timeout a timeout in milliseconds, after which this command will return with an error */ - if (isNaN(timeout)) { - throw new SeleniumError("Timeout is not a number: " + timeout); - } - return decorateFunctionWithTimeout(function () { + return Selenium.decorateFunctionWithTimeout(function () { return eval(script); }, timeout); }; @@ -1781,32 +2052,32 @@ Selenium.prototype.doWaitForCondition = function(script, timeout) { Selenium.prototype.doWaitForCondition.dontCheckAlertsAndConfirms = true; Selenium.prototype.doSetTimeout = function(timeout) { - /** - * Specifies the amount of time that Selenium will wait for actions to complete. - * - * <p>Actions that require waiting include "open" and the "waitFor*" actions.</p> - * The default timeout is 30 seconds. - * @param timeout a timeout in milliseconds, after which the action will return with an error - */ - this.defaultTimeout = parseInt(timeout); + /** + * Specifies the amount of time that Selenium will wait for actions to complete. + * + * <p>Actions that require waiting include "open" and the "waitFor*" actions.</p> + * The default timeout is 30 seconds. + * @param timeout a timeout in milliseconds, after which the action will return with an error + */ + if (!timeout) { + timeout = Selenium.DEFAULT_TIMEOUT; + } + this.defaultTimeout = timeout; } Selenium.prototype.doWaitForPageToLoad = function(timeout) { - /** - * Waits for a new page to load. - * - * <p>You can use this command instead of the "AndWait" suffixes, "clickAndWait", "selectAndWait", "typeAndWait" etc. - * (which are only available in the JS API).</p> - * - * <p>Selenium constantly keeps track of new pages loading, and sets a "newPageLoaded" - * flag when it first notices a page load. Running any other Selenium command after - * turns the flag to false. Hence, if you want to wait for a page to load, you must - * wait immediately after a Selenium command that caused a page-load.</p> - * @param timeout a timeout in milliseconds, after which this command will return with an error - */ - if (isNaN(timeout)) { - throw new SeleniumError("Timeout is not a number: " + timeout); - } + /** + * Waits for a new page to load. + * + * <p>You can use this command instead of the "AndWait" suffixes, "clickAndWait", "selectAndWait", "typeAndWait" etc. + * (which are only available in the JS API).</p> + * + * <p>Selenium constantly keeps track of new pages loading, and sets a "newPageLoaded" + * flag when it first notices a page load. Running any other Selenium command after + * turns the flag to false. Hence, if you want to wait for a page to load, you must + * wait immediately after a Selenium command that caused a page-load.</p> + * @param timeout a timeout in milliseconds, after which this command will return with an error + */ // in pi-mode, the test and the harness share the window; thus if we are executing this code, then we have loaded if (window["proxyInjectionMode"] == null || !window["proxyInjectionMode"]) { return this.makePageLoadCondition(timeout); @@ -1862,7 +2133,7 @@ Selenium.prototype.getCookie = function() { * * @return string all cookies of the current page under test */ - var doc = this.page().document(); + var doc = this.browserbot.getDocument(); return doc.cookie; }; @@ -1888,9 +2159,17 @@ Selenium.prototype.doCreateCookie = function(nameValuePair, optionsString) { } results = /path=([^\s,]+)[,]?/.exec(optionsString); if (results) { - cookie += "; path=" + results[1]; + var path = results[1]; + if (browserVersion.khtml) { + // Safari and conquerer don't like paths with / at the end + if ("/" != path) { + path = path.replace(/\/$/, ""); + } + } + cookie += "; path=" + path; } - this.page().document().cookie = cookie; + LOG.debug("Setting cookie to: " + cookie); + this.browserbot.getDocument().cookie = cookie; } Selenium.prototype.doDeleteCookie = function(name,path) { @@ -1901,8 +2180,17 @@ Selenium.prototype.doDeleteCookie = function(name,path) { * @param path the path property of the cookie to be deleted */ // set the expire time of the cookie to be deleted to one minute before now. + path = path.trim(); + if (browserVersion.khtml) { + // Safari and conquerer don't like paths with / at the end + if ("/" != path) { + path = path.replace(/\/$/, ""); + } + } var expireDateInMilliseconds = (new Date()).getTime() + (-1 * 1000); - this.page().document().cookie = name.trim() + "=deleted; path=" + path.trim() + "; expires=" + new Date(expireDateInMilliseconds).toGMTString(); + var cookie = name.trim() + "=deleted; path=" + path + "; expires=" + new Date(expireDateInMilliseconds).toGMTString(); + LOG.debug("Setting cookie to: " + cookie); + this.browserbot.getDocument().cookie = cookie; } @@ -2015,7 +2303,7 @@ OptionLocatorFactory.prototype.OptionLocatorByIndex = function(index) { }; this.assertSelected = function(element) { - Assert.equals(this.index, element.selectedIndex); + Assert.equals(this.index, element.selectedIndex); }; }; diff --git a/tests/test_tools/selenium/core/scripts/selenium-browserbot.js b/tests/test_tools/selenium/core/scripts/selenium-browserbot.js index 22df0fdb..633289e2 100644 --- a/tests/test_tools/selenium/core/scripts/selenium-browserbot.js +++ b/tests/test_tools/selenium/core/scripts/selenium-browserbot.js @@ -26,19 +26,24 @@ // The window to which the commands will be sent. For example, to click on a // popup window, first select that window, and then do a normal click command. - var BrowserBot = function(topLevelApplicationWindow) { this.topWindow = topLevelApplicationWindow; + this.topFrame = this.topWindow; + this.baseUrl=window.location.href; // the buttonWindow is the Selenium window // it contains the Run/Pause buttons... this should *not* be the AUT window - // todo: Here the buttonWindow is not Selenium window. It will be set to Selenium window in pollForLoad. - // Change this!!! - this.buttonWindow = this.topWindow; - // not sure what this is used for - this.currentPage = null; + this.buttonWindow = window; this.currentWindow = this.topWindow; this.currentWindowName = null; + + // We need to know this in advance, in case the frame closes unexpectedly + this.isSubFrameSelected = false; + + this.altKeyDown = false; + this.controlKeyDown = false; + this.shiftKeyDown = false; + this.metaKeyDown = false; this.modalDialogTest = null; this.recordedAlerts = new Array(); @@ -49,35 +54,56 @@ var BrowserBot = function(topLevelApplicationWindow) { this.nextPromptResult = ''; this.newPageLoaded = false; this.pageLoadError = null; + + this.shouldHighlightLocatedElement = false; this.uniqueId = new Date().getTime(); this.pollingForLoad = new Object(); + this.permDeniedCount = new Object(); this.windowPollers = new Array(); + // DGF for backwards compatibility + this.browserbot = this; var self = this; - this.recordPageLoad = function() { + + objectExtend(this, PageBot.prototype); + this._registerAllLocatorFunctions(); + + this.recordPageLoad = function(elementOrWindow) { LOG.debug("Page load detected"); try { - LOG.debug("Page load location=" + self.getCurrentWindow(true).location); + if (elementOrWindow.location && elementOrWindow.location.href) { + LOG.debug("Page load location=" + elementOrWindow.location.href); + } else if (elementOrWindow.contentWindow && elementOrWindow.contentWindow.location && elementOrWindow.contentWindow.location.href) { + LOG.debug("Page load location=" + elementOrWindow.contentWindow.location.href); + } else { + LOG.debug("Page load location unknown, current window location=" + this.getCurrentWindow(true).location); + } } catch (e) { + LOG.error("Caught an exception attempting to log location; this should get noticed soon!"); + LOG.exception(e); self.pageLoadError = e; return; } - self.currentPage = null; self.newPageLoaded = true; }; this.isNewPageLoaded = function() { if (this.pageLoadError) { + LOG.error("isNewPageLoaded found an old pageLoadError"); var e = this.pageLoadError; this.pageLoadError = null; throw e; } return self.newPageLoaded; }; + }; -BrowserBot.createForWindow = function(window) { +// DGF PageBot exists for backwards compatibility with old user-extensions +var PageBot = function(){}; + +BrowserBot.createForWindow = function(window, proxyInjectionMode) { var browserbot; LOG.debug('createForWindow'); LOG.debug("browserName: " + browserVersion.name); @@ -98,8 +124,9 @@ BrowserBot.createForWindow = function(window) { // Use mozilla by default browserbot = new MozillaBrowserBot(window); } - browserbot.getCurrentWindow(); - // todo: why? + // getCurrentWindow has the side effect of modifying it to handle page loads etc + browserbot.proxyInjectionMode = proxyInjectionMode; + browserbot.getCurrentWindow(); // for modifyWindow side effect. This is not a transparent style return browserbot; }; @@ -156,6 +183,74 @@ BrowserBot.prototype.getNextPrompt = function() { return t; }; +/* Fire a mouse event in a browser-compatible manner */ + +BrowserBot.prototype.triggerMouseEvent = function(element, eventType, canBubble, clientX, clientY) { + clientX = clientX ? clientX : 0; + clientY = clientY ? clientY : 0; + + LOG.warn("triggerMouseEvent assumes setting screenX and screenY to 0 is ok"); + var screenX = 0; + var screenY = 0; + + canBubble = (typeof(canBubble) == undefined) ? true : canBubble; + if (element.fireEvent) { + LOG.info("element has fireEvent"); + var evt = createEventObject(element, this.controlKeyDown, this.altKeyDown, this.shiftKeyDown, this.metaKeyDown); + evt.detail = 0; + evt.button = 1; + evt.relatedTarget = null; + if (!screenX && !screenY && !clientX && !clientY && !this.controlKeyDown && !this.altKeyDown && !this.shiftKeyDown && !this.metaKeyDown) { + element.fireEvent('on' + eventType); + } + else { + evt.screenX = screenX; + evt.screenY = screenY; + evt.clientX = clientX; + evt.clientY = clientY; + + // when we go this route, window.event is never set to contain the event we have just created. + // ideally we could just slide it in as follows in the try-block below, but this normally + // doesn't work. This is why I try to avoid this code path, which is only required if we need to + // set attributes on the event (e.g., clientX). + try { + window.event = evt; + } + catch(e) { + // getting an "Object does not support this action or property" error. Save the event away + // for future reference. + // TODO: is there a way to update window.event? + + // work around for http://jira.openqa.org/browse/SEL-280 -- make the event available somewhere: + selenium.browserbot.getCurrentWindow().selenium_event = evt; + } + element.fireEvent('on' + eventType, evt); + } + } + else { + LOG.info("element doesn't have fireEvent"); + var evt = document.createEvent('MouseEvents'); + if (evt.initMouseEvent) + { + LOG.info("element has initMouseEvent"); + //Safari + evt.initMouseEvent(eventType, canBubble, true, document.defaultView, 1, screenX, screenY, clientX, clientY, + this.controlKeyDown, this.altKeyDown, this.shiftKeyDown, this.metaKeyDown, 0, null); + } + else { + LOG.warn("element doesn't have initMouseEvent; firing an event which should -- but doesn't -- have other mouse-event related attributes here, as well as controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown"); + evt.initEvent(eventType, canBubble, true); + + evt.shiftKey = this.shiftKeyDown; + evt.metaKey = this.metaKeyDown; + evt.altKey = this.altKeyDown; + evt.ctrlKey = this.controlKeyDown; + + } + element.dispatchEvent(evt); + } +} + BrowserBot.prototype._windowClosed = function(win) { var c = win.closed; if (c == null) return true; @@ -163,25 +258,35 @@ BrowserBot.prototype._windowClosed = function(win) { }; BrowserBot.prototype._modifyWindow = function(win) { + // In proxyInjectionMode, have to suppress LOG calls in _modifyWindow to avoid an infinite loop if (this._windowClosed(win)) { - LOG.error("modifyWindow: Window was closed!"); + if (!this.proxyInjectionMode) { + LOG.error("modifyWindow: Window was closed!"); + } return null; } - LOG.debug('modifyWindow ' + this.uniqueId + ":" + win[this.uniqueId]); + if (!this.proxyInjectionMode) { + LOG.debug('modifyWindow ' + this.uniqueId + ":" + win[this.uniqueId]); + } if (!win[this.uniqueId]) { win[this.uniqueId] = true; this.modifyWindowToRecordPopUpDialogs(win, this); - this.currentPage = PageBot.createForWindow(this); - this.newPageLoaded = false; } - this.modifySeparateTestWindowToDetectPageLoads(win); + // In proxyInjection mode, we have our own mechanism for detecting page loads + if (!this.proxyInjectionMode) { + this.modifySeparateTestWindowToDetectPageLoads(win); + } + if (win.frames && win.frames.length && win.frames.length > 0) { + for (var i = 0; i < win.frames.length; i++) { + try { + this._modifyWindow(win.frames[i]); + } catch (e) {} // we're just trying to be opportunistic; don't worry if this doesn't work out + } + } return win; }; BrowserBot.prototype.selectWindow = function(target) { - // we've moved to a new page - clear the current one - this.currentPage = null; - if (target && target != "null") { this._selectWindowByName(target); } else { @@ -192,67 +297,117 @@ BrowserBot.prototype.selectWindow = function(target) { BrowserBot.prototype._selectTopWindow = function() { this.currentWindowName = null; this.currentWindow = this.topWindow; + this.topFrame = this.topWindow; + this.isSubFrameSelected = false; } BrowserBot.prototype._selectWindowByName = function(target) { this.currentWindow = this.getWindowByName(target, false); + this.topFrame = this.currentWindow; this.currentWindowName = target; + this.isSubFrameSelected = false; } BrowserBot.prototype.selectFrame = function(target) { if (target == "relative=up") { this.currentWindow = this.getCurrentWindow().parent; + this.isSubFrameSelected = (this._getFrameElement(this.currentWindow) != null); } else if (target == "relative=top") { - this.currentWindow = this.topWindow; + this.currentWindow = this.topFrame; + this.isSubFrameSelected = false; } else { - var frame = this.getCurrentPage().findElement(target); + var frame = this.findElement(target); if (frame == null) { throw new SeleniumError("Not found: " + target); } // now, did they give us a frame or a frame ELEMENT? + var match = false; if (frame.contentWindow) { // this must be a frame element - this.currentWindow = frame.contentWindow; - } else if (frame.document) { + if (browserVersion.isHTA) { + // stupid HTA bug; can't get in the front door + target = frame.contentWindow.name; + } else { + this.currentWindow = frame.contentWindow; + this.isSubFrameSelected = true; + match = true; + } + } else if (frame.document && frame.location) { // must be an actual window frame this.currentWindow = frame; - } else { - // neither - throw new SeleniumError("Not a frame: " + target); + this.isSubFrameSelected = true; + match = true; + } + + if (!match) { + // neither, let's loop through the frame names + var win = this.getCurrentWindow(); + + if (win && win.frames && win.frames.length) { + for (var i = 0; i < win.frames.length; i++) { + if (win.frames[i].name == target) { + this.currentWindow = win.frames[i]; + this.isSubFrameSelected = true; + match = true; + break; + } + } + } + if (!match) { + throw new SeleniumError("Not a frame: " + target); + } } } - this.currentPage = null; + // modifies the window + this.getCurrentWindow(); }; BrowserBot.prototype.openLocation = function(target) { // We're moving to a new page - clear the current one var win = this.getCurrentWindow(); LOG.debug("openLocation newPageLoaded = false"); - this.currentPage = null; this.newPageLoaded = false; this.setOpenLocation(win, target); }; +BrowserBot.prototype.openWindow = function(url, windowID) { + if (url != "") { + url = absolutify(url, this.baseUrl); + } + if (browserVersion.isHTA) { + // in HTA mode, calling .open on the window interprets the url relative to that window + // we need to absolute-ize the URL to make it consistent + var child = this.getCurrentWindow().open(url, windowID); + selenium.browserbot.openedWindows[windowID] = child; + } else { + this.getCurrentWindow().open(url, windowID); + } +}; + BrowserBot.prototype.setIFrameLocation = function(iframe, location) { iframe.src = location; }; BrowserBot.prototype.setOpenLocation = function(win, loc) { - - // is there a Permission Denied risk here? setting a timeout breaks Firefox - //win.setTimeout(function() { win.location.href = loc; }, 0); - win.location.href = loc; + loc = absolutify(loc, this.baseUrl); + if (browserVersion.isHTA) { + var oldHref = win.location.href; + win.location.href = loc; + var marker = null; + try { + marker = this.isPollingForLoad(win); + if (marker && win.location[marker]) { + win.location[marker] = false; + } + } catch (e) {} // DGF don't know why, but this often fails + } else { + win.location.href = loc; + } }; BrowserBot.prototype.getCurrentPage = function() { - if (this.currentPage == null) { - var testWindow = this.getCurrentWindow(); - this.currentPage = PageBot.createForWindow(this); - this.newPageLoaded = false; - } - - return this.currentPage; + return this; }; BrowserBot.prototype.modifyWindowToRecordPopUpDialogs = function(windowToModify, browserBot) { @@ -283,11 +438,45 @@ BrowserBot.prototype.modifyWindowToRecordPopUpDialogs = function(windowToModify, // Keep a reference to all popup windows by name // note that in IE the "windowName" argument must be a valid javascript identifier, it seems. var originalOpen = windowToModify.open; - windowToModify.open = function(url, windowName, windowFeatures, replaceFlag) { - var openedWindow = originalOpen(url, windowName, windowFeatures, replaceFlag); + var originalOpenReference; + if (browserVersion.isHTA) { + originalOpenReference = 'selenium_originalOpen' + new Date().getTime(); + windowToModify[originalOpenReference] = windowToModify.open; + } + + var isHTA = browserVersion.isHTA; + + var newOpen = function(url, windowName, windowFeatures, replaceFlag) { + var myOriginalOpen = originalOpen; + if (isHTA) { + myOriginalOpen = this[originalOpenReference]; + } + var openedWindow = myOriginalOpen(url, windowName, windowFeatures, replaceFlag); + LOG.debug("window.open call intercepted; window ID (which you can use with selectWindow()) is \"" + windowName + "\""); + if (windowName!=null) { + openedWindow["seleniumWindowName"] = windowName; + } selenium.browserbot.openedWindows[windowName] = openedWindow; return openedWindow; }; + + if (browserVersion.isHTA) { + originalOpenReference = 'selenium_originalOpen' + new Date().getTime(); + newOpenReference = 'selenium_newOpen' + new Date().getTime(); + var setOriginalRef = "this['" + originalOpenReference + "'] = this.open;"; + + if (windowToModify.eval) { + windowToModify.eval(setOriginalRef); + windowToModify.open = newOpen; + } else { + // DGF why can't I eval here? Seems like I'm querying the window at a bad time, maybe? + setOriginalRef += "this.open = this['" + newOpenReference + "'];"; + windowToModify[newOpenReference] = newOpen; + windowToModify.setTimeout(setOriginalRef, 0); + } + } else { + windowToModify.open = newOpen; + } }; /** @@ -311,27 +500,51 @@ BrowserBot.prototype.modifySeparateTestWindowToDetectPageLoads = function(window } var marker = 'selenium' + new Date().getTime(); - LOG.debug("Starting pollForLoad (" + marker + "): " + windowObject.document.location); + LOG.debug("Starting pollForLoad (" + marker + "): " + windowObject.location); this.pollingForLoad[marker] = true; // if this is a frame, add a load listener, otherwise, attach a poller - if (this._getFrameElement(windowObject)) { + var frameElement = this._getFrameElement(windowObject); + // DGF HTA mode can't attach load listeners to subframes (yuk!) + var htaSubFrame = this._isHTASubFrame(windowObject); + if (frameElement && !htaSubFrame) { LOG.debug("modifySeparateTestWindowToDetectPageLoads: this window is a frame; attaching a load listener"); - addLoadListener(windowObject.frameElement, this.recordPageLoad); - windowObject.frameElement[marker] = true; - windowObject.frameElement[this.uniqueId] = marker; + addLoadListener(frameElement, this.recordPageLoad); + frameElement[marker] = true; + frameElement[this.uniqueId] = marker; } else { - windowObject.document.location[marker] = true; + windowObject.location[marker] = true; windowObject[this.uniqueId] = marker; this.pollForLoad(this.recordPageLoad, windowObject, windowObject.document, windowObject.location, windowObject.location.href, marker); } }; +BrowserBot.prototype._isHTASubFrame = function(win) { + if (!browserVersion.isHTA) return false; + // DGF this is wrong! what if "win" isn't the selected window? + return this.isSubFrameSelected; +} + BrowserBot.prototype._getFrameElement = function(win) { var frameElement = null; + var caught; try { frameElement = win.frameElement; } catch (e) { - } // on IE, checking frameElement on a pop-up results in a "No such interface supported" exception + caught = true; + } + if (caught) { + // on IE, checking frameElement in a pop-up results in a "No such interface supported" exception + // but it might have a frame element anyway! + var parentContainsIdenticallyNamedFrame = false; + try { + parentContainsIdenticallyNamedFrame = win.parent.frames[win.name]; + } catch (e) {} // this may fail if access is denied to the parent; in that case, assume it's not a pop-up + + if (parentContainsIdenticallyNamedFrame) { + // it can't be a coincidence that the parent has a frame with the same name as myself! + return BrowserBot.prototype.locateElementByName(win.name, win.parent.document, win.parent); + } + } return frameElement; } @@ -342,18 +555,12 @@ BrowserBot.prototype._getFrameElement = function(win) { */ BrowserBot.prototype.pollForLoad = function(loadFunction, windowObject, originalDocument, originalLocation, originalHref, marker) { LOG.debug("pollForLoad original (" + marker + "): " + originalHref); - try { if (this._windowClosed(windowObject)) { LOG.debug("pollForLoad WINDOW CLOSED (" + marker + ")"); delete this.pollingForLoad[marker]; return; } - // todo: Change this!!! - // under multi-window layout, buttonWindow should be TestRunner window - // but only after the _windowClosed checking, we can ensure that this.topWindow exists - // then we can assign the TestRunner window to buttonWindow - this.buttonWindow = windowObject.opener; var isSamePage = this._isSamePage(windowObject, originalDocument, originalLocation, originalHref, marker); var rs = this.getReadyState(windowObject, windowObject.document); @@ -369,13 +576,18 @@ BrowserBot.prototype.pollForLoad = function(loadFunction, windowObject, original this.modifySeparateTestWindowToDetectPageLoads(windowObject); } newMarker = this.isPollingForLoad(windowObject); + var currentlySelectedWindow; + var currentlySelectedWindowMarker; + currentlySelectedWindow =this.getCurrentWindow(true); + currentlySelectedWindowMarker = currentlySelectedWindow[this.uniqueId]; + LOG.debug("pollForLoad (" + marker + ") restarting " + newMarker); if (/(TestRunner-splash|Blank)\.html\?start=true$/.test(currentHref)) { LOG.debug("pollForLoad Oh, it's just the starting page. Never mind!"); - } else if (this.currentWindow[this.uniqueId] == newMarker) { - loadFunction(); + } else if (currentlySelectedWindowMarker == newMarker) { + loadFunction(currentlySelectedWindow); } else { - LOG.debug("pollForLoad page load detected in non-current window; ignoring"); + LOG.debug("pollForLoad page load detected in non-current window; ignoring (currentlySelected="+currentlySelectedWindowMarker+", detection in "+newMarker+")"); } return; } @@ -396,6 +608,19 @@ BrowserBot.prototype._isSamePage = function(windowObject, originalDocument, orig var sameDoc = this._isSameDocument(originalDocument, currentDocument); var sameLoc = (originalLocation === currentLocation); + + // hash marks don't meant the page has loaded, so we need to strip them off if they exist... + var currentHash = currentHref.indexOf('#'); + if (currentHash > 0) { + currentHref = currentHref.substring(0, currentHash); + } + var originalHash = originalHref.indexOf('#'); + if (originalHash > 0) { + originalHref = originalHref.substring(0, originalHash); + } + LOG.debug("_isSamePage: currentHref: " + currentHref); + LOG.debug("_isSamePage: originalHref: " + originalHref); + var sameHref = (originalHref === currentHref); var markedLoc = currentLocation[marker]; @@ -403,6 +628,13 @@ BrowserBot.prototype._isSamePage = function(windowObject, originalDocument, orig // the mark disappears too early on these browsers markedLoc = true; } + + // since this is some _very_ important logic, especially for PI and multiWindow mode, we should log all these out + LOG.debug("_isSamePage: sameDoc: " + sameDoc); + LOG.debug("_isSamePage: sameLoc: " + sameLoc); + LOG.debug("_isSamePage: sameHref: " + sameHref); + LOG.debug("_isSamePage: markedLoc: " + markedLoc); + return sameDoc && sameLoc && sameHref && markedLoc }; @@ -489,17 +721,21 @@ BrowserBot.prototype.reschedulePoller = function(loadFunction, windowObject, ori }; BrowserBot.prototype.runScheduledPollers = function() { + LOG.debug("runScheduledPollers"); var oldPollers = this.windowPollers; this.windowPollers = new Array(); for (var i = 0; i < oldPollers.length; i++) { oldPollers[i].call(); } + LOG.debug("runScheduledPollers DONE"); }; BrowserBot.prototype.isPollingForLoad = function(win) { var marker; - if (this._getFrameElement(win)) { - marker = win.frameElement[this.uniqueId]; + var frameElement = this._getFrameElement(win); + var htaSubFrame = this._isHTASubFrame(win); + if (frameElement && !htaSubFrame) { + marker = frameElement[this.uniqueId]; } else { marker = win[this.uniqueId]; } @@ -521,9 +757,32 @@ BrowserBot.prototype.getWindowByName = function(windowName, doNotModify) { if (!targetWindow) { targetWindow = this.topWindow[windowName]; } + if (!targetWindow && windowName == "_blank") { + for (var winName in this.openedWindows) { + // _blank can match selenium_blank*, if it looks like it's OK (valid href, not closed) + if (/^selenium_blank/.test(winName)) { + targetWindow = this.openedWindows[winName]; + var ok; + try { + if (!this._windowClosed(targetWindow)) { + ok = targetWindow.location.href; + } + } catch (e) {} + if (ok) break; + } + } + } if (!targetWindow) { throw new SeleniumError("Window does not exist"); } + if (browserVersion.isHTA) { + try { + targetWindow.location.href; + } catch (e) { + targetWindow = window.open("", targetWindow.name); + this.openedWindows[targetWindow.name] = targetWindow; + } + } if (!doNotModify) { this._modifyWindow(targetWindow); } @@ -534,210 +793,62 @@ BrowserBot.prototype.getCurrentWindow = function(doNotModify) { var testWindow = this.currentWindow; if (!doNotModify) { this._modifyWindow(testWindow); + if (!this.proxyInjectionMode) { + // In proxy injection mode, have to avoid logging during getCurrentWindow to avoid an infinite loop + LOG.debug("getCurrentWindow newPageLoaded = false"); + } + this.newPageLoaded = false; } + testWindow = this._handleClosedSubFrame(testWindow, doNotModify); return testWindow; }; -function MozillaBrowserBot(frame) { - BrowserBot.call(this, frame); -} -MozillaBrowserBot.prototype = new BrowserBot; - -function KonquerorBrowserBot(frame) { - BrowserBot.call(this, frame); -} -KonquerorBrowserBot.prototype = new BrowserBot; - -KonquerorBrowserBot.prototype.setIFrameLocation = function(iframe, location) { - // Window doesn't fire onload event when setting src to the current value, - // so we set it to blank first. - iframe.src = "about:blank"; - iframe.src = location; -}; - -KonquerorBrowserBot.prototype.setOpenLocation = function(win, loc) { - // Window doesn't fire onload event when setting src to the current value, - // so we set it to blank first. - win.location.href = "about:blank"; - win.location.href = loc; - // force the current polling thread to detect a page load - var marker = this.isPollingForLoad(win); - if (marker) { - delete win.location[marker]; - } -}; - -KonquerorBrowserBot.prototype._isSameDocument = function(originalDocument, currentDocument) { - // under Konqueror, there may be this case: - // originalDocument and currentDocument are different objects - // while their location are same. - if (originalDocument) { - return originalDocument.location == currentDocument.location - } else { - return originalDocument === currentDocument; - } -}; - -function SafariBrowserBot(frame) { - BrowserBot.call(this, frame); -} -SafariBrowserBot.prototype = new BrowserBot; - -SafariBrowserBot.prototype.setIFrameLocation = KonquerorBrowserBot.prototype.setIFrameLocation; -SafariBrowserBot.prototype.setOpenLocation = KonquerorBrowserBot.prototype.setOpenLocation; - - -function OperaBrowserBot(frame) { - BrowserBot.call(this, frame); -} -OperaBrowserBot.prototype = new BrowserBot; -OperaBrowserBot.prototype.setIFrameLocation = function(iframe, location) { - if (iframe.src == location) { - iframe.src = location + '?reload'; - } else { - iframe.src = location; +BrowserBot.prototype._handleClosedSubFrame = function(testWindow, doNotModify) { + if (this.proxyInjectionMode) { + return testWindow; } -} - -function IEBrowserBot(frame) { - BrowserBot.call(this, frame); -} -IEBrowserBot.prototype = new BrowserBot; - -IEBrowserBot.prototype.modifyWindowToRecordPopUpDialogs = function(windowToModify, browserBot) { - BrowserBot.prototype.modifyWindowToRecordPopUpDialogs(windowToModify, browserBot); - - // we will call the previous version of this method from within our own interception - oldShowModalDialog = windowToModify.showModalDialog; - - windowToModify.showModalDialog = function(url, args, features) { - // Get relative directory to where TestRunner.html lives - // A risky assumption is that the user's TestRunner is named TestRunner.html - var doc_location = document.location.toString(); - var end_of_base_ref = doc_location.indexOf('TestRunner.html'); - var base_ref = doc_location.substring(0, end_of_base_ref); - - var fullURL = base_ref + "TestRunner.html?singletest=" + escape(browserBot.modalDialogTest) + "&autoURL=" + escape(url) + "&runInterval=" + runOptions.runInterval; - browserBot.modalDialogTest = null; - - var returnValue = oldShowModalDialog(fullURL, args, features); - return returnValue; - }; -}; - -IEBrowserBot.prototype.modifySeparateTestWindowToDetectPageLoads = function(windowObject) { - this.pageUnloading = false; - this.permDeniedCount = 0; - var self = this; - var pageUnloadDetector = function() { - self.pageUnloading = true; - }; - windowObject.attachEvent("onbeforeunload", pageUnloadDetector); - BrowserBot.prototype.modifySeparateTestWindowToDetectPageLoads.call(this, windowObject); -}; - -IEBrowserBot.prototype.pollForLoad = function(loadFunction, windowObject, originalDocument, originalLocation, originalHref, marker) { - BrowserBot.prototype.pollForLoad.call(this, loadFunction, windowObject, originalDocument, originalLocation, originalHref, marker); - if (this.pageLoadError) { - if (this.pageUnloading) { - var self = this; - LOG.warn("pollForLoad UNLOADING (" + marker + "): caught exception while firing events on unloading page: " + this.pageLoadError.message); - this.reschedulePoller(loadFunction, windowObject, originalDocument, originalLocation, originalHref, marker); - this.pageLoadError = null; - return; - } else if (((this.pageLoadError.message == "Permission denied") || (/^Access is denied/.test(this.pageLoadError.message))) - && this.permDeniedCount++ < 4) { - var self = this; - LOG.warn("pollForLoad (" + marker + "): " + this.pageLoadError.message + " (" + this.permDeniedCount + "), waiting to see if it goes away"); - this.reschedulePoller(loadFunction, windowObject, originalDocument, originalLocation, originalHref, marker); - this.pageLoadError = null; - return; - } - //handy for debugging! - //throw this.pageLoadError; - } -}; - -IEBrowserBot.prototype._windowClosed = function(win) { - try { - var c = win.closed; - // frame windows claim to be non-closed when their parents are closed - // but you can't access their document objects in that case - if (!c) { - try { - win.document; - } catch (de) { - if (de.message == "Permission denied") { - // the window is probably unloading, which means it's probably not closed yet - return false; - } - else if (/^Access is denied/.test(de.message)) { - // rare variation on "Permission denied"? - LOG.debug("IEBrowserBot.windowClosed: got " + de.message + " (this.pageUnloading=" + this.pageUnloading + "); assuming window is unloading, probably not closed yet"); - return false; - } else { - // this is probably one of those frame window situations - LOG.debug("IEBrowserBot.windowClosed: couldn't read win.document, assume closed: " + de.message + " (this.pageUnloading=" + this.pageUnloading + ")"); - return true; + + if (this.isSubFrameSelected) { + var missing = true; + if (testWindow.parent && testWindow.parent.frames && testWindow.parent.frames.length) { + for (var i = 0; i < testWindow.parent.frames.length; i++) { + if (testWindow.parent.frames[i] == testWindow) { + missing = false; + break; } } } - if (c == null) { - LOG.debug("IEBrowserBot.windowClosed: win.closed was null, assuming closed"); - return true; - } - return c; - } catch (e) { - // Got an exception trying to read win.closed; we'll have to take a guess! - if (browserVersion.isHTA) { - if (e.message == "Permission denied") { - // the window is probably unloading, which means it's probably not closed yet - return false; - } else { - // there's a good chance that we've lost contact with the window object if it is closed - return true; - } - } else { - // the window is probably unloading, which means it's probably not closed yet - return false; + if (missing) { + LOG.warn("Current subframe appears to have closed; selecting top frame"); + this.selectFrame("relative=top"); + return this.getCurrentWindow(doNotModify); } + } else if (this._windowClosed(testWindow)) { + var closedError = new SeleniumError("Current window or frame is closed!"); + closedError.windowClosed = true; + throw closedError; } + return testWindow; }; -SafariBrowserBot.prototype.modifyWindowToRecordPopUpDialogs = function(windowToModify, browserBot) { - BrowserBot.prototype.modifyWindowToRecordPopUpDialogs(windowToModify, browserBot); - - var originalOpen = windowToModify.open; - /* - * Safari seems to be broken, so that when we manually trigger the onclick method - * of a button/href, any window.open calls aren't resolved relative to the app location. - * So here we replace the open() method with one that does resolve the url correctly. - */ - windowToModify.open = function(url, windowName, windowFeatures, replaceFlag) { - - if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("/")) { - return originalOpen(url, windowName, windowFeatures, replaceFlag); - } - - // Reduce the current path to the directory - var currentPath = windowToModify.location.pathname || "/"; - currentPath = currentPath.replace(/\/[^\/]*$/, "/"); - - // Remove any leading "./" from the new url. - url = url.replace(/^\.\//, ""); +BrowserBot.prototype.highlight = function (element, force) { + if (force || this.shouldHighlightLocatedElement) { + try { + highlight(element); + } catch (e) {} // DGF element highlighting is low-priority and possibly dangerous + } + return element; +} - newUrl = currentPath + url; +BrowserBot.prototype.setShouldHighlightElement = function (shouldHighlight) { + this.shouldHighlightLocatedElement = shouldHighlight; +} - return originalOpen(newUrl, windowName, windowFeatures, replaceFlag); - }; -}; +/*****************************************************************/ +/* BROWSER-SPECIFIC FUNCTIONS ONLY AFTER THIS LINE */ -var PageBot = function(browserbot) { - this.browserbot = browserbot; - this._registerAllLocatorFunctions(); -}; -PageBot.prototype._registerAllLocatorFunctions = function() { +BrowserBot.prototype._registerAllLocatorFunctions = function() { // TODO - don't do this in the constructor - only needed once ever this.locationStrategies = {}; for (var functionName in this) { @@ -779,15 +890,11 @@ PageBot.prototype._registerAllLocatorFunctions = function() { }; } -PageBot.prototype.getDocument = function() { +BrowserBot.prototype.getDocument = function() { return this.getCurrentWindow().document; } -PageBot.prototype.getCurrentWindow = function() { - return this.browserbot.getCurrentWindow(); -} - -PageBot.prototype.getTitle = function() { +BrowserBot.prototype.getTitle = function() { var t = this.getDocument().title; if (typeof(t) == "string") { t = t.trim(); @@ -795,55 +902,30 @@ PageBot.prototype.getTitle = function() { return t; } -// todo: this is a bad name ... we're not passing a window in -PageBot.createForWindow = function(browserbot) { - if (browserVersion.isIE) { - return new IEPageBot(browserbot); - } - else if (browserVersion.isKonqueror) { - return new KonquerorPageBot(browserbot); - } - else if (browserVersion.isSafari) { - return new SafariPageBot(browserbot); - } - else if (browserVersion.isOpera) { - return new OperaPageBot(browserbot); +/* + * Finds an element recursively in frames and nested frames + * in the specified document, using various lookup protocols + */ +BrowserBot.prototype.findElementRecursive = function(locatorType, locatorString, inDocument, inWindow) { + + var element = this.findElementBy(locatorType, locatorString, inDocument, inWindow); + if (element != null) { + return element; } - else { - // Use mozilla by default - return new MozillaPageBot(browserbot); + + for (var i = 0; i < inWindow.frames.length; i++) { + element = this.findElementRecursive(locatorType, locatorString, inWindow.frames[i].document, inWindow.frames[i]); + + if (element != null) { + return element; + } } }; -var MozillaPageBot = function(browserbot) { - PageBot.call(this, browserbot); -}; -MozillaPageBot.prototype = new PageBot(); - -var KonquerorPageBot = function(browserbot) { - PageBot.call(this, browserbot); -}; -KonquerorPageBot.prototype = new PageBot(); - -var SafariPageBot = function(browserbot) { - PageBot.call(this, browserbot); -}; -SafariPageBot.prototype = new PageBot(); - -var IEPageBot = function(browserbot) { - PageBot.call(this, browserbot); -}; -IEPageBot.prototype = new PageBot(); - -var OperaPageBot = function(browserbot) { - PageBot.call(this, browserbot); -}; -OperaPageBot.prototype = new PageBot(); - /* * Finds an element on the current page, using various lookup protocols */ -PageBot.prototype.findElement = function(locator) { +BrowserBot.prototype.findElement = function(locator) { var locatorType = 'implicit'; var locatorString = locator; @@ -853,57 +935,31 @@ PageBot.prototype.findElement = function(locator) { locatorType = result[1].toLowerCase(); locatorString = result[2]; } - - var element = this.findElementBy(locatorType, locatorString, this.getDocument(), this.getCurrentWindow()); + + var element = this.findElementRecursive(locatorType, locatorString, this.getDocument(), this.getCurrentWindow()) + if (element != null) { - return this.highlight(element); - } - for (var i = 0; i < this.getCurrentWindow().frames.length; i++) { - element = this.findElementBy(locatorType, locatorString, this.getCurrentWindow().frames[i].document, this.getCurrentWindow().frames[i]); - if (element != null) { - return this.highlight(element); - } + return this.browserbot.highlight(element); } // Element was not found by any locator function. throw new SeleniumError("Element " + locator + " not found"); }; -PageBot.prototype.highlight = function (element) { - if (shouldHighlightLocatedElement) { - Effect.highlight(element); - } - return element; -} - -// as a static variable. -var shouldHighlightLocatedElement = false; - -PageBot.prototype.setHighlightElement = function (shouldHighlight) { - shouldHighlightLocatedElement = shouldHighlight; -} - /** * In non-IE browsers, getElementById() does not search by name. Instead, we * we search separately by id and name. */ -PageBot.prototype.locateElementByIdentifier = function(identifier, inDocument, inWindow) { - return PageBot.prototype.locateElementById(identifier, inDocument, inWindow) - || PageBot.prototype.locateElementByName(identifier, inDocument, inWindow) +BrowserBot.prototype.locateElementByIdentifier = function(identifier, inDocument, inWindow) { + return BrowserBot.prototype.locateElementById(identifier, inDocument, inWindow) + || BrowserBot.prototype.locateElementByName(identifier, inDocument, inWindow) || null; }; /** - * In IE, getElementById() also searches by name - this is an optimisation for IE. - */ -IEPageBot.prototype.locateElementByIdentifer = function(identifier, inDocument, inWindow) { - return inDocument.getElementById(identifier); -}; - -/** * Find the element with id - can't rely on getElementById, coz it returns by name as well in IE.. */ -PageBot.prototype.locateElementById = function(identifier, inDocument, inWindow) { +BrowserBot.prototype.locateElementById = function(identifier, inDocument, inWindow) { var element = inDocument.getElementById(identifier); if (element && element.id === identifier) { return element; @@ -917,7 +973,7 @@ PageBot.prototype.locateElementById = function(identifier, inDocument, inWindow) * Find an element by name, refined by (optional) element-filter * expressions. */ -PageBot.prototype.locateElementByName = function(locator, document, inWindow) { +BrowserBot.prototype.locateElementByName = function(locator, document, inWindow) { var elements = document.getElementsByTagName("*"); var filters = locator.split(' '); @@ -937,15 +993,12 @@ PageBot.prototype.locateElementByName = function(locator, document, inWindow) { /** * Finds an element using by evaluating the specfied string. */ -PageBot.prototype.locateElementByDomTraversal = function(domTraversal, inDocument, inWindow) { +BrowserBot.prototype.locateElementByDomTraversal = function(domTraversal, document, window) { + var browserbot = this.browserbot; var element = null; try { - if (browserVersion.isOpera) { - element = inWindow.eval(domTraversal); - } else { - element = eval("inWindow." + domTraversal); - } + element = eval(domTraversal); } catch (e) { e.isSeleniumError = true; throw e; @@ -957,13 +1010,13 @@ PageBot.prototype.locateElementByDomTraversal = function(domTraversal, inDocumen return element; }; -PageBot.prototype.locateElementByDomTraversal.prefix = "dom"; +BrowserBot.prototype.locateElementByDomTraversal.prefix = "dom"; /** * Finds an element identified by the xpath expression. Expressions _must_ * begin with "//". */ -PageBot.prototype.locateElementByXPath = function(xpath, inDocument, inWindow) { +BrowserBot.prototype.locateElementByXPath = function(xpath, inDocument, inWindow) { // Trim any trailing "/": not valid xpath, and remains from attribute // locator. @@ -982,12 +1035,21 @@ PageBot.prototype.locateElementByXPath = function(xpath, inDocument, inWindow) { // Handle //tag[@attr='value'] var match = xpath.match(/^\/\/(\w+|\*)\[@(\w+)=('([^\']+)'|"([^\"]+)")\]$/); if (match) { - return this._findElementByTagNameAndAttributeValue( + // We don't return the value without checking if it is null first. + // This is beacuse in some rare cases, this shortcut actually WONT work + // but that the full XPath WILL. A known case, for example, is in IE + // when the attribute is onclick/onblur/onsubmit/etc. Due to a bug in IE + // this shortcut won't work because the actual function is returned + // by getAttribute() rather than the text of the attribute. + var val = this._findElementByTagNameAndAttributeValue( inDocument, match[1].toUpperCase(), match[2].toLowerCase(), match[3].slice(1, -1) ); + if (val) { + return val; + } } // Handle //tag[text()='value'] @@ -1003,7 +1065,7 @@ PageBot.prototype.locateElementByXPath = function(xpath, inDocument, inWindow) { return this._findElementUsingFullXPath(xpath, inDocument); }; -PageBot.prototype._findElementByTagNameAndAttributeValue = function( +BrowserBot.prototype._findElementByTagNameAndAttributeValue = function( inDocument, tagName, attributeName, attributeValue ) { if (browserVersion.isIE && attributeName == "class") { @@ -1019,7 +1081,7 @@ PageBot.prototype._findElementByTagNameAndAttributeValue = function( return null; }; -PageBot.prototype._findElementByTagNameAndText = function( +BrowserBot.prototype._findElementByTagNameAndText = function( inDocument, tagName, text ) { var elements = inDocument.getElementsByTagName(tagName); @@ -1031,7 +1093,7 @@ PageBot.prototype._findElementByTagNameAndText = function( return null; }; -PageBot.prototype._namespaceResolver = function(prefix) { +BrowserBot.prototype._namespaceResolver = function(prefix) { if (prefix == 'html' || prefix == 'xhtml' || prefix == 'x') { return 'http://www.w3.org/1999/xhtml'; } else if (prefix == 'mathml') { @@ -1041,7 +1103,7 @@ PageBot.prototype._namespaceResolver = function(prefix) { } } -PageBot.prototype._findElementUsingFullXPath = function(xpath, inDocument, inWindow) { +BrowserBot.prototype._findElementUsingFullXPath = function(xpath, inDocument, inWindow) { // HUGE hack - remove namespace from xpath for IE if (browserVersion.isIE) { xpath = xpath.replace(/x:/g, '') @@ -1066,7 +1128,7 @@ PageBot.prototype._findElementUsingFullXPath = function(xpath, inDocument, inWin * Finds a link element with text matching the expression supplied. Expressions must * begin with "link:". */ -PageBot.prototype.locateElementByLinkText = function(linkText, inDocument, inWindow) { +BrowserBot.prototype.locateElementByLinkText = function(linkText, inDocument, inWindow) { var links = inDocument.getElementsByTagName('a'); for (var i = 0; i < links.length; i++) { var element = links[i]; @@ -1076,13 +1138,13 @@ PageBot.prototype.locateElementByLinkText = function(linkText, inDocument, inWin } return null; }; -PageBot.prototype.locateElementByLinkText.prefix = "link"; +BrowserBot.prototype.locateElementByLinkText.prefix = "link"; /** * Returns an attribute based on an attribute locator. This is made up of an element locator * suffixed with @attribute-name. */ -PageBot.prototype.findAttribute = function(locator) { +BrowserBot.prototype.findAttribute = function(locator) { // Split into locator + attributeName var attributePos = locator.lastIndexOf("@"); var elementLocator = locator.slice(0, attributePos); @@ -1105,7 +1167,7 @@ PageBot.prototype.findAttribute = function(locator) { /* * Select the specified option and trigger the relevant events of the element. */ -PageBot.prototype.selectOption = function(element, optionToSelect) { +BrowserBot.prototype.selectOption = function(element, optionToSelect) { triggerEvent(element, 'focus', false); var changed = false; for (var i = 0; i < element.options.length; i++) { @@ -1128,7 +1190,7 @@ PageBot.prototype.selectOption = function(element, optionToSelect) { /* * Select the specified option and trigger the relevant events of the element. */ -PageBot.prototype.addSelection = function(element, option) { +BrowserBot.prototype.addSelection = function(element, option) { this.checkMultiselect(element); triggerEvent(element, 'focus', false); if (!option.selected) { @@ -1140,7 +1202,7 @@ PageBot.prototype.addSelection = function(element, option) { /* * Select the specified option and trigger the relevant events of the element. */ -PageBot.prototype.removeSelection = function(element, option) { +BrowserBot.prototype.removeSelection = function(element, option) { this.checkMultiselect(element); triggerEvent(element, 'focus', false); if (option.selected) { @@ -1149,7 +1211,7 @@ PageBot.prototype.removeSelection = function(element, option) { } }; -PageBot.prototype.checkMultiselect = function(element) { +BrowserBot.prototype.checkMultiselect = function(element) { if (!element.multiple) { throw new SeleniumError("Not a multi-select"); @@ -1157,7 +1219,7 @@ PageBot.prototype.checkMultiselect = function(element) { }; -PageBot.prototype.replaceText = function(element, stringValue) { +BrowserBot.prototype.replaceText = function(element, stringValue) { triggerEvent(element, 'focus', false); triggerEvent(element, 'select', true); var maxLengthAttr = element.getAttribute("maxLength"); @@ -1170,42 +1232,83 @@ PageBot.prototype.replaceText = function(element, stringValue) { LOG.warn("AFTER") } } - element.value = actualValue; + + if (getTagName(element) == "body") { + if (element.ownerDocument && element.ownerDocument.designMode) { + var designMode = new String(element.ownerDocument.designMode).toLowerCase(); + if (designMode = "on") { + // this must be a rich text control! + element.innerHTML = actualValue; + } + } + } else { + element.value = actualValue; + } // DGF this used to be skipped in chrome URLs, but no longer. Is xpcnativewrappers to blame? - triggerEvent(element, 'change', true); + try { + triggerEvent(element, 'change', true); + } catch (e) {} }; -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; - - 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. - // In chrome URL, the link action is already executed by triggerMouseEvent. - if (!browserVersion.isChrome && !preventDefault) { - var targetWindow = this.browserbot._getTargetWindow(element); - if (element.href) { - targetWindow.location.href = element.href; +BrowserBot.prototype.submit = function(formElement) { + var actuallySubmit = true; + this._modifyElementTarget(formElement); + if (formElement.onsubmit) { + if (browserVersion.isHTA) { + // run the code in the correct window so alerts are handled correctly even in HTA mode + var win = this.browserbot.getCurrentWindow(); + var now = new Date().getTime(); + var marker = 'marker' + now; + win[marker] = formElement; + win.setTimeout("var actuallySubmit = "+marker+".onsubmit();" + + "if (actuallySubmit) { " + + marker+".submit(); " + + "if ("+marker+".target && !/^_/.test("+marker+".target)) {"+ + "window.open('', "+marker+".target);"+ + "}"+ + "};"+ + marker+"=null", 0); + // pause for up to 2s while this command runs + var terminationCondition = function () { + return !win[marker]; + } + return Selenium.decorateFunctionWithTimeout(terminationCondition, 2000); } else { - this.browserbot._handleClickingImagesInsideLinks(targetWindow, element); + actuallySubmit = formElement.onsubmit(); + if (actuallySubmit) { + formElement.submit(); + if (formElement.target && !/^_/.test(formElement.target)) { + this.browserbot.openWindow('', formElement.target); + } + } } + } else { + formElement.submit(); } +} - if (this._windowClosed()) { - return; +BrowserBot.prototype.clickElement = function(element, clientX, clientY) { + this._fireEventOnElement("click", element, clientX, clientY); +}; + +BrowserBot.prototype.doubleClickElement = function(element, clientX, clientY) { + this._fireEventOnElement("dblclick", element, clientX, clientY); +}; + +BrowserBot.prototype._modifyElementTarget = function(element) { + if (element.target) { + if (element.target == "_blank" || /^selenium_blank/.test(element.target) ) { + var tagName = getTagName(element); + if (tagName == "a" || tagName == "form") { + var newTarget = "selenium_blank" + Math.round(100000 * Math.random()); + LOG.warn("Link has target '_blank', which is not supported in Selenium! Randomizing target to be: " + newTarget); + this.browserbot.openWindow('', newTarget); + element.target = newTarget; + } + } } +} -}; BrowserBot.prototype._handleClickingImagesInsideLinks = function(targetWindow, element) { if (element.parentNode && element.parentNode.href) { @@ -1214,126 +1317,38 @@ BrowserBot.prototype._handleClickingImagesInsideLinks = function(targetWindow, e } BrowserBot.prototype._getTargetWindow = function(element) { - var targetWindow = this.getCurrentWindow(); + var targetWindow = element.ownerDocument.defaultView; if (element.target) { - var frame = this._getFrameFromGlobal(element.target); - targetWindow = frame.contentWindow; + targetWindow = this._getFrameFromGlobal(element.target); } return targetWindow; } BrowserBot.prototype._getFrameFromGlobal = function(target) { - pagebot = PageBot.createForWindow(this); - return pagebot.findElementBy("implicit", target, this.topWindow.document, this.topWindow); + + if (target == "_top") { + return this.topFrame; + } else if (target == "_parent") { + return this.getCurrentWindow().parent; + } else if (target == "_blank") { + // TODO should this set cleverer window defaults? + return this.getCurrentWindow().open('', '_blank'); + } + var frameElement = this.findElementBy("implicit", target, this.topFrame.document, this.topFrame); + if (frameElement) { + return frameElement.contentWindow; + } + var win = this.getWindowByName(target); + if (win) return win; + return this.getCurrentWindow().open('', target); } -OperaPageBot.prototype.clickElement = function(element, clientX, clientY) { - - triggerEvent(element, 'focus', false); - - // Trigger the click event. - triggerMouseEvent(element, 'click', true, clientX, clientY); - - if (this._windowClosed()) { - return; - } - -}; - - -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; - } - -}; - -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 { - var targetWindow = this.browserbot._getTargetWindow(element); - // todo: what if the target anchor is on another page? - if (element.href && element.href.indexOf("#") != -1) { - var b = targetWindow.document.getElementById(element.href.split("#")[1]); - targetWindow.document.body.scrollTop = b.offsetTop; - } else { - triggerMouseEvent(element, 'click', true, clientX, clientY); - } - - } - -}; - -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.getCurrentWindow().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.getCurrentWindow().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); - } - - } - 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.logHook = function() { - }; - LOG.warn("Caught exception when firing events on unloading page: " + e.message); - return; - } - throw e; - } -}; - -PageBot.prototype._windowClosed = function(element) { - return selenium.browserbot._windowClosed(this.getCurrentWindow()); -}; - -PageBot.prototype.bodyText = function() { +BrowserBot.prototype.bodyText = function() { return getText(this.getDocument().body); }; -PageBot.prototype.getAllButtons = function() { +BrowserBot.prototype.getAllButtons = function() { var elements = this.getDocument().getElementsByTagName('input'); var result = ''; @@ -1349,7 +1364,7 @@ PageBot.prototype.getAllButtons = function() { }; -PageBot.prototype.getAllFields = function() { +BrowserBot.prototype.getAllFields = function() { var elements = this.getDocument().getElementsByTagName('input'); var result = ''; @@ -1364,7 +1379,7 @@ PageBot.prototype.getAllFields = function() { return result; }; -PageBot.prototype.getAllLinks = function() { +BrowserBot.prototype.getAllLinks = function() { var elements = this.getDocument().getElementsByTagName('a'); var result = ''; @@ -1377,7 +1392,8 @@ PageBot.prototype.getAllLinks = function() { return result; }; -PageBot.prototype.setContext = function(strContext, logLevel) { +BrowserBot.prototype.setContext = function(strContext, logLevel) { + //set the current test title var ctx = document.getElementById("context"); if (ctx != null) { @@ -1392,31 +1408,31 @@ function isDefined(value) { return typeof(value) != undefined; } -PageBot.prototype.goBack = function() { +BrowserBot.prototype.goBack = function() { this.getCurrentWindow().history.back(); }; -PageBot.prototype.goForward = function() { +BrowserBot.prototype.goForward = function() { this.getCurrentWindow().history.forward(); }; -PageBot.prototype.close = function() { - if (browserVersion.isChrome || browserVersion.isSafari) { +BrowserBot.prototype.close = function() { + if (browserVersion.isChrome || browserVersion.isSafari || browserVersion.isOpera) { this.getCurrentWindow().close(); } else { this.getCurrentWindow().eval("window.close();"); } }; -PageBot.prototype.refresh = function() { +BrowserBot.prototype.refresh = function() { this.getCurrentWindow().location.reload(true); }; /** * Refine a list of elements using a filter. */ -PageBot.prototype.selectElementsBy = function(filterType, filter, elements) { - var filterFunction = PageBot.filterFunctions[filterType]; +BrowserBot.prototype.selectElementsBy = function(filterType, filter, elements) { + var filterFunction = BrowserBot.filterFunctions[filterType]; if (! filterFunction) { throw new SeleniumError("Unrecognised element-filter type: '" + filterType + "'"); } @@ -1424,9 +1440,9 @@ PageBot.prototype.selectElementsBy = function(filterType, filter, elements) { return filterFunction(filter, elements); }; -PageBot.filterFunctions = {}; +BrowserBot.filterFunctions = {}; -PageBot.filterFunctions.name = function(name, elements) { +BrowserBot.filterFunctions.name = function(name, elements) { var selectedElements = []; for (var i = 0; i < elements.length; i++) { if (elements[i].name === name) { @@ -1436,7 +1452,7 @@ PageBot.filterFunctions.name = function(name, elements) { return selectedElements; }; -PageBot.filterFunctions.value = function(value, elements) { +BrowserBot.filterFunctions.value = function(value, elements) { var selectedElements = []; for (var i = 0; i < elements.length; i++) { if (elements[i].value === value) { @@ -1446,7 +1462,7 @@ PageBot.filterFunctions.value = function(value, elements) { return selectedElements; }; -PageBot.filterFunctions.index = function(index, elements) { +BrowserBot.filterFunctions.index = function(index, elements) { index = Number(index); if (isNaN(index) || index < 0) { throw new SeleniumError("Illegal Index: " + index); @@ -1457,7 +1473,7 @@ PageBot.filterFunctions.index = function(index, elements) { return [elements[index]]; }; -PageBot.prototype.selectElements = function(filterExpr, elements, defaultFilterType) { +BrowserBot.prototype.selectElements = function(filterExpr, elements, defaultFilterType) { var filterType = (defaultFilterType || 'value'); @@ -1474,8 +1490,8 @@ PageBot.prototype.selectElements = function(filterExpr, elements, defaultFilterT /** * Find an element by class */ -PageBot.prototype.locateElementByClass = function(locator, document) { - return Element.findFirstMatchingChild(document, +BrowserBot.prototype.locateElementByClass = function(locator, document) { + return elementFindFirstMatchingChild(document, function(element) { return element.className == locator } @@ -1485,8 +1501,8 @@ PageBot.prototype.locateElementByClass = function(locator, document) { /** * Find an element by alt */ -PageBot.prototype.locateElementByAlt = function(locator, document) { - return Element.findFirstMatchingChild(document, +BrowserBot.prototype.locateElementByAlt = function(locator, document) { + return elementFindFirstMatchingChild(document, function(element) { return element.alt == locator } @@ -1496,9 +1512,435 @@ PageBot.prototype.locateElementByAlt = function(locator, document) { /** * Find an element by css selector */ -PageBot.prototype.locateElementByCss = function(locator, document) { +BrowserBot.prototype.locateElementByCss = function(locator, document) { var elements = cssQuery(locator, document); if (elements.length != 0) return elements[0]; return null; } + + +/*****************************************************************/ +/* BROWSER-SPECIFIC FUNCTIONS ONLY AFTER THIS LINE */ + +function MozillaBrowserBot(frame) { + BrowserBot.call(this, frame); +} +objectExtend(MozillaBrowserBot.prototype, BrowserBot.prototype); + +function KonquerorBrowserBot(frame) { + BrowserBot.call(this, frame); +} +objectExtend(KonquerorBrowserBot.prototype, BrowserBot.prototype); + +KonquerorBrowserBot.prototype.setIFrameLocation = function(iframe, location) { + // Window doesn't fire onload event when setting src to the current value, + // so we set it to blank first. + iframe.src = "about:blank"; + iframe.src = location; +}; + +KonquerorBrowserBot.prototype.setOpenLocation = function(win, loc) { + // Window doesn't fire onload event when setting src to the current value, + // so we just refresh in that case instead. + loc = absolutify(loc, this.baseUrl); + loc = canonicalize(loc); + var startLoc = parseUrl(win.location.href); + startLoc.hash = null; + var startUrl = reassembleLocation(startLoc); + LOG.debug("startUrl="+startUrl); + LOG.debug("win.location.href="+win.location.href); + LOG.debug("loc="+loc); + if (startUrl == loc) { + LOG.debug("opening exact same location"); + this.refresh(); + } else { + LOG.debug("locations differ"); + win.location.href = loc; + } + // force the current polling thread to detect a page load + var marker = this.isPollingForLoad(win); + if (marker) { + delete win.location[marker]; + } +}; + +KonquerorBrowserBot.prototype._isSameDocument = function(originalDocument, currentDocument) { + // under Konqueror, there may be this case: + // originalDocument and currentDocument are different objects + // while their location are same. + if (originalDocument) { + return originalDocument.location == currentDocument.location + } else { + return originalDocument === currentDocument; + } +}; + +function SafariBrowserBot(frame) { + BrowserBot.call(this, frame); +} +objectExtend(SafariBrowserBot.prototype, BrowserBot.prototype); + +SafariBrowserBot.prototype.setIFrameLocation = KonquerorBrowserBot.prototype.setIFrameLocation; +SafariBrowserBot.prototype.setOpenLocation = KonquerorBrowserBot.prototype.setOpenLocation; + + +function OperaBrowserBot(frame) { + BrowserBot.call(this, frame); +} +objectExtend(OperaBrowserBot.prototype, BrowserBot.prototype); +OperaBrowserBot.prototype.setIFrameLocation = function(iframe, location) { + if (iframe.src == location) { + iframe.src = location + '?reload'; + } else { + iframe.src = location; + } +} + +function IEBrowserBot(frame) { + BrowserBot.call(this, frame); +} +objectExtend(IEBrowserBot.prototype, BrowserBot.prototype); + +IEBrowserBot.prototype._handleClosedSubFrame = function(testWindow, doNotModify) { + if (this.proxyInjectionMode) { + return testWindow; + } + + try { + testWindow.location.href; + this.permDenied = 0; + } catch (e) { + this.permDenied++; + } + if (this._windowClosed(testWindow) || this.permDenied > 4) { + if (this.isSubFrameSelected) { + LOG.warn("Current subframe appears to have closed; selecting top frame"); + this.selectFrame("relative=top"); + return this.getCurrentWindow(doNotModify); + } else { + var closedError = new SeleniumError("Current window or frame is closed!"); + closedError.windowClosed = true; + throw closedError; + } + } + return testWindow; +}; + +IEBrowserBot.prototype.modifyWindowToRecordPopUpDialogs = function(windowToModify, browserBot) { + BrowserBot.prototype.modifyWindowToRecordPopUpDialogs(windowToModify, browserBot); + + // we will call the previous version of this method from within our own interception + oldShowModalDialog = windowToModify.showModalDialog; + + windowToModify.showModalDialog = function(url, args, features) { + // Get relative directory to where TestRunner.html lives + // A risky assumption is that the user's TestRunner is named TestRunner.html + var doc_location = document.location.toString(); + var end_of_base_ref = doc_location.indexOf('TestRunner.html'); + var base_ref = doc_location.substring(0, end_of_base_ref); + + var fullURL = base_ref + "TestRunner.html?singletest=" + escape(browserBot.modalDialogTest) + "&autoURL=" + escape(url) + "&runInterval=" + runOptions.runInterval; + browserBot.modalDialogTest = null; + + var returnValue = oldShowModalDialog(fullURL, args, features); + return returnValue; + }; +}; + +IEBrowserBot.prototype.modifySeparateTestWindowToDetectPageLoads = function(windowObject) { + this.pageUnloading = false; + var self = this; + var pageUnloadDetector = function() { + self.pageUnloading = true; + }; + windowObject.attachEvent("onbeforeunload", pageUnloadDetector); + BrowserBot.prototype.modifySeparateTestWindowToDetectPageLoads.call(this, windowObject); +}; + +IEBrowserBot.prototype.pollForLoad = function(loadFunction, windowObject, originalDocument, originalLocation, originalHref, marker) { + LOG.debug("IEBrowserBot.pollForLoad: " + marker); + if (!this.permDeniedCount[marker]) this.permDeniedCount[marker] = 0; + BrowserBot.prototype.pollForLoad.call(this, loadFunction, windowObject, originalDocument, originalLocation, originalHref, marker); + if (this.pageLoadError) { + if (this.pageUnloading) { + var self = this; + LOG.warn("pollForLoad UNLOADING (" + marker + "): caught exception while firing events on unloading page: " + this.pageLoadError.message); + this.reschedulePoller(loadFunction, windowObject, originalDocument, originalLocation, originalHref, marker); + this.pageLoadError = null; + return; + } else if (((this.pageLoadError.message == "Permission denied") || (/^Access is denied/.test(this.pageLoadError.message))) + && this.permDeniedCount[marker]++ < 8) { + if (this.permDeniedCount[marker] > 4) { + var canAccessThisWindow; + var canAccessCurrentlySelectedWindow; + try { + windowObject.location.href; + canAccessThisWindow = true; + } catch (e) {} + try { + this.getCurrentWindow(true).location.href; + canAccessCurrentlySelectedWindow = true; + } catch (e) {} + if (canAccessCurrentlySelectedWindow & !canAccessThisWindow) { + LOG.warn("pollForLoad (" + marker + ") ABORTING: " + this.pageLoadError.message + " (" + this.permDeniedCount[marker] + "), but the currently selected window is fine"); + // returning without rescheduling + this.pageLoadError = null; + return; + } + } + + var self = this; + LOG.warn("pollForLoad (" + marker + "): " + this.pageLoadError.message + " (" + this.permDeniedCount[marker] + "), waiting to see if it goes away"); + this.reschedulePoller(loadFunction, windowObject, originalDocument, originalLocation, originalHref, marker); + this.pageLoadError = null; + return; + } + //handy for debugging! + //throw this.pageLoadError; + } +}; + +IEBrowserBot.prototype._windowClosed = function(win) { + try { + var c = win.closed; + // frame windows claim to be non-closed when their parents are closed + // but you can't access their document objects in that case + if (!c) { + try { + win.document; + } catch (de) { + if (de.message == "Permission denied") { + // the window is probably unloading, which means it's probably not closed yet + return false; + } + else if (/^Access is denied/.test(de.message)) { + // rare variation on "Permission denied"? + LOG.debug("IEBrowserBot.windowClosed: got " + de.message + " (this.pageUnloading=" + this.pageUnloading + "); assuming window is unloading, probably not closed yet"); + return false; + } else { + // this is probably one of those frame window situations + LOG.debug("IEBrowserBot.windowClosed: couldn't read win.document, assume closed: " + de.message + " (this.pageUnloading=" + this.pageUnloading + ")"); + return true; + } + } + } + if (c == null) { + LOG.debug("IEBrowserBot.windowClosed: win.closed was null, assuming closed"); + return true; + } + return c; + } catch (e) { + LOG.debug("IEBrowserBot._windowClosed: Got an exception trying to read win.closed; we'll have to take a guess!"); + + if (browserVersion.isHTA) { + if (e.message == "Permission denied") { + // the window is probably unloading, which means it's not closed yet + return false; + } else { + // there's a good chance that we've lost contact with the window object if it is closed + return true; + } + } else { + // the window is probably unloading, which means it's not closed yet + return false; + } + } +}; + +/** + * In IE, getElementById() also searches by name - this is an optimisation for IE. + */ +IEBrowserBot.prototype.locateElementByIdentifer = function(identifier, inDocument, inWindow) { + return inDocument.getElementById(identifier); +}; + +SafariBrowserBot.prototype.modifyWindowToRecordPopUpDialogs = function(windowToModify, browserBot) { + BrowserBot.prototype.modifyWindowToRecordPopUpDialogs(windowToModify, browserBot); + + var originalOpen = windowToModify.open; + /* + * Safari seems to be broken, so that when we manually trigger the onclick method + * of a button/href, any window.open calls aren't resolved relative to the app location. + * So here we replace the open() method with one that does resolve the url correctly. + */ + windowToModify.open = function(url, windowName, windowFeatures, replaceFlag) { + + if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("/")) { + return originalOpen(url, windowName, windowFeatures, replaceFlag); + } + + // Reduce the current path to the directory + var currentPath = windowToModify.location.pathname || "/"; + currentPath = currentPath.replace(/\/[^\/]*$/, "/"); + + // Remove any leading "./" from the new url. + url = url.replace(/^\.\//, ""); + + newUrl = currentPath + url; + + var openedWindow = originalOpen(newUrl, windowName, windowFeatures, replaceFlag); + LOG.debug("window.open call intercepted; window ID (which you can use with selectWindow()) is \"" + windowName + "\""); + if (windowName!=null) { + openedWindow["seleniumWindowName"] = windowName; + } + return openedWindow; + }; +}; + +MozillaBrowserBot.prototype._fireEventOnElement = function(eventType, element, clientX, clientY) { + var win = this.getCurrentWindow(); + 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) + // we capture the whole event, rather than the getPreventDefault() state at the time, + // because we need to let the entire event bubbling and capturing to go through + // before making a decision on whether we should force the href + var savedEvent = null; + + element.addEventListener(eventType, function(evt) { + savedEvent = evt; + }, false); + + this._modifyElementTarget(element); + + // Trigger the event. + this.browserbot.triggerMouseEvent(element, eventType, true, clientX, clientY); + + if (this._windowClosed(win)) { + return; + } + + // Perform the link action if preventDefault was set. + // In chrome URL, the link action is already executed by triggerMouseEvent. + if (!browserVersion.isChrome && savedEvent != null && !savedEvent.getPreventDefault()) { + var targetWindow = this.browserbot._getTargetWindow(element); + if (element.href) { + targetWindow.location.href = element.href; + } else { + this.browserbot._handleClickingImagesInsideLinks(targetWindow, element); + } + } + +}; + + +OperaBrowserBot.prototype._fireEventOnElement = function(eventType, element, clientX, clientY) { + var win = this.getCurrentWindow(); + triggerEvent(element, 'focus', false); + + this._modifyElementTarget(element); + + // Trigger the click event. + this.browserbot.triggerMouseEvent(element, eventType, true, clientX, clientY); + + if (this._windowClosed(win)) { + return; + } + +}; + + +KonquerorBrowserBot.prototype._fireEventOnElement = function(eventType, element, clientX, clientY) { + var win = this.getCurrentWindow(); + triggerEvent(element, 'focus', false); + + this._modifyElementTarget(element); + + if (element[eventType]) { + element[eventType](); + } + else { + this.browserbot.triggerMouseEvent(element, eventType, true, clientX, clientY); + } + + if (this._windowClosed(win)) { + return; + } + +}; + +SafariBrowserBot.prototype._fireEventOnElement = function(eventType, element, clientX, clientY) { + triggerEvent(element, 'focus', false); + var wasChecked = element.checked; + + this._modifyElementTarget(element); + + // For form element it is simple. + if (element[eventType]) { + element[eventType](); + } + // For links and other elements, event emulation is required. + else { + var targetWindow = this.browserbot._getTargetWindow(element); + // todo: deal with anchors? + this.browserbot.triggerMouseEvent(element, eventType, true, clientX, clientY); + + } + +}; + +SafariBrowserBot.prototype.refresh = function() { + var win = this.getCurrentWindow(); + if (win.location.hash) { + // DGF Safari refuses to refresh when there's a hash symbol in the URL + win.location.hash = ""; + var actuallyReload = function() { + win.location.reload(true); + } + window.setTimeout(actuallyReload, 1); + } else { + win.location.reload(true); + } +}; + +IEBrowserBot.prototype._fireEventOnElement = function(eventType, element, clientX, clientY) { + var win = this.getCurrentWindow(); + 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; + }; + win.attachEvent("onbeforeunload", pageUnloadDetector); + this._modifyElementTarget(element); + if (element[eventType]) { + element[eventType](); + } + else { + this.browserbot.triggerMouseEvent(element, eventType, true, clientX, clientY); + } + + + // 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 { + win.detachEvent("onbeforeunload", pageUnloadDetector); + + if (this._windowClosed(win)) { + return; + } + + // Onchange event is not triggered automatically in IE. + if (isDefined(element.checked) && wasChecked != element.checked) { + triggerEvent(element, 'change', true); + } + + } + 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.logHook = function() { + }; + LOG.warn("Caught exception when firing events on unloading page: " + e.message); + return; + } + throw e; + } +}; diff --git a/tests/test_tools/selenium/core/scripts/selenium-browserdetect.js b/tests/test_tools/selenium/core/scripts/selenium-browserdetect.js index d97e5a58..a9607371 100644 --- a/tests/test_tools/selenium/core/scripts/selenium-browserdetect.js +++ b/tests/test_tools/selenium/core/scripts/selenium-browserdetect.js @@ -30,32 +30,66 @@ var BrowserVersion = function() { return; } + var _getQueryParameter = function(searchKey) { + var str = location.search.substr(1); + if (str == null) return null; + var clauses = str.split('&'); + for (var i = 0; i < clauses.length; i++) { + var keyValuePair = clauses[i].split('=', 2); + var key = unescape(keyValuePair[0]); + if (key == searchKey) { + return unescape(keyValuePair[1]); + } + } + return null; + }; + var self = this; var checkChrome = function() { var loc = window.document.location.href; try { loc = window.top.document.location.href; + if (/^chrome:\/\//.test(loc)) { + self.isChrome = true; + } else { + self.isChrome = false; + } } catch (e) { // can't see the top (that means we might be chrome, but it's impossible to be sure) self.isChromeDetectable = "no, top location couldn't be read in this window"; + if (_getQueryParameter('thisIsChrome')) { + self.isChrome = true; + } else { + self.isChrome = false; + } } - if (/^chrome:\/\//.test(loc)) { - self.isChrome = true; - } else { - self.isChrome = false; - } + } + + if (this.name == "Microsoft Internet Explorer") { this.browser = BrowserVersion.IE; this.isIE = true; - if (window.top.SeleniumHTARunner && window.top.document.location.pathname.match(/.hta$/i)) { - this.isHTA = true; + try { + if (window.top.SeleniumHTARunner && window.top.document.location.pathname.match(/.hta$/i)) { + this.isHTA = true; + } + } catch (e) { + this.isHTADetectable = "no, top location couldn't be read in this window"; + if (_getQueryParameter('thisIsHTA')) { + self.isHTA = true; + } else { + self.isHTA = false; + } } if ("0" == navigator.appMinorVersion) { this.preSV1 = true; + if (navigator.appVersion.match(/MSIE 6.0/)) { + this.appearsToBeBrokenInitialIE6 = true; + } } return; } diff --git a/tests/test_tools/selenium/core/scripts/selenium-commandhandlers.js b/tests/test_tools/selenium/core/scripts/selenium-commandhandlers.js index c11a80ad..a23e9335 100644 --- a/tests/test_tools/selenium/core/scripts/selenium-commandhandlers.js +++ b/tests/test_tools/selenium/core/scripts/selenium-commandhandlers.js @@ -12,141 +12,165 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. -* */ -function CommandHandlerFactory() { - - var self = this; - - this.handlers = {}; - - this.registerAction = function(name, action, wait, dontCheckAlertsAndConfirms) { - var handler = new ActionHandler(action, wait, dontCheckAlertsAndConfirms); - this.handlers[name] = handler; - }; - - this.registerAccessor = function(name, accessor) { - var handler = new AccessorHandler(accessor); - this.handlers[name] = handler; - }; - - this.registerAssert = function(name, assertion, haltOnFailure) { - var handler = new AssertHandler(assertion, haltOnFailure); - this.handlers[name] = handler; - }; - - this.getCommandHandler = function(name) { - return this.handlers[name] || null; // todo: why null, and not undefined? - }; - - // Methods of the form getFoo(target) result in commands: - // getFoo, assertFoo, verifyFoo, assertNotFoo, verifyNotFoo - // storeFoo, waitForFoo, and waitForNotFoo. - var _registerAllAccessors = function(commandObject) { - for (var functionName in commandObject) { - var matchForGetter = /^get([A-Z].+)$/.exec(functionName); - if (matchForGetter != null) { - var accessor = commandObject[functionName]; - var baseName = matchForGetter[1]; - self.registerAccessor(functionName, accessor); - self.registerAssertionsBasedOnAccessor(accessor, baseName); - self.registerStoreCommandBasedOnAccessor(accessor, baseName); - self.registerWaitForCommandsBasedOnAccessor(accessor, baseName); - } - var matchForIs = /^is([A-Z].+)$/.exec(functionName); - if (matchForIs != null) { - var accessor = commandObject[functionName]; - var baseName = matchForIs[1]; - var predicate = self.createPredicateFromBooleanAccessor(accessor); - self.registerAccessor(functionName, accessor); - self.registerAssertionsBasedOnAccessor(accessor, baseName, predicate); - self.registerStoreCommandBasedOnAccessor(accessor, baseName); - self.registerWaitForCommandsBasedOnAccessor(accessor, baseName, predicate); + +// A naming convention used in this file: +// +// +// - a "seleniumApi" is an instance of the Selenium object, defined in selenium-api.js. +// +// - a "Method" is an unbound function whose target must be supplied when it's called, ie. +// it should be invoked using Function.call() or Function.apply() +// +// - a "Block" is a function that has been bound to a target object, so can be called invoked directly +// (or with a null target) +// +// - "CommandHandler" is effectively an abstract base for +// various handlers including ActionHandler, AccessorHandler and AssertHandler. +// Subclasses need to implement an execute(seleniumApi, command) function, +// where seleniumApi is the Selenium object, and command a SeleniumCommand object. +// +// - Handlers will return a "result" object (ActionResult, AccessorResult, AssertResult). +// ActionResults may contain a .terminationCondition function which is run by +// -executionloop.js after the command is run; we'll run it over and over again +// until it returns true or the .terminationCondition throws an exception. +// AccessorResults will contain the results of running getter (e.g. getTitle returns +// the title as a string). + +var CommandHandlerFactory = classCreate(); +objectExtend(CommandHandlerFactory.prototype, { + + initialize: function() { + this.handlers = {}; + }, + + registerAction: function(name, actionBlock, wait, dontCheckAlertsAndConfirms) { + this.handlers[name] = new ActionHandler(actionBlock, wait, dontCheckAlertsAndConfirms); + }, + + registerAccessor: function(name, accessBlock) { + this.handlers[name] = new AccessorHandler(accessBlock); + }, + + registerAssert: function(name, assertBlock, haltOnFailure) { + this.handlers[name] = new AssertHandler(assertBlock, haltOnFailure); + }, + + getCommandHandler: function(name) { + return this.handlers[name]; + }, + + _registerAllAccessors: function(seleniumApi) { + // Methods of the form getFoo(target) result in commands: + // getFoo, assertFoo, verifyFoo, assertNotFoo, verifyNotFoo + // storeFoo, waitForFoo, and waitForNotFoo. + for (var functionName in seleniumApi) { + var match = /^(get|is)([A-Z].+)$/.exec(functionName); + if (match) { + var accessMethod = seleniumApi[functionName]; + var accessBlock = fnBind(accessMethod, seleniumApi); + var baseName = match[2]; + var isBoolean = (match[1] == "is"); + var requiresTarget = (accessMethod.length == 1); + + this.registerAccessor(functionName, accessBlock); + this._registerStoreCommandForAccessor(baseName, accessBlock, requiresTarget); + + var predicateBlock = this._predicateForAccessor(accessBlock, requiresTarget, isBoolean); + this._registerAssertionsForPredicate(baseName, predicateBlock); + this._registerWaitForCommandsForPredicate(seleniumApi, baseName, predicateBlock); } } - }; - - var _registerAllActions = function(commandObject) { - for (var functionName in commandObject) { - var result = /^do([A-Z].+)$/.exec(functionName); - if (result != null) { - var actionName = result[1].lcfirst(); - - // Register the action without the wait flag. - var action = commandObject[functionName]; - self.registerAction(actionName, action, false, action.dontCheckAlertsAndConfirms); - - // Register actionName + "AndWait" with the wait flag; - var waitActionName = actionName + "AndWait"; - self.registerAction(waitActionName, action, true, action.dontCheckAlertsAndConfirms); + }, + + _registerAllActions: function(seleniumApi) { + for (var functionName in seleniumApi) { + var match = /^do([A-Z].+)$/.exec(functionName); + if (match) { + var actionName = match[1].lcfirst(); + var actionMethod = seleniumApi[functionName]; + var dontCheckPopups = actionMethod.dontCheckAlertsAndConfirms; + var actionBlock = fnBind(actionMethod, seleniumApi); + this.registerAction(actionName, actionBlock, false, dontCheckPopups); + this.registerAction(actionName + "AndWait", actionBlock, true, dontCheckPopups); } } - }; + }, - var _registerAllAsserts = function(commandObject) { - for (var functionName in commandObject) { - var result = /^assert([A-Z].+)$/.exec(functionName); - if (result != null) { - var assert = commandObject[functionName]; + _registerAllAsserts: function(seleniumApi) { + for (var functionName in seleniumApi) { + var match = /^assert([A-Z].+)$/.exec(functionName); + if (match) { + var assertBlock = fnBind(seleniumApi[functionName], seleniumApi); // Register the assert with the "assert" prefix, and halt on failure. var assertName = functionName; - self.registerAssert(assertName, assert, true); + this.registerAssert(assertName, assertBlock, true); // Register the assert with the "verify" prefix, and do not halt on failure. - var verifyName = "verify" + result[1]; - self.registerAssert(verifyName, assert, false); + var verifyName = "verify" + match[1]; + this.registerAssert(verifyName, assertBlock, false); } } - }; - - this.registerAll = function(commandObject) { - _registerAllAccessors(commandObject); - _registerAllActions(commandObject); - _registerAllAsserts(commandObject); - }; - - // Given an accessor function getBlah(target), - // return a "predicate" equivalient to isBlah(target, value) that - // is true when the value returned by the accessor matches the specified value. - this.createPredicateFromSingleArgAccessor = function(accessor) { + }, + + registerAll: function(seleniumApi) { + this._registerAllAccessors(seleniumApi); + this._registerAllActions(seleniumApi); + this._registerAllAsserts(seleniumApi); + }, + + _predicateForAccessor: function(accessBlock, requiresTarget, isBoolean) { + if (isBoolean) { + return this._predicateForBooleanAccessor(accessBlock); + } + if (requiresTarget) { + return this._predicateForSingleArgAccessor(accessBlock); + } + return this._predicateForNoArgAccessor(accessBlock); + }, + + _predicateForSingleArgAccessor: function(accessBlock) { + // Given an accessor function getBlah(target), + // return a "predicate" equivalient to isBlah(target, value) that + // is true when the value returned by the accessor matches the specified value. return function(target, value) { - var accessorResult = accessor.call(this, target); + var accessorResult = accessBlock(target); if (PatternMatcher.matches(value, accessorResult)) { return new PredicateResult(true, "Actual value '" + accessorResult + "' did match '" + value + "'"); } else { return new PredicateResult(false, "Actual value '" + accessorResult + "' did not match '" + value + "'"); } }; - }; + }, - // Given a (no-arg) accessor function getBlah(), - // return a "predicate" equivalient to isBlah(value) that - // is true when the value returned by the accessor matches the specified value. - this.createPredicateFromNoArgAccessor = function(accessor) { + _predicateForNoArgAccessor: function(accessBlock) { + // Given a (no-arg) accessor function getBlah(), + // return a "predicate" equivalient to isBlah(value) that + // is true when the value returned by the accessor matches the specified value. return function(value) { - var accessorResult = accessor.call(this); + var accessorResult = accessBlock(); if (PatternMatcher.matches(value, accessorResult)) { return new PredicateResult(true, "Actual value '" + accessorResult + "' did match '" + value + "'"); } else { return new PredicateResult(false, "Actual value '" + accessorResult + "' did not match '" + value + "'"); } }; - }; + }, - // Given a boolean accessor function isBlah(), - // return a "predicate" equivalient to isBlah() that - // returns an appropriate PredicateResult value. - this.createPredicateFromBooleanAccessor = function(accessor) { + _predicateForBooleanAccessor: function(accessBlock) { + // Given a boolean accessor function isBlah(), + // return a "predicate" equivalient to isBlah() that + // returns an appropriate PredicateResult value. return function() { var accessorResult; if (arguments.length > 2) throw new SeleniumError("Too many arguments! " + arguments.length); if (arguments.length == 2) { - accessorResult = accessor.call(this, arguments[0], arguments[1]); + accessorResult = accessBlock(arguments[0], arguments[1]); } else if (arguments.length == 1) { - accessorResult = accessor.call(this, arguments[0]); + accessorResult = accessBlock(arguments[0]); } else { - accessorResult = accessor.call(this); + accessorResult = accessBlock(); } if (accessorResult) { return new PredicateResult(true, "true"); @@ -154,71 +178,56 @@ function CommandHandlerFactory() { return new PredicateResult(false, "false"); } }; - }; - - // Given an accessor fuction getBlah([target]) (target is optional) - // return a predicate equivalent to isBlah([target,] value) that - // is true when the value returned by the accessor matches the specified value. - this.createPredicateFromAccessor = function(accessor) { - if (accessor.length == 0) { - return self.createPredicateFromNoArgAccessor(accessor); - } - return self.createPredicateFromSingleArgAccessor(accessor); - }; + }, - // Given a predicate, return the negation of that predicate. - // Leaves the message unchanged. - // Used to create assertNot, verifyNot, and waitForNot commands. - this.invertPredicate = function(predicate) { + _invertPredicate: function(predicateBlock) { + // Given a predicate, return the negation of that predicate. + // Leaves the message unchanged. + // Used to create assertNot, verifyNot, and waitForNot commands. return function(target, value) { - var result = predicate.call(this, target, value); - result.isTrue = ! result.isTrue; + var result = predicateBlock(target, value); + result.isTrue = !result.isTrue; return result; }; - }; + }, - // Convert an isBlahBlah(target, value) function into an assertBlahBlah(target, value) function. - this.createAssertionFromPredicate = function(predicate) { + createAssertionFromPredicate: function(predicateBlock) { + // Convert an isBlahBlah(target, value) function into an assertBlahBlah(target, value) function. return function(target, value) { - var result = predicate.call(this, target, value); + var result = predicateBlock(target, value); if (!result.isTrue) { Assert.fail(result.message); } }; - }; - + }, - var _negtiveName = function(baseName) { + _invertPredicateName: function(baseName) { var matchResult = /^(.*)Present$/.exec(baseName); if (matchResult != null) { return matchResult[1] + "NotPresent"; } return "Not" + baseName; - }; - - // Register an assertion, a verification, a negative assertion, - // and a negative verification based on the specified accessor. - this.registerAssertionsBasedOnAccessor = function(accessor, baseName, predicate) { - if (predicate == null) { - predicate = self.createPredicateFromAccessor(accessor); - } - var assertion = self.createAssertionFromPredicate(predicate); - self.registerAssert("assert" + baseName, assertion, true); - self.registerAssert("verify" + baseName, assertion, false); - - var invertedPredicate = self.invertPredicate(predicate); - var negativeAssertion = self.createAssertionFromPredicate(invertedPredicate); - self.registerAssert("assert" + _negtiveName(baseName), negativeAssertion, true); - self.registerAssert("verify" + _negtiveName(baseName), negativeAssertion, false); - }; - - // Convert an isBlahBlah(target, value) function into a waitForBlahBlah(target, value) function. - this.createWaitForActionFromPredicate = function(predicate) { + }, + + _registerAssertionsForPredicate: function(baseName, predicateBlock) { + // Register an assertion, a verification, a negative assertion, + // and a negative verification based on the specified accessor. + var assertBlock = this.createAssertionFromPredicate(predicateBlock); + this.registerAssert("assert" + baseName, assertBlock, true); + this.registerAssert("verify" + baseName, assertBlock, false); + + var invertedPredicateBlock = this._invertPredicate(predicateBlock); + var negativeassertBlock = this.createAssertionFromPredicate(invertedPredicateBlock); + this.registerAssert("assert" + this._invertPredicateName(baseName), negativeassertBlock, true); + this.registerAssert("verify" + this._invertPredicateName(baseName), negativeassertBlock, false); + }, + + _waitForActionForPredicate: function(predicateBlock) { + // Convert an isBlahBlah(target, value) function into a waitForBlahBlah(target, value) function. return function(target, value) { - var seleniumApi = this; - return function () { + var terminationCondition = function () { try { - return predicate.call(seleniumApi, target, value).isTrue; + return predicateBlock(target, value).isTrue; } catch (e) { // Treat exceptions as meaning the condition is not yet met. // Useful, for example, for waitForValue when the element has @@ -227,40 +236,41 @@ function CommandHandlerFactory() { return false; } }; + return Selenium.decorateFunctionWithTimeout(terminationCondition, this.defaultTimeout); }; - }; - - // Register a waitForBlahBlah and waitForNotBlahBlah based on the specified accessor. - this.registerWaitForCommandsBasedOnAccessor = function(accessor, baseName, predicate) { - if (predicate==null) { - predicate = self.createPredicateFromAccessor(accessor); - } - var waitForAction = self.createWaitForActionFromPredicate(predicate); - self.registerAction("waitFor"+baseName, waitForAction, false, true); - var invertedPredicate = self.invertPredicate(predicate); - var waitForNotAction = self.createWaitForActionFromPredicate(invertedPredicate); - self.registerAction("waitFor"+_negtiveName(baseName), waitForNotAction, false, true); + }, + + _registerWaitForCommandsForPredicate: function(seleniumApi, baseName, predicateBlock) { + // Register a waitForBlahBlah and waitForNotBlahBlah based on the specified accessor. + var waitForActionMethod = this._waitForActionForPredicate(predicateBlock); + var waitForActionBlock = fnBind(waitForActionMethod, seleniumApi); + + var invertedPredicateBlock = this._invertPredicate(predicateBlock); + var waitForNotActionMethod = this._waitForActionForPredicate(invertedPredicateBlock); + var waitForNotActionBlock = fnBind(waitForNotActionMethod, seleniumApi); + + this.registerAction("waitFor" + baseName, waitForActionBlock, false, true); + this.registerAction("waitFor" + this._invertPredicateName(baseName), waitForNotActionBlock, false, true); //TODO decide remove "waitForNot.*Present" action name or not //for the back compatiblity issues we still make waitForNot.*Present availble - self.registerAction("waitForNot"+baseName, waitForNotAction, false, true); - } + this.registerAction("waitForNot" + baseName, waitForNotActionBlock, false, true); + }, - // Register a storeBlahBlah based on the specified accessor. - this.registerStoreCommandBasedOnAccessor = function(accessor, baseName) { + _registerStoreCommandForAccessor: function(baseName, accessBlock, requiresTarget) { var action; - if (accessor.length == 1) { + if (requiresTarget) { action = function(target, varName) { - storedVars[varName] = accessor.call(this, target); + storedVars[varName] = accessBlock(target); }; } else { action = function(varName) { - storedVars[varName] = accessor.call(this); + storedVars[varName] = accessBlock(); }; } - self.registerAction("store"+baseName, action, false, accessor.dontCheckAlertsAndConfirms); - }; + this.registerAction("store" + baseName, action, false, true); + } -} +}); function PredicateResult(isTrue, message) { this.isTrue = isTrue; @@ -271,17 +281,17 @@ function PredicateResult(isTrue, message) { // various handlers including ActionHandler, AccessorHandler and AssertHandler. // Subclasses need to implement an execute(seleniumApi, command) function, // where seleniumApi is the Selenium object, and command a SeleniumCommand object. -function CommandHandler(type, haltOnFailure, executor) { +function CommandHandler(type, haltOnFailure) { this.type = type; this.haltOnFailure = haltOnFailure; - this.executor = executor; } // An ActionHandler is a command handler that executes the sepcified action, // possibly checking for alerts and confirmations (if checkAlerts is set), and // possibly waiting for a page load if wait is set. -function ActionHandler(action, wait, dontCheckAlerts) { - CommandHandler.call(this, "action", true, action); +function ActionHandler(actionBlock, wait, dontCheckAlerts) { + this.actionBlock = actionBlock; + CommandHandler.call(this, "action", true); if (wait) { this.wait = true; } @@ -290,10 +300,11 @@ function ActionHandler(action, wait, dontCheckAlerts) { } ActionHandler.prototype = new CommandHandler; ActionHandler.prototype.execute = function(seleniumApi, command) { - if (this.checkAlerts && (null==/(Alert|Confirmation)(Not)?Present/.exec(command.command))) { + if (this.checkAlerts && (null == /(Alert|Confirmation)(Not)?Present/.exec(command.command))) { + // todo: this conditional logic is ugly seleniumApi.ensureNoUnhandledPopups(); } - var terminationCondition = this.executor.call(seleniumApi, command.target, command.value); + var terminationCondition = this.actionBlock(command.target, command.value); // If the handler didn't return a wait flag, check to see if the // handler was registered with the wait flag. if (terminationCondition == undefined && this.wait) { @@ -306,12 +317,13 @@ function ActionResult(terminationCondition) { this.terminationCondition = terminationCondition; } -function AccessorHandler(accessor) { - CommandHandler.call(this, "accessor", true, accessor); +function AccessorHandler(accessBlock) { + this.accessBlock = accessBlock; + CommandHandler.call(this, "accessor", true); } AccessorHandler.prototype = new CommandHandler; AccessorHandler.prototype.execute = function(seleniumApi, command) { - var returnValue = this.executor.call(seleniumApi, command.target, command.value); + var returnValue = this.accessBlock(command.target, command.value); return new AccessorResult(returnValue); }; @@ -322,14 +334,15 @@ function AccessorResult(result) { /** * Handler for assertions and verifications. */ -function AssertHandler(assertion, haltOnFailure) { - CommandHandler.call(this, "assert", haltOnFailure || false, assertion); +function AssertHandler(assertBlock, haltOnFailure) { + this.assertBlock = assertBlock; + CommandHandler.call(this, "assert", haltOnFailure || false); } AssertHandler.prototype = new CommandHandler; AssertHandler.prototype.execute = function(seleniumApi, command) { var result = new AssertResult(); try { - this.executor.call(seleniumApi, command.target, command.value); + this.assertBlock(command.target, command.value); } catch (e) { // If this is not a AssertionFailedError, or we should haltOnFailure, rethrow. if (!e.isAssertionFailedError) { diff --git a/tests/test_tools/selenium/core/scripts/selenium-executionloop.js b/tests/test_tools/selenium/core/scripts/selenium-executionloop.js index d59fc148..be54115e 100644 --- a/tests/test_tools/selenium/core/scripts/selenium-executionloop.js +++ b/tests/test_tools/selenium/core/scripts/selenium-executionloop.js @@ -64,7 +64,7 @@ TestLoop.prototype = { // Pause: enable the "next/continue" button this.pause(); } else { - window.setTimeout(this.resume.bind(this), delay); + window.setTimeout(fnBind(this.resume, this), delay); } }, @@ -78,9 +78,11 @@ TestLoop.prototype = { this._executeCurrentCommand(); this.continueTestWhenConditionIsTrue(); } catch (e) { - this._handleCommandError(e); - this._testComplete(); - return; + if (!this._handleCommandError(e)) { + this._testComplete(); + } else { + this.continueTest(); + } } }, @@ -107,11 +109,10 @@ TestLoop.prototype = { command.target = selenium.preprocessParameter(command.target); command.value = selenium.preprocessParameter(command.value); LOG.debug("Command found, going to execute " + command.command); - var result = handler.execute(selenium, command); - LOG.debug("Command complete"); - this.commandComplete(result); + this.result = handler.execute(selenium, command); + - this.waitForCondition = result.terminationCondition; + this.waitForCondition = this.result.terminationCondition; }, @@ -122,10 +123,10 @@ TestLoop.prototype = { if (e.message) { msg += " The error message is: " + e.message; } - this.commandError(msg); + return this.commandError(msg); } else { LOG.error(e.message); - this.commandError(e.message); + return this.commandError(e.message); } }, @@ -135,23 +136,30 @@ TestLoop.prototype = { * on with test. Fail the current test if there's a timeout or an * exception. */ - LOG.debug("currentTest.continueTestWhenConditionIsTrue()"); + //LOG.debug("currentTest.continueTestWhenConditionIsTrue()"); selenium.browserbot.runScheduledPollers(); try { - if (this.waitForCondition == null || this.waitForCondition()) { + if (this.waitForCondition == null) { + LOG.debug("null condition; let's continueTest()"); + LOG.debug("Command complete"); + this.commandComplete(this.result); + this.continueTest(); + } else if (this.waitForCondition()) { LOG.debug("condition satisfied; let's continueTest()"); this.waitForCondition = null; + LOG.debug("Command complete"); + this.commandComplete(this.result); this.continueTest(); } else { - LOG.debug("waitForCondition was false; keep waiting!"); - window.setTimeout(this.continueTestWhenConditionIsTrue.bind(this), 100); + //LOG.debug("waitForCondition was false; keep waiting!"); + window.setTimeout(fnBind(this.continueTestWhenConditionIsTrue, this), 10); } } catch (e) { - var lastResult = {}; - lastResult.failed = true; - lastResult.failureMessage = e.message; - this.commandComplete(lastResult); - this.testComplete(); + this.result = {}; + this.result.failed = true; + this.result.failureMessage = extractExceptionMessage(e); + this.commandComplete(this.result); + this.continueTest(); } }, @@ -167,20 +175,3 @@ TestLoop.prototype = { } } - -function decorateFunctionWithTimeout(f, timeout) { - if (f == null) { - return null; - } - if (isNaN(timeout)) { - throw new SeleniumError("Timeout is not a number: " + timeout); - } - var now = new Date().getTime(); - var timeoutTime = now + timeout; - return function() { - if (new Date().getTime() > timeoutTime) { - throw new SeleniumError("Timed out after " + timeout + "ms"); - } - return f(); - }; -} diff --git a/tests/test_tools/selenium/core/scripts/selenium-logging.js b/tests/test_tools/selenium/core/scripts/selenium-logging.js index 25e11463..6dac9518 100644 --- a/tests/test_tools/selenium/core/scripts/selenium-logging.js +++ b/tests/test_tools/selenium/core/scripts/selenium-logging.js @@ -52,6 +52,13 @@ Logger.prototype = { "width=600,height=1000,bottom=0,right=0,status,scrollbars,resizable" ); this.logWindow.moveTo(window.screenX + 1210, window.screenY + window.outerHeight - 1400); + if (browserVersion.appearsToBeBrokenInitialIE6) { + // I would really prefer for the message to immediately appear in the log window, the instant the user requests that the log window be + // visible. But when I initially coded it this way, thou message simply didn't appear unless I stepped through the code with a debugger. + // So obviously there is some timing issue here which I don't have the patience to figure out. + var pendingMessage = new LogMessage("warn", "You appear to be running an unpatched IE 6, which is not stable and can crash due to memory problems. We recommend you run Windows update to install a more stable version of IE."); + this.pendingMessages.push(pendingMessage); + } return this.logWindow; }, @@ -59,14 +66,15 @@ Logger.prototype = { if (! this.getLogWindow()) { this.openLogWindow(); } + setTimeout(function(){LOG.info("Log window displayed");}, 500); }, - logHook: function(message, className) { + logHook: function(className, message) { }, - log: function(message, className) { + log: function(className, message) { var logWindow = this.getLogWindow(); - this.logHook(message, className); + this.logHook(className, message); if (logWindow) { if (logWindow.append) { if (this.pendingMessages.length > 0) { @@ -84,7 +92,7 @@ Logger.prototype = { /* these logging messages are never flushed, which creates an enormous array of strings that never stops growing. Only turn this on if you need it for debugging! */ - //this.pendingMessages.push(new LogMessage(message, className)); + //this.pendingMessages.push(new LogMessage(className, message)); } }, @@ -101,31 +109,31 @@ Logger.prototype = { }, debug: function(message) { - this.log(message, "debug"); + this.log("debug", message); }, info: function(message) { - this.log(message, "info"); + this.log("info", message); }, warn: function(message) { - this.log(message, "warn"); + this.log("warn", message); }, error: function(message) { - this.log(message, "error"); + this.log("error", message); }, exception: function(exception) { - var msg = "Unexpected Exception: " + describe(exception, ', '); - this.error(msg); + this.error("Unexpected Exception: " + extractExceptionMessage(exception)); + this.error("Exception details: " + describe(exception, ', ')); } }; var LOG = new Logger(); -var LogMessage = function(msg, type) { +var LogMessage = function(type, msg) { this.type = type; this.msg = msg; } diff --git a/tests/test_tools/selenium/core/scripts/selenium-testrunner.js b/tests/test_tools/selenium/core/scripts/selenium-testrunner.js index b5104d39..fd7f2076 100644 --- a/tests/test_tools/selenium/core/scripts/selenium-testrunner.js +++ b/tests/test_tools/selenium/core/scripts/selenium-testrunner.js @@ -20,36 +20,55 @@ var currentTest = null; // TODO: get rid of this global, which mirrors the htmlT var selenium = null; var htmlTestRunner; -var HtmlTestRunner = Class.create(); -Object.extend(HtmlTestRunner.prototype, { +var HtmlTestRunner = classCreate(); +objectExtend(HtmlTestRunner.prototype, { initialize: function() { this.metrics = new Metrics(); this.controlPanel = new HtmlTestRunnerControlPanel(); - this.htmlTestSuite = null; this.testFailed = false; this.currentTest = null; this.runAllTests = false; this.appWindow = null; // we use a timeout here to make sure the LOG has loaded first, so we can see _every_ error - setTimeout(function() { + setTimeout(fnBind(function() { this.loadSuiteFrame(); - }.bind(this), 500); + }, this), 500); + }, + + getTestSuite: function() { + return suiteFrame.getCurrentTestSuite(); }, markFailed: function() { this.testFailed = true; - this.htmlTestSuite.markFailed(); + this.getTestSuite().markFailed(); }, loadSuiteFrame: function() { if (selenium == null) { - selenium = Selenium.createForWindow(this._getApplicationWindow()); + var appWindow = this._getApplicationWindow(); + try { appWindow.location; } + catch (e) { + // when reloading, we may be pointing at an old window (Perm Denied) + setTimeout(fnBind(function() { + this.loadSuiteFrame(); + }, this), 50); + return; + } + selenium = Selenium.createForWindow(appWindow); this._registerCommandHandlers(); } this.controlPanel.setHighlightOption(); var testSuiteName = this.controlPanel.getTestSuiteName(); + var self = this; if (testSuiteName) { - suiteFrame.load(testSuiteName, this._onloadTestSuite.bind(this)); + suiteFrame.load(testSuiteName, function() {setTimeout(fnBind(self._onloadTestSuite, self), 50)} ); + selenium.browserbot.baseUrl = absolutify(testSuiteName, window.location.href); + } + // DGF or should we use the old default? + // selenium.browserbot.baseUrl = window.location.href; + if (this.controlPanel.getBaseUrl()) { + selenium.browserbot.baseUrl = this.controlPanel.getBaseUrl(); } }, @@ -62,31 +81,30 @@ Object.extend(HtmlTestRunner.prototype, { _getSeparateApplicationWindow: function () { if (this.appWindow == null) { - this.appWindow = openSeparateApplicationWindow('TestRunner-splash.html'); + this.appWindow = openSeparateApplicationWindow('TestRunner-splash.html', this.controlPanel.isAutomatedRun()); } return this.appWindow; }, _onloadTestSuite:function () { - this.htmlTestSuite = new HtmlTestSuite(suiteFrame.getDocument()); - if (! this.htmlTestSuite.isAvailable()) { + if (! this.getTestSuite().isAvailable()) { return; } if (this.controlPanel.isAutomatedRun()) { - htmlTestRunner.startTestSuite(); + this.startTestSuite(); } else if (this.controlPanel.getAutoUrl()) { //todo what is the autourl doing, left to check it out - addLoadListener(this._getApplicationWindow(), this._startSingleTest.bind(this)); + addLoadListener(this._getApplicationWindow(), fnBind(this._startSingleTest, this)); this._getApplicationWindow().src = this.controlPanel.getAutoUrl(); } else { - this.htmlTestSuite.getSuiteRows()[0].loadTestCase(); + this.getTestSuite().getSuiteRows()[0].loadTestCase(); } }, _startSingleTest:function () { - removeLoadListener(getApplicationWindow(), this._startSingleTest.bind(this)); + removeLoadListener(getApplicationWindow(), fnBind(this._startSingleTest, this)); var singleTestName = this.controlPanel.getSingleTestName(); - testFrame.load(singleTestName, this.startTest.bind(this)); + testFrame.load(singleTestName, fnBind(this.startTest, this)); }, _registerCommandHandlers: function () { @@ -97,16 +115,17 @@ Object.extend(HtmlTestRunner.prototype, { startTestSuite: function() { this.controlPanel.reset(); this.metrics.resetMetrics(); - this.htmlTestSuite.reset(); + this.getTestSuite().reset(); this.runAllTests = true; this.runNextTest(); }, runNextTest: function () { + this.getTestSuite().updateSuiteWithResultOfPreviousTest(); if (!this.runAllTests) { return; } - this.htmlTestSuite.runNextTestInSuite(); + this.getTestSuite().runNextTestInSuite(); }, startTest: function () { @@ -127,26 +146,15 @@ Object.extend(HtmlTestRunner.prototype, { } }); -var FeedbackColors = Class.create(); -Object.extend(FeedbackColors, { - passColor : "#ccffcc", - doneColor : "#eeffee", - failColor : "#ffcccc", - workingColor : "#ffffcc", - breakpointColor : "#cccccc" -}); - - var runInterval = 0; - /** SeleniumFrame encapsulates an iframe element */ -var SeleniumFrame = Class.create(); -Object.extend(SeleniumFrame.prototype, { +var SeleniumFrame = classCreate(); +objectExtend(SeleniumFrame.prototype, { initialize : function(frame) { this.frame = frame; - addLoadListener(this.frame, this._handleLoad.bind(this)); + addLoadListener(this.frame, fnBind(this._handleLoad, this)); }, getDocument : function() { @@ -154,13 +162,38 @@ Object.extend(SeleniumFrame.prototype, { }, _handleLoad: function() { - this._onLoad(); + this._attachStylesheet(); + this._onLoad(); if (this.loadCallback) { this.loadCallback(); this.loadCallback = null; } }, + _attachStylesheet: function() { + var d = this.getDocument(); + var head = d.getElementsByTagName('head').item(0); + var styleLink = d.createElement("link"); + styleLink.rel = "stylesheet"; + styleLink.type = "text/css"; + if (browserVersion && browserVersion.isChrome) { + // DGF We have to play a clever trick to get the right absolute path. + // This trick works on most browsers, (not IE), but is only needed in + // chrome + var tempLink = window.document.createElement("link"); + tempLink.href = "selenium-test.css"; // this will become an absolute href + styleLink.href = tempLink.href; + } else { + // this works in every browser (except Firefox in chrome mode) + var styleSheetPath = window.location.pathname.replace(/[^\/\\]+$/, "selenium-test.css"); + if (browserVersion.isIE && window.location.protocol == "file:") { + styleSheetPath = "file://" + styleSheetPath; + } + styleLink.href = styleSheetPath; + } + head.appendChild(styleLink); + }, + _onLoad: function() { }, @@ -169,10 +202,14 @@ Object.extend(SeleniumFrame.prototype, { }, _setLocation: function(location) { + var isChrome = browserVersion.isChrome || false; + var isHTA = browserVersion.isHTA || false; + // DGF TODO multiWindow + location += "?thisIsChrome=" + isChrome + "&thisIsHTA=" + isHTA; if (browserVersion.isSafari) { - // safari doesn't reload the page when the location equals to current location. - // hence, set the location to blank so that the page will reload automatically. - this.frame.src = "about:blank"; + // safari doesn't reload the page when the location equals to current location. + // hence, set the location to blank so that the page will reload automatically. + this.frame.src = "about:blank"; this.frame.src = location; } else { this.frame.contentWindow.location.replace(location); @@ -189,18 +226,27 @@ Object.extend(SeleniumFrame.prototype, { }); +/** HtmlTestSuiteFrame - encapsulates the suite iframe element */ +var HtmlTestSuiteFrame = classCreate(); +objectExtend(HtmlTestSuiteFrame.prototype, SeleniumFrame.prototype); +objectExtend(HtmlTestSuiteFrame.prototype, { + + getCurrentTestSuite: function() { + if (!this.currentTestSuite) { + this.currentTestSuite = new HtmlTestSuite(this.getDocument()); + } + return this.currentTestSuite; + } + +}); + /** HtmlTestFrame - encapsulates the test-case iframe element */ -var HtmlTestFrame = Class.create(); -Object.extend(HtmlTestFrame.prototype, SeleniumFrame.prototype); -Object.extend(HtmlTestFrame.prototype, { +var HtmlTestFrame = classCreate(); +objectExtend(HtmlTestFrame.prototype, SeleniumFrame.prototype); +objectExtend(HtmlTestFrame.prototype, { _onLoad: function() { - this.setCurrentTestCase(); - }, - - setCurrentTestCase: function() { - //todo: this is not good looking - this.currentTestCase = new HtmlTestCase(this.getDocument(), htmlTestRunner.htmlTestSuite.getCurrentRow()); + this.currentTestCase = new HtmlTestCase(this.getDocument(), htmlTestRunner.getTestSuite().getCurrentRow()); }, getCurrentTestCase: function() { @@ -210,14 +256,14 @@ Object.extend(HtmlTestFrame.prototype, { }); function onSeleniumLoad() { - suiteFrame = new SeleniumFrame(getSuiteFrame()); + suiteFrame = new HtmlTestSuiteFrame(getSuiteFrame()); testFrame = new HtmlTestFrame(getTestFrame()); htmlTestRunner = new HtmlTestRunner(); } - var suiteFrame; var testFrame; + function getSuiteFrame() { var f = $('testSuiteFrame'); if (f == null) { @@ -236,9 +282,9 @@ function getTestFrame() { return f; } -var HtmlTestRunnerControlPanel = Class.create(); -Object.extend(HtmlTestRunnerControlPanel.prototype, URLConfiguration.prototype); -Object.extend(HtmlTestRunnerControlPanel.prototype, { +var HtmlTestRunnerControlPanel = classCreate(); +objectExtend(HtmlTestRunnerControlPanel.prototype, URLConfiguration.prototype); +objectExtend(HtmlTestRunnerControlPanel.prototype, { initialize: function() { this._acquireQueryString(); @@ -248,16 +294,17 @@ Object.extend(HtmlTestRunnerControlPanel.prototype, { this.pauseButton = $('pauseTest'); this.stepButton = $('stepTest'); - this.highlightOption.onclick = (function() { + this.highlightOption.onclick = fnBindAsEventListener((function() { this.setHighlightOption(); - }).bindAsEventListener(this); - this.pauseButton.onclick = this.pauseCurrentTest.bindAsEventListener(this); - this.stepButton.onclick = this.stepCurrentTest.bindAsEventListener(this); + }), this); + this.pauseButton.onclick = fnBindAsEventListener(this.pauseCurrentTest, this); + this.stepButton.onclick = fnBindAsEventListener(this.stepCurrentTest, this); + this.speedController = new Control.Slider('speedHandle', 'speedTrack', { range: $R(0, 1000), - onSlide: this.setRunInterval.bindAsEventListener(this), - onChange: this.setRunInterval.bindAsEventListener(this) + onSlide: fnBindAsEventListener(this.setRunInterval, this), + onChange: fnBindAsEventListener(this.setRunInterval, this) }); this._parseQueryParameter(); @@ -265,7 +312,7 @@ Object.extend(HtmlTestRunnerControlPanel.prototype, { setHighlightOption: function () { var isHighlight = this.highlightOption.checked; - selenium.browserbot.getCurrentPage().setHighlightElement(isHighlight); + selenium.browserbot.setShouldHighlightElement(isHighlight); }, _parseQueryParameter: function() { @@ -295,19 +342,19 @@ Object.extend(HtmlTestRunnerControlPanel.prototype, { }, reset: function() { - this.runInterval = this.speedController.value; + // this.runInterval = this.speedController.value; this._switchContinueButtonToPause(); }, _switchContinueButtonToPause: function() { - this.pauseButton.innerHTML = "Pause"; - this.pauseButton.onclick = this.pauseCurrentTest.bindAsEventListener(this); + this.pauseButton.className = "cssPauseTest"; + this.pauseButton.onclick = fnBindAsEventListener(this.pauseCurrentTest, this); }, _switchPauseButtonToContinue: function() { $('stepTest').disabled = false; - this.pauseButton.innerHTML = "Continue"; - this.pauseButton.onclick = this.continueCurrentTest.bindAsEventListener(this); + this.pauseButton.className = "cssContinueTest"; + this.pauseButton.onclick = fnBindAsEventListener(this.continueCurrentTest, this); }, stepCurrentTest: function () { @@ -356,36 +403,58 @@ Object.extend(HtmlTestRunnerControlPanel.prototype, { }); - -var AbstractResultAwareRow = Class.create(); -Object.extend(AbstractResultAwareRow.prototype, { +var AbstractResultAwareRow = classCreate(); +objectExtend(AbstractResultAwareRow.prototype, { initialize: function(trElement) { this.trElement = trElement; }, - markWorking: function() { - this.trElement.bgColor = FeedbackColors.workingColor; + setStatus: function(status) { + this.unselect(); + this.trElement.className = this.trElement.className.replace(/status_[a-z]+/, ""); + if (status) { + addClassName(this.trElement, "status_" + status); + } + }, + + select: function() { + addClassName(this.trElement, "selected"); safeScrollIntoView(this.trElement); }, + unselect: function() { + removeClassName(this.trElement, "selected"); + }, + markPassed: function() { - this.trElement.bgColor = FeedbackColors.passColor; + this.setStatus("passed"); }, markDone: function() { - this.trElement.bgColor = FeedbackColors.doneColor; + this.setStatus("done"); }, markFailed: function() { - this.trElement.bgColor = FeedbackColors.failColor; + this.setStatus("failed"); + } + +}); + +var TitleRow = classCreate(); +objectExtend(TitleRow.prototype, AbstractResultAwareRow.prototype); +objectExtend(TitleRow.prototype, { + + initialize: function(trElement) { + this.trElement = trElement; + trElement.className = "title"; } }); -var HtmlTestCaseRow = Class.create(); -Object.extend(HtmlTestCaseRow.prototype, AbstractResultAwareRow.prototype); -Object.extend(HtmlTestCaseRow.prototype, { +var HtmlTestCaseRow = classCreate(); +objectExtend(HtmlTestCaseRow.prototype, AbstractResultAwareRow.prototype); +objectExtend(HtmlTestCaseRow.prototype, { getCommand: function () { return new SeleniumCommand(getText(this.trElement.cells[0]), @@ -395,16 +464,16 @@ Object.extend(HtmlTestCaseRow.prototype, { }, markFailed: function(errorMsg) { - this.trElement.bgColor = FeedbackColors.failColor; + AbstractResultAwareRow.prototype.markFailed.call(this, errorMsg); this.setMessage(errorMsg); }, setMessage: function(message) { - this.trElement.cells[2].innerHTML = message; + setText(this.trElement.cells[2], message); }, reset: function() { - this.trElement.bgColor = ''; + this.setStatus(null); var thirdCell = this.trElement.cells[2]; if (thirdCell) { if (thirdCell.originalHTML) { @@ -418,19 +487,18 @@ Object.extend(HtmlTestCaseRow.prototype, { onClick: function() { if (this.trElement.isBreakpoint == undefined) { this.trElement.isBreakpoint = true; - this.trElement.beforeBackgroundColor = Element.getStyle(this.trElement, "backgroundColor"); - Element.setStyle(this.trElement, {"background-color" : FeedbackColors.breakpointColor}); + addClassName(this.trElement, "breakpoint"); } else { this.trElement.isBreakpoint = undefined; - Element.setStyle(this.trElement, {"background-color" : this.trElement.beforeBackgroundColor}); + removeClassName(this.trElement, "breakpoint"); } }, addBreakpointSupport: function() { - Element.setStyle(this.trElement, {"cursor" : "pointer"}); - this.trElement.onclick = function() { + elementSetStyle(this.trElement, {"cursor" : "pointer"}); + this.trElement.onclick = fnBindAsEventListener(function() { this.onClick(); - }.bindAsEventListener(this); + }, this); }, isBreakpoint: function() { @@ -441,42 +509,44 @@ Object.extend(HtmlTestCaseRow.prototype, { } }); -var HtmlTestSuiteRow = Class.create(); -Object.extend(HtmlTestSuiteRow.prototype, AbstractResultAwareRow.prototype); -Object.extend(HtmlTestSuiteRow.prototype, { +var HtmlTestSuiteRow = classCreate(); +objectExtend(HtmlTestSuiteRow.prototype, AbstractResultAwareRow.prototype); +objectExtend(HtmlTestSuiteRow.prototype, { initialize: function(trElement, testFrame, htmlTestSuite) { this.trElement = trElement; this.testFrame = testFrame; this.htmlTestSuite = htmlTestSuite; this.link = trElement.getElementsByTagName("a")[0]; - this.link.onclick = this._onClick.bindAsEventListener(this); + this.link.onclick = fnBindAsEventListener(this._onClick, this); }, reset: function() { - this.trElement.bgColor = ''; + this.setStatus(null); }, _onClick: function() { - // todo: just send a message to the testSuite this.loadTestCase(null); return false; }, loadTestCase: function(onloadFunction) { + this.htmlTestSuite.unselectCurrentRow(); + this.select(); this.htmlTestSuite.currentRowInSuite = this.trElement.rowIndex - 1; // If the row has a stored results table, use that var resultsFromPreviousRun = this.trElement.cells[1]; if (resultsFromPreviousRun) { - // this.testFrame.restoreTestCase(resultsFromPreviousRun.innerHTML); + // todo: delegate to TestFrame, e.g. + // this.testFrame.restoreTestCase(resultsFromPreviousRun.innerHTML); var testBody = this.testFrame.getDocument().body; testBody.innerHTML = resultsFromPreviousRun.innerHTML; - testFrame.setCurrentTestCase(); + this.testFrame._onLoad(); if (onloadFunction) { onloadFunction(); } } else { - this.testFrame.load(this.link.href, onloadFunction); + this.testFrame.load(this.link.href, onloadFunction); } }, @@ -498,28 +568,24 @@ Object.extend(HtmlTestSuiteRow.prototype, { }); -var HtmlTestSuite = Class.create(); -Object.extend(HtmlTestSuite.prototype, { +var HtmlTestSuite = classCreate(); +objectExtend(HtmlTestSuite.prototype, { initialize: function(suiteDocument) { this.suiteDocument = suiteDocument; this.suiteRows = this._collectSuiteRows(); - this.titleRow = this.getTestTable().rows[0]; - this.title = this.titleRow.cells[0].innerHTML; + this.titleRow = new TitleRow(this.getTestTable().rows[0]); this.reset(); }, reset: function() { this.failed = false; this.currentRowInSuite = -1; - this.titleRow.bgColor = ''; - this.suiteRows.each(function(row) { + this.titleRow.setStatus(null); + for (var i = 0; i < this.suiteRows.length; i++) { + var row = this.suiteRows[i]; row.reset(); - }); - }, - - getTitle: function() { - return this.title; + } }, getSuiteRows: function() { @@ -537,31 +603,51 @@ Object.extend(HtmlTestSuite.prototype, { _collectSuiteRows: function () { var result = []; - for (rowNum = 1; rowNum < this.getTestTable().rows.length; rowNum++) { - var rowElement = this.getTestTable().rows[rowNum]; + var tables = $A(this.suiteDocument.getElementsByTagName("table")); + var testTable = tables[0]; + for (rowNum = 1; rowNum < testTable.rows.length; rowNum++) { + var rowElement = testTable.rows[rowNum]; result.push(new HtmlTestSuiteRow(rowElement, testFrame, this)); } + + // process the unsuited rows as well + for (var tableNum = 1; tableNum < $A(this.suiteDocument.getElementsByTagName("table")).length; tableNum++) { + testTable = tables[tableNum]; + for (rowNum = 1; rowNum < testTable.rows.length; rowNum++) { + var rowElement = testTable.rows[rowNum]; + new HtmlTestSuiteRow(rowElement, testFrame, this); + } + } return result; }, getCurrentRow: function() { + if (this.currentRowInSuite == -1) { + return null; + } return this.suiteRows[this.currentRowInSuite]; }, + unselectCurrentRow: function() { + var currentRow = this.getCurrentRow() + if (currentRow) { + currentRow.unselect(); + } + }, + markFailed: function() { this.failed = true; - this.titleRow.bgColor = FeedbackColors.failColor; + this.titleRow.markFailed(); }, markDone: function() { if (!this.failed) { - this.titleRow.bgColor = FeedbackColors.passColor; + this.titleRow.markPassed(); } }, _startCurrentTestCase: function() { - this.getCurrentRow().markWorking(); - this.getCurrentRow().loadTestCase(htmlTestRunner.startTest.bind(htmlTestRunner)); + this.getCurrentRow().loadTestCase(fnBind(htmlTestRunner.startTest, htmlTestRunner)); }, _onTestSuiteComplete: function() { @@ -569,14 +655,13 @@ Object.extend(HtmlTestSuite.prototype, { new TestResult(this.failed, this.getTestTable()).post(); }, - _updateSuiteWithResultOfPreviousTest: function() { + updateSuiteWithResultOfPreviousTest: function() { if (this.currentRowInSuite >= 0) { this.getCurrentRow().saveTestResults(); } }, runNextTestInSuite: function() { - this._updateSuiteWithResultOfPreviousTest(); this.currentRowInSuite++; // If we are done with all of the tests, set the title bar as pass or fail @@ -591,8 +676,8 @@ Object.extend(HtmlTestSuite.prototype, { }); -var TestResult = Class.create(); -Object.extend(TestResult.prototype, { +var TestResult = classCreate(); +objectExtend(TestResult.prototype, { // Post the results to a servlet, CGI-script, etc. The URL of the // results-handler defaults to "/postResults", but an alternative location @@ -724,8 +809,8 @@ Object.extend(TestResult.prototype, { }); /** HtmlTestCase encapsulates an HTML test document */ -var HtmlTestCase = Class.create(); -Object.extend(HtmlTestCase.prototype, { +var HtmlTestCase = classCreate(); +objectExtend(HtmlTestCase.prototype, { initialize: function(testDocument, htmlTestSuiteRow) { if (testDocument == null) { @@ -736,6 +821,7 @@ Object.extend(HtmlTestCase.prototype, { } this.testDocument = testDocument; this.htmlTestSuiteRow = htmlTestSuiteRow; + this.headerRow = new TitleRow(this.testDocument.getElementsByTagName("tr")[0]); this.commandRows = this._collectCommandRows(); this.nextCommandRowIndex = 0; this._addBreakpointSupport(); @@ -745,13 +831,16 @@ Object.extend(HtmlTestCase.prototype, { var commandRows = []; var tables = $A(this.testDocument.getElementsByTagName("table")); var self = this; - tables.each(function (table) { - $A(table.rows).each(function(candidateRow) { + for (var i = 0; i < tables.length; i++) { + var table = tables[i]; + var tableRows = $A(table.rows); + for (var j = 0; j < tableRows.length; j++) { + var candidateRow = tableRows[j]; if (self.isCommandRow(candidateRow)) { commandRows.push(new HtmlTestCaseRow(candidateRow)); } - }.bind(this)); - }); + } + } return commandRows; }, @@ -765,15 +854,16 @@ Object.extend(HtmlTestCase.prototype, { */ this.nextCommandRowIndex = 0; - this._setTitleColor(''); - this.commandRows.each(function(row) { + this.setStatus(''); + for (var i = 0; i < this.commandRows.length; i++) { + var row = this.commandRows[i]; row.reset(); - }); + } // remove any additional fake "error" row added to the end of the document var errorElement = this.testDocument.getElementById('error'); if (errorElement) { - Element.remove(errorElement); + errorElement.parentNode.removeChild(errorElement); } }, @@ -781,39 +871,38 @@ Object.extend(HtmlTestCase.prototype, { return this.commandRows; }, - _setTitleColor: function(color) { - var headerRow = this.testDocument.getElementsByTagName("tr")[0]; - if (headerRow) { - headerRow.bgColor = color; - } + setStatus: function(status) { + this.headerRow.setStatus(status); }, markFailed: function() { - this._setTitleColor(FeedbackColors.failColor); + this.setStatus("failed"); this.htmlTestSuiteRow.markFailed(); }, markPassed: function() { - this._setTitleColor(FeedbackColors.passColor); + this.setStatus("passed"); this.htmlTestSuiteRow.markPassed(); }, addErrorMessage: function(errorMsg, currentRow) { + errorMsg = errorMsg.replace(/ /g, String.fromCharCode(160)).replace("\n", '\\n'); if (currentRow) { currentRow.markFailed(errorMsg); } else { var errorElement = this.testDocument.createElement("p"); errorElement.id = "error"; - errorElement.innerHTML = errorMsg; + setText(errorElement, errorMsg); this.testDocument.body.appendChild(errorElement); - Element.setStyle(errorElement, {'backgroundColor': FeedbackColors.failColor}); + errorElement.className = "status_failed"; } }, _addBreakpointSupport: function() { - this.commandRows.each(function(row) { + for (var i = 0; i < this.commandRows.length; i++) { + var row = this.commandRows[i]; row.addBreakpointSupport(); - }); + } }, hasMoreCommandRows: function() { @@ -850,8 +939,8 @@ var get_new_rows = function() { }; -var Metrics = Class.create(); -Object.extend(Metrics.prototype, { +var Metrics = classCreate(); +objectExtend(Metrics.prototype, { initialize: function() { // The number of tests run this.numTestPasses = 0; @@ -903,20 +992,13 @@ Object.extend(Metrics.prototype, { }); -var HtmlRunnerCommandFactory = Class.create(); -Object.extend(HtmlRunnerCommandFactory.prototype, { +var HtmlRunnerCommandFactory = classCreate(); +objectExtend(HtmlRunnerCommandFactory.prototype, { initialize: function(seleniumCommandFactory, testLoop) { this.seleniumCommandFactory = seleniumCommandFactory; this.testLoop = testLoop; - this.handlers = { - pause: { - execute: function(selenium, command) { - testLoop.pauseInterval = command.target; - return {}; - } - } - }; + this.handlers = {}; //todo: register commands }, @@ -929,9 +1011,9 @@ Object.extend(HtmlRunnerCommandFactory.prototype, { }); -var HtmlRunnerTestLoop = Class.create(); -Object.extend(HtmlRunnerTestLoop.prototype, new TestLoop()); -Object.extend(HtmlRunnerTestLoop.prototype, { +var HtmlRunnerTestLoop = classCreate(); +objectExtend(HtmlRunnerTestLoop.prototype, new TestLoop()); +objectExtend(HtmlRunnerTestLoop.prototype, { initialize: function(htmlTestCase, metrics, seleniumCommandFactory) { this.commandFactory = new HtmlRunnerCommandFactory(seleniumCommandFactory, this); @@ -948,6 +1030,8 @@ Object.extend(HtmlRunnerTestLoop.prototype, { // used for selenium tests in javascript this.currentItem = null; this.commandAgenda = new Array(); + this.expectedFailure = null; + this.expectedFailureType = null; this.htmlTestCase.reset(); @@ -986,11 +1070,12 @@ Object.extend(HtmlRunnerTestLoop.prototype, { commandStarted : function() { $('pauseTest').disabled = false; - this.currentRow.markWorking(); + this.currentRow.select(); this.metrics.printMetrics(); }, commandComplete : function(result) { + this._checkExpectedFailure(result); if (result.failed) { this.metrics.numCommandFailures += 1; this._recordFailure(result.failureMessage); @@ -1002,7 +1087,49 @@ Object.extend(HtmlRunnerTestLoop.prototype, { } }, + _checkExpectedFailure : function(result) { + if (this.expectedFailure != null) { + if (this.expectedFailureJustSet) { + this.expectedFailureJustSet = false; + return; + } + if (!result.failed) { + result.passed = false; + result.failed = true; + result.failureMessage = "Expected " + this.expectedFailureType + " did not occur."; + } else { + if (PatternMatcher.matches(this.expectedFailure, result.failureMessage)) { + var failureType = result.error ? "error" : "failure"; + if (failureType == this.expectedFailureType) { + result.failed = false; + result.passed = true; + } else { + result.failed = true; + result.failureMessage = "Expected "+this.expectedFailureType+", but "+failureType+" occurred instead"; + } + } else { + result.failed = true; + result.failureMessage = "Expected " + this.expectedFailureType + " message '" + this.expectedFailure + + "' but was '" + result.failureMessage + "'"; + } + } + this.expectedFailure = null; + this.expectedFailureType = null; + } + }, + commandError : function(errorMessage) { + var tempResult = {}; + tempResult.passed = false; + tempResult.failed = true; + tempResult.error = true; + tempResult.failureMessage = errorMessage; + this._checkExpectedFailure(tempResult); + if (tempResult.passed) { + this.currentRow.markDone(); + return true; + } + errorMessage = tempResult.failureMessage; this.metrics.numCommandErrors += 1; this._recordFailure(errorMessage); }, @@ -1062,6 +1189,13 @@ Object.extend(HtmlRunnerTestLoop.prototype, { }); +Selenium.prototype.doPause = function(waitTime) { + /** Wait for the specified amount of time (in milliseconds) + * @param waitTime the amount of time to sleep (in milliseconds) + */ + // todo: should not refer to currentTest directly + currentTest.pauseInterval = waitTime; +}; Selenium.prototype.doBreak = function() { /** Halt the currently running test, and wait for the user to press the Continue button. @@ -1115,81 +1249,33 @@ Selenium.prototype.assertSelected = function(selectLocator, optionLocator) { locator.assertSelected(element); }; -/** - * Tell Selenium to expect a failure on the next command execution. This - * command temporarily installs a CommandFactory that generates - * CommandHandlers that expect a failure. - */ Selenium.prototype.assertFailureOnNext = function(message) { + /** + * Tell Selenium to expect a failure on the next command execution. + * @param message The failure message we should expect. This command will fail if the wrong failure message appears. + */ if (!message) { - throw new Error("Message must be provided"); + throw new SeleniumError("Message must be provided"); } - var expectFailureCommandFactory = - new ExpectFailureCommandFactory(currentTest.commandFactory, message, "failure", executeCommandAndReturnFailureMessage); - currentTest.commandFactory = expectFailureCommandFactory; + currentTest.expectedFailure = message; + currentTest.expectedFailureType = "failure"; + currentTest.expectedFailureJustSet = true; }; -/** - * Tell Selenium to expect an error on the next command execution. This - * command temporarily installs a CommandFactory that generates - * CommandHandlers that expect a failure. - */ Selenium.prototype.assertErrorOnNext = function(message) { + /** + * Tell Selenium to expect an error on the next command execution. + * @param message The error message we should expect. This command will fail if the wrong error message appears. + */ + // This command temporarily installs a CommandFactory that generates + // CommandHandlers that expect an error. if (!message) { - throw new Error("Message must be provided"); + throw new SeleniumError("Message must be provided"); } - var expectFailureCommandFactory = - new ExpectFailureCommandFactory(currentTest.commandFactory, message, "error", executeCommandAndReturnErrorMessage); - currentTest.commandFactory = expectFailureCommandFactory; -}; - -function executeCommandAndReturnFailureMessage(baseHandler, originalArguments) { - var baseResult = baseHandler.execute.apply(baseHandler, originalArguments); - if (baseResult.passed) { - return null; - } - return baseResult.failureMessage; -}; - -function executeCommandAndReturnErrorMessage(baseHandler, originalArguments) { - try { - baseHandler.execute.apply(baseHandler, originalArguments); - return null; - } - catch (expected) { - return expected.message; - } + currentTest.expectedFailure = message; + currentTest.expectedFailureType = "error"; + currentTest.expectedFailureJustSet = true; }; -function ExpectFailureCommandHandler(baseHandler, originalCommandFactory, expectedErrorMessage, errorType, decoratedExecutor) { - this.execute = function() { - var baseFailureMessage = decoratedExecutor(baseHandler, arguments); - var result = {}; - if (!baseFailureMessage) { - result.failed = true; - result.failureMessage = "Expected " + errorType + " did not occur."; - } - else { - if (! PatternMatcher.matches(expectedErrorMessage, baseFailureMessage)) { - result.failed = true; - result.failureMessage = "Expected " + errorType + " message '" + expectedErrorMessage - + "' but was '" + baseFailureMessage + "'"; - } - else { - result.passed = true; - result.result = baseFailureMessage; - } - } - currentTest.commandFactory = originalCommandFactory; - return result; - }; -} - -function ExpectFailureCommandFactory(originalCommandFactory, expectedErrorMessage, errorType, decoratedExecutor) { - this.getCommandHandler = function(name) { - var baseHandler = originalCommandFactory.getCommandHandler(name); - return new ExpectFailureCommandHandler(baseHandler, originalCommandFactory, expectedErrorMessage, errorType, decoratedExecutor); - }; -}; diff --git a/tests/test_tools/selenium/core/scripts/selenium-version.js b/tests/test_tools/selenium/core/scripts/selenium-version.js index c4a5508c..4156723d 100644 --- a/tests/test_tools/selenium/core/scripts/selenium-version.js +++ b/tests/test_tools/selenium/core/scripts/selenium-version.js @@ -1,5 +1,5 @@ -Selenium.version = "0.8.0"; -Selenium.revision = "1472:1473"; +Selenium.version = "0.8.2"; +Selenium.revision = "1727"; window.top.document.title += " v" + Selenium.version + " [" + Selenium.revision + "]"; diff --git a/tests/test_tools/selenium/core/selenium.css b/tests/test_tools/selenium/core/selenium.css index 963c63ad..f5240822 100644 --- a/tests/test_tools/selenium/core/selenium.css +++ b/tests/test_tools/selenium/core/selenium.css @@ -17,12 +17,12 @@ /*---( Layout )---*/ * { - margin: 0px; - padding: 0px; } body { overflow: auto; + margin: 0px; + padding: 0px; } td { @@ -84,9 +84,6 @@ body, html { color: black; } -#controlPanel button { - margin: 0.5ex; -} #controlPanel table { font-size: 100%; @@ -206,33 +203,6 @@ button, label { color: green; } -table.selenium { - font-family: Verdana, Arial, sans-serif; - font-size: 12; - border-width: 1px 1px 1px 1px; - border-spacing: 2px; - border-style: solid none solid none; - border-color: gray gray gray gray; - border-collapse: separate; - background-color: white; -} - -table.selenium th { - border-width: 1px 1px 1px 1px; - padding: 1px 1px 1px 1px; - border-style: none none none none; - border-color: gray gray gray gray; - -moz-border-radius: 0px 0px 0px 0px; -} - -table.selenium td { - border-width: 1px 1px 1px 1px; - padding: 1px 1px 1px 1px; - border-style: none none none none; - border-color: gray gray gray gray; - -moz-border-radius: 0px 0px 0px 0px; -} - div.executionOptions { padding-left: 5em; } @@ -259,10 +229,7 @@ div.executionOptions br { background-color: #333; width: 260px; height: 2px; - <!--[if IE]> - height: 2px; - line-height: 2px; - <![endif]--> + line-height: 2px; z-index: 1; border: 1px solid; border-color: #999 #ddd #ddd #999; @@ -276,10 +243,7 @@ div.executionOptions br { position: relative; margin: 0px; height: 8px; - <!--[if IE]> - height: 8px; - line-height: 8px; - <![endif]--> + line-height: 8px; z-index: 1; border: 1px solid; border-color: #999 #333 #333 #999; |