summaryrefslogtreecommitdiff
path: root/tests/test_tools/selenium/core/scripts/htmlutils.js
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_tools/selenium/core/scripts/htmlutils.js')
-rw-r--r--tests/test_tools/selenium/core/scripts/htmlutils.js490
1 files changed, 328 insertions, 162 deletions
diff --git a/tests/test_tools/selenium/core/scripts/htmlutils.js b/tests/test_tools/selenium/core/scripts/htmlutils.js
index fcb1ee44..4d78e1a6 100644
--- a/tests/test_tools/selenium/core/scripts/htmlutils.js
+++ b/tests/test_tools/selenium/core/scripts/htmlutils.js
@@ -14,20 +14,21 @@
* limitations under the License.
*
*/
-
-// This script contains some HTML utility functions that
-// make it possible to handle elements in a way that is
-// compatible with both IE-like and Mozilla-like browsers
+
+// This script contains a badly-organised collection of miscellaneous
+// functions that really better homes.
String.prototype.trim = function() {
- var result = this.replace( /^\s+/g, "" );// strip leading
- return result.replace( /\s+$/g, "" );// strip trailing
+ var result = this.replace(/^\s+/g, "");
+ // strip leading
+ return result.replace(/\s+$/g, "");
+ // strip trailing
};
String.prototype.lcfirst = function() {
- return this.charAt(0).toLowerCase() + this.substr(1);
+ return this.charAt(0).toLowerCase() + this.substr(1);
};
String.prototype.ucfirst = function() {
- return this.charAt(0).toUpperCase() + this.substr(1);
+ return this.charAt(0).toUpperCase() + this.substr(1);
};
String.prototype.startsWith = function(str) {
return this.indexOf(str) == 0;
@@ -37,23 +38,12 @@ String.prototype.startsWith = function(str) {
function getText(element) {
var text = "";
- if(browserVersion.isFirefox && browserVersion.firefoxVersion >= "1.5")
- {
- var dummyElement = element.cloneNode(true);
- renderWhitespaceInTextContent(dummyElement);
- text = dummyElement.textContent;
- } else if (browserVersion.isOpera) {
- var dummyElement = element.cloneNode(true);
- renderWhitespaceInTextContent(dummyElement);
- text = dummyElement.innerText;
- text = xmlDecode(text);
- }
- else if(element.textContent)
- {
+ var isRecentFirefox = (browserVersion.isFirefox && browserVersion.firefoxVersion >= "1.5");
+ if (isRecentFirefox || browserVersion.isKonqueror || browserVersion.isSafari || browserVersion.isOpera) {
+ text = getTextContent(element);
+ } else if (element.textContent) {
text = element.textContent;
- }
- else if(element.innerText)
- {
+ } else if (element.innerText) {
text = element.innerText;
}
@@ -63,62 +53,34 @@ function getText(element) {
return text.trim();
}
-function renderWhitespaceInTextContent(element) {
- // Remove non-visible newlines in text nodes
- if (element.nodeType == Node.TEXT_NODE)
- {
- element.data = element.data.replace(/\n|\r|\t/g, " ");
- return;
- }
-
- if (element.nodeType == Node.COMMENT_NODE)
- {
- element.data = "";
- return;
- }
-
- // Don't modify PRE elements
- if (element.tagName == "PRE")
- {
- return;
- }
-
- // Handle inline element that force newlines
- if (tagIs(element, ["BR", "HR"]))
- {
- // Replace this element with a newline text element
- element.parentNode.replaceChild(element.ownerDocument.createTextNode("\n"), element)
- }
-
- for (var i = 0; i < element.childNodes.length; i++)
- {
- var child = element.childNodes.item(i)
- renderWhitespaceInTextContent(child);
- }
-
- // Handle block elements that introduce newlines
-// -- From HTML spec:
-//<!ENTITY % block
-// "P | %heading; | %list; | %preformatted; | DL | DIV | NOSCRIPT |
-// BLOCKQUOTE | FORM | HR | TABLE | FIELDSET | ADDRESS">
- if (tagIs(element, ["P", "DIV"]))
- {
- element.appendChild(element.ownerDocument.createTextNode("\n"), element)
+function getTextContent(element, preformatted) {
+ if (element.nodeType == 3 /*Node.TEXT_NODE*/) {
+ var text = element.data;
+ if (!preformatted) {
+ text = text.replace(/\n|\r|\t/g, " ");
+ }
+ return text;
}
-
-}
-
-function tagIs(element, tags)
-{
- var tag = element.tagName;
- for (var i = 0; i < tags.length; i++)
- {
- if (tags[i] == tag)
- {
- return true;
+ if (element.nodeType == 1 /*Node.ELEMENT_NODE*/) {
+ var childrenPreformatted = preformatted || (element.tagName == "PRE");
+ var text = "";
+ for (var i = 0; i < element.childNodes.length; i++) {
+ var child = element.childNodes.item(i);
+ text += getTextContent(child, childrenPreformatted);
}
+ // Handle block elements that introduce newlines
+ // -- From HTML spec:
+ //<!ENTITY % block
+ // "P | %heading; | %list; | %preformatted; | DL | DIV | NOSCRIPT |
+ // BLOCKQUOTE | F:wORM | HR | TABLE | FIELDSET | ADDRESS">
+ //
+ // TODO: should potentially introduce multiple newlines to separate blocks
+ if (element.tagName == "P" || element.tagName == "BR" || element.tagName == "HR" || element.tagName == "DIV") {
+ text += "\n";
+ }
+ return text;
}
- return false;
+ return '';
}
/**
@@ -145,25 +107,36 @@ function normalizeSpaces(text)
text = text.replace(/\ +/g, " ");
// Replace &nbsp; with a space
- var pat = String.fromCharCode(160); // Opera doesn't like /\240/g
- var re = new RegExp(pat, "g");
- return text.replace(re, " ");
+ var nbspPattern = new RegExp(String.fromCharCode(160), "g");
+ if (browserVersion.isSafari) {
+ return replaceAll(text, String.fromCharCode(160), " ");
+ } else {
+ return text.replace(nbspPattern, " ");
+ }
+}
+
+function replaceAll(text, oldText, newText) {
+ while (text.indexOf(oldText) != -1) {
+ text = text.replace(oldText, newText);
+ }
+ return text;
}
+
function xmlDecode(text) {
- text = text.replace(/&quot;/g, '"');
- text = text.replace(/&apos;/g, "'");
- text = text.replace(/&lt;/g, "<");
- text = text.replace(/&gt;/g, ">");
- text = text.replace(/&amp;/g, "&");
- return text;
+ text = text.replace(/&quot;/g, '"');
+ text = text.replace(/&apos;/g, "'");
+ text = text.replace(/&lt;/g, "<");
+ text = text.replace(/&gt;/g, ">");
+ text = text.replace(/&amp;/g, "&");
+ return text;
}
// Sets the text in this element
function setText(element, text) {
- if(element.textContent) {
+ if (element.textContent) {
element.textContent = text;
- } else if(element.innerText) {
+ } else if (element.innerText) {
element.innerText = text;
}
}
@@ -191,43 +164,105 @@ function triggerEvent(element, eventType, canBubble) {
}
}
-function triggerKeyEvent(element, eventType, keycode, canBubble) {
+function getKeyCodeFromKeySequence(keySequence) {
+ var match = /^\\(\d{1,3})$/.exec(keySequence);
+ if (match != null) {
+ return match[1];
+ }
+ match = /^.$/.exec(keySequence);
+ if (match != null) {
+ return match[0].charCodeAt(0);
+ }
+ // this is for backward compatibility with existing tests
+ // 1 digit ascii codes will break however because they are used for the digit chars
+ match = /^\d{2,3}$/.exec(keySequence);
+ if (match != null) {
+ return match[0];
+ }
+ throw SeleniumError("invalid keySequence");
+}
+
+function triggerKeyEvent(element, eventType, keySequence, canBubble) {
+ var keycode = getKeyCodeFromKeySequence(keySequence);
canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
if (element.fireEvent) {
- keyEvent = parent.frames['myiframe'].document.createEventObject();
- keyEvent.keyCode=keycode;
- element.fireEvent('on' + eventType, keyEvent);
+ keyEvent = element.ownerDocument.createEventObject();
+ keyEvent.keyCode = keycode;
+ element.fireEvent('on' + eventType, keyEvent);
}
else {
- var evt;
- if( window.KeyEvent ) {
- evt = document.createEvent('KeyEvents');
- evt.initKeyEvent(eventType, true, true, window, false, false, false, false, keycode, keycode);
- } else {
- evt = document.createEvent('UIEvents');
- evt.initUIEvent( eventType, true, true, window, 1 );
- evt.keyCode = keycode;
- }
-
+ var evt;
+ if (window.KeyEvent) {
+ evt = document.createEvent('KeyEvents');
+ evt.initKeyEvent(eventType, true, true, window, false, false, false, false, keycode, keycode);
+ } else {
+ evt = document.createEvent('UIEvents');
+ evt.initUIEvent(eventType, true, true, window, 1);
+ evt.keyCode = keycode;
+ }
+
element.dispatchEvent(evt);
}
}
/* Fire a mouse event in a browser-compatible manner */
-function triggerMouseEvent(element, eventType, canBubble) {
+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) {
- element.fireEvent('on' + eventType);
+ 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)
{
- evt.initMouseEvent(eventType, canBubble, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null)
+ 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
- {
- // Safari
+ else {
+ LOG.error("element doesen't has initMouseEvent");
// TODO we should be initialising other mouse-event related attributes here
evt.initEvent(eventType, canBubble, true);
}
@@ -236,6 +271,7 @@ function triggerMouseEvent(element, eventType, canBubble) {
}
function removeLoadListener(element, command) {
+ LOG.info('Removing loadListenter for ' + element + ', ' + command);
if (window.removeEventListener)
element.removeEventListener("load", command, true);
else if (window.detachEvent)
@@ -243,17 +279,11 @@ function removeLoadListener(element, command) {
}
function addLoadListener(element, command) {
+ LOG.info('Adding loadListenter for ' + element + ', ' + command);
if (window.addEventListener && !browserVersion.isOpera)
- element.addEventListener("load",command, true);
+ element.addEventListener("load", command, true);
else if (window.attachEvent)
- element.attachEvent("onload",command);
-}
-
-function addUnloadListener(element, command) {
- if (window.addEventListener)
- element.addEventListener("unload",command, true);
- else if (window.attachEvent)
- element.attachEvent("onunload",command);
+ element.attachEvent("onload", command);
}
/**
@@ -261,19 +291,19 @@ function addUnloadListener(element, command) {
* This file must be loaded _after_ the jsunitCore.js
*/
function getFunctionName(aFunction) {
- var regexpResult = aFunction.toString().match(/function (\w*)/);
- if (regexpResult && regexpResult[1]) {
- return regexpResult[1];
- }
- return 'anonymous';
+ var regexpResult = aFunction.toString().match(/function (\w*)/);
+ if (regexpResult && regexpResult[1]) {
+ return regexpResult[1];
+ }
+ return 'anonymous';
}
function getDocumentBase(doc) {
- var bases = document.getElementsByTagName("base");
- if (bases && bases.length && bases[0].href) {
- return bases[0].href;
- }
- return "";
+ var bases = document.getElementsByTagName("base");
+ if (bases && bases.length && bases[0].href) {
+ return bases[0].href;
+ }
+ return "";
}
function describe(object, delimiter) {
@@ -291,10 +321,15 @@ PatternMatcher.prototype = {
selectStrategy: function(pattern) {
this.pattern = pattern;
- var strategyName = 'glob'; // by default
+ var strategyName = 'glob';
+ // by default
if (/^([a-z-]+):(.*)/.test(pattern)) {
- strategyName = RegExp.$1;
- pattern = RegExp.$2;
+ var possibleNewStrategyName = RegExp.$1;
+ var possibleNewPattern = RegExp.$2;
+ if (PatternMatcher.strategies[possibleNewStrategyName]) {
+ strategyName = possibleNewStrategyName;
+ pattern = possibleNewPattern;
+ }
}
var matchStrategy = PatternMatcher.strategies[strategyName];
if (!matchStrategy) {
@@ -320,9 +355,9 @@ PatternMatcher.matches = function(pattern, actual) {
PatternMatcher.strategies = {
- /**
- * Exact matching, e.g. "exact:***"
- */
+/**
+ * Exact matching, e.g. "exact:***"
+ */
exact: function(expected) {
this.expected = expected;
this.matches = function(actual) {
@@ -330,9 +365,9 @@ PatternMatcher.strategies = {
};
},
- /**
- * Match by regular expression, e.g. "regexp:^[0-9]+$"
- */
+/**
+ * Match by regular expression, e.g. "regexp:^[0-9]+$"
+ */
regexp: function(regexpString) {
this.regexp = new RegExp(regexpString);
this.matches = function(actual) {
@@ -340,17 +375,24 @@ PatternMatcher.strategies = {
};
},
- /**
- * "globContains" (aka "wildmat") patterns, e.g. "glob:one,two,*",
- * but don't require a perfect match; instead succeed if actual
- * contains something that matches globString.
- * Making this distinction is motivated by a bug in IE6 which
- * leads to the browser hanging if we implement *TextPresent tests
- * by just matching against a regular expression beginning and
- * ending with ".*". The globcontains strategy allows us to satisfy
- * the functional needs of the *TextPresent ops more efficiently
- * and so avoid running into this IE6 freeze.
- */
+ regex: function(regexpString) {
+ this.regexp = new RegExp(regexpString);
+ this.matches = function(actual) {
+ return this.regexp.test(actual);
+ };
+ },
+
+/**
+ * "globContains" (aka "wildmat") patterns, e.g. "glob:one,two,*",
+ * but don't require a perfect match; instead succeed if actual
+ * contains something that matches globString.
+ * Making this distinction is motivated by a bug in IE6 which
+ * leads to the browser hanging if we implement *TextPresent tests
+ * by just matching against a regular expression beginning and
+ * ending with ".*". The globcontains strategy allows us to satisfy
+ * the functional needs of the *TextPresent ops more efficiently
+ * and so avoid running into this IE6 freeze.
+ */
globContains: function(globString) {
this.regexp = new RegExp(PatternMatcher.regexpFromGlobContains(globString));
this.matches = function(actual) {
@@ -359,9 +401,9 @@ PatternMatcher.strategies = {
},
- /**
- * "glob" (aka "wildmat") patterns, e.g. "glob:one,two,*"
- */
+/**
+ * "glob" (aka "wildmat") patterns, e.g. "glob:one,two,*"
+ */
glob: function(globString) {
this.regexp = new RegExp(PatternMatcher.regexpFromGlob(globString));
this.matches = function(actual) {
@@ -393,9 +435,9 @@ var Assert = {
throw new AssertionFailedError(message);
},
- /*
- * Assert.equals(comment?, expected, actual)
- */
+/*
+* Assert.equals(comment?, expected, actual)
+*/
equals: function() {
var args = new AssertionArguments(arguments);
if (args.expected === args.actual) {
@@ -406,9 +448,9 @@ var Assert = {
"' but was '" + args.actual + "'");
},
- /*
- * Assert.matches(comment?, pattern, actual)
- */
+/*
+* Assert.matches(comment?, pattern, actual)
+*/
matches: function() {
var args = new AssertionArguments(arguments);
if (PatternMatcher.matches(args.expected, args.actual)) {
@@ -419,9 +461,9 @@ var Assert = {
"' did not match '" + args.expected + "'");
},
- /*
- * Assert.notMtches(comment?, pattern, actual)
- */
+/*
+* Assert.notMtches(comment?, pattern, actual)
+*/
notMatches: function() {
var args = new AssertionArguments(arguments);
if (!PatternMatcher.matches(args.expected, args.actual)) {
@@ -447,8 +489,6 @@ function AssertionArguments(args) {
}
}
-
-
function AssertionFailedError(message) {
this.isAssertionFailedError = true;
this.isSeleniumError = true;
@@ -460,4 +500,130 @@ function SeleniumError(message) {
var error = new Error(message);
error.isSeleniumError = true;
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() {
+ //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);
+ }
+});
+
+
+// for use from vs.2003 debugger
+function objToString(obj) {
+ var s = "";
+ for (key in obj) {
+ var line = key + "->" + obj[key];
+ line.replace("\n", " ");
+ s += line + "\n";
+ }
+ return s;
+}
+
+var seenReadyStateWarning = false;
+
+function openSeparateApplicationWindow(url) {
+ // resize the Selenium window itself
+ window.resizeTo(1200, 500);
+ window.moveTo(window.screenX, 0);
+
+ var appWindow = window.open(url + '?start=true', 'main');
+ try {
+ var windowHeight = 500;
+ if (window.outerHeight) {
+ windowHeight = window.outerHeight;
+ } else if (document.documentElement && document.documentElement.offsetHeight) {
+ windowHeight = document.documentElement.offsetHeight;
+ }
+
+ if (window.screenLeft && !window.screenX) window.screenX = window.screenLeft;
+ if (window.screenTop && !window.screenY) window.screenY = window.screenTop;
+
+ appWindow.resizeTo(1200, screen.availHeight - windowHeight - 60);
+ appWindow.moveTo(window.screenX, window.screenY + windowHeight + 25);
+ } catch (e) {
+ LOG.error("Couldn't resize app window");
+ LOG.exception(e);
+ }
+
+
+ if (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;
+ }
+
+ return appWindow;
+}
+
+var URLConfiguration = Class.create();
+Object.extend(URLConfiguration.prototype, {
+ initialize: function() {
+ },
+ _isQueryParameterTrue: function (name) {
+ var parameterValue = this._getQueryParameter(name);
+ if (parameterValue == null) return false;
+ if (parameterValue.toLowerCase() == "true") return true;
+ if (parameterValue.toLowerCase() == "on") return true;
+ return false;
+ },
+
+ _getQueryParameter: function(searchKey) {
+ var str = this.queryString
+ 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;
+ },
+
+ _extractArgs: function() {
+ var str = SeleniumHTARunner.commandLine;
+ if (str == null || str == "") return new Array();
+ var matches = str.match(/(?:\"([^\"]+)\"|(?!\"([^\"]+)\")(\S+))/g);
+ // We either want non quote stuff ([^"]+) surrounded by quotes
+ // or we want to look-ahead, see that the next character isn't
+ // a quoted argument, and then grab all the non-space stuff
+ // this will return for the line: "foo" bar
+ // the results "\"foo\"" and "bar"
+
+ // So, let's unquote the quoted arguments:
+ var args = new Array;
+ for (var i = 0; i < matches.length; i++) {
+ args[i] = matches[i];
+ args[i] = args[i].replace(/^"(.*)"$/, "$1");
+ }
+ return args;
+ },
+
+ isMultiWindowMode:function() {
+ return this._isQueryParameterTrue('multiWindow');
+ }
+});
+
+
+function safeScrollIntoView(element) {
+ if (element.scrollIntoView) {
+ element.scrollIntoView(false);
+ return;
+ }
+ // TODO: work out how to scroll browsers that don't support
+ // scrollIntoView (like Konqueror)
+}