selenium-core-runner 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/Gemfile +9 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +30 -0
  5. data/app/controllers/selenium_core_runner/suites_controller.rb +19 -0
  6. data/app/views/selenium_core_runner/suites/index.html.erb +177 -0
  7. data/app/views/selenium_core_runner/suites/show.html.erb +0 -0
  8. data/config/routes.rb +6 -0
  9. data/lib/selenium-core-runner/engine.rb +19 -0
  10. data/lib/selenium-core-runner.rb +3 -0
  11. data/public/selenium-core-runner/Blank.html +7 -0
  12. data/public/selenium-core-runner/InjectedRemoteRunner.html +8 -0
  13. data/public/selenium-core-runner/RemoteRunner.html +101 -0
  14. data/public/selenium-core-runner/SeleniumLog.html +109 -0
  15. data/public/selenium-core-runner/TestPrompt.html +145 -0
  16. data/public/selenium-core-runner/TestRunner-splash.html +55 -0
  17. data/public/selenium-core-runner/TestRunner.hta +177 -0
  18. data/public/selenium-core-runner/TestRunner.html +177 -0
  19. data/public/selenium-core-runner/icons/all.png +0 -0
  20. data/public/selenium-core-runner/icons/continue.png +0 -0
  21. data/public/selenium-core-runner/icons/continue_disabled.png +0 -0
  22. data/public/selenium-core-runner/icons/pause.png +0 -0
  23. data/public/selenium-core-runner/icons/pause_disabled.png +0 -0
  24. data/public/selenium-core-runner/icons/selected.png +0 -0
  25. data/public/selenium-core-runner/icons/step.png +0 -0
  26. data/public/selenium-core-runner/icons/step_disabled.png +0 -0
  27. data/public/selenium-core-runner/iedoc-core.xml +1789 -0
  28. data/public/selenium-core-runner/iedoc.xml +1830 -0
  29. data/public/selenium-core-runner/lib/cssQuery/cssQuery-p.js +6 -0
  30. data/public/selenium-core-runner/lib/cssQuery/src/cssQuery-level2.js +142 -0
  31. data/public/selenium-core-runner/lib/cssQuery/src/cssQuery-level3.js +150 -0
  32. data/public/selenium-core-runner/lib/cssQuery/src/cssQuery-standard.js +53 -0
  33. data/public/selenium-core-runner/lib/cssQuery/src/cssQuery.js +356 -0
  34. data/public/selenium-core-runner/lib/prototype.js +2006 -0
  35. data/public/selenium-core-runner/lib/scriptaculous/builder.js +101 -0
  36. data/public/selenium-core-runner/lib/scriptaculous/controls.js +815 -0
  37. data/public/selenium-core-runner/lib/scriptaculous/dragdrop.js +915 -0
  38. data/public/selenium-core-runner/lib/scriptaculous/effects.js +958 -0
  39. data/public/selenium-core-runner/lib/scriptaculous/scriptaculous.js +47 -0
  40. data/public/selenium-core-runner/lib/scriptaculous/slider.js +283 -0
  41. data/public/selenium-core-runner/lib/scriptaculous/unittest.js +383 -0
  42. data/public/selenium-core-runner/lib/snapsie.js +91 -0
  43. data/public/selenium-core-runner/scripts/find_matching_child.js +69 -0
  44. data/public/selenium-core-runner/scripts/htmlutils.js +1623 -0
  45. data/public/selenium-core-runner/scripts/injection.html +72 -0
  46. data/public/selenium-core-runner/scripts/selenium-api.js +3240 -0
  47. data/public/selenium-core-runner/scripts/selenium-browserbot.js +2333 -0
  48. data/public/selenium-core-runner/scripts/selenium-browserdetect.js +153 -0
  49. data/public/selenium-core-runner/scripts/selenium-commandhandlers.js +379 -0
  50. data/public/selenium-core-runner/scripts/selenium-executionloop.js +175 -0
  51. data/public/selenium-core-runner/scripts/selenium-logging.js +148 -0
  52. data/public/selenium-core-runner/scripts/selenium-remoterunner.js +695 -0
  53. data/public/selenium-core-runner/scripts/selenium-testrunner.js +1362 -0
  54. data/public/selenium-core-runner/scripts/selenium-version.js +5 -0
  55. data/public/selenium-core-runner/scripts/ui-doc.html +803 -0
  56. data/public/selenium-core-runner/scripts/ui-element.js +1627 -0
  57. data/public/selenium-core-runner/scripts/ui-map-sample.js +979 -0
  58. data/public/selenium-core-runner/scripts/user-extensions.js +3 -0
  59. data/public/selenium-core-runner/scripts/user-extensions.js.sample +75 -0
  60. data/public/selenium-core-runner/scripts/xmlextras.js +153 -0
  61. data/public/selenium-core-runner/selenium-logo.png +0 -0
  62. data/public/selenium-core-runner/selenium-test.css +43 -0
  63. data/public/selenium-core-runner/selenium.css +316 -0
  64. data/public/selenium-core-runner/xpath/dom.js +566 -0
  65. data/public/selenium-core-runner/xpath/javascript-xpath-0.1.11.js +2816 -0
  66. data/public/selenium-core-runner/xpath/util.js +549 -0
  67. data/public/selenium-core-runner/xpath/xmltoken.js +149 -0
  68. data/public/selenium-core-runner/xpath/xpath.js +2481 -0
  69. metadata +121 -0
@@ -0,0 +1,1623 @@
1
+ /*
2
+ * Copyright 2004 ThoughtWorks, Inc
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ */
17
+
18
+ // This script contains a badly-organised collection of miscellaneous
19
+ // functions that really better homes.
20
+
21
+ function classCreate() {
22
+ return function() {
23
+ this.initialize.apply(this, arguments);
24
+ }
25
+ }
26
+
27
+ function objectExtend(destination, source) {
28
+ for (var property in source) {
29
+ destination[property] = source[property];
30
+ }
31
+ return destination;
32
+ }
33
+
34
+ function sel$() {
35
+ var results = [], element;
36
+ for (var i = 0; i < arguments.length; i++) {
37
+ element = arguments[i];
38
+ if (typeof element == 'string')
39
+ element = document.getElementById(element);
40
+ results[results.length] = element;
41
+ }
42
+ return results.length < 2 ? results[0] : results;
43
+ }
44
+
45
+ function sel$A(iterable) {
46
+ if (!iterable) return [];
47
+ if (iterable.toArray) {
48
+ return iterable.toArray();
49
+ } else {
50
+ var results = [];
51
+ for (var i = 0; i < iterable.length; i++)
52
+ results.push(iterable[i]);
53
+ return results;
54
+ }
55
+ }
56
+
57
+ function fnBind() {
58
+ var args = sel$A(arguments), __method = args.shift(), object = args.shift();
59
+ var retval = function() {
60
+ return __method.apply(object, args.concat(sel$A(arguments)));
61
+ }
62
+ retval.__method = __method;
63
+ return retval;
64
+ }
65
+
66
+ function fnBindAsEventListener(fn, object) {
67
+ var __method = fn;
68
+ return function(event) {
69
+ return __method.call(object, event || window.event);
70
+ }
71
+ }
72
+
73
+ function removeClassName(element, name) {
74
+ var re = new RegExp("\\b" + name + "\\b", "g");
75
+ element.className = element.className.replace(re, "");
76
+ }
77
+
78
+ function addClassName(element, name) {
79
+ element.className = element.className + ' ' + name;
80
+ }
81
+
82
+ function elementSetStyle(element, style) {
83
+ for (var name in style) {
84
+ var value = style[name];
85
+ if (value == null) value = "";
86
+ element.style[name] = value;
87
+ }
88
+ }
89
+
90
+ function elementGetStyle(element, style) {
91
+ var value = element.style[style];
92
+ if (!value) {
93
+ if (document.defaultView && document.defaultView.getComputedStyle) {
94
+ var css = document.defaultView.getComputedStyle(element, null);
95
+ value = css ? css.getPropertyValue(style) : null;
96
+ } else if (element.currentStyle) {
97
+ value = element.currentStyle[style];
98
+ }
99
+ }
100
+
101
+ /** DGF necessary?
102
+ if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
103
+ if (Element.getStyle(element, 'position') == 'static') value = 'auto'; */
104
+
105
+ return value == 'auto' ? null : value;
106
+ }
107
+
108
+ String.prototype.trim = function() {
109
+ var result = this.replace(/^\s+/g, "");
110
+ // strip leading
111
+ return result.replace(/\s+$/g, "");
112
+ // strip trailing
113
+ };
114
+ String.prototype.lcfirst = function() {
115
+ return this.charAt(0).toLowerCase() + this.substr(1);
116
+ };
117
+ String.prototype.ucfirst = function() {
118
+ return this.charAt(0).toUpperCase() + this.substr(1);
119
+ };
120
+ String.prototype.startsWith = function(str) {
121
+ return this.indexOf(str) == 0;
122
+ };
123
+
124
+ /**
125
+ * Given a string literal that would appear in an XPath, puts it in quotes and
126
+ * returns it. Special consideration is given to literals who themselves
127
+ * contain quotes. It's possible for a concat() expression to be returned.
128
+ */
129
+ String.prototype.quoteForXPath = function()
130
+ {
131
+ if (/\'/.test(this)) {
132
+ if (/\"/.test(this)) {
133
+ // concat scenario
134
+ var pieces = [];
135
+ var a = "'", b = '"', c;
136
+ for (var i = 0, j = 0; i < this.length;) {
137
+ if (this.charAt(i) == a) {
138
+ // encountered a quote that cannot be contained in current
139
+ // quote, so need to flip-flop quoting scheme
140
+ if (j < i) {
141
+ pieces.push(a + this.substring(j, i) + a);
142
+ j = i;
143
+ }
144
+ c = a;
145
+ a = b;
146
+ b = c;
147
+ }
148
+ else {
149
+ ++i;
150
+ }
151
+ }
152
+ pieces.push(a + this.substring(j) + a);
153
+ return 'concat(' + pieces.join(', ') + ')';
154
+ }
155
+ else {
156
+ // quote with doubles
157
+ return '"' + this + '"';
158
+ }
159
+ }
160
+ // quote with singles
161
+ return "'" + this + "'";
162
+ };
163
+
164
+ // Returns the text in this element
165
+ function getText(element) {
166
+ var text = "";
167
+
168
+ var isRecentFirefox = (browserVersion.isFirefox && browserVersion.firefoxVersion >= "1.5");
169
+ if (isRecentFirefox || browserVersion.isKonqueror || browserVersion.isSafari || browserVersion.isOpera) {
170
+ text = getTextContent(element);
171
+ } else if (element.textContent) {
172
+ text = element.textContent;
173
+ } else if (element.innerText) {
174
+ text = element.innerText;
175
+ }
176
+
177
+ text = normalizeNewlines(text);
178
+ text = normalizeSpaces(text);
179
+
180
+ return text.trim();
181
+ }
182
+
183
+ function getTextContent(element, preformatted) {
184
+ if (element.style && (element.style.visibility == 'hidden' || element.style.display == 'none')) return '';
185
+ if (element.nodeType == 3 /*Node.TEXT_NODE*/) {
186
+ var text = element.data;
187
+ if (!preformatted) {
188
+ text = text.replace(/\n|\r|\t/g, " ");
189
+ }
190
+ return text;
191
+ }
192
+ if (element.nodeType == 1 /*Node.ELEMENT_NODE*/ && element.nodeName != 'SCRIPT') {
193
+ var childrenPreformatted = preformatted || (element.tagName == "PRE");
194
+ var text = "";
195
+ for (var i = 0; i < element.childNodes.length; i++) {
196
+ var child = element.childNodes.item(i);
197
+ text += getTextContent(child, childrenPreformatted);
198
+ }
199
+ // Handle block elements that introduce newlines
200
+ // -- From HTML spec:
201
+ //<!ENTITY % block
202
+ // "P | %heading; | %list; | %preformatted; | DL | DIV | NOSCRIPT |
203
+ // BLOCKQUOTE | F:wORM | HR | TABLE | FIELDSET | ADDRESS">
204
+ //
205
+ // TODO: should potentially introduce multiple newlines to separate blocks
206
+ if (element.tagName == "P" || element.tagName == "BR" || element.tagName == "HR" || element.tagName == "DIV") {
207
+ text += "\n";
208
+ }
209
+ return text;
210
+ }
211
+ return '';
212
+ }
213
+
214
+ /**
215
+ * Convert all newlines to \n
216
+ */
217
+ function normalizeNewlines(text)
218
+ {
219
+ return text.replace(/\r\n|\r/g, "\n");
220
+ }
221
+
222
+ /**
223
+ * Replace multiple sequential spaces with a single space, and then convert &nbsp; to space.
224
+ */
225
+ function normalizeSpaces(text)
226
+ {
227
+ // IE has already done this conversion, so doing it again will remove multiple nbsp
228
+ if (browserVersion.isIE)
229
+ {
230
+ return text;
231
+ }
232
+
233
+ // Replace multiple spaces with a single space
234
+ // TODO - this shouldn't occur inside PRE elements
235
+ text = text.replace(/\ +/g, " ");
236
+
237
+ // Replace &nbsp; with a space
238
+ var nbspPattern = new RegExp(String.fromCharCode(160), "g");
239
+ if (browserVersion.isSafari) {
240
+ return replaceAll(text, String.fromCharCode(160), " ");
241
+ } else {
242
+ return text.replace(nbspPattern, " ");
243
+ }
244
+ }
245
+
246
+ function replaceAll(text, oldText, newText) {
247
+ while (text.indexOf(oldText) != -1) {
248
+ text = text.replace(oldText, newText);
249
+ }
250
+ return text;
251
+ }
252
+
253
+
254
+ function xmlDecode(text) {
255
+ text = text.replace(/&quot;/g, '"');
256
+ text = text.replace(/&apos;/g, "'");
257
+ text = text.replace(/&lt;/g, "<");
258
+ text = text.replace(/&gt;/g, ">");
259
+ text = text.replace(/&amp;/g, "&");
260
+ return text;
261
+ }
262
+
263
+ // Sets the text in this element
264
+ function setText(element, text) {
265
+ if (element.textContent != null) {
266
+ element.textContent = text;
267
+ } else if (element.innerText != null) {
268
+ element.innerText = text;
269
+ }
270
+ }
271
+
272
+ // Get the value of an <input> element
273
+ function getInputValue(inputElement) {
274
+ if (inputElement.type) {
275
+ if (inputElement.type.toUpperCase() == 'CHECKBOX' ||
276
+ inputElement.type.toUpperCase() == 'RADIO')
277
+ {
278
+ return (inputElement.checked ? 'on' : 'off');
279
+ }
280
+ }
281
+ if (inputElement.value == null) {
282
+ throw new SeleniumError("This element has no value; is it really a form field?");
283
+ }
284
+ return inputElement.value;
285
+ }
286
+
287
+ /* Fire an event in a browser-compatible manner */
288
+ function triggerEvent(element, eventType, canBubble, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown) {
289
+ canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
290
+ if (element.fireEvent && element.ownerDocument && element.ownerDocument.createEventObject) { // IE
291
+ var evt = createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown);
292
+ element.fireEvent('on' + eventType, evt);
293
+ }
294
+ else {
295
+ var evt = document.createEvent('HTMLEvents');
296
+
297
+ try {
298
+ evt.shiftKey = shiftKeyDown;
299
+ evt.metaKey = metaKeyDown;
300
+ evt.altKey = altKeyDown;
301
+ evt.ctrlKey = controlKeyDown;
302
+ } catch (e) {
303
+ // On Firefox 1.0, you can only set these during initMouseEvent or initKeyEvent
304
+ // we'll have to ignore them here
305
+ LOG.exception(e);
306
+ }
307
+
308
+ evt.initEvent(eventType, canBubble, true);
309
+ element.dispatchEvent(evt);
310
+ }
311
+ }
312
+
313
+ function getKeyCodeFromKeySequence(keySequence) {
314
+ var match = /^\\(\d{1,3})$/.exec(keySequence);
315
+ if (match != null) {
316
+ return match[1];
317
+ }
318
+ match = /^.$/.exec(keySequence);
319
+ if (match != null) {
320
+ return match[0].charCodeAt(0);
321
+ }
322
+ // this is for backward compatibility with existing tests
323
+ // 1 digit ascii codes will break however because they are used for the digit chars
324
+ match = /^\d{2,3}$/.exec(keySequence);
325
+ if (match != null) {
326
+ return match[0];
327
+ }
328
+ throw new SeleniumError("invalid keySequence");
329
+ }
330
+
331
+ function createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown) {
332
+ var evt = element.ownerDocument.createEventObject();
333
+ evt.shiftKey = shiftKeyDown;
334
+ evt.metaKey = metaKeyDown;
335
+ evt.altKey = altKeyDown;
336
+ evt.ctrlKey = controlKeyDown;
337
+ return evt;
338
+ }
339
+
340
+ function triggerKeyEvent(element, eventType, keySequence, canBubble, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown) {
341
+ var keycode = getKeyCodeFromKeySequence(keySequence);
342
+ canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
343
+ if (element.fireEvent && element.ownerDocument && element.ownerDocument.createEventObject) { // IE
344
+ var keyEvent = createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown);
345
+ keyEvent.keyCode = keycode;
346
+ element.fireEvent('on' + eventType, keyEvent);
347
+ }
348
+ else {
349
+ var evt;
350
+ if (window.KeyEvent) {
351
+ evt = document.createEvent('KeyEvents');
352
+ evt.initKeyEvent(eventType, true, true, window, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown, keycode, keycode);
353
+ } else {
354
+ evt = document.createEvent('UIEvents');
355
+
356
+ evt.shiftKey = shiftKeyDown;
357
+ evt.metaKey = metaKeyDown;
358
+ evt.altKey = altKeyDown;
359
+ evt.ctrlKey = controlKeyDown;
360
+
361
+ evt.initUIEvent(eventType, true, true, window, 1);
362
+ evt.keyCode = keycode;
363
+ evt.which = keycode;
364
+ }
365
+
366
+ element.dispatchEvent(evt);
367
+ }
368
+ }
369
+
370
+ function removeLoadListener(element, command) {
371
+ LOG.debug('Removing loadListenter for ' + element + ', ' + command);
372
+ if (window.removeEventListener)
373
+ element.removeEventListener("load", command, true);
374
+ else if (window.detachEvent)
375
+ element.detachEvent("onload", command);
376
+ }
377
+
378
+ function addLoadListener(element, command) {
379
+ LOG.debug('Adding loadListenter for ' + element + ', ' + command);
380
+ var augmentedCommand = function() {
381
+ command.call(this, element);
382
+ }
383
+ if (window.addEventListener && !browserVersion.isOpera)
384
+ element.addEventListener("load", augmentedCommand, true);
385
+ else if (window.attachEvent)
386
+ element.attachEvent("onload", augmentedCommand);
387
+ }
388
+
389
+ /**
390
+ * Override the broken getFunctionName() method from JsUnit
391
+ * This file must be loaded _after_ the jsunitCore.js
392
+ */
393
+ function getFunctionName(aFunction) {
394
+ var regexpResult = aFunction.toString().match(/function (\w*)/);
395
+ if (regexpResult && regexpResult[1]) {
396
+ return regexpResult[1];
397
+ }
398
+ return 'anonymous';
399
+ }
400
+
401
+ function getDocumentBase(doc) {
402
+ var bases = document.getElementsByTagName("base");
403
+ if (bases && bases.length && bases[0].href) {
404
+ return bases[0].href;
405
+ }
406
+ return "";
407
+ }
408
+
409
+ function getTagName(element) {
410
+ var tagName;
411
+ if (element && element.tagName && element.tagName.toLowerCase) {
412
+ tagName = element.tagName.toLowerCase();
413
+ }
414
+ return tagName;
415
+ }
416
+
417
+ function selArrayToString(a) {
418
+ if (isArray(a)) {
419
+ // DGF copying the array, because the array-like object may be a non-modifiable nodelist
420
+ var retval = [];
421
+ for (var i = 0; i < a.length; i++) {
422
+ var item = a[i];
423
+ var replaced = new String(item).replace(/([,\\])/g, '\\$1');
424
+ retval[i] = replaced;
425
+ }
426
+ return retval;
427
+ }
428
+ return new String(a);
429
+ }
430
+
431
+
432
+ function isArray(x) {
433
+ return ((typeof x) == "object") && (x["length"] != null);
434
+ }
435
+
436
+ function absolutify(url, baseUrl) {
437
+ /** returns a relative url in its absolute form, given by baseUrl.
438
+ *
439
+ * This function is a little odd, because it can take baseUrls that
440
+ * aren't necessarily directories. It uses the same rules as the HTML
441
+ * &lt;base&gt; tag; if the baseUrl doesn't end with "/", we'll assume
442
+ * that it points to a file, and strip the filename off to find its
443
+ * base directory.
444
+ *
445
+ * So absolutify("foo", "http://x/bar") will return "http://x/foo" (stripping off bar),
446
+ * whereas absolutify("foo", "http://x/bar/") will return "http://x/bar/foo" (preserving bar).
447
+ * Naturally absolutify("foo", "http://x") will return "http://x/foo", appropriately.
448
+ *
449
+ * @param url the url to make absolute; if this url is already absolute, we'll just return that, unchanged
450
+ * @param baseUrl the baseUrl from which we'll absolutify, following the rules above.
451
+ * @return 'url' if it was already absolute, or the absolutized version of url if it was not absolute.
452
+ */
453
+
454
+ // DGF isn't there some library we could use for this?
455
+
456
+ if (/^\w+:/.test(url)) {
457
+ // it's already absolute
458
+ return url;
459
+ }
460
+
461
+ var loc;
462
+ try {
463
+ loc = parseUrl(baseUrl);
464
+ } catch (e) {
465
+ // is it an absolute windows file path? let's play the hero in that case
466
+ if (/^\w:\\/.test(baseUrl)) {
467
+ baseUrl = "file:///" + baseUrl.replace(/\\/g, "/");
468
+ loc = parseUrl(baseUrl);
469
+ } else {
470
+ throw new SeleniumError("baseUrl wasn't absolute: " + baseUrl);
471
+ }
472
+ }
473
+ loc.search = null;
474
+ loc.hash = null;
475
+
476
+ // if url begins with /, then that's the whole pathname
477
+ if (/^\//.test(url)) {
478
+ loc.pathname = url;
479
+ var result = reassembleLocation(loc);
480
+ return result;
481
+ }
482
+
483
+ // if pathname is null, then we'll just append "/" + the url
484
+ if (!loc.pathname) {
485
+ loc.pathname = "/" + url;
486
+ var result = reassembleLocation(loc);
487
+ return result;
488
+ }
489
+
490
+ // if pathname ends with /, just append url
491
+ if (/\/$/.test(loc.pathname)) {
492
+ loc.pathname += url;
493
+ var result = reassembleLocation(loc);
494
+ return result;
495
+ }
496
+
497
+ // if we're here, then the baseUrl has a pathname, but it doesn't end with /
498
+ // in that case, we replace everything after the final / with the relative url
499
+ loc.pathname = loc.pathname.replace(/[^\/\\]+$/, url);
500
+ var result = reassembleLocation(loc);
501
+ return result;
502
+
503
+ }
504
+
505
+ var URL_REGEX = /^((\w+):\/\/)(([^:]+):?([^@]+)?@)?([^\/\?:]*):?(\d+)?(\/?[^\?#]+)?\??([^#]+)?#?(.+)?/;
506
+
507
+ function parseUrl(url) {
508
+ var fields = ['url', null, 'protocol', null, 'username', 'password', 'host', 'port', 'pathname', 'search', 'hash'];
509
+ var result = URL_REGEX.exec(url);
510
+ if (!result) {
511
+ throw new SeleniumError("Invalid URL: " + url);
512
+ }
513
+ var loc = new Object();
514
+ for (var i = 0; i < fields.length; i++) {
515
+ var field = fields[i];
516
+ if (field == null) {
517
+ continue;
518
+ }
519
+ loc[field] = result[i];
520
+ }
521
+ return loc;
522
+ }
523
+
524
+ function reassembleLocation(loc) {
525
+ if (!loc.protocol) {
526
+ throw new Error("Not a valid location object: " + o2s(loc));
527
+ }
528
+ var protocol = loc.protocol;
529
+ protocol = protocol.replace(/:$/, "");
530
+ var url = protocol + "://";
531
+ if (loc.username) {
532
+ url += loc.username;
533
+ if (loc.password) {
534
+ url += ":" + loc.password;
535
+ }
536
+ url += "@";
537
+ }
538
+ if (loc.host) {
539
+ url += loc.host;
540
+ }
541
+
542
+ if (loc.port) {
543
+ url += ":" + loc.port;
544
+ }
545
+
546
+ if (loc.pathname) {
547
+ url += loc.pathname;
548
+ }
549
+
550
+ if (loc.search) {
551
+ url += "?" + loc.search;
552
+ }
553
+ if (loc.hash) {
554
+ var hash = loc.hash;
555
+ hash = loc.hash.replace(/^#/, "");
556
+ url += "#" + hash;
557
+ }
558
+ return url;
559
+ }
560
+
561
+ function canonicalize(url) {
562
+ if(url == "about:blank")
563
+ {
564
+ return url;
565
+ }
566
+ var tempLink = window.document.createElement("link");
567
+ tempLink.href = url; // this will canonicalize the href on most browsers
568
+ var loc = parseUrl(tempLink.href)
569
+ if (!/\/\.\.\//.test(loc.pathname)) {
570
+ return tempLink.href;
571
+ }
572
+ // didn't work... let's try it the hard way
573
+ var originalParts = loc.pathname.split("/");
574
+ var newParts = [];
575
+ newParts.push(originalParts.shift());
576
+ for (var i = 0; i < originalParts.length; i++) {
577
+ var part = originalParts[i];
578
+ if (".." == part) {
579
+ newParts.pop();
580
+ continue;
581
+ }
582
+ newParts.push(part);
583
+ }
584
+ loc.pathname = newParts.join("/");
585
+ return reassembleLocation(loc);
586
+ }
587
+
588
+ function extractExceptionMessage(ex) {
589
+ if (ex == null) return "null exception";
590
+ if (ex.message != null) return ex.message;
591
+ if (ex.toString && ex.toString() != null) return ex.toString();
592
+ }
593
+
594
+
595
+ function describe(object, delimiter) {
596
+ var props = new Array();
597
+ for (var prop in object) {
598
+ try {
599
+ props.push(prop + " -> " + object[prop]);
600
+ } catch (e) {
601
+ props.push(prop + " -> [htmlutils: ack! couldn't read this property! (Permission Denied?)]");
602
+ }
603
+ }
604
+ return props.join(delimiter || '\n');
605
+ }
606
+
607
+ var PatternMatcher = function(pattern) {
608
+ this.selectStrategy(pattern);
609
+ };
610
+ PatternMatcher.prototype = {
611
+
612
+ selectStrategy: function(pattern) {
613
+ this.pattern = pattern;
614
+ var strategyName = 'glob';
615
+ // by default
616
+ if (/^([a-z-]+):(.*)/.test(pattern)) {
617
+ var possibleNewStrategyName = RegExp.$1;
618
+ var possibleNewPattern = RegExp.$2;
619
+ if (PatternMatcher.strategies[possibleNewStrategyName]) {
620
+ strategyName = possibleNewStrategyName;
621
+ pattern = possibleNewPattern;
622
+ }
623
+ }
624
+ var matchStrategy = PatternMatcher.strategies[strategyName];
625
+ if (!matchStrategy) {
626
+ throw new SeleniumError("cannot find PatternMatcher.strategies." + strategyName);
627
+ }
628
+ this.strategy = matchStrategy;
629
+ this.matcher = new matchStrategy(pattern);
630
+ },
631
+
632
+ matches: function(actual) {
633
+ return this.matcher.matches(actual + '');
634
+ // Note: appending an empty string avoids a Konqueror bug
635
+ }
636
+
637
+ };
638
+
639
+ /**
640
+ * A "static" convenience method for easy matching
641
+ */
642
+ PatternMatcher.matches = function(pattern, actual) {
643
+ return new PatternMatcher(pattern).matches(actual);
644
+ };
645
+
646
+ PatternMatcher.strategies = {
647
+
648
+ /**
649
+ * Exact matching, e.g. "exact:***"
650
+ */
651
+ exact: function(expected) {
652
+ this.expected = expected;
653
+ this.matches = function(actual) {
654
+ return actual == this.expected;
655
+ };
656
+ },
657
+
658
+ /**
659
+ * Match by regular expression, e.g. "regexp:^[0-9]+$"
660
+ */
661
+ regexp: function(regexpString) {
662
+ this.regexp = new RegExp(regexpString);
663
+ this.matches = function(actual) {
664
+ return this.regexp.test(actual);
665
+ };
666
+ },
667
+
668
+ regex: function(regexpString) {
669
+ this.regexp = new RegExp(regexpString);
670
+ this.matches = function(actual) {
671
+ return this.regexp.test(actual);
672
+ };
673
+ },
674
+
675
+ regexpi: function(regexpString) {
676
+ this.regexp = new RegExp(regexpString, "i");
677
+ this.matches = function(actual) {
678
+ return this.regexp.test(actual);
679
+ };
680
+ },
681
+
682
+ regexi: function(regexpString) {
683
+ this.regexp = new RegExp(regexpString, "i");
684
+ this.matches = function(actual) {
685
+ return this.regexp.test(actual);
686
+ };
687
+ },
688
+
689
+ /**
690
+ * "globContains" (aka "wildmat") patterns, e.g. "glob:one,two,*",
691
+ * but don't require a perfect match; instead succeed if actual
692
+ * contains something that matches globString.
693
+ * Making this distinction is motivated by a bug in IE6 which
694
+ * leads to the browser hanging if we implement *TextPresent tests
695
+ * by just matching against a regular expression beginning and
696
+ * ending with ".*". The globcontains strategy allows us to satisfy
697
+ * the functional needs of the *TextPresent ops more efficiently
698
+ * and so avoid running into this IE6 freeze.
699
+ */
700
+ globContains: function(globString) {
701
+ this.regexp = new RegExp(PatternMatcher.regexpFromGlobContains(globString));
702
+ this.matches = function(actual) {
703
+ return this.regexp.test(actual);
704
+ };
705
+ },
706
+
707
+
708
+ /**
709
+ * "glob" (aka "wildmat") patterns, e.g. "glob:one,two,*"
710
+ */
711
+ glob: function(globString) {
712
+ this.regexp = new RegExp(PatternMatcher.regexpFromGlob(globString));
713
+ this.matches = function(actual) {
714
+ return this.regexp.test(actual);
715
+ };
716
+ }
717
+
718
+ };
719
+
720
+ PatternMatcher.convertGlobMetaCharsToRegexpMetaChars = function(glob) {
721
+ var re = glob;
722
+ re = re.replace(/([.^$+(){}\[\]\\|])/g, "\\$1");
723
+ re = re.replace(/\?/g, "(.|[\r\n])");
724
+ re = re.replace(/\*/g, "(.|[\r\n])*");
725
+ return re;
726
+ };
727
+
728
+ PatternMatcher.regexpFromGlobContains = function(globContains) {
729
+ return PatternMatcher.convertGlobMetaCharsToRegexpMetaChars(globContains);
730
+ };
731
+
732
+ PatternMatcher.regexpFromGlob = function(glob) {
733
+ return "^" + PatternMatcher.convertGlobMetaCharsToRegexpMetaChars(glob) + "$";
734
+ };
735
+
736
+ if (!this["Assert"]) Assert = {};
737
+
738
+
739
+ Assert.fail = function(message) {
740
+ throw new AssertionFailedError(message);
741
+ };
742
+
743
+ /*
744
+ * Assert.equals(comment?, expected, actual)
745
+ */
746
+ Assert.equals = function() {
747
+ var args = new AssertionArguments(arguments);
748
+ if (args.expected === args.actual) {
749
+ return;
750
+ }
751
+ Assert.fail(args.comment +
752
+ "Expected '" + args.expected +
753
+ "' but was '" + args.actual + "'");
754
+ };
755
+
756
+ Assert.assertEquals = Assert.equals;
757
+
758
+ /*
759
+ * Assert.matches(comment?, pattern, actual)
760
+ */
761
+ Assert.matches = function() {
762
+ var args = new AssertionArguments(arguments);
763
+ if (PatternMatcher.matches(args.expected, args.actual)) {
764
+ return;
765
+ }
766
+ Assert.fail(args.comment +
767
+ "Actual value '" + args.actual +
768
+ "' did not match '" + args.expected + "'");
769
+ }
770
+
771
+ /*
772
+ * Assert.notMtches(comment?, pattern, actual)
773
+ */
774
+ Assert.notMatches = function() {
775
+ var args = new AssertionArguments(arguments);
776
+ if (!PatternMatcher.matches(args.expected, args.actual)) {
777
+ return;
778
+ }
779
+ Assert.fail(args.comment +
780
+ "Actual value '" + args.actual +
781
+ "' did match '" + args.expected + "'");
782
+ }
783
+
784
+
785
+ // Preprocess the arguments to allow for an optional comment.
786
+ function AssertionArguments(args) {
787
+ if (args.length == 2) {
788
+ this.comment = "";
789
+ this.expected = args[0];
790
+ this.actual = args[1];
791
+ } else {
792
+ this.comment = args[0] + "; ";
793
+ this.expected = args[1];
794
+ this.actual = args[2];
795
+ }
796
+ }
797
+
798
+ function AssertionFailedError(message) {
799
+ this.isAssertionFailedError = true;
800
+ this.isSeleniumError = true;
801
+ this.message = message;
802
+ this.failureMessage = message;
803
+ }
804
+
805
+ function SeleniumError(message) {
806
+ var error = new Error(message);
807
+ if (typeof(arguments.caller) != 'undefined') { // IE, not ECMA
808
+ var result = '';
809
+ for (var a = arguments.caller; a != null; a = a.caller) {
810
+ result += '> ' + a.callee.toString() + '\n';
811
+ if (a.caller == a) {
812
+ result += '*';
813
+ break;
814
+ }
815
+ }
816
+ error.stack = result;
817
+ }
818
+ error.isSeleniumError = true;
819
+ return error;
820
+ }
821
+
822
+ function highlight(element) {
823
+ var highLightColor = "yellow";
824
+ if (element.originalColor == undefined) { // avoid picking up highlight
825
+ element.originalColor = elementGetStyle(element, "background-color");
826
+ }
827
+ elementSetStyle(element, {"backgroundColor" : highLightColor});
828
+ window.setTimeout(function() {
829
+ try {
830
+ //if element is orphan, probably page of it has already gone, so ignore
831
+ if (!element.parentNode) {
832
+ return;
833
+ }
834
+ elementSetStyle(element, {"backgroundColor" : element.originalColor});
835
+ } catch (e) {} // DGF unhighlighting is very dangerous and low priority
836
+ }, 200);
837
+ }
838
+
839
+
840
+
841
+ // for use from vs.2003 debugger
842
+ function o2s(obj) {
843
+ var s = "";
844
+ for (key in obj) {
845
+ var line = key + "->" + obj[key];
846
+ line.replace("\n", " ");
847
+ s += line + "\n";
848
+ }
849
+ return s;
850
+ }
851
+
852
+ var seenReadyStateWarning = false;
853
+
854
+ function openSeparateApplicationWindow(url, suppressMozillaWarning) {
855
+ // resize the Selenium window itself
856
+ window.resizeTo(1200, 500);
857
+ window.moveTo(window.screenX, 0);
858
+
859
+ var appWindow = window.open(url + '?start=true', 'selenium_main_app_window');
860
+ if (appWindow == null) {
861
+ var errorMessage = "Couldn't open app window; is the pop-up blocker enabled?"
862
+ LOG.error(errorMessage);
863
+ throw new Error("Couldn't open app window; is the pop-up blocker enabled?");
864
+ }
865
+ try {
866
+ var windowHeight = 500;
867
+ if (window.outerHeight) {
868
+ windowHeight = window.outerHeight;
869
+ } else if (document.documentElement && document.documentElement.offsetHeight) {
870
+ windowHeight = document.documentElement.offsetHeight;
871
+ }
872
+
873
+ if (window.screenLeft && !window.screenX) window.screenX = window.screenLeft;
874
+ if (window.screenTop && !window.screenY) window.screenY = window.screenTop;
875
+
876
+ appWindow.resizeTo(1200, screen.availHeight - windowHeight - 60);
877
+ appWindow.moveTo(window.screenX, window.screenY + windowHeight + 25);
878
+ } catch (e) {
879
+ LOG.error("Couldn't resize app window");
880
+ LOG.exception(e);
881
+ }
882
+
883
+
884
+ if (!suppressMozillaWarning && window.document.readyState == null && !seenReadyStateWarning) {
885
+ 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.");
886
+ seenReadyStateWarning = true;
887
+ }
888
+
889
+ return appWindow;
890
+ }
891
+
892
+ var URLConfiguration = classCreate();
893
+ objectExtend(URLConfiguration.prototype, {
894
+ initialize: function() {
895
+ },
896
+ _isQueryParameterTrue: function (name) {
897
+ var parameterValue = this._getQueryParameter(name);
898
+ if (parameterValue == null) return false;
899
+ if (parameterValue.toLowerCase() == "true") return true;
900
+ if (parameterValue.toLowerCase() == "on") return true;
901
+ return false;
902
+ },
903
+
904
+ _getQueryParameter: function(searchKey) {
905
+ var str = this.queryString
906
+ if (str == null) return null;
907
+ var clauses = str.split('&');
908
+ for (var i = 0; i < clauses.length; i++) {
909
+ var keyValuePair = clauses[i].split('=', 2);
910
+ var key = unescape(keyValuePair[0]);
911
+ if (key == searchKey) {
912
+ return unescape(keyValuePair[1]);
913
+ }
914
+ }
915
+ return null;
916
+ },
917
+
918
+ _extractArgs: function() {
919
+ var str = SeleniumHTARunner.commandLine;
920
+ if (str == null || str == "") return new Array();
921
+ var matches = str.match(/(?:\"([^\"]+)\"|(?!\"([^\"]+)\")(\S+))/g);
922
+ // We either want non quote stuff ([^"]+) surrounded by quotes
923
+ // or we want to look-ahead, see that the next character isn't
924
+ // a quoted argument, and then grab all the non-space stuff
925
+ // this will return for the line: "foo" bar
926
+ // the results "\"foo\"" and "bar"
927
+
928
+ // So, let's unquote the quoted arguments:
929
+ var args = new Array;
930
+ for (var i = 0; i < matches.length; i++) {
931
+ args[i] = matches[i];
932
+ args[i] = args[i].replace(/^"(.*)"$/, "$1");
933
+ }
934
+ return args;
935
+ },
936
+
937
+ isMultiWindowMode:function() {
938
+ return this._isQueryParameterTrue('multiWindow');
939
+ },
940
+
941
+ getBaseUrl:function() {
942
+ return this._getQueryParameter('baseUrl');
943
+
944
+ }
945
+ });
946
+
947
+
948
+ function safeScrollIntoView(element) {
949
+ if (element.scrollIntoView) {
950
+ element.scrollIntoView(false);
951
+ return;
952
+ }
953
+ // TODO: work out how to scroll browsers that don't support
954
+ // scrollIntoView (like Konqueror)
955
+ }
956
+
957
+ /**
958
+ * Returns the absolute time represented as an offset of the current time.
959
+ * Throws a SeleniumException if timeout is invalid.
960
+ *
961
+ * @param timeout the number of milliseconds from "now" whose absolute time
962
+ * to return
963
+ */
964
+ function getTimeoutTime(timeout) {
965
+ var now = new Date().getTime();
966
+ var timeoutLength = parseInt(timeout);
967
+
968
+ if (isNaN(timeoutLength)) {
969
+ throw new SeleniumError("Timeout is not a number: '" + timeout + "'");
970
+ }
971
+
972
+ return now + timeoutLength;
973
+ }
974
+
975
+ /**
976
+ * Returns true iff the current environment is the IDE, and is not the chrome
977
+ * runner launched by the IDE.
978
+ */
979
+ function is_IDE() {
980
+ var locstr = window.location.href;
981
+
982
+ if (locstr.indexOf('chrome://selenium-ide-testrunner') == 0) {
983
+ return false;
984
+ }
985
+
986
+ return (typeof(SeleniumIDE) != 'undefined');
987
+ }
988
+
989
+ /**
990
+ * Logs a message if the Logger exists, and does nothing if it doesn't exist.
991
+ *
992
+ * @param level the level to log at
993
+ * @param msg the message to log
994
+ */
995
+ function safe_log(level, msg)
996
+ {
997
+ try {
998
+ LOG[level](msg);
999
+ }
1000
+ catch (e) {
1001
+ // couldn't log!
1002
+ }
1003
+ }
1004
+
1005
+ /**
1006
+ * Displays a warning message to the user appropriate to the context under
1007
+ * which the issue is encountered. This is primarily used to avoid popping up
1008
+ * alert dialogs that might pause an automated test suite.
1009
+ *
1010
+ * @param msg the warning message to display
1011
+ */
1012
+ function safe_alert(msg)
1013
+ {
1014
+ if (is_IDE()) {
1015
+ alert(msg);
1016
+ }
1017
+ }
1018
+
1019
+ /**
1020
+ * Returns true iff the given element represents a link with a javascript
1021
+ * href attribute, and does not have an onclick attribute defined.
1022
+ *
1023
+ * @param element the element to test
1024
+ */
1025
+ function hasJavascriptHref(element) {
1026
+ if (getTagName(element) != 'a') {
1027
+ return false;
1028
+ }
1029
+ if (element.getAttribute('onclick')) {
1030
+ return false;
1031
+ }
1032
+ if (! element.href) {
1033
+ return false;
1034
+ }
1035
+ if (! /\s*javascript:/i.test(element.href)) {
1036
+ return false;
1037
+ }
1038
+ return true;
1039
+ }
1040
+
1041
+ /**
1042
+ * Returns the given element, or its nearest ancestor, that satisfies
1043
+ * hasJavascriptHref(). Returns null if none is found.
1044
+ *
1045
+ * @param element the element whose ancestors to test
1046
+ */
1047
+ function getAncestorOrSelfWithJavascriptHref(element) {
1048
+ if (hasJavascriptHref(element)) {
1049
+ return element;
1050
+ }
1051
+ if (element.parentNode == null) {
1052
+ return null;
1053
+ }
1054
+ return getAncestorOrSelfWithJavascriptHref(element.parentNode);
1055
+ }
1056
+
1057
+ //******************************************************************************
1058
+ // Locator evaluation support
1059
+
1060
+ /**
1061
+ * Parses a Selenium locator, returning its type and the unprefixed locator
1062
+ * string as an object.
1063
+ *
1064
+ * @param locator the locator to parse
1065
+ */
1066
+ function parse_locator(locator)
1067
+ {
1068
+ var result = locator.match(/^([A-Za-z]+)=(.+)/);
1069
+ if (result) {
1070
+ return { type: result[1].toLowerCase(), string: result[2] };
1071
+ }
1072
+ return { type: 'implicit', string: locator };
1073
+ }
1074
+
1075
+ /**
1076
+ * Evaluates an xpath on a document, and returns a list containing nodes in the
1077
+ * resulting nodeset. The browserbot xpath methods are now backed by this
1078
+ * function. A context node may optionally be provided, and the xpath will be
1079
+ * evaluated from that context.
1080
+ *
1081
+ * @param xpath the xpath to evaluate
1082
+ * @param inDocument the document in which to evaluate the xpath.
1083
+ * @param opts (optional) An object containing various flags that can
1084
+ * modify how the xpath is evaluated. Here's a listing of
1085
+ * the meaningful keys:
1086
+ *
1087
+ * contextNode:
1088
+ * the context node from which to evaluate the xpath. If
1089
+ * unspecified, the context will be the root document
1090
+ * element.
1091
+ *
1092
+ * namespaceResolver:
1093
+ * the namespace resolver function. Defaults to null.
1094
+ *
1095
+ * xpathLibrary:
1096
+ * the javascript library to use for XPath. "ajaxslt" is
1097
+ * the default. "javascript-xpath" is newer and faster,
1098
+ * but needs more testing.
1099
+ *
1100
+ * allowNativeXpath:
1101
+ * whether to allow native evaluate(). Defaults to true.
1102
+ *
1103
+ * ignoreAttributesWithoutValue:
1104
+ * whether it's ok to ignore attributes without value
1105
+ * when evaluating the xpath. This can greatly improve
1106
+ * performance in IE; however, if your xpaths depend on
1107
+ * such attributes, you can't ignore them! Defaults to
1108
+ * true.
1109
+ *
1110
+ * returnOnFirstMatch:
1111
+ * whether to optimize the XPath evaluation to only
1112
+ * return the first match. The match, if any, will still
1113
+ * be returned in a list. Defaults to false.
1114
+ */
1115
+ function eval_xpath(xpath, inDocument, opts)
1116
+ {
1117
+ if (!opts) {
1118
+ var opts = {};
1119
+ }
1120
+ var contextNode = opts.contextNode
1121
+ ? opts.contextNode : inDocument;
1122
+ var namespaceResolver = opts.namespaceResolver
1123
+ ? opts.namespaceResolver : null;
1124
+ var xpathLibrary = opts.xpathLibrary
1125
+ ? opts.xpathLibrary : null;
1126
+ var allowNativeXpath = (opts.allowNativeXpath != undefined)
1127
+ ? opts.allowNativeXpath : true;
1128
+ var ignoreAttributesWithoutValue = (opts.ignoreAttributesWithoutValue != undefined)
1129
+ ? opts.ignoreAttributesWithoutValue : true;
1130
+ var returnOnFirstMatch = (opts.returnOnFirstMatch != undefined)
1131
+ ? opts.returnOnFirstMatch : false;
1132
+
1133
+ // Trim any trailing "/": not valid xpath, and remains from attribute
1134
+ // locator.
1135
+ if (xpath.charAt(xpath.length - 1) == '/') {
1136
+ xpath = xpath.slice(0, -1);
1137
+ }
1138
+ // HUGE hack - remove namespace from xpath for IE
1139
+ if (browserVersion && browserVersion.isIE) {
1140
+ xpath = xpath.replace(/x:/g, '')
1141
+ }
1142
+
1143
+ var nativeXpathAvailable = inDocument.evaluate;
1144
+ var useNativeXpath = allowNativeXpath && nativeXpathAvailable;
1145
+ var useDocumentEvaluate = useNativeXpath;
1146
+
1147
+ // When using the new and faster javascript-xpath library,
1148
+ // we'll use the TestRunner's document object, not the App-Under-Test's document.
1149
+ // The new library only modifies the TestRunner document with the new
1150
+ // functionality.
1151
+ if (xpathLibrary == 'javascript-xpath' && !useNativeXpath) {
1152
+ documentForXpath = document;
1153
+ useDocumentEvaluate = true;
1154
+ } else {
1155
+ documentForXpath = inDocument;
1156
+ }
1157
+ var results = [];
1158
+
1159
+ // this is either native xpath or javascript-xpath via TestRunner.evaluate
1160
+ if (useDocumentEvaluate) {
1161
+ try {
1162
+ // Regarding use of the second argument to document.evaluate():
1163
+ // http://groups.google.com/group/comp.lang.javascript/browse_thread/thread/a59ce20639c74ba1/a9d9f53e88e5ebb5
1164
+ var xpathResult = documentForXpath
1165
+ .evaluate((contextNode == inDocument ? xpath : '.' + xpath),
1166
+ contextNode, namespaceResolver, 0, null);
1167
+ }
1168
+ catch (e) {
1169
+ throw new SeleniumError("Invalid xpath [1]: " + extractExceptionMessage(e));
1170
+ }
1171
+ finally{
1172
+ if (xpathResult == null) {
1173
+ // If the result is null, we should still throw an Error.
1174
+ throw new SeleniumError("Invalid xpath [2]: " + xpath);
1175
+ }
1176
+ }
1177
+ var result = xpathResult.iterateNext();
1178
+ while (result) {
1179
+ results.push(result);
1180
+ result = xpathResult.iterateNext();
1181
+ }
1182
+ return results;
1183
+ }
1184
+
1185
+ // If not, fall back to slower JavaScript implementation
1186
+ // DGF set xpathdebug = true (using getEval, if you like) to turn on JS XPath debugging
1187
+ //xpathdebug = true;
1188
+ var context;
1189
+ if (contextNode == inDocument) {
1190
+ context = new ExprContext(inDocument);
1191
+ }
1192
+ else {
1193
+ // provide false values to get the default constructor values
1194
+ context = new ExprContext(contextNode, false, false,
1195
+ contextNode.parentNode);
1196
+ }
1197
+ context.setCaseInsensitive(true);
1198
+ context.setIgnoreAttributesWithoutValue(ignoreAttributesWithoutValue);
1199
+ context.setReturnOnFirstMatch(returnOnFirstMatch);
1200
+ var xpathObj;
1201
+ try {
1202
+ xpathObj = xpathParse(xpath);
1203
+ }
1204
+ catch (e) {
1205
+ throw new SeleniumError("Invalid xpath [3]: " + extractExceptionMessage(e));
1206
+ }
1207
+ var xpathResult = xpathObj.evaluate(context);
1208
+ if (xpathResult && xpathResult.value) {
1209
+ for (var i = 0; i < xpathResult.value.length; ++i) {
1210
+ results.push(xpathResult.value[i]);
1211
+ }
1212
+ }
1213
+ return results;
1214
+ }
1215
+
1216
+ /**
1217
+ * Returns the full resultset of a CSS selector evaluation.
1218
+ */
1219
+ function eval_css(locator, inDocument)
1220
+ {
1221
+ return cssQuery(locator, inDocument);
1222
+ }
1223
+
1224
+ /**
1225
+ * This function duplicates part of BrowserBot.findElement() to open up locator
1226
+ * evaluation on arbitrary documents. It returns a plain old array of located
1227
+ * elements found by using a Selenium locator.
1228
+ *
1229
+ * Multiple results may be generated for xpath and CSS locators. Even though a
1230
+ * list could potentially be generated for other locator types, such as link,
1231
+ * we don't try for them, because they aren't very expressive location
1232
+ * strategies; if you want a list, use xpath or CSS. Furthermore, strategies
1233
+ * for these locators have been optimized to only return the first result. For
1234
+ * these types of locators, performance is more important than ideal behavior.
1235
+ *
1236
+ * @param locator a locator string
1237
+ * @param inDocument the document in which to apply the locator
1238
+ * @param opt_contextNode the context within which to evaluate the locator
1239
+ *
1240
+ * @return a list of result elements
1241
+ */
1242
+ function eval_locator(locator, inDocument, opt_contextNode)
1243
+ {
1244
+ locator = parse_locator(locator);
1245
+
1246
+ var pageBot;
1247
+ if (typeof(selenium) != 'undefined' && selenium != undefined) {
1248
+ if (typeof(editor) == 'undefined' || editor.state == 'playing') {
1249
+ safe_log('info', 'Trying [' + locator.type + ']: '
1250
+ + locator.string);
1251
+ }
1252
+ pageBot = selenium.browserbot;
1253
+ }
1254
+ else {
1255
+ if (!UI_GLOBAL.mozillaBrowserBot) {
1256
+ // create a browser bot to evaluate the locator. Hand it the IDE
1257
+ // window as a dummy window, and cache it for future use.
1258
+ UI_GLOBAL.mozillaBrowserBot = new MozillaBrowserBot(window)
1259
+ }
1260
+ pageBot = UI_GLOBAL.mozillaBrowserBot;
1261
+ }
1262
+
1263
+ var results = [];
1264
+
1265
+ if (locator.type == 'xpath' || (locator.string.charAt(0) == '/' &&
1266
+ locator.type == 'implicit')) {
1267
+ results = eval_xpath(locator.string, inDocument,
1268
+ { contextNode: opt_contextNode });
1269
+ }
1270
+ else if (locator.type == 'css') {
1271
+ results = eval_css(locator.string, inDocument);
1272
+ }
1273
+ else {
1274
+ var element = pageBot
1275
+ .findElementBy(locator.type, locator.string, inDocument);
1276
+ if (element != null) {
1277
+ results.push(element);
1278
+ }
1279
+ }
1280
+
1281
+ return results;
1282
+ }
1283
+
1284
+ //******************************************************************************
1285
+ // UI-Element
1286
+
1287
+ /**
1288
+ * Escapes the special regular expression characters in a string intended to be
1289
+ * used as a regular expression.
1290
+ *
1291
+ * Based on: http://simonwillison.net/2006/Jan/20/escape/
1292
+ */
1293
+ RegExp.escape = (function() {
1294
+ var specials = [
1295
+ '/', '.', '*', '+', '?', '|', '^', '$',
1296
+ '(', ')', '[', ']', '{', '}', '\\'
1297
+ ];
1298
+
1299
+ var sRE = new RegExp(
1300
+ '(\\' + specials.join('|\\') + ')', 'g'
1301
+ );
1302
+
1303
+ return function(text) {
1304
+ return text.replace(sRE, '\\$1');
1305
+ }
1306
+ })();
1307
+
1308
+ /**
1309
+ * Returns true if two arrays are identical, and false otherwise.
1310
+ *
1311
+ * @param a1 the first array, may only contain simple values (strings or
1312
+ * numbers)
1313
+ * @param a2 the second array, same restricts on data as for a1
1314
+ * @return true if the arrays are equivalent, false otherwise.
1315
+ */
1316
+ function are_equal(a1, a2)
1317
+ {
1318
+ if (typeof(a1) != typeof(a2))
1319
+ return false;
1320
+
1321
+ switch(typeof(a1)) {
1322
+ case 'object':
1323
+ // arrays
1324
+ if (a1.length) {
1325
+ if (a1.length != a2.length)
1326
+ return false;
1327
+ for (var i = 0; i < a1.length; ++i) {
1328
+ if (!are_equal(a1[i], a2[i]))
1329
+ return false
1330
+ }
1331
+ }
1332
+ // associative arrays
1333
+ else {
1334
+ var keys = {};
1335
+ for (var key in a1) {
1336
+ keys[key] = true;
1337
+ }
1338
+ for (var key in a2) {
1339
+ keys[key] = true;
1340
+ }
1341
+ for (var key in keys) {
1342
+ if (!are_equal(a1[key], a2[key]))
1343
+ return false;
1344
+ }
1345
+ }
1346
+ return true;
1347
+
1348
+ default:
1349
+ return a1 == a2;
1350
+ }
1351
+ }
1352
+
1353
+
1354
+ /**
1355
+ * Create a clone of an object and return it. This is a deep copy of everything
1356
+ * but functions, whose references are copied. You shouldn't expect a deep copy
1357
+ * of functions anyway.
1358
+ *
1359
+ * @param orig the original object to copy
1360
+ * @return a deep copy of the original object. Any functions attached,
1361
+ * however, will have their references copied only.
1362
+ */
1363
+ function clone(orig) {
1364
+ var copy;
1365
+ switch(typeof(orig)) {
1366
+ case 'object':
1367
+ copy = (orig.length) ? [] : {};
1368
+ for (var attr in orig) {
1369
+ copy[attr] = clone(orig[attr]);
1370
+ }
1371
+ break;
1372
+ default:
1373
+ copy = orig;
1374
+ break;
1375
+ }
1376
+ return copy;
1377
+ }
1378
+
1379
+ /**
1380
+ * Emulates php's print_r() functionality. Returns a nicely formatted string
1381
+ * representation of an object. Very useful for debugging.
1382
+ *
1383
+ * @param object the object to dump
1384
+ * @param maxDepth the maximum depth to recurse into the object. Ellipses will
1385
+ * be shown for objects whose depth exceeds the maximum.
1386
+ * @param indent the string to use for indenting progressively deeper levels
1387
+ * of the dump.
1388
+ * @return a string representing a dump of the object
1389
+ */
1390
+ function print_r(object, maxDepth, indent)
1391
+ {
1392
+ var parentIndent, attr, str = "";
1393
+ if (arguments.length == 1) {
1394
+ var maxDepth = Number.MAX_VALUE;
1395
+ } else {
1396
+ maxDepth--;
1397
+ }
1398
+ if (arguments.length < 3) {
1399
+ parentIndent = ''
1400
+ var indent = ' ';
1401
+ } else {
1402
+ parentIndent = indent;
1403
+ indent += ' ';
1404
+ }
1405
+
1406
+ switch(typeof(object)) {
1407
+ case 'object':
1408
+ if (object.length != undefined) {
1409
+ if (object.length == 0) {
1410
+ str += "Array ()\r\n";
1411
+ }
1412
+ else {
1413
+ str += "Array (\r\n";
1414
+ for (var i = 0; i < object.length; ++i) {
1415
+ str += indent + '[' + i + '] => ';
1416
+ if (maxDepth == 0)
1417
+ str += "...\r\n";
1418
+ else
1419
+ str += print_r(object[i], maxDepth, indent);
1420
+ }
1421
+ str += parentIndent + ")\r\n";
1422
+ }
1423
+ }
1424
+ else {
1425
+ str += "Object (\r\n";
1426
+ for (attr in object) {
1427
+ str += indent + "[" + attr + "] => ";
1428
+ if (maxDepth == 0)
1429
+ str += "...\r\n";
1430
+ else
1431
+ str += print_r(object[attr], maxDepth, indent);
1432
+ }
1433
+ str += parentIndent + ")\r\n";
1434
+ }
1435
+ break;
1436
+ case 'boolean':
1437
+ str += (object ? 'true' : 'false') + "\r\n";
1438
+ break;
1439
+ case 'function':
1440
+ str += "Function\r\n";
1441
+ break;
1442
+ default:
1443
+ str += object + "\r\n";
1444
+ break;
1445
+
1446
+ }
1447
+ return str;
1448
+ }
1449
+
1450
+ /**
1451
+ * Return an array containing all properties of an object. Perl-style.
1452
+ *
1453
+ * @param object the object whose keys to return
1454
+ * @return array of object keys, as strings
1455
+ */
1456
+ function keys(object)
1457
+ {
1458
+ var keys = [];
1459
+ for (var k in object) {
1460
+ keys.push(k);
1461
+ }
1462
+ return keys;
1463
+ }
1464
+
1465
+ /**
1466
+ * Emulates python's range() built-in. Returns an array of integers, counting
1467
+ * up (or down) from start to end. Note that the range returned is up to, but
1468
+ * NOT INCLUDING, end.
1469
+ *.
1470
+ * @param start integer from which to start counting. If the end parameter is
1471
+ * not provided, this value is considered the end and start will
1472
+ * be zero.
1473
+ * @param end integer to which to count. If omitted, the function will count
1474
+ * up from zero to the value of the start parameter. Note that
1475
+ * the array returned will count up to but will not include this
1476
+ * value.
1477
+ * @return an array of consecutive integers.
1478
+ */
1479
+ function range(start, end)
1480
+ {
1481
+ if (arguments.length == 1) {
1482
+ var end = start;
1483
+ start = 0;
1484
+ }
1485
+
1486
+ var r = [];
1487
+ if (start < end) {
1488
+ while (start != end)
1489
+ r.push(start++);
1490
+ }
1491
+ else {
1492
+ while (start != end)
1493
+ r.push(start--);
1494
+ }
1495
+ return r;
1496
+ }
1497
+
1498
+ /**
1499
+ * Parses a python-style keyword arguments string and returns the pairs in a
1500
+ * new object.
1501
+ *
1502
+ * @param kwargs a string representing a set of keyword arguments. It should
1503
+ * look like <tt>keyword1=value1, keyword2=value2, ...</tt>
1504
+ * @return an object mapping strings to strings
1505
+ */
1506
+ function parse_kwargs(kwargs)
1507
+ {
1508
+ var args = new Object();
1509
+ var pairs = kwargs.split(/,/);
1510
+ for (var i = 0; i < pairs.length;) {
1511
+ if (i > 0 && pairs[i].indexOf('=') == -1) {
1512
+ // the value string contained a comma. Glue the parts back together.
1513
+ pairs[i-1] += ',' + pairs.splice(i, 1)[0];
1514
+ }
1515
+ else {
1516
+ ++i;
1517
+ }
1518
+ }
1519
+ for (var i = 0; i < pairs.length; ++i) {
1520
+ var splits = pairs[i].split(/=/);
1521
+ if (splits.length == 1) {
1522
+ continue;
1523
+ }
1524
+ var key = splits.shift();
1525
+ var value = splits.join('=');
1526
+ args[key.trim()] = value.trim();
1527
+ }
1528
+ return args;
1529
+ }
1530
+
1531
+ /**
1532
+ * Creates a python-style keyword arguments string from an object.
1533
+ *
1534
+ * @param args an associative array mapping strings to strings
1535
+ * @param sortedKeys (optional) a list of keys of the args parameter that
1536
+ * specifies the order in which the arguments will appear in
1537
+ * the returned kwargs string
1538
+ *
1539
+ * @return a kwarg string representation of args
1540
+ */
1541
+ function to_kwargs(args, sortedKeys)
1542
+ {
1543
+ var s = '';
1544
+ if (!sortedKeys) {
1545
+ var sortedKeys = keys(args).sort();
1546
+ }
1547
+ for (var i = 0; i < sortedKeys.length; ++i) {
1548
+ var k = sortedKeys[i];
1549
+ if (args[k] != undefined) {
1550
+ if (s) {
1551
+ s += ', ';
1552
+ }
1553
+ s += k + '=' + args[k];
1554
+ }
1555
+ }
1556
+ return s;
1557
+ }
1558
+
1559
+ /**
1560
+ * Returns true if a node is an ancestor node of a target node, and false
1561
+ * otherwise.
1562
+ *
1563
+ * @param node the node being compared to the target node
1564
+ * @param target the target node
1565
+ * @return true if node is an ancestor node of target, false otherwise.
1566
+ */
1567
+ function is_ancestor(node, target)
1568
+ {
1569
+ while (target.parentNode) {
1570
+ target = target.parentNode;
1571
+ if (node == target)
1572
+ return true;
1573
+ }
1574
+ return false;
1575
+ }
1576
+
1577
+ //******************************************************************************
1578
+ // parseUri 1.2.1
1579
+ // MIT License
1580
+
1581
+ /*
1582
+ Copyright (c) 2007 Steven Levithan <stevenlevithan.com>
1583
+
1584
+ Permission is hereby granted, free of charge, to any person obtaining a copy
1585
+ of this software and associated documentation files (the "Software"), to deal
1586
+ in the Software without restriction, including without limitation the rights
1587
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1588
+ copies of the Software, and to permit persons to whom the Software is
1589
+ furnished to do so, subject to the following conditions:
1590
+
1591
+ The above copyright notice and this permission notice shall be included in
1592
+ all copies or substantial portions of the Software.
1593
+ */
1594
+
1595
+ function parseUri (str) {
1596
+ var o = parseUri.options,
1597
+ m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
1598
+ uri = {},
1599
+ i = 14;
1600
+
1601
+ while (i--) uri[o.key[i]] = m[i] || "";
1602
+
1603
+ uri[o.q.name] = {};
1604
+ uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
1605
+ if ($1) uri[o.q.name][$1] = $2;
1606
+ });
1607
+
1608
+ return uri;
1609
+ };
1610
+
1611
+ parseUri.options = {
1612
+ strictMode: false,
1613
+ key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
1614
+ q: {
1615
+ name: "queryKey",
1616
+ parser: /(?:^|&)([^&=]*)=?([^&]*)/g
1617
+ },
1618
+ parser: {
1619
+ strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
1620
+ loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
1621
+ }
1622
+ };
1623
+