selenium-webdriver 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. data/chrome/prebuilt/Win32/Release/npchromedriver.dll +0 -0
  2. data/chrome/prebuilt/x64/Release/npchromedriver.dll +0 -0
  3. data/chrome/src/extension/background.html +9 -0
  4. data/chrome/src/extension/background.js +933 -0
  5. data/chrome/src/extension/content_script.js +1286 -0
  6. data/chrome/src/extension/manifest-nonwin.json +15 -0
  7. data/chrome/src/extension/manifest-win.json +16 -0
  8. data/chrome/src/extension/toolstrip.html +28 -0
  9. data/chrome/src/extension/utils.js +196 -0
  10. data/chrome/src/rb/lib/selenium/webdriver/chrome.rb +8 -0
  11. data/chrome/src/rb/lib/selenium/webdriver/chrome/bridge.rb +324 -0
  12. data/chrome/src/rb/lib/selenium/webdriver/chrome/command_executor.rb +70 -0
  13. data/chrome/src/rb/lib/selenium/webdriver/chrome/launcher.rb +119 -0
  14. data/common/src/js/abstractcommandprocessor.js +161 -0
  15. data/common/src/js/asserts.js +296 -0
  16. data/common/src/js/by.js +147 -0
  17. data/common/src/js/command.js +274 -0
  18. data/common/src/js/context.js +58 -0
  19. data/common/src/js/extension/README +2 -0
  20. data/common/src/js/extension/dommessenger.js +152 -0
  21. data/common/src/js/factory.js +55 -0
  22. data/common/src/js/future.js +118 -0
  23. data/common/src/js/key.js +117 -0
  24. data/common/src/js/localcommandprocessor.js +181 -0
  25. data/common/src/js/logging.js +249 -0
  26. data/common/src/js/testrunner.js +605 -0
  27. data/common/src/js/timing.js +89 -0
  28. data/common/src/js/wait.js +199 -0
  29. data/common/src/js/webdriver.js +853 -0
  30. data/common/src/js/webelement.js +683 -0
  31. data/common/src/rb/lib/selenium-webdriver.rb +1 -0
  32. data/common/src/rb/lib/selenium/webdriver.rb +52 -0
  33. data/common/src/rb/lib/selenium/webdriver/bridge_helper.rb +88 -0
  34. data/common/src/rb/lib/selenium/webdriver/child_process.rb +85 -0
  35. data/common/src/rb/lib/selenium/webdriver/core_ext/dir.rb +41 -0
  36. data/common/src/rb/lib/selenium/webdriver/driver.rb +128 -0
  37. data/common/src/rb/lib/selenium/webdriver/element.rb +126 -0
  38. data/common/src/rb/lib/selenium/webdriver/error.rb +68 -0
  39. data/common/src/rb/lib/selenium/webdriver/find.rb +69 -0
  40. data/common/src/rb/lib/selenium/webdriver/navigation.rb +23 -0
  41. data/common/src/rb/lib/selenium/webdriver/options.rb +50 -0
  42. data/common/src/rb/lib/selenium/webdriver/platform.rb +82 -0
  43. data/common/src/rb/lib/selenium/webdriver/target_locator.rb +23 -0
  44. data/firefox/prebuilt/nsICommandProcessor.xpt +0 -0
  45. data/firefox/prebuilt/nsINativeEvents.xpt +0 -0
  46. data/firefox/prebuilt/nsIResponseHandler.xpt +0 -0
  47. data/firefox/src/extension/chrome.manifest +3 -0
  48. data/firefox/src/extension/components/context.js +37 -0
  49. data/firefox/src/extension/components/driver-component.js +127 -0
  50. data/firefox/src/extension/components/firefoxDriver.js +706 -0
  51. data/firefox/src/extension/components/json2.js +273 -0
  52. data/firefox/src/extension/components/keytest.html +554 -0
  53. data/firefox/src/extension/components/nsCommandProcessor.js +586 -0
  54. data/firefox/src/extension/components/screenshooter.js +70 -0
  55. data/firefox/src/extension/components/socketListener.js +185 -0
  56. data/firefox/src/extension/components/utils.js +1200 -0
  57. data/firefox/src/extension/components/webLoadingListener.js +57 -0
  58. data/firefox/src/extension/components/webdriverserver.js +101 -0
  59. data/firefox/src/extension/components/wrappedElement.js +609 -0
  60. data/firefox/src/extension/content/fxdriver.xul +30 -0
  61. data/firefox/src/extension/content/server.js +95 -0
  62. data/firefox/src/extension/idl/nsICommandProcessor.idl +38 -0
  63. data/firefox/src/extension/idl/nsIResponseHandler.idl +34 -0
  64. data/firefox/src/extension/install.rdf +29 -0
  65. data/firefox/src/rb/lib/selenium/webdriver/firefox.rb +21 -0
  66. data/firefox/src/rb/lib/selenium/webdriver/firefox/binary.rb +86 -0
  67. data/firefox/src/rb/lib/selenium/webdriver/firefox/bridge.rb +426 -0
  68. data/firefox/src/rb/lib/selenium/webdriver/firefox/extension_connection.rb +82 -0
  69. data/firefox/src/rb/lib/selenium/webdriver/firefox/launcher.rb +132 -0
  70. data/firefox/src/rb/lib/selenium/webdriver/firefox/profile.rb +174 -0
  71. data/firefox/src/rb/lib/selenium/webdriver/firefox/profiles_ini.rb +60 -0
  72. data/firefox/src/rb/lib/selenium/webdriver/firefox/util.rb +23 -0
  73. data/jobbie/prebuilt/Win32/Release/InternetExplorerDriver.dll +0 -0
  74. data/jobbie/prebuilt/x64/Release/InternetExplorerDriver.dll +0 -0
  75. data/jobbie/src/rb/lib/selenium/webdriver/ie.rb +14 -0
  76. data/jobbie/src/rb/lib/selenium/webdriver/ie/bridge.rb +552 -0
  77. data/jobbie/src/rb/lib/selenium/webdriver/ie/lib.rb +94 -0
  78. data/jobbie/src/rb/lib/selenium/webdriver/ie/util.rb +147 -0
  79. data/remote/client/src/rb/lib/selenium/webdriver/remote.rb +16 -0
  80. data/remote/client/src/rb/lib/selenium/webdriver/remote/bridge.rb +374 -0
  81. data/remote/client/src/rb/lib/selenium/webdriver/remote/capabilities.rb +105 -0
  82. data/remote/client/src/rb/lib/selenium/webdriver/remote/commands.rb +53 -0
  83. data/remote/client/src/rb/lib/selenium/webdriver/remote/default_http_client.rb +71 -0
  84. data/remote/client/src/rb/lib/selenium/webdriver/remote/response.rb +43 -0
  85. data/remote/client/src/rb/lib/selenium/webdriver/remote/server_error.rb +32 -0
  86. metadata +182 -0
@@ -0,0 +1,249 @@
1
+ /** @license
2
+ Copyright 2007-2009 WebDriver committers
3
+ Copyright 2007-2009 Google Inc.
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+ */
17
+
18
+ /**
19
+ * @fileoverview Defines a logging API that logs to a DOM on the current page as
20
+ * well as the Firebug console.
21
+ * @author jmelyba@gmail.com (Jason Leyba)
22
+ */
23
+
24
+ goog.provide('webdriver.logging');
25
+ goog.provide('webdriver.logging.Level');
26
+
27
+ goog.require('goog.dom');
28
+ goog.require('goog.string');
29
+
30
+
31
+ /**
32
+ * Represents a level that can be used to filter log messages.
33
+ * @param {number} code Numeric value for this level.
34
+ * @param {string} firebugLogFnName The name of the firebug function to use for
35
+ * logging messages at this level to the console.
36
+ * @param {string} domColor The color string to use for logging messages to the
37
+ * DOM at this level.
38
+ * @constructor
39
+ */
40
+ webdriver.logging.Level = function(code, firebugLogFnName, domColor) {
41
+ this.code = code;
42
+ this.firebugLogFnName = firebugLogFnName;
43
+ this.domColor = domColor;
44
+ };
45
+
46
+
47
+ webdriver.logging.Level.ERROR = new webdriver.logging.Level(0, 'error', 'red');
48
+ webdriver.logging.Level.WARN = new webdriver.logging.Level(1, 'warn', 'orange');
49
+ webdriver.logging.Level.INFO = new webdriver.logging.Level(2, 'info', 'black');
50
+ webdriver.logging.Level.DEBUG = new webdriver.logging.Level(3, 'debug', 'gray');
51
+
52
+
53
+ /**
54
+ * A reference to the Firebug console. Will be undefined if the current browser
55
+ * does not have Firebug installed (or if the current browser is not Firefox).
56
+ * @type {?Object}
57
+ * @private
58
+ */
59
+ webdriver.logging.console_ = window['console'];
60
+
61
+
62
+ /**
63
+ * Whether to log to the Firebug console if it is available.
64
+ * @type {boolean}
65
+ * @private
66
+ */
67
+ webdriver.logging.firebugLogging_ = false;
68
+
69
+
70
+ /**
71
+ * Whether to log messages to the DOM.
72
+ * @type {boolean}
73
+ * @private
74
+ */
75
+ webdriver.logging.domLogging_ = true;
76
+
77
+
78
+ /**
79
+ * The current logging level. Messages below this level will not be logged.
80
+ * @type {webdriver.logging.Level}
81
+ * @private
82
+ */
83
+ webdriver.logging.currentLevel_ = webdriver.logging.Level.INFO;
84
+
85
+
86
+ /**
87
+ * @param {boolean} enable Whether to enable logging to the Firebug console.
88
+ */
89
+ webdriver.logging.enableFirebugLogging = function(enable) {
90
+ webdriver.logging.firebugLogging_ = enable;
91
+ };
92
+
93
+
94
+ /**
95
+ * @param {boolean} enable Whether to enable logging to the DOM.
96
+ */
97
+ webdriver.logging.enableDomLogging = function(enable) {
98
+ webdriver.logging.domLogging_ = enable;
99
+ };
100
+
101
+
102
+ /**
103
+ * @param {webdriver.logging.Level} newLevel The new level to filter messages
104
+ * by.
105
+ */
106
+ webdriver.logging.setLevel = function (newLevel) {
107
+ webdriver.logging.currentLevel_ = newLevel;
108
+ };
109
+
110
+
111
+ /**
112
+ * Clears all log messages.
113
+ */
114
+ webdriver.logging.clear = function() {
115
+ if (webdriver.logging.div_) {
116
+ goog.dom.setTextContent(webdriver.logging.div_, '');
117
+ }
118
+
119
+ if (webdriver.logging.firebugLogging_ &&
120
+ webdriver.logging.console_ &&
121
+ goog.isFunction(webdriver.logging.console_['clear'])) {
122
+ webdriver.logging.console_['clear']();
123
+ }
124
+ };
125
+
126
+
127
+ /**
128
+ * Utility function for logging status messages.
129
+ * @param {string} msg The message to log.
130
+ * @param {?webdriver.logging.Level} opt_logLevel The level to log the message
131
+ * at. Defaults to {@code webdriver.LogLevel.INFO}.
132
+ */
133
+ webdriver.logging.log = function(msg, opt_logLevel) {
134
+ var logLevel = opt_logLevel || webdriver.logging.Level.INFO;
135
+ if (logLevel.code > webdriver.logging.currentLevel_.code) {
136
+ return;
137
+ }
138
+
139
+ msg = '[' + goog.now() + ']: ' + msg;
140
+
141
+ if (webdriver.logging.firebugLogging_) {
142
+ var consoleLoggerFn = webdriver.logging.console_ ?
143
+ (webdriver.logging.console_[logLevel.firebugLogFnName] ||
144
+ webdriver.logging.console_['log']) : null;
145
+ if (goog.isFunction(consoleLoggerFn)) {
146
+ consoleLoggerFn(msg);
147
+ }
148
+ }
149
+
150
+ if (!webdriver.logging.domLogging_) {
151
+ return;
152
+ }
153
+
154
+ if (!webdriver.logging.div_) {
155
+ webdriver.logging.div_ = goog.dom.createDom('DIV', {
156
+ style: 'border: 1px solid black; margin: 3px; padding: 3px'
157
+ });
158
+ goog.dom.appendChild(goog.dom.getDocument().body, webdriver.logging.div_);
159
+ }
160
+
161
+ var logRecord = goog.dom.createDom('DIV', {
162
+ style: ('font-family: Courier; font-size: 9pt; ' +
163
+ 'color: ' + logLevel.domColor + ';' +
164
+ 'border-top: 1px solid silver;')
165
+ });
166
+ logRecord.innerHTML = webdriver.logging.jsStringToHtml(msg);
167
+ goog.dom.appendChild(webdriver.logging.div_, logRecord);
168
+ };
169
+
170
+
171
+ /**
172
+ * Escapes a JavaScript string so it can be inserted as HTML.
173
+ * - Converts newlines to BR tags
174
+ * - Replaces all whitespace with {@code nbsp;}
175
+ * - Escapes all appropriate characters with HTML entities (<, >, etc.)
176
+ * @param {string} str The string to convert to HTML.
177
+ * @return {string} The converted string.
178
+ */
179
+ webdriver.logging.jsStringToHtml = function(str) {
180
+ str = goog.string.canonicalizeNewlines(str);
181
+ str = goog.string.htmlEscape(str);
182
+ return str.replace(/\n/g, '<br/>').replace(/\s/g, '&nbsp;');
183
+ };
184
+
185
+
186
+ /**
187
+ * An alias for {@code webdriver.logging.log} used to log messages at the
188
+ * {@code DEBUG} level.
189
+ * @param {string} message The message to log.
190
+ */
191
+ webdriver.logging.debug = function(message) {
192
+ webdriver.logging.log(message, webdriver.logging.Level.DEBUG);
193
+ };
194
+
195
+
196
+ /**
197
+ * An alias for {@code webdriver.logging.log} used to log messages at the
198
+ * {@code INFO} level.
199
+ * @param {string} message The message to log.
200
+ */
201
+ webdriver.logging.info = function(message) {
202
+ webdriver.logging.log(message, webdriver.logging.Level.INFO);
203
+ };
204
+
205
+
206
+ /**
207
+ * An alias for {@code webdriver.logging.log} used to log messages at the
208
+ * {@code WARN} level.
209
+ * @param {string} message The message to log.
210
+ */
211
+ webdriver.logging.warn = function(message) {
212
+ webdriver.logging.log(message, webdriver.logging.Level.WARN);
213
+ };
214
+
215
+
216
+ /**
217
+ * An alias for {@code webdriver.logging.log} used to log messages at the
218
+ * {@code ERROR} level.
219
+ * @param {string} message The message to log.
220
+ */
221
+ webdriver.logging.error = function(message) {
222
+ webdriver.logging.log(message, webdriver.logging.Level.ERROR);
223
+ };
224
+
225
+
226
+ /**
227
+ * Utility function for recursively describing all of the properties in an
228
+ * object using a DFS traversal.
229
+ * @param {*} obj The object to describe.
230
+ * @param {string} opt_indent Indentation for the current DFS level.
231
+ * @return {string} The object description.
232
+ */
233
+ webdriver.logging.describe = function(obj, opt_indent) {
234
+ var indent = opt_indent || '';
235
+ var msgLines = [];
236
+ if (goog.isString(obj)) {
237
+ msgLines.push(indent + ' (' + goog.typeOf(obj) + ') ' + obj);
238
+ } else {
239
+ for (var prop in obj) {
240
+ msgLines.push(
241
+ indent + prop + ': (' + goog.typeOf(obj[prop]) + ') ' + obj[prop]);
242
+ if (goog.isObject(obj[prop]) && !goog.isFunction(obj[prop]) &&
243
+ goog.isArray(obj[prop])) {
244
+ msgLines.push(webdriver.logging.describe(obj[prop], indent + ' '));
245
+ }
246
+ };
247
+ }
248
+ return msgLines.join('\n');
249
+ };
@@ -0,0 +1,605 @@
1
+ /** @license
2
+ Copyright 2007-2009 WebDriver committers
3
+ Copyright 2007-2009 Google Inc.
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+ */
17
+
18
+ /**
19
+ * @fileoverview A xUnit framework for writing unit tests using the WebDriver
20
+ * JavaScript API.
21
+ * Example Usage:
22
+ * <code>
23
+ * goog.require('webdriver.TestRunner');
24
+ * goog.require('webdriver.factory');
25
+ *
26
+ * function testGoogleSearch(driver) {
27
+ * driver.get('http://www.google.com');
28
+ * driver.findElement({name: 'q'}).sendKeys('webdriver');
29
+ * driver.findElement({name: 'btnG'}).click();
30
+ * }
31
+ *
32
+ * window.onload = function() {
33
+ * new webdriver.TestRunner(webdriver.factory.createLocalWebDriver).
34
+ * go();
35
+ * };
36
+ * </code>
37
+ *
38
+ * @author jmleyba@gmail.com (Jason Leyba)
39
+ */
40
+
41
+ goog.provide('webdriver.TestCase');
42
+ goog.provide('webdriver.TestResult');
43
+ goog.provide('webdriver.TestRunner');
44
+
45
+ goog.require('goog.Uri');
46
+ goog.require('goog.dom');
47
+ goog.require('goog.style');
48
+ goog.require('webdriver.WebDriver.EventType');
49
+ goog.require('webdriver.factory');
50
+ goog.require('webdriver.logging');
51
+ goog.require('webdriver.timing');
52
+
53
+
54
+ /**
55
+ * Represents a single test function to be executed by the
56
+ * {@code webdriver.TestRunner}.
57
+ * @param {string} name The name of the test function.
58
+ * @param {function} testFn The test function that will be executed.
59
+ * @constructor
60
+ */
61
+ webdriver.TestCase = function(name, testFn) {
62
+ this.name = name;
63
+ this.testFn = testFn;
64
+ };
65
+
66
+
67
+ /**
68
+ * Stores the result of a {@code webdriver.TestCase}.
69
+ * @param {webdriver.TestCase} testCase The test this is a result for.
70
+ * @param {boolean} passed Whether the test passed.
71
+ * @param {string} opt_errMsg The error message describing the test failure.
72
+ * @constructor
73
+ */
74
+ webdriver.TestResult = function(testCase, passed, opt_errMsg) {
75
+ this.testCase = testCase;
76
+ this.passed = passed;
77
+ this.errMsg = opt_errMsg || '';
78
+ };
79
+
80
+
81
+ /**
82
+ * @return {string} A summary of this result.
83
+ */
84
+ webdriver.TestResult.prototype.getSummary = function() {
85
+ if (this.passed) {
86
+ return this.testCase.name + ' [PASSED]';
87
+ } else {
88
+ return this.testCase.name + ' [FAILED]\n ' +
89
+ this.errMsg.replace(/\n/g, '\n ');
90
+ }
91
+ };
92
+
93
+
94
+ /**
95
+ * The actual test runner. When created, scans the global scope for all test
96
+ * functions (those functions whose name is prefixed with "test"). Once
97
+ * started, the TestRunner will execute each test case and pass a new
98
+ * {@code webdriver.WebDriver} instance to the test for it to issue commands to.
99
+ * The driver will be paused while commands are collected from the test
100
+ * function. Once resumed, the TestRunner will listen for the driver's
101
+ * {@code IDLE} and {@code ERROR} events to determine when the test is done and
102
+ * what its result was.
103
+ * @param {function} opt_driverFactoryFn The factory function to call for
104
+ * creating new {@code webdriver.WebDriver} instances. A new instance will
105
+ * be created for each test case; defaults to
106
+ * {@code webdriver.factory.createLocalWebDriver}.
107
+ * @param {goog.dom.DomHelper} opt_dom A DomHelper for the content window to
108
+ * scan for test functions.
109
+ * @constructor
110
+ */
111
+ webdriver.TestRunner = function(opt_driverFactoryFn, opt_dom) {
112
+
113
+ /**
114
+ * Factory function to call for creating new instances of
115
+ * {@code webdriver.WebDriver}.
116
+ * @type {function}
117
+ * @private
118
+ */
119
+ this.driverFactoryFn_ =
120
+ opt_driverFactoryFn || webdriver.factory.createLocalWebDriver;
121
+
122
+ /**
123
+ * DomHelper for the content window to scan for test functions.
124
+ * @type {goog.dom.DomHelper}
125
+ * @private
126
+ */
127
+ this.dom_ = opt_dom || goog.dom.getDomHelper();
128
+
129
+ /**
130
+ * Whether this instance has started executing tests.
131
+ * @type {boolean}
132
+ * @private
133
+ */
134
+ this.started_ = false;
135
+
136
+ /**
137
+ * Whether this instance has finished executing tests.
138
+ * @type {boolean}
139
+ * @private
140
+ */
141
+ this.finished_ = false;
142
+
143
+ /**
144
+ * The tests discovered on the page that will be executed.
145
+ * @type {Array.<webdriver.TestCase>}
146
+ * @private
147
+ */
148
+ this.tests_ = [];
149
+
150
+ /**
151
+ * A hash of all known test case names.
152
+ * @type {Object}
153
+ * @private
154
+ */
155
+ this.testNames_ = {};
156
+
157
+ /**
158
+ * The index of the test currently running.
159
+ * @type {number}
160
+ * @private
161
+ */
162
+ this.currentTest_ = -1;
163
+
164
+ /**
165
+ * Results for the executed tests.
166
+ * @type {Array.<webdriver.TestResult>}
167
+ * @private
168
+ */
169
+ this.results_ = [];
170
+
171
+ /**
172
+ * The {@code setUp} function, if any, to call before each test is executed.
173
+ * @type {function}
174
+ * @private
175
+ */
176
+ this.setUpFn_ = null;
177
+
178
+ /**
179
+ * The {@code tearDown} function, if any, to call after each test is executed.
180
+ * @type {function}
181
+ * @private
182
+ */
183
+ this.tearDownFn_ = null;
184
+
185
+ /**
186
+ * The number of tests that have passed.
187
+ * @type {number}
188
+ * @private
189
+ */
190
+ this.numPassing_ = 0;
191
+
192
+ /**
193
+ * DOM element to log results to; lazily initialized in
194
+ * {@code initResultsSection_}.
195
+ * @type {Element}
196
+ * @private
197
+ */
198
+ this.resultsDiv_ = null;
199
+
200
+ /**
201
+ * DOM element that logs the current progress of the TestRunner; lazily
202
+ * initialized in {@code initResultsSection_}.
203
+ * @type {Element}
204
+ * @private
205
+ */
206
+ this.headerDiv_ = null;
207
+
208
+ /**
209
+ * Element in the progress message showing the number of tests that have
210
+ * passed; lazily initialized in {@code initResultsSection_}.
211
+ * @type {Element}
212
+ * @private
213
+ */
214
+ this.numPassedSpan_ = null;
215
+
216
+ /**
217
+ * Element in the progress message showing the number of tests that have yet
218
+ * to be executed; lazily initialized in {@code initResultsSection_}.
219
+ * @type {Element}
220
+ * @private
221
+ */
222
+ this.numPendingSpan_ = null;
223
+
224
+ this.findTestFunctions_();
225
+ this.initResultsSection_();
226
+ };
227
+
228
+
229
+ webdriver.TestRunner.SINGLETON = null;
230
+
231
+
232
+ webdriver.TestRunner.start = function(factoryFn) {
233
+ if (webdriver.TestRunner.SINGLETON) {
234
+ throw new Error('Singleton already initialized');
235
+ }
236
+ webdriver.TestRunner.SINGLETON = new webdriver.TestRunner(factoryFn);
237
+ webdriver.TestRunner.SINGLETON.go();
238
+ };
239
+
240
+
241
+ /**
242
+ * Scans this instance's test window for any global test functions and for the
243
+ * setUp and tearDown functions.
244
+ * @private
245
+ */
246
+ webdriver.TestRunner.prototype.findTestFunctions_ = function() {
247
+ webdriver.logging.info('Locating test functions');
248
+
249
+ var uri = new goog.Uri(this.dom_.getWindow().location.href);
250
+ var testName = uri.getParameterValue('test');
251
+ var matchFn;
252
+ if (testName) {
253
+ webdriver.logging.info('...searching for test matching "' + testName +'"');
254
+ matchFn = function(prop) {
255
+ return testName == prop;
256
+ };
257
+ } else {
258
+ var testRegex = /^test\w+$/;
259
+ webdriver.logging.info('...searching for tests matching ' + testRegex);
260
+ matchFn = function(prop) {
261
+ return testRegex.test(prop);
262
+ };
263
+ }
264
+
265
+
266
+ // This won't work on IE. There's a different way of querying for global
267
+ // functions in IE (of course).
268
+ // TODO(jmleyba): Look it up and make it so.
269
+ var win = this.dom_.getWindow();
270
+ for (var prop in win) {
271
+ if (matchFn(prop) && goog.isFunction(win[prop])) {
272
+ this.tests_.push(new webdriver.TestCase(prop, win[prop]));
273
+ if (prop in this.testNames_) {
274
+ webdriver.logging.error('Duplicate test name found: ' + prop);
275
+ } else {
276
+ this.testNames_[prop] = true;
277
+ }
278
+ }
279
+ }
280
+ webdriver.logging.info('...found ' + this.tests_.length + ' test(s)');
281
+
282
+ function getGlobal(name) {
283
+ var fn = goog.global[name];
284
+ return goog.isFunction(fn) ? fn : goog.nullFunction;
285
+ }
286
+ this.setUpPageFn_ = getGlobal('setUpPage');
287
+ this.setUpFn_ = getGlobal('setUp');
288
+ this.tearDownFn_ = getGlobal('tearDown');
289
+ this.tearDownPageFn_ = getGlobal('tearDownPage');
290
+ };
291
+
292
+
293
+ /**
294
+ * Initializes the result DOM for reporting results at the top of the page.
295
+ * @private
296
+ */
297
+ webdriver.TestRunner.prototype.initResultsSection_ = function() {
298
+ this.resultsDiv_ = this.dom_.createDom('DIV');
299
+ var doc = this.dom_.getDocument();
300
+ if (doc.body.firstChild) {
301
+ goog.dom.insertSiblingBefore(this.resultsDiv_, doc.body.firstChild);
302
+ } else {
303
+ goog.dom.appendChild(doc.body, this.resultsDiv_);
304
+ }
305
+
306
+ this.headerDiv_ = this.dom_.createDom('DIV');
307
+ goog.style.setStyle(this.headerDiv_, 'fontFamily', 'Courier;');
308
+ goog.style.setStyle(this.headerDiv_, 'fontSize', '10pt;');
309
+ goog.style.setStyle(this.headerDiv_, 'fontWeight', 'bold');
310
+ goog.dom.appendChild(this.resultsDiv_, this.headerDiv_);
311
+
312
+ if (this.tests_.length) {
313
+ this.numPassedSpan_ = this.dom_.createDom('SPAN');
314
+ goog.dom.setTextContent(this.numPassedSpan_, '0');
315
+ goog.dom.appendChild(this.headerDiv_, this.numPassedSpan_);
316
+ goog.dom.appendChild(this.headerDiv_,
317
+ this.dom_.createTextNode('/' + this.tests_.length + ' tests passed ('));
318
+
319
+ this.numPendingSpan_ = this.dom_.createDom('SPAN');
320
+ goog.dom.setTextContent(this.numPendingSpan_, this.tests_.length);
321
+ goog.dom.appendChild(this.headerDiv_, this.numPendingSpan_);
322
+ goog.dom.appendChild(this.headerDiv_,
323
+ this.dom_.createTextNode(' tests pending)'));
324
+ } else {
325
+ goog.dom.setTextContent(this.headerDiv_, 'No tests to run');
326
+ }
327
+ };
328
+
329
+
330
+ /**
331
+ * Reports a result of a test on the page.
332
+ * @private
333
+ */
334
+ webdriver.TestRunner.prototype.reportResult_ = function(result, driver) {
335
+ if (driver) {
336
+ driver.dispose();
337
+ }
338
+ this.errorListener_ = null;
339
+ // TODO(jmleyba): Should quit the driver for remote driver instances.
340
+
341
+ this.results_.push(result);
342
+
343
+ var resultDiv = this.dom_.createDom('DIV');
344
+ goog.style.setStyle(resultDiv, 'fontFamily', 'Courier');
345
+ goog.style.setStyle(resultDiv, 'fontSize', '9pt');
346
+ if (result.passed) {
347
+ goog.dom.appendChild(this.resultsDiv_, resultDiv);
348
+ if (!this.firstPassing_) {
349
+ this.firstPassing_ = resultDiv;
350
+ }
351
+ this.numPassing_ += 1;
352
+ goog.dom.setTextContent(resultDiv, result.testCase.name + ' [PASSED]');
353
+ goog.style.setStyle(resultDiv, 'color', 'green');
354
+ } else {
355
+ if (this.firstPassing_) {
356
+ goog.dom.insertSiblingBefore(resultDiv, this.firstPassing_);
357
+ } else {
358
+ goog.dom.appendChild(this.resultsDiv_, resultDiv);
359
+ }
360
+ goog.dom.setTextContent(resultDiv, result.testCase.name + ' [FAILED]');
361
+ goog.style.setStyle(resultDiv, 'color', 'red');
362
+
363
+ var uri = new goog.Uri(this.dom_.getWindow().location.href);
364
+ uri.getQueryData().clear();
365
+ uri.getQueryData().add('test', result.testCase.name);
366
+ var link = this.dom_.createDom('A', {
367
+ 'href': uri.toString()
368
+ });
369
+ goog.dom.setTextContent(link, '(run individually)');
370
+ goog.dom.appendChild(resultDiv, link);
371
+
372
+
373
+ var reason = this.dom_.createDom('DIV');
374
+ goog.style.setStyle(reason, 'color', 'black');
375
+ goog.dom.appendChild(resultDiv, reason);
376
+ reason.innerHTML = webdriver.logging.jsStringToHtml(result.errMsg);
377
+ webdriver.logging.warn(result.errMsg);
378
+ }
379
+
380
+ goog.dom.setTextContent(this.numPassedSpan_, this.numPassing_);
381
+ goog.dom.setTextContent(this.numPendingSpan_,
382
+ (this.tests_.length - this.currentTest_ - 1));
383
+ webdriver.logging.info('scheduling next test');
384
+ webdriver.timing.setTimeout(goog.bind(this.executeNextTest_, this), 0);
385
+ };
386
+
387
+
388
+ /**
389
+ * Event handler for when a test pauses the command processing. Adds a button
390
+ * to the DOM that resumes the driver when clicked.
391
+ * @param {goog.event.Event} e The pause event to handle. The target of this
392
+ * event will be the paused {@code webdriver.WebDriver} instance.
393
+ * @private
394
+ */
395
+ webdriver.TestRunner.prototype.onPause_ = function(e) {
396
+ this.pausedDriver = e.target;
397
+ if (!this.pausedButton_) {
398
+ this.pausedButton_ = this.dom_.createDom('INPUT', {
399
+ type: 'button',
400
+ style: 'margin-left: 10px',
401
+ value: 'Click to resume'
402
+ });
403
+ this.pausedDiv_ = this.dom_.createDom('DIV', null,
404
+ this.dom_.createTextNode('WebDriver paused'),
405
+ this.pausedButton_);
406
+ goog.style.setStyle(this.pausedDiv_, 'width', '100%');
407
+ goog.style.setStyle(this.pausedDiv_, 'backgroundColor', 'yellow');
408
+ goog.style.setStyle(this.pausedDiv_, 'fontFamily', 'Courier;');
409
+ goog.style.setStyle(this.pausedDiv_, 'fontSize', '10pt;');
410
+ goog.style.setStyle(this.pausedDiv_, 'fontWeight', 'bold');
411
+ goog.dom.insertSiblingAfter(this.pausedDiv_, this.headerDiv_);
412
+ }
413
+
414
+ goog.style.setStyle(this.pausedDiv_, 'display', 'block');
415
+ this.pausedButton_.disabled = false;
416
+ goog.events.listenOnce(this.pausedButton_, goog.events.EventType.CLICK,
417
+ function() {
418
+ this.pausedButton_.disabled = true;
419
+ goog.style.setStyle(this.pausedDiv_, 'display', 'none');
420
+ this.pausedDriver.resume();
421
+ }, false, this);
422
+ };
423
+
424
+
425
+ /**
426
+ * Kicks off the execution of the tests gathered by this instance. This is a
427
+ * no-op if this instance has already started.
428
+ */
429
+ webdriver.TestRunner.prototype.go = function() {
430
+ if (this.started_) {
431
+ return;
432
+ }
433
+ this.started_ = true;
434
+ this.setUpPageFn_();
435
+ this.executeNextTest_();
436
+ };
437
+
438
+
439
+ /**
440
+ * @return {boolean} Whether this instance has finished executing tests.
441
+ */
442
+ webdriver.TestRunner.prototype.isFinished = function() {
443
+ return this.finished_;
444
+ };
445
+
446
+
447
+ /**
448
+ * @return {number} The current number of passing tests executed by this
449
+ * runner.
450
+ */
451
+ webdriver.TestRunner.prototype.getNumPassed = function() {
452
+ return this.numPassing_;
453
+ };
454
+
455
+
456
+ /**
457
+ * @return {number} The number of tests executed by this runner.
458
+ */
459
+ webdriver.TestRunner.prototype.getNumTests = function() {
460
+ return this.results_.length;
461
+ };
462
+
463
+
464
+ /**
465
+ * @return {string} A summary of all tests that have been completed by this
466
+ * runner.
467
+ */
468
+ webdriver.TestRunner.prototype.getReport = function() {
469
+ if (!this.isFinished()) {
470
+ return null;
471
+ }
472
+ return goog.array.map(this.results_, function(result) {
473
+ return result.getSummary();
474
+ }).join('\n') + '\n';
475
+ };
476
+
477
+
478
+ /**
479
+ * Executes the next test.
480
+ * @private
481
+ */
482
+ webdriver.TestRunner.prototype.executeNextTest_ = function() {
483
+ this.currentTest_ += 1;
484
+ if (this.currentTest_ >= this.tests_.length) {
485
+ webdriver.logging.info('No more tests');
486
+ this.tearDownPageFn_();
487
+ this.finished_ = true;
488
+ return;
489
+ }
490
+
491
+ var test = this.tests_[this.currentTest_];
492
+ var result = new webdriver.TestResult(test, true);
493
+
494
+ webdriver.logging.info('>>>>>> Starting ' + test.name);
495
+
496
+ var driver;
497
+ try {
498
+ driver = this.driverFactoryFn_();
499
+ var driverError = goog.bind(this.handleDriverError_, this, result);
500
+ goog.events.listen(driver,
501
+ webdriver.WebDriver.EventType.ERROR, driverError);
502
+
503
+ driver.newSession(true);
504
+ webdriver.timing.setTimeout(
505
+ goog.bind(this.setUp_, this, result, driver), 0);
506
+ } catch (ex) {
507
+ result.passed = false;
508
+ result.errMsg = ex.message + (ex.stack ? ('\n' + ex.stack) : '');
509
+ this.reportResult_(result, driver);
510
+ }
511
+ };
512
+
513
+
514
+ /**
515
+ * Internal method for collecting and executing driver commands before calling
516
+ * the next test phase.
517
+ * @param {webdriver.TestResult} result Result object for the current test.
518
+ * @param {webdriver.WebDriver} driver The WebDriver instance to pass to the
519
+ * test function.
520
+ * @param {function} commandFn The function to collect driver commands from.
521
+ * The function should take a single {@code webdriver.WebDriver} argument.
522
+ * @param {function} nextPhase The next phase in the test (e.g. setUp, test,
523
+ * tearDown).
524
+ * @private
525
+ */
526
+ webdriver.TestRunner.prototype.collectAndRunDriverCommands_ = function(
527
+ result, driver, commandFn, nextPhase) {
528
+ try {
529
+ commandFn.apply(result.testCase, [driver]);
530
+ driver.callFunction(nextPhase, this, result, driver);
531
+ } catch (ex) {
532
+ result.passed = false;
533
+ result.errMsg = ex.message + (ex.stack ? ('\n' + ex.stack) : '');
534
+ this.reportResult_(result, driver);
535
+ }
536
+ };
537
+
538
+
539
+ /**
540
+ * Executes {@code setUp} if one was found in the global scope.
541
+ * @param {webdriver.TestResult} result Result object for the current test.
542
+ * @param {webdriver.WebDriver} driver The WebDriver instance to pass to the
543
+ * test function.
544
+ * @private
545
+ */
546
+ webdriver.TestRunner.prototype.setUp_ = function(result, driver) {
547
+ this.collectAndRunDriverCommands_(
548
+ result, driver, this.setUpFn_, this.runTest_);
549
+ };
550
+
551
+
552
+ /**
553
+ * Executes a test function.
554
+ * @param {webdriver.TestResult} result Result object for the current test.
555
+ * @param {webdriver.WebDriver} driver The WebDriver instance to pass to the
556
+ * test function.
557
+ * @private
558
+ */
559
+ webdriver.TestRunner.prototype.runTest_ = function(result, driver) {
560
+ this.collectAndRunDriverCommands_(
561
+ result, driver, result.testCase.testFn, this.tearDown_);
562
+ };
563
+
564
+
565
+ /**
566
+ * Executes {@code tearDown} if one was found in the global scope.
567
+ * @param {webdriver.TestResult} result Result object for the current test.
568
+ * @param {webdriver.WebDriver} driver The WebDriver instance to pass to the
569
+ * test function.
570
+ * @private
571
+ */
572
+ webdriver.TestRunner.prototype.tearDown_ = function(result, driver) {
573
+ this.collectAndRunDriverCommands_(
574
+ result, driver, this.tearDownFn_, this.reportResult_);
575
+ };
576
+
577
+
578
+ /**
579
+ * Event handler that fails the current test if {@code webdriver.WebDriver}
580
+ * dispatches an {@code webdriver.WebDriver.EventType.ERROR} event.
581
+ * @param {webdriver.TestResult} result Result object for the current test.
582
+ * @param {goog.events.Event} e The error event whose target should be a
583
+ * {@code webdriver.WebDriver} instance.
584
+ * @private
585
+ */
586
+ webdriver.TestRunner.prototype.handleDriverError_ = function(result, e) {
587
+ result.passed = false;
588
+ var failingCommand = e.target.getPendingCommand();
589
+ var response = failingCommand ? failingCommand.response : null;
590
+ if (response) {
591
+ result.errMsg = [];
592
+ if (response.value) {
593
+ result.errMsg.push(' ' + response.value);
594
+ }
595
+ goog.array.extend(
596
+ result.errMsg, goog.array.map(response.errors, function(error) {
597
+ return error.message + (error.stack ? ('\n' + error.stack) : '');
598
+ }));
599
+ result.errMsg = result.errMsg.join('\n');
600
+ } else {
601
+ // Should never happen, but just in case.
602
+ result.errMsg = 'Unknown error!';
603
+ }
604
+ this.reportResult_(result, e.target);
605
+ };