selenium-webdriver 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ };