rainux-selenium-webdriver 0.0.17

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 (192) hide show
  1. data/COPYING +204 -0
  2. data/chrome/prebuilt/Win32/Release/npchromedriver.dll +0 -0
  3. data/chrome/prebuilt/x64/Release/npchromedriver.dll +0 -0
  4. data/chrome/src/extension/background.html +9 -0
  5. data/chrome/src/extension/background.js +995 -0
  6. data/chrome/src/extension/content_script.js +1321 -0
  7. data/chrome/src/extension/icons/busy.png +0 -0
  8. data/chrome/src/extension/icons/free.png +0 -0
  9. data/chrome/src/extension/manifest-nonwin.json +19 -0
  10. data/chrome/src/extension/manifest-win.json +20 -0
  11. data/chrome/src/extension/utils.js +231 -0
  12. data/chrome/src/rb/lib/selenium/webdriver/chrome.rb +8 -0
  13. data/chrome/src/rb/lib/selenium/webdriver/chrome/bridge.rb +358 -0
  14. data/chrome/src/rb/lib/selenium/webdriver/chrome/command_executor.rb +124 -0
  15. data/chrome/src/rb/lib/selenium/webdriver/chrome/launcher.rb +135 -0
  16. data/common/src/js/abstractcommandprocessor.js +134 -0
  17. data/common/src/js/asserts.js +296 -0
  18. data/common/src/js/by.js +149 -0
  19. data/common/src/js/command.js +304 -0
  20. data/common/src/js/context.js +58 -0
  21. data/common/src/js/core/Blank.html +7 -0
  22. data/common/src/js/core/InjectedRemoteRunner.html +8 -0
  23. data/common/src/js/core/RemoteRunner.html +101 -0
  24. data/common/src/js/core/SeleniumLog.html +109 -0
  25. data/common/src/js/core/TestPrompt.html +145 -0
  26. data/common/src/js/core/TestRunner-splash.html +55 -0
  27. data/common/src/js/core/TestRunner.html +165 -0
  28. data/common/src/js/core/icons/all.png +0 -0
  29. data/common/src/js/core/icons/continue.png +0 -0
  30. data/common/src/js/core/icons/continue_disabled.png +0 -0
  31. data/common/src/js/core/icons/pause.png +0 -0
  32. data/common/src/js/core/icons/pause_disabled.png +0 -0
  33. data/common/src/js/core/icons/selected.png +0 -0
  34. data/common/src/js/core/icons/step.png +0 -0
  35. data/common/src/js/core/icons/step_disabled.png +0 -0
  36. data/common/src/js/core/lib/cssQuery/cssQuery-p.js +6 -0
  37. data/common/src/js/core/lib/cssQuery/src/cssQuery-level2.js +142 -0
  38. data/common/src/js/core/lib/cssQuery/src/cssQuery-level3.js +150 -0
  39. data/common/src/js/core/lib/cssQuery/src/cssQuery-standard.js +53 -0
  40. data/common/src/js/core/lib/cssQuery/src/cssQuery.js +356 -0
  41. data/common/src/js/core/lib/prototype.js +2006 -0
  42. data/common/src/js/core/lib/scriptaculous/builder.js +101 -0
  43. data/common/src/js/core/lib/scriptaculous/controls.js +815 -0
  44. data/common/src/js/core/lib/scriptaculous/dragdrop.js +915 -0
  45. data/common/src/js/core/lib/scriptaculous/effects.js +958 -0
  46. data/common/src/js/core/lib/scriptaculous/scriptaculous.js +47 -0
  47. data/common/src/js/core/lib/scriptaculous/slider.js +283 -0
  48. data/common/src/js/core/lib/scriptaculous/unittest.js +383 -0
  49. data/common/src/js/core/lib/snapsie.js +91 -0
  50. data/common/src/js/core/scripts/find_matching_child.js +69 -0
  51. data/common/src/js/core/scripts/htmlutils.js +8716 -0
  52. data/common/src/js/core/scripts/injection.html +72 -0
  53. data/common/src/js/core/scripts/selenium-api.js +3291 -0
  54. data/common/src/js/core/scripts/selenium-browserbot.js +2457 -0
  55. data/common/src/js/core/scripts/selenium-browserdetect.js +153 -0
  56. data/common/src/js/core/scripts/selenium-commandhandlers.js +379 -0
  57. data/common/src/js/core/scripts/selenium-executionloop.js +175 -0
  58. data/common/src/js/core/scripts/selenium-logging.js +148 -0
  59. data/common/src/js/core/scripts/selenium-remoterunner.js +695 -0
  60. data/common/src/js/core/scripts/selenium-testrunner.js +1362 -0
  61. data/common/src/js/core/scripts/selenium-version.js +5 -0
  62. data/common/src/js/core/scripts/ui-doc.html +808 -0
  63. data/common/src/js/core/scripts/ui-element.js +1644 -0
  64. data/common/src/js/core/scripts/ui-map-sample.js +979 -0
  65. data/common/src/js/core/scripts/user-extensions.js +3 -0
  66. data/common/src/js/core/scripts/user-extensions.js.sample +75 -0
  67. data/common/src/js/core/scripts/xmlextras.js +153 -0
  68. data/common/src/js/core/selenium-logo.png +0 -0
  69. data/common/src/js/core/selenium-test.css +43 -0
  70. data/common/src/js/core/selenium.css +316 -0
  71. data/common/src/js/core/xpath/dom.js +566 -0
  72. data/common/src/js/core/xpath/javascript-xpath-0.1.11.js +2816 -0
  73. data/common/src/js/core/xpath/util.js +549 -0
  74. data/common/src/js/core/xpath/xmltoken.js +149 -0
  75. data/common/src/js/core/xpath/xpath.js +2481 -0
  76. data/common/src/js/extension/README +2 -0
  77. data/common/src/js/extension/dommessenger.js +152 -0
  78. data/common/src/js/factory.js +55 -0
  79. data/common/src/js/future.js +141 -0
  80. data/common/src/js/jsunit.js +40 -0
  81. data/common/src/js/jsunit/app/css/jsUnitStyle.css +50 -0
  82. data/common/src/js/jsunit/app/css/readme +10 -0
  83. data/common/src/js/jsunit/app/emptyPage.html +11 -0
  84. data/common/src/js/jsunit/app/jsUnitCore.js +534 -0
  85. data/common/src/js/jsunit/app/jsUnitMockTimeout.js +81 -0
  86. data/common/src/js/jsunit/app/jsUnitTestManager.js +705 -0
  87. data/common/src/js/jsunit/app/jsUnitTestSuite.js +44 -0
  88. data/common/src/js/jsunit/app/jsUnitTracer.js +102 -0
  89. data/common/src/js/jsunit/app/jsUnitVersionCheck.js +59 -0
  90. data/common/src/js/jsunit/app/main-counts-errors.html +12 -0
  91. data/common/src/js/jsunit/app/main-counts-failures.html +13 -0
  92. data/common/src/js/jsunit/app/main-counts-runs.html +13 -0
  93. data/common/src/js/jsunit/app/main-counts.html +21 -0
  94. data/common/src/js/jsunit/app/main-data.html +178 -0
  95. data/common/src/js/jsunit/app/main-errors.html +23 -0
  96. data/common/src/js/jsunit/app/main-frame.html +19 -0
  97. data/common/src/js/jsunit/app/main-loader.html +45 -0
  98. data/common/src/js/jsunit/app/main-progress.html +25 -0
  99. data/common/src/js/jsunit/app/main-results.html +67 -0
  100. data/common/src/js/jsunit/app/main-status.html +13 -0
  101. data/common/src/js/jsunit/app/testContainer.html +16 -0
  102. data/common/src/js/jsunit/app/testContainerController.html +77 -0
  103. data/common/src/js/jsunit/app/xbDebug.js +306 -0
  104. data/common/src/js/jsunit/changelog.txt +60 -0
  105. data/common/src/js/jsunit/css/jsUnitStyle.css +83 -0
  106. data/common/src/js/jsunit/images/green.gif +0 -0
  107. data/common/src/js/jsunit/images/logo_jsunit.gif +0 -0
  108. data/common/src/js/jsunit/images/powerby-transparent.gif +0 -0
  109. data/common/src/js/jsunit/images/red.gif +0 -0
  110. data/common/src/js/jsunit/licenses/JDOM_license.txt +56 -0
  111. data/common/src/js/jsunit/licenses/Jetty_license.html +213 -0
  112. data/common/src/js/jsunit/licenses/MPL-1.1.txt +470 -0
  113. data/common/src/js/jsunit/licenses/gpl-2.txt +340 -0
  114. data/common/src/js/jsunit/licenses/index.html +141 -0
  115. data/common/src/js/jsunit/licenses/lgpl-2.1.txt +504 -0
  116. data/common/src/js/jsunit/licenses/mpl-tri-license-c.txt +35 -0
  117. data/common/src/js/jsunit/licenses/mpl-tri-license-html.txt +35 -0
  118. data/common/src/js/jsunit/readme.txt +19 -0
  119. data/common/src/js/jsunit/testRunner.html +167 -0
  120. data/common/src/js/jsunit/version.txt +1 -0
  121. data/common/src/js/key.js +117 -0
  122. data/common/src/js/localcommandprocessor.js +185 -0
  123. data/common/src/js/testcase.js +217 -0
  124. data/common/src/js/timing.js +89 -0
  125. data/common/src/js/webdriver.js +890 -0
  126. data/common/src/js/webelement.js +485 -0
  127. data/common/src/rb/README +30 -0
  128. data/common/src/rb/lib/selenium-webdriver.rb +1 -0
  129. data/common/src/rb/lib/selenium/webdriver.rb +67 -0
  130. data/common/src/rb/lib/selenium/webdriver/bridge_helper.rb +91 -0
  131. data/common/src/rb/lib/selenium/webdriver/child_process.rb +180 -0
  132. data/common/src/rb/lib/selenium/webdriver/core_ext/dir.rb +41 -0
  133. data/common/src/rb/lib/selenium/webdriver/driver.rb +252 -0
  134. data/common/src/rb/lib/selenium/webdriver/driver_extensions/takes_screenshot.rb +24 -0
  135. data/common/src/rb/lib/selenium/webdriver/element.rb +262 -0
  136. data/common/src/rb/lib/selenium/webdriver/error.rb +67 -0
  137. data/common/src/rb/lib/selenium/webdriver/find.rb +89 -0
  138. data/common/src/rb/lib/selenium/webdriver/keys.rb +84 -0
  139. data/common/src/rb/lib/selenium/webdriver/navigation.rb +27 -0
  140. data/common/src/rb/lib/selenium/webdriver/options.rb +50 -0
  141. data/common/src/rb/lib/selenium/webdriver/platform.rb +86 -0
  142. data/common/src/rb/lib/selenium/webdriver/target_locator.rb +70 -0
  143. data/firefox/prebuilt/Win32/Release/webdriver-firefox.dll +0 -0
  144. data/firefox/prebuilt/linux/Release/libwebdriver-firefox.so +0 -0
  145. data/firefox/prebuilt/linux/Release/x_ignore_nofocus.so +0 -0
  146. data/firefox/prebuilt/linux64/Release/libwebdriver-firefox.so +0 -0
  147. data/firefox/prebuilt/linux64/Release/x_ignore_nofocus.so +0 -0
  148. data/firefox/prebuilt/nsICommandProcessor.xpt +0 -0
  149. data/firefox/prebuilt/nsINativeEvents.xpt +0 -0
  150. data/firefox/prebuilt/nsIResponseHandler.xpt +0 -0
  151. data/firefox/src/extension/chrome.manifest +3 -0
  152. data/firefox/src/extension/components/badCertListener.js +294 -0
  153. data/firefox/src/extension/components/context.js +37 -0
  154. data/firefox/src/extension/components/driver-component.js +127 -0
  155. data/firefox/src/extension/components/firefoxDriver.js +810 -0
  156. data/firefox/src/extension/components/json2.js +273 -0
  157. data/firefox/src/extension/components/keytest.html +554 -0
  158. data/firefox/src/extension/components/nsCommandProcessor.js +643 -0
  159. data/firefox/src/extension/components/promptService.js +208 -0
  160. data/firefox/src/extension/components/screenshooter.js +81 -0
  161. data/firefox/src/extension/components/socketListener.js +185 -0
  162. data/firefox/src/extension/components/utils.js +1263 -0
  163. data/firefox/src/extension/components/webLoadingListener.js +57 -0
  164. data/firefox/src/extension/components/webdriverserver.js +106 -0
  165. data/firefox/src/extension/components/wrappedElement.js +683 -0
  166. data/firefox/src/extension/content/fxdriver.xul +30 -0
  167. data/firefox/src/extension/content/server.js +95 -0
  168. data/firefox/src/extension/idl/nsICommandProcessor.idl +38 -0
  169. data/firefox/src/extension/idl/nsIResponseHandler.idl +34 -0
  170. data/firefox/src/extension/install.rdf +29 -0
  171. data/firefox/src/rb/lib/selenium/webdriver/firefox.rb +31 -0
  172. data/firefox/src/rb/lib/selenium/webdriver/firefox/binary.rb +107 -0
  173. data/firefox/src/rb/lib/selenium/webdriver/firefox/bridge.rb +484 -0
  174. data/firefox/src/rb/lib/selenium/webdriver/firefox/extension_connection.rb +90 -0
  175. data/firefox/src/rb/lib/selenium/webdriver/firefox/launcher.rb +155 -0
  176. data/firefox/src/rb/lib/selenium/webdriver/firefox/profile.rb +233 -0
  177. data/firefox/src/rb/lib/selenium/webdriver/firefox/profiles_ini.rb +59 -0
  178. data/firefox/src/rb/lib/selenium/webdriver/firefox/util.rb +23 -0
  179. data/jobbie/prebuilt/Win32/Release/InternetExplorerDriver.dll +0 -0
  180. data/jobbie/prebuilt/x64/Release/InternetExplorerDriver.dll +0 -0
  181. data/jobbie/src/rb/lib/selenium/webdriver/ie.rb +14 -0
  182. data/jobbie/src/rb/lib/selenium/webdriver/ie/bridge.rb +565 -0
  183. data/jobbie/src/rb/lib/selenium/webdriver/ie/lib.rb +99 -0
  184. data/jobbie/src/rb/lib/selenium/webdriver/ie/util.rb +147 -0
  185. data/remote/client/src/rb/lib/selenium/webdriver/remote.rb +16 -0
  186. data/remote/client/src/rb/lib/selenium/webdriver/remote/bridge.rb +408 -0
  187. data/remote/client/src/rb/lib/selenium/webdriver/remote/capabilities.rb +105 -0
  188. data/remote/client/src/rb/lib/selenium/webdriver/remote/commands.rb +53 -0
  189. data/remote/client/src/rb/lib/selenium/webdriver/remote/default_http_client.rb +71 -0
  190. data/remote/client/src/rb/lib/selenium/webdriver/remote/response.rb +49 -0
  191. data/remote/client/src/rb/lib/selenium/webdriver/remote/server_error.rb +32 -0
  192. metadata +303 -0
@@ -0,0 +1,1644 @@
1
+ //******************************************************************************
2
+ // Globals, including constants
3
+
4
+ var UI_GLOBAL = {
5
+ UI_PREFIX: 'ui'
6
+ , XHTML_DOCTYPE: '<!DOCTYPE html PUBLIC '
7
+ + '"-//W3C//DTD XHTML 1.0 Strict//EN" '
8
+ + '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
9
+ , XHTML_XMLNS: 'http://www.w3.org/1999/xhtml'
10
+ };
11
+
12
+ //*****************************************************************************
13
+ // Exceptions
14
+
15
+ function UIElementException(message)
16
+ {
17
+ this.message = message;
18
+ this.name = 'UIElementException';
19
+ }
20
+
21
+ function UIArgumentException(message)
22
+ {
23
+ this.message = message;
24
+ this.name = 'UIArgumentException';
25
+ }
26
+
27
+ function PagesetException(message)
28
+ {
29
+ this.message = message;
30
+ this.name = 'PagesetException';
31
+ }
32
+
33
+ function UISpecifierException(message)
34
+ {
35
+ this.message = message;
36
+ this.name = 'UISpecifierException';
37
+ }
38
+
39
+ function CommandMatcherException(message)
40
+ {
41
+ this.message = message;
42
+ this.name = 'CommandMatcherException';
43
+ }
44
+
45
+ //*****************************************************************************
46
+ // UI-Element core
47
+
48
+ /**
49
+ * The UIElement object. This has been crafted along with UIMap to make
50
+ * specifying UI elements using JSON as simple as possible. Object construction
51
+ * will fail if 1) a proper name isn't provided, 2) a faulty args argument is
52
+ * given, or 3) getLocator() returns undefined for a valid permutation of
53
+ * default argument values. See ui-doc.html for the documentation on the
54
+ * builder syntax.
55
+ *
56
+ * @param uiElementShorthand an object whose contents conform to the
57
+ * UI-Element builder syntax.
58
+ *
59
+ * @return a new UIElement object
60
+ * @throws UIElementException
61
+ */
62
+ function UIElement(uiElementShorthand)
63
+ {
64
+ // a shorthand object might look like:
65
+ //
66
+ // {
67
+ // name: 'topic'
68
+ // , description: 'sidebar links to topic categories'
69
+ // , args: [
70
+ // {
71
+ // name: 'name'
72
+ // , description: 'the name of the topic'
73
+ // , defaultValues: topLevelTopics
74
+ // }
75
+ // ]
76
+ // , getLocator: function(args) {
77
+ // return this._listXPath +
78
+ // "/a[text()=" + args.name.quoteForXPath() + "]";
79
+ // }
80
+ // , getGenericLocator: function() {
81
+ // return this._listXPath + '/a';
82
+ // }
83
+ // // maintain testcases for getLocator()
84
+ // , testcase1: {
85
+ // // defaultValues used if args not specified
86
+ // args: { name: 'foo' }
87
+ // , xhtml: '<div id="topiclist">'
88
+ // + '<ul><li><a expected-result="1">foo</a></li></ul>'
89
+ // + '</div>'
90
+ // }
91
+ // // set a local element variable
92
+ // , _listXPath: "//div[@id='topiclist']/ul/li"
93
+ // }
94
+ //
95
+ // name cannot be null or an empty string. Enforce the same requirement for
96
+ // the description.
97
+
98
+ /**
99
+ * Recursively returns all permutations of argument-value pairs, given
100
+ * a list of argument definitions. Each argument definition will have
101
+ * a set of default values to use in generating said pairs. If an argument
102
+ * has no default values defined, it will not be included among the
103
+ * permutations.
104
+ *
105
+ * @param args a list of UIArguments
106
+ * @param inDocument the document object to pass to the getDefaultValues()
107
+ * method of each argument.
108
+ *
109
+ * @return a list of associative arrays containing key value pairs
110
+ */
111
+ this.permuteArgs = function(args, inDocument) {
112
+ if (args.length == 0) {
113
+ return [];
114
+ }
115
+
116
+ var permutations = [];
117
+ var arg = args[0];
118
+ var remainingArgs = args.slice(1);
119
+ var subsequentPermutations = this.permuteArgs(remainingArgs,
120
+ inDocument);
121
+ var defaultValues = arg.getDefaultValues(inDocument);
122
+
123
+ // skip arguments for which no default values are defined. If the
124
+ // argument is a required one, then no permutations are possible.
125
+ if (defaultValues.length == 0) {
126
+ if (arg.required) {
127
+ return [];
128
+ }
129
+ else {
130
+ return subsequentPermutations;
131
+ }
132
+ }
133
+
134
+ for (var i = 0; i < defaultValues.length; ++i) {
135
+ var value = defaultValues[i];
136
+ var permutation;
137
+
138
+ if (subsequentPermutations.length == 0) {
139
+ permutation = {};
140
+ permutation[arg.name] = value + "";
141
+ permutations.push(permutation);
142
+ }
143
+ else {
144
+ for (var j = 0; j < subsequentPermutations.length; ++j) {
145
+ permutation = clone(subsequentPermutations[j]);
146
+ permutation[arg.name] = value + "";
147
+ permutations.push(permutation);
148
+ }
149
+ }
150
+ }
151
+
152
+ return permutations;
153
+ }
154
+
155
+
156
+
157
+ /**
158
+ * Returns a list of all testcases for this UIElement.
159
+ */
160
+ this.getTestcases = function()
161
+ {
162
+ return this.testcases;
163
+ }
164
+
165
+
166
+
167
+ /**
168
+ * Run all unit tests, stopping at the first failure, if any. Return true
169
+ * if no failures encountered, false otherwise. See the following thread
170
+ * regarding use of getElementById() on XML documents created by parsing
171
+ * text via the DOMParser:
172
+ *
173
+ * http://groups.google.com/group/comp.lang.javascript/browse_thread/thread/2b1b82b3c53a1282/
174
+ */
175
+ this.test = function()
176
+ {
177
+ var parser = new DOMParser();
178
+ var testcases = this.getTestcases();
179
+ testcaseLoop: for (var i = 0; i < testcases.length; ++i) {
180
+ var testcase = testcases[i];
181
+ var xhtml = UI_GLOBAL.XHTML_DOCTYPE + '<html xmlns="'
182
+ + UI_GLOBAL.XHTML_XMLNS + '">' + testcase.xhtml + '</html>';
183
+ var doc = parser.parseFromString(xhtml, "text/xml");
184
+ if (doc.firstChild.nodeName == 'parsererror') {
185
+ safe_alert('Error parsing XHTML in testcase "' + testcase.name
186
+ + '" for UI element "' + this.name + '": ' + "\n"
187
+ + doc.firstChild.firstChild.nodeValue);
188
+ }
189
+
190
+ // we're no longer using the default locators when testing, because
191
+ // args is now required
192
+ var locator = parse_locator(this.getLocator(testcase.args));
193
+ var results;
194
+ if (locator.type == 'xpath' || (locator.type == 'implicit' &&
195
+ locator.string.substring(0, 2) == '//')) {
196
+ // try using the javascript xpath engine to avoid namespace
197
+ // issues. The xpath does have to be lowercase however, it
198
+ // seems.
199
+ results = eval_xpath(locator.string, doc,
200
+ { allowNativeXpath: false, returnOnFirstMatch: true });
201
+ }
202
+ else {
203
+ // piece the locator back together
204
+ locator = (locator.type == 'implicit')
205
+ ? locator.string
206
+ : locator.type + '=' + locator.string;
207
+ results = eval_locator(locator, doc);
208
+ }
209
+ if (results.length && results[0].hasAttribute('expected-result')) {
210
+ continue testcaseLoop;
211
+ }
212
+
213
+ // testcase failed
214
+ if (is_IDE()) {
215
+ var msg = 'Testcase "' + testcase.name
216
+ + '" failed for UI element "' + this.name + '":';
217
+ if (!results.length) {
218
+ msg += '\n"' + locator + '" did not match any elements!';
219
+ }
220
+ else {
221
+ msg += '\n' + results[0] + ' was not the expected result!';
222
+ }
223
+ safe_alert(msg);
224
+ }
225
+ return false;
226
+ }
227
+ return true;
228
+ };
229
+
230
+
231
+
232
+ /**
233
+ * Creates a set of locators using permutations of default values for
234
+ * arguments used in the locator construction. The set is returned as an
235
+ * object mapping locators to key-value arguments objects containing the
236
+ * values passed to getLocator() to create the locator.
237
+ *
238
+ * @param opt_inDocument (optional) the document object of the "current"
239
+ * page when this method is invoked. Some arguments
240
+ * may have default value lists that are calculated
241
+ * based on the contents of the page.
242
+ *
243
+ * @return a list of locator strings
244
+ * @throws UIElementException
245
+ */
246
+ this.getDefaultLocators = function(opt_inDocument) {
247
+ var defaultLocators = {};
248
+ if (this.args.length == 0) {
249
+ defaultLocators[this.getLocator({})] = {};
250
+ }
251
+ else {
252
+ var permutations = this.permuteArgs(this.args, opt_inDocument);
253
+ if (permutations.length != 0) {
254
+ for (var i = 0; i < permutations.length; ++i) {
255
+ var args = permutations[i];
256
+ var locator = this.getLocator(args);
257
+ if (!locator) {
258
+ throw new UIElementException('Error in UIElement(): '
259
+ + 'no getLocator return value for element "' + name
260
+ + '"');
261
+ }
262
+ defaultLocators[locator] = args;
263
+ }
264
+ }
265
+ else {
266
+ // try using no arguments. Parse the locator to make sure it's
267
+ // really good. If it doesn't work, fine.
268
+ try {
269
+ var locator = this.getLocator();
270
+ parse_locator(locator);
271
+ defaultLocators[locator] = {};
272
+ }
273
+ catch (e) {
274
+ safe_log('debug', e.message);
275
+ }
276
+ }
277
+ }
278
+ return defaultLocators;
279
+ };
280
+
281
+
282
+
283
+ /**
284
+ * Validate the structure of the shorthand notation this object is being
285
+ * initialized with. Throws an exception if there's a validation error.
286
+ *
287
+ * @param uiElementShorthand
288
+ *
289
+ * @throws UIElementException
290
+ */
291
+ this.validate = function(uiElementShorthand)
292
+ {
293
+ var msg = "UIElement validation error:\n" + print_r(uiElementShorthand);
294
+ if (!uiElementShorthand.name) {
295
+ throw new UIElementException(msg + 'no name specified!');
296
+ }
297
+ if (!uiElementShorthand.description) {
298
+ throw new UIElementException(msg + 'no description specified!');
299
+ }
300
+ if (!uiElementShorthand.locator
301
+ && !uiElementShorthand.getLocator
302
+ && !uiElementShorthand.xpath
303
+ && !uiElementShorthand.getXPath) {
304
+ throw new UIElementException(msg + 'no locator specified!');
305
+ }
306
+ };
307
+
308
+
309
+
310
+ this.init = function(uiElementShorthand)
311
+ {
312
+ this.validate(uiElementShorthand);
313
+
314
+ this.name = uiElementShorthand.name;
315
+ this.description = uiElementShorthand.description;
316
+
317
+ // construct a new getLocator() method based on the locator property,
318
+ // or use the provided function. We're deprecating the xpath property
319
+ // and getXPath() function, but still allow for them for backwards
320
+ // compatability.
321
+ if (uiElementShorthand.locator) {
322
+ this.getLocator = function(args) {
323
+ return uiElementShorthand.locator;
324
+ };
325
+ }
326
+ else if (uiElementShorthand.getLocator) {
327
+ this.getLocator = uiElementShorthand.getLocator;
328
+ }
329
+ else if (uiElementShorthand.xpath) {
330
+ this.getLocator = function(args) {
331
+ return uiElementShorthand.xpath;
332
+ };
333
+ }
334
+ else {
335
+ this.getLocator = uiElementShorthand.getXPath;
336
+ }
337
+
338
+ if (uiElementShorthand.genericLocator) {
339
+ this.getGenericLocator = function() {
340
+ return uiElementShorthand.genericLocator;
341
+ };
342
+ }
343
+ else if (uiElementShorthand.getGenericLocator) {
344
+ this.getGenericLocator = uiElementShorthand.getGenericLocator;
345
+ }
346
+
347
+ if (uiElementShorthand.getOffsetLocator) {
348
+ this.getOffsetLocator = uiElementShorthand.getOffsetLocator;
349
+ }
350
+
351
+ // get the testcases and local variables
352
+ this.testcases = [];
353
+ var localVars = {};
354
+ for (var attr in uiElementShorthand) {
355
+ if (attr.match(/^testcase/)) {
356
+ var testcase = uiElementShorthand[attr];
357
+ if (uiElementShorthand.args &&
358
+ uiElementShorthand.args.length && !testcase.args) {
359
+ safe_alert('No args defined in ' + attr + ' for UI element '
360
+ + this.name + '! Skipping testcase.');
361
+ continue;
362
+ }
363
+ testcase.name = attr;
364
+ this.testcases.push(testcase);
365
+ }
366
+ else if (attr.match(/^_/)) {
367
+ this[attr] = uiElementShorthand[attr];
368
+ localVars[attr] = uiElementShorthand[attr];
369
+ }
370
+ }
371
+
372
+ // create the arguments
373
+ this.args = []
374
+ this.argsOrder = [];
375
+ if (uiElementShorthand.args) {
376
+ for (var i = 0; i < uiElementShorthand.args.length; ++i) {
377
+ var arg = new UIArgument(uiElementShorthand.args[i], localVars);
378
+ this.args.push(arg);
379
+ this.argsOrder.push(arg.name);
380
+
381
+ // if an exception is thrown when invoking getDefaultValues()
382
+ // with no parameters passed in, assume the method requires an
383
+ // inDocument parameter, and thus may only be invoked at run
384
+ // time. Mark the UI element object accordingly.
385
+ try {
386
+ arg.getDefaultValues();
387
+ }
388
+ catch (e) {
389
+ this.isDefaultLocatorConstructionDeferred = true;
390
+ }
391
+ }
392
+
393
+ }
394
+
395
+ if (!this.isDefaultLocatorConstructionDeferred) {
396
+ this.defaultLocators = this.getDefaultLocators();
397
+ }
398
+ };
399
+
400
+
401
+
402
+ this.init(uiElementShorthand);
403
+ }
404
+
405
+ // hang this off the UIElement "namespace". This is a composite strategy.
406
+ UIElement.defaultOffsetLocatorStrategy = function(locatedElement, pageElement) {
407
+ var strategies = [
408
+ UIElement.linkXPathOffsetLocatorStrategy
409
+ , UIElement.preferredAttributeXPathOffsetLocatorStrategy
410
+ , UIElement.simpleXPathOffsetLocatorStrategy
411
+ ];
412
+
413
+ for (var i = 0; i < strategies.length; ++i) {
414
+ var strategy = strategies[i];
415
+ var offsetLocator = strategy(locatedElement, pageElement);
416
+
417
+ if (offsetLocator) {
418
+ return offsetLocator;
419
+ }
420
+ }
421
+
422
+ return null;
423
+ };
424
+
425
+ UIElement.simpleXPathOffsetLocatorStrategy = function(locatedElement,
426
+ pageElement)
427
+ {
428
+ if (is_ancestor(locatedElement, pageElement)) {
429
+ var xpath = "";
430
+ var recorder = Recorder.get(locatedElement.ownerDocument.defaultView);
431
+ var locatorBuilders = recorder.locatorBuilders;
432
+ var currentNode = pageElement;
433
+
434
+ while (currentNode != null && currentNode != locatedElement) {
435
+ xpath = locatorBuilders.relativeXPathFromParent(currentNode)
436
+ + xpath;
437
+ currentNode = currentNode.parentNode;
438
+ }
439
+
440
+ var results = eval_xpath(xpath, locatedElement.ownerDocument,
441
+ { contextNode: locatedElement });
442
+
443
+ if (results.length > 0 && results[0] == pageElement) {
444
+ return xpath;
445
+ }
446
+ }
447
+
448
+ return null;
449
+ };
450
+
451
+ UIElement.linkXPathOffsetLocatorStrategy = function(locatedElement, pageElement)
452
+ {
453
+ if (pageElement.nodeName == 'A' && is_ancestor(locatedElement, pageElement))
454
+ {
455
+ var text = pageElement.textContent
456
+ .replace(/^\s+/, "")
457
+ .replace(/\s+$/, "");
458
+
459
+ if (text) {
460
+ var xpath = '/descendant::a[normalize-space()='
461
+ + text.quoteForXPath() + ']';
462
+
463
+ var results = eval_xpath(xpath, locatedElement.ownerDocument,
464
+ { contextNode: locatedElement });
465
+
466
+ if (results.length > 0 && results[0] == pageElement) {
467
+ return xpath;
468
+ }
469
+ }
470
+ }
471
+
472
+ return null;
473
+ };
474
+
475
+ // compare to the "xpath:attributes" locator strategy defined in the IDE source
476
+ UIElement.preferredAttributeXPathOffsetLocatorStrategy =
477
+ function(locatedElement, pageElement)
478
+ {
479
+ // this is an ordered listing of single attributes
480
+ var preferredAttributes = [
481
+ 'name'
482
+ , 'value'
483
+ , 'type'
484
+ , 'action'
485
+ , 'alt'
486
+ , 'title'
487
+ , 'class'
488
+ , 'src'
489
+ , 'href'
490
+ , 'onclick'
491
+ ];
492
+
493
+ if (is_ancestor(locatedElement, pageElement)) {
494
+ var xpathBase = '/descendant::' + pageElement.nodeName.toLowerCase();
495
+
496
+ for (var i = 0; i < preferredAttributes.length; ++i) {
497
+ var name = preferredAttributes[i];
498
+ var value = pageElement.getAttribute(name);
499
+
500
+ if (value) {
501
+ var xpath = xpathBase + '[@' + name + '='
502
+ + value.quoteForXPath() + ']';
503
+
504
+ var results = eval_xpath(xpath, locatedElement.ownerDocument,
505
+ { contextNode: locatedElement });
506
+
507
+ if (results.length > 0 && results[0] == pageElement) {
508
+ return xpath;
509
+ }
510
+ }
511
+ }
512
+ }
513
+
514
+ return null;
515
+ };
516
+
517
+
518
+
519
+ /**
520
+ * Constructs a UIArgument. This is mostly for checking that the values are
521
+ * valid.
522
+ *
523
+ * @param uiArgumentShorthand
524
+ * @param localVars
525
+ *
526
+ * @throws UIArgumentException
527
+ */
528
+ function UIArgument(uiArgumentShorthand, localVars)
529
+ {
530
+ /**
531
+ * @param uiArgumentShorthand
532
+ *
533
+ * @throws UIArgumentException
534
+ */
535
+ this.validate = function(uiArgumentShorthand)
536
+ {
537
+ var msg = "UIArgument validation error:\n"
538
+ + print_r(uiArgumentShorthand);
539
+
540
+ // try really hard to throw an exception!
541
+ if (!uiArgumentShorthand.name) {
542
+ throw new UIArgumentException(msg + 'no name specified!');
543
+ }
544
+ if (!uiArgumentShorthand.description) {
545
+ throw new UIArgumentException(msg + 'no description specified!');
546
+ }
547
+ if (!uiArgumentShorthand.defaultValues &&
548
+ !uiArgumentShorthand.getDefaultValues) {
549
+ throw new UIArgumentException(msg + 'no default values specified!');
550
+ }
551
+ };
552
+
553
+
554
+
555
+ /**
556
+ * @param uiArgumentShorthand
557
+ * @param localVars a list of local variables
558
+ */
559
+ this.init = function(uiArgumentShorthand, localVars)
560
+ {
561
+ this.validate(uiArgumentShorthand);
562
+
563
+ this.name = uiArgumentShorthand.name;
564
+ this.description = uiArgumentShorthand.description;
565
+ this.required = uiArgumentShorthand.required || false;
566
+
567
+ if (uiArgumentShorthand.defaultValues) {
568
+ var defaultValues = uiArgumentShorthand.defaultValues;
569
+ this.getDefaultValues =
570
+ function() { return defaultValues; }
571
+ }
572
+ else {
573
+ this.getDefaultValues = uiArgumentShorthand.getDefaultValues;
574
+ }
575
+
576
+ for (var name in localVars) {
577
+ this[name] = localVars[name];
578
+ }
579
+ }
580
+
581
+
582
+
583
+ this.init(uiArgumentShorthand, localVars);
584
+ }
585
+
586
+
587
+
588
+ /**
589
+ * The UISpecifier constructor is overloaded. If less than three arguments are
590
+ * provided, the first argument will be considered a UI specifier string, and
591
+ * will be split out accordingly. Otherwise, the first argument will be
592
+ * considered the path.
593
+ *
594
+ * @param uiSpecifierStringOrPagesetName a UI specifier string, or the pageset
595
+ * name of the UI specifier
596
+ * @param elementName the name of the element
597
+ * @param args an object associating keys to values
598
+ *
599
+ * @return new UISpecifier object
600
+ */
601
+ function UISpecifier(uiSpecifierStringOrPagesetName, elementName, args)
602
+ {
603
+ /**
604
+ * Initializes this object from a UI specifier string of the form:
605
+ *
606
+ * pagesetName::elementName(arg1=value1, arg2=value2, ...)
607
+ *
608
+ * into its component parts, and returns them as an object.
609
+ *
610
+ * @return an object containing the components of the UI specifier
611
+ * @throws UISpecifierException
612
+ */
613
+ this._initFromUISpecifierString = function(uiSpecifierString) {
614
+ var matches = /^(.*)::([^\(]+)\((.*)\)$/.exec(uiSpecifierString);
615
+ if (matches == null) {
616
+ throw new UISpecifierException('Error in '
617
+ + 'UISpecifier._initFromUISpecifierString(): "'
618
+ + this.string + '" is not a valid UI specifier string');
619
+ }
620
+ this.pagesetName = matches[1];
621
+ this.elementName = matches[2];
622
+ this.args = (matches[3]) ? parse_kwargs(matches[3]) : {};
623
+ };
624
+
625
+
626
+
627
+ /**
628
+ * Override the toString() method to return the UI specifier string when
629
+ * evaluated in a string context. Combines the UI specifier components into
630
+ * a canonical UI specifier string and returns it.
631
+ *
632
+ * @return a UI specifier string
633
+ */
634
+ this.toString = function() {
635
+ // empty string is acceptable for the path, but it must be defined
636
+ if (this.pagesetName == undefined) {
637
+ throw new UISpecifierException('Error in UISpecifier.toString(): "'
638
+ + this.pagesetName + '" is not a valid UI specifier pageset '
639
+ + 'name');
640
+ }
641
+ if (!this.elementName) {
642
+ throw new UISpecifierException('Error in UISpecifier.unparse(): "'
643
+ + this.elementName + '" is not a valid UI specifier element '
644
+ + 'name');
645
+ }
646
+ if (!this.args) {
647
+ throw new UISpecifierException('Error in UISpecifier.unparse(): "'
648
+ + this.args + '" are not valid UI specifier args');
649
+ }
650
+
651
+ uiElement = UIMap.getInstance()
652
+ .getUIElement(this.pagesetName, this.elementName);
653
+ if (uiElement != null) {
654
+ var kwargs = to_kwargs(this.args, uiElement.argsOrder);
655
+ }
656
+ else {
657
+ // probably under unit test
658
+ var kwargs = to_kwargs(this.args);
659
+ }
660
+
661
+ return this.pagesetName + '::' + this.elementName + '(' + kwargs + ')';
662
+ };
663
+
664
+ // construct the object
665
+ if (arguments.length < 2) {
666
+ this._initFromUISpecifierString(uiSpecifierStringOrPagesetName);
667
+ }
668
+ else {
669
+ this.pagesetName = uiSpecifierStringOrPagesetName;
670
+ this.elementName = elementName;
671
+ this.args = (args) ? clone(args) : {};
672
+ }
673
+ }
674
+
675
+
676
+
677
+ function Pageset(pagesetShorthand)
678
+ {
679
+ /**
680
+ * Returns true if the page is included in this pageset, false otherwise.
681
+ * The page is specified by a document object.
682
+ *
683
+ * @param inDocument the document object representing the page
684
+ */
685
+ this.contains = function(inDocument)
686
+ {
687
+ var urlParts = parseUri(unescape(inDocument.location.href));
688
+ var path = urlParts.path
689
+ .replace(/^\//, "")
690
+ .replace(/\/$/, "");
691
+ if (!this.pathRegexp.test(path)) {
692
+ return false;
693
+ }
694
+ for (var paramName in this.paramRegexps) {
695
+ var paramRegexp = this.paramRegexps[paramName];
696
+ if (!paramRegexp.test(urlParts.queryKey[paramName])) {
697
+ return false;
698
+ }
699
+ }
700
+ if (!this.pageContent(inDocument)) {
701
+ return false;
702
+ }
703
+
704
+ return true;
705
+ }
706
+
707
+
708
+
709
+ this.getUIElements = function()
710
+ {
711
+ var uiElements = [];
712
+ for (var uiElementName in this.uiElements) {
713
+ uiElements.push(this.uiElements[uiElementName]);
714
+ }
715
+ return uiElements;
716
+ };
717
+
718
+
719
+
720
+ /**
721
+ * Returns a list of UI specifier string stubs representing all UI elements
722
+ * for this pageset. Stubs contain all required arguments, but leave
723
+ * argument values blank. Each element stub is paired with the element's
724
+ * description.
725
+ *
726
+ * @return a list of UI specifier string stubs
727
+ */
728
+ this.getUISpecifierStringStubs = function()
729
+ {
730
+ var stubs = [];
731
+ for (var name in this.uiElements) {
732
+ var uiElement = this.uiElements[name];
733
+ var args = {};
734
+ for (var i = 0; i < uiElement.args.length; ++i) {
735
+ args[uiElement.args[i].name] = '';
736
+ }
737
+ var uiSpecifier = new UISpecifier(this.name, uiElement.name, args);
738
+ stubs.push([
739
+ UI_GLOBAL.UI_PREFIX + '=' + uiSpecifier.toString()
740
+ , uiElement.description
741
+ ]);
742
+ }
743
+ return stubs;
744
+ }
745
+
746
+
747
+
748
+ /**
749
+ * Throws an exception on validation failure.
750
+ */
751
+ this._validate = function(pagesetShorthand)
752
+ {
753
+ var msg = "Pageset validation error:\n"
754
+ + print_r(pagesetShorthand);
755
+ if (!pagesetShorthand.name) {
756
+ throw new PagesetException(msg + 'no name specified!');
757
+ }
758
+ if (!pagesetShorthand.description) {
759
+ throw new PagesetException(msg + 'no description specified!');
760
+ }
761
+ if (!pagesetShorthand.paths &&
762
+ !pagesetShorthand.pathRegexp &&
763
+ !pagesetShorthand.pageContent) {
764
+ throw new PagesetException(msg
765
+ + 'no path, pathRegexp, or pageContent specified!');
766
+ }
767
+ };
768
+
769
+
770
+
771
+ this.init = function(pagesetShorthand)
772
+ {
773
+ this._validate(pagesetShorthand);
774
+
775
+ this.name = pagesetShorthand.name;
776
+ this.description = pagesetShorthand.description;
777
+
778
+ var pathPrefixRegexp = pagesetShorthand.pathPrefix
779
+ ? RegExp.escape(pagesetShorthand.pathPrefix) : "";
780
+ var pathRegexp = '^' + pathPrefixRegexp;
781
+
782
+ if (pagesetShorthand.paths != undefined) {
783
+ pathRegexp += '(?:';
784
+ for (var i = 0; i < pagesetShorthand.paths.length; ++i) {
785
+ if (i > 0) {
786
+ pathRegexp += '|';
787
+ }
788
+ pathRegexp += RegExp.escape(pagesetShorthand.paths[i]);
789
+ }
790
+ pathRegexp += ')$';
791
+ }
792
+ else if (pagesetShorthand.pathRegexp) {
793
+ pathRegexp += '(?:' + pagesetShorthand.pathRegexp + ')$';
794
+ }
795
+
796
+ this.pathRegexp = new RegExp(pathRegexp);
797
+ this.paramRegexps = {};
798
+ for (var paramName in pagesetShorthand.paramRegexps) {
799
+ this.paramRegexps[paramName] =
800
+ new RegExp(pagesetShorthand.paramRegexps[paramName]);
801
+ }
802
+ this.pageContent = pagesetShorthand.pageContent ||
803
+ function() { return true; };
804
+ this.uiElements = {};
805
+ };
806
+
807
+
808
+
809
+ this.init(pagesetShorthand);
810
+ }
811
+
812
+
813
+
814
+ /**
815
+ * Construct the UI map object, and return it. Once the object is instantiated,
816
+ * it binds to a global variable and will not leave scope.
817
+ *
818
+ * @return new UIMap object
819
+ */
820
+ function UIMap()
821
+ {
822
+ // the singleton pattern, split into two parts so that "new" can still
823
+ // be used, in addition to "getInstance()"
824
+ UIMap.self = this;
825
+
826
+ // need to attach variables directly to the Editor object in order for them
827
+ // to be in scope for Editor methods
828
+ if (is_IDE()) {
829
+ Editor.uiMap = this;
830
+ Editor.UI_PREFIX = UI_GLOBAL.UI_PREFIX;
831
+ }
832
+
833
+ this.pagesets = new Object();
834
+
835
+
836
+
837
+ /**
838
+ * pageset[pagesetName]
839
+ * regexp
840
+ * elements[elementName]
841
+ * UIElement
842
+ */
843
+ this.addPageset = function(pagesetShorthand)
844
+ {
845
+ try {
846
+ var pageset = new Pageset(pagesetShorthand);
847
+ }
848
+ catch (e) {
849
+ safe_alert("Could not create pageset from shorthand:\n"
850
+ + print_r(pagesetShorthand) + "\n" + e.message);
851
+ return false;
852
+ }
853
+
854
+ if (this.pagesets[pageset.name]) {
855
+ safe_alert('Could not add pageset "' + pageset.name
856
+ + '": a pageset with that name already exists!');
857
+ return false;
858
+ }
859
+
860
+ this.pagesets[pageset.name] = pageset;
861
+ return true;
862
+ };
863
+
864
+
865
+
866
+ /**
867
+ * @param pagesetName
868
+ * @param uiElementShorthand a representation of a UIElement object in
869
+ * shorthand JSON.
870
+ */
871
+ this.addElement = function(pagesetName, uiElementShorthand)
872
+ {
873
+ try {
874
+ var uiElement = new UIElement(uiElementShorthand);
875
+ }
876
+ catch (e) {
877
+ safe_alert("Could not create UI element from shorthand:\n"
878
+ + print_r(uiElementShorthand) + "\n" + e.message);
879
+ return false;
880
+ }
881
+
882
+ // run the element's unit tests only for the IDE, and only when the
883
+ // IDE is starting. Make a rough guess as to the latter condition.
884
+ if (is_IDE() && !editor.selDebugger && !uiElement.test()) {
885
+ safe_alert('Could not add UI element "' + uiElement.name
886
+ + '": failed testcases!');
887
+ return false;
888
+ }
889
+
890
+ try {
891
+ this.pagesets[pagesetName].uiElements[uiElement.name] = uiElement;
892
+ }
893
+ catch (e) {
894
+ safe_alert("Could not add UI element '" + uiElement.name
895
+ + "' to pageset '" + pagesetName + "':\n" + e.message);
896
+ return false;
897
+ }
898
+
899
+ return true;
900
+ };
901
+
902
+
903
+
904
+ /**
905
+ * Returns the pageset for a given UI specifier string.
906
+ *
907
+ * @param uiSpecifierString
908
+ * @return a pageset object
909
+ */
910
+ this.getPageset = function(uiSpecifierString)
911
+ {
912
+ try {
913
+ var uiSpecifier = new UISpecifier(uiSpecifierString);
914
+ return this.pagesets[uiSpecifier.pagesetName];
915
+ }
916
+ catch (e) {
917
+ return null;
918
+ }
919
+ }
920
+
921
+
922
+
923
+ /**
924
+ * Returns the UIElement that a UISpecifierString or pageset and element
925
+ * pair refer to.
926
+ *
927
+ * @param pagesetNameOrUISpecifierString
928
+ * @return a UIElement, or null if none is found associated with
929
+ * uiSpecifierString
930
+ */
931
+ this.getUIElement = function(pagesetNameOrUISpecifierString, uiElementName)
932
+ {
933
+ var pagesetName = pagesetNameOrUISpecifierString;
934
+ if (arguments.length == 1) {
935
+ var uiSpecifierString = pagesetNameOrUISpecifierString;
936
+ try {
937
+ var uiSpecifier = new UISpecifier(uiSpecifierString);
938
+ pagesetName = uiSpecifier.pagesetName;
939
+ var uiElementName = uiSpecifier.elementName;
940
+ }
941
+ catch (e) {
942
+ return null;
943
+ }
944
+ }
945
+ try {
946
+ return this.pagesets[pagesetName].uiElements[uiElementName];
947
+ }
948
+ catch (e) {
949
+ return null;
950
+ }
951
+ };
952
+
953
+
954
+
955
+ /**
956
+ * Returns a list of pagesets that "contains" the provided page,
957
+ * represented as a document object. Containership is defined by the
958
+ * Pageset object's contain() method.
959
+ *
960
+ * @param inDocument the page to get pagesets for
961
+ * @return a list of pagesets
962
+ */
963
+ this.getPagesetsForPage = function(inDocument)
964
+ {
965
+ var pagesets = [];
966
+ for (var pagesetName in this.pagesets) {
967
+ var pageset = this.pagesets[pagesetName];
968
+ if (pageset.contains(inDocument)) {
969
+ pagesets.push(pageset);
970
+ }
971
+ }
972
+ return pagesets;
973
+ };
974
+
975
+
976
+
977
+ /**
978
+ * Returns a list of all pagesets.
979
+ *
980
+ * @return a list of pagesets
981
+ */
982
+ this.getPagesets = function()
983
+ {
984
+ var pagesets = [];
985
+ for (var pagesetName in this.pagesets) {
986
+ pagesets.push(this.pagesets[pagesetName]);
987
+ }
988
+ return pagesets;
989
+ };
990
+
991
+
992
+
993
+ /**
994
+ * Returns a list of elements on a page that a given UI specifier string,
995
+ * maps to. If no elements are mapped to, returns an empty list..
996
+ *
997
+ * @param uiSpecifierString a String that specifies a UI element with
998
+ * attendant argument values
999
+ * @param inDocument the document object the specified UI element
1000
+ * appears in
1001
+ * @return a potentially-empty list of elements
1002
+ * specified by uiSpecifierString
1003
+ */
1004
+ this.getPageElements = function(uiSpecifierString, inDocument)
1005
+ {
1006
+ var locator = this.getLocator(uiSpecifierString);
1007
+ var results = locator ? eval_locator(locator, inDocument) : [];
1008
+ return results;
1009
+ };
1010
+
1011
+
1012
+
1013
+ /**
1014
+ * Returns the locator string that a given UI specifier string maps to, or
1015
+ * null if it cannot be mapped.
1016
+ *
1017
+ * @param uiSpecifierString
1018
+ */
1019
+ this.getLocator = function(uiSpecifierString)
1020
+ {
1021
+ try {
1022
+ var uiSpecifier = new UISpecifier(uiSpecifierString);
1023
+ }
1024
+ catch (e) {
1025
+ safe_alert('Could not create UISpecifier for string "'
1026
+ + uiSpecifierString + '": ' + e.message);
1027
+ return null;
1028
+ }
1029
+
1030
+ var uiElement = this.getUIElement(uiSpecifier.pagesetName,
1031
+ uiSpecifier.elementName);
1032
+ try {
1033
+ return uiElement.getLocator(uiSpecifier.args);
1034
+ }
1035
+ catch (e) {
1036
+ return null;
1037
+ }
1038
+ }
1039
+
1040
+
1041
+
1042
+ /**
1043
+ * Finds and returns a UI specifier string given an element and the page
1044
+ * that it appears on.
1045
+ *
1046
+ * @param pageElement the document element to map to a UI specifier
1047
+ * @param inDocument the document the element appears in
1048
+ * @return a UI specifier string, or false if one cannot be
1049
+ * constructed
1050
+ */
1051
+ this.getUISpecifierString = function(pageElement, inDocument)
1052
+ {
1053
+ var is_fuzzy_match =
1054
+ BrowserBot.prototype.locateElementByUIElement.is_fuzzy_match;
1055
+ var pagesets = this.getPagesetsForPage(inDocument);
1056
+
1057
+ for (var i = 0; i < pagesets.length; ++i) {
1058
+ var pageset = pagesets[i];
1059
+ var uiElements = pageset.getUIElements();
1060
+
1061
+ for (var j = 0; j < uiElements.length; ++j) {
1062
+ var uiElement = uiElements[j];
1063
+
1064
+ // first test against the generic locator, if there is one.
1065
+ // This should net some performance benefit when recording on
1066
+ // more complicated pages.
1067
+ if (uiElement.getGenericLocator) {
1068
+ var passedTest = false;
1069
+ var results =
1070
+ eval_locator(uiElement.getGenericLocator(), inDocument);
1071
+ for (var i = 0; i < results.length; ++i) {
1072
+ if (results[i] == pageElement) {
1073
+ passedTest = true;
1074
+ break;
1075
+ }
1076
+ }
1077
+ if (!passedTest) {
1078
+ continue;
1079
+ }
1080
+ }
1081
+
1082
+ var defaultLocators;
1083
+ if (uiElement.isDefaultLocatorConstructionDeferred) {
1084
+ defaultLocators = uiElement.getDefaultLocators(inDocument);
1085
+ }
1086
+ else {
1087
+ defaultLocators = uiElement.defaultLocators;
1088
+ }
1089
+
1090
+ //safe_alert(print_r(uiElement.defaultLocators));
1091
+ for (var locator in defaultLocators) {
1092
+ var locatedElements = eval_locator(locator, inDocument);
1093
+ if (locatedElements.length) {
1094
+ var locatedElement = locatedElements[0];
1095
+ }
1096
+ else {
1097
+ continue;
1098
+ }
1099
+
1100
+ // use a heuristic to determine whether the element
1101
+ // specified is the "same" as the element we're matching
1102
+ if (is_fuzzy_match) {
1103
+ if (is_fuzzy_match(locatedElement, pageElement)) {
1104
+ return UI_GLOBAL.UI_PREFIX + '=' +
1105
+ new UISpecifier(pageset.name, uiElement.name,
1106
+ defaultLocators[locator]);
1107
+ }
1108
+ }
1109
+ else {
1110
+ if (locatedElement == pageElement) {
1111
+ return UI_GLOBAL.UI_PREFIX + '=' +
1112
+ new UISpecifier(pageset.name, uiElement.name,
1113
+ defaultLocators[locator]);
1114
+ }
1115
+ }
1116
+
1117
+ // ok, matching the element failed. See if an offset
1118
+ // locator can complete the match.
1119
+ if (uiElement.getOffsetLocator) {
1120
+ for (var k = 0; k < locatedElements.length; ++k) {
1121
+ var offsetLocator = uiElement
1122
+ .getOffsetLocator(locatedElements[k], pageElement);
1123
+ if (offsetLocator) {
1124
+ return UI_GLOBAL.UI_PREFIX + '=' +
1125
+ new UISpecifier(pageset.name,
1126
+ uiElement.name,
1127
+ defaultLocators[locator])
1128
+ + '->' + offsetLocator;
1129
+ }
1130
+ }
1131
+ }
1132
+ }
1133
+ }
1134
+ }
1135
+ return false;
1136
+ };
1137
+
1138
+
1139
+
1140
+ /**
1141
+ * Returns a sorted list of UI specifier string stubs representing possible
1142
+ * UI elements for all pagesets, paired the their descriptions. Stubs
1143
+ * contain all required arguments, but leave argument values blank.
1144
+ *
1145
+ * @return a list of UI specifier string stubs
1146
+ */
1147
+ this.getUISpecifierStringStubs = function() {
1148
+ var stubs = [];
1149
+ var pagesets = this.getPagesets();
1150
+ for (var i = 0; i < pagesets.length; ++i) {
1151
+ stubs = stubs.concat(pagesets[i].getUISpecifierStringStubs());
1152
+ }
1153
+ stubs.sort(function(a, b) {
1154
+ if (a[0] < b[0]) {
1155
+ return -1;
1156
+ }
1157
+ return a[0] == b[0] ? 0 : 1;
1158
+ });
1159
+ return stubs;
1160
+ }
1161
+ }
1162
+
1163
+ UIMap.getInstance = function() {
1164
+ return (UIMap.self == null) ? new UIMap() : UIMap.self;
1165
+ }
1166
+
1167
+ //******************************************************************************
1168
+ // Rollups
1169
+
1170
+ /**
1171
+ * The Command object isn't available in the Selenium RC. We introduce an
1172
+ * object with the identical constructor. In the IDE, this will be redefined,
1173
+ * which is just fine.
1174
+ *
1175
+ * @param command
1176
+ * @param target
1177
+ * @param value
1178
+ */
1179
+ if (typeof(Command) == 'undefined') {
1180
+ function Command(command, target, value) {
1181
+ this.command = command != null ? command : '';
1182
+ this.target = target != null ? target : '';
1183
+ this.value = value != null ? value : '';
1184
+ }
1185
+ }
1186
+
1187
+
1188
+
1189
+ /**
1190
+ * A CommandMatcher object matches commands during the application of a
1191
+ * RollupRule. It's specified with a shorthand format, for example:
1192
+ *
1193
+ * new CommandMatcher({
1194
+ * command: 'click'
1195
+ * , target: 'ui=allPages::.+'
1196
+ * })
1197
+ *
1198
+ * which is intended to match click commands whose target is an element in the
1199
+ * allPages PageSet. The matching expressions are given as regular expressions;
1200
+ * in the example above, the command must be "click"; "clickAndWait" would be
1201
+ * acceptable if 'click.*' were used. Here's a more complete example:
1202
+ *
1203
+ * new CommandMatcher({
1204
+ * command: 'type'
1205
+ * , target: 'ui=loginPages::username()'
1206
+ * , value: '.+_test'
1207
+ * , updateArgs: function(command, args) {
1208
+ * args.username = command.value;
1209
+ * }
1210
+ * })
1211
+ *
1212
+ * Here, the command and target are fixed, but there is variability in the
1213
+ * value of the command. When a command matches, the username is saved to the
1214
+ * arguments object.
1215
+ */
1216
+ function CommandMatcher(commandMatcherShorthand)
1217
+ {
1218
+ /**
1219
+ * Ensure the shorthand notation used to initialize the CommandMatcher has
1220
+ * all required values.
1221
+ *
1222
+ * @param commandMatcherShorthand an object containing information about
1223
+ * the CommandMatcher
1224
+ */
1225
+ this.validate = function(commandMatcherShorthand) {
1226
+ var msg = "CommandMatcher validation error:\n"
1227
+ + print_r(commandMatcherShorthand);
1228
+ if (!commandMatcherShorthand.command) {
1229
+ throw new CommandMatcherException(msg + 'no command specified!');
1230
+ }
1231
+ if (!commandMatcherShorthand.target) {
1232
+ throw new CommandMatcherException(msg + 'no target specified!');
1233
+ }
1234
+ if (commandMatcherShorthand.minMatches &&
1235
+ commandMatcherShorthand.maxMatches &&
1236
+ commandMatcherShorthand.minMatches >
1237
+ commandMatcherShorthand.maxMatches) {
1238
+ throw new CommandMatcherException(msg + 'minMatches > maxMatches!');
1239
+ }
1240
+ };
1241
+
1242
+ /**
1243
+ * Initialize this object.
1244
+ *
1245
+ * @param commandMatcherShorthand an object containing information used to
1246
+ * initialize the CommandMatcher
1247
+ */
1248
+ this.init = function(commandMatcherShorthand) {
1249
+ this.validate(commandMatcherShorthand);
1250
+
1251
+ this.command = commandMatcherShorthand.command;
1252
+ this.target = commandMatcherShorthand.target;
1253
+ this.value = commandMatcherShorthand.value || null;
1254
+ this.minMatches = commandMatcherShorthand.minMatches || 1;
1255
+ this.maxMatches = commandMatcherShorthand.maxMatches || 1;
1256
+ this.updateArgs = commandMatcherShorthand.updateArgs ||
1257
+ function(command, args) { return args; };
1258
+ };
1259
+
1260
+ /**
1261
+ * Determines whether a given command matches. Updates args by "reference"
1262
+ * and returns true if it does; return false otherwise.
1263
+ *
1264
+ * @param command the command to attempt to match
1265
+ */
1266
+ this.isMatch = function(command) {
1267
+ var re = new RegExp('^' + this.command + '$');
1268
+ if (! re.test(command.command)) {
1269
+ return false;
1270
+ }
1271
+ re = new RegExp('^' + this.target + '$');
1272
+ if (! re.test(command.target)) {
1273
+ return false;
1274
+ }
1275
+ if (this.value != null) {
1276
+ re = new RegExp('^' + this.value + '$');
1277
+ if (! re.test(command.value)) {
1278
+ return false;
1279
+ }
1280
+ }
1281
+
1282
+ // okay, the command matches
1283
+ return true;
1284
+ };
1285
+
1286
+ // initialization
1287
+ this.init(commandMatcherShorthand);
1288
+ }
1289
+
1290
+
1291
+
1292
+ function RollupRuleException(message)
1293
+ {
1294
+ this.message = message;
1295
+ this.name = 'RollupRuleException';
1296
+ }
1297
+
1298
+ function RollupRule(rollupRuleShorthand)
1299
+ {
1300
+ /**
1301
+ * Ensure the shorthand notation used to initialize the RollupRule has all
1302
+ * required values.
1303
+ *
1304
+ * @param rollupRuleShorthand an object containing information about the
1305
+ * RollupRule
1306
+ */
1307
+ this.validate = function(rollupRuleShorthand) {
1308
+ var msg = "RollupRule validation error:\n"
1309
+ + print_r(rollupRuleShorthand);
1310
+ if (!rollupRuleShorthand.name) {
1311
+ throw new RollupRuleException(msg + 'no name specified!');
1312
+ }
1313
+ if (!rollupRuleShorthand.description) {
1314
+ throw new RollupRuleException(msg + 'no description specified!');
1315
+ }
1316
+ // rollupRuleShorthand.args is optional
1317
+ if (!rollupRuleShorthand.commandMatchers &&
1318
+ !rollupRuleShorthand.getRollup) {
1319
+ throw new RollupRuleException(msg
1320
+ + 'no command matchers specified!');
1321
+ }
1322
+ if (!rollupRuleShorthand.expandedCommands &&
1323
+ !rollupRuleShorthand.getExpandedCommands) {
1324
+ throw new RollupRuleException(msg
1325
+ + 'no expanded commands specified!');
1326
+ }
1327
+
1328
+ return true;
1329
+ };
1330
+
1331
+ /**
1332
+ * Initialize this object.
1333
+ *
1334
+ * @param rollupRuleShorthand an object containing information used to
1335
+ * initialize the RollupRule
1336
+ */
1337
+ this.init = function(rollupRuleShorthand) {
1338
+ this.validate(rollupRuleShorthand);
1339
+
1340
+ this.name = rollupRuleShorthand.name;
1341
+ this.description = rollupRuleShorthand.description;
1342
+ this.pre = rollupRuleShorthand.pre || '';
1343
+ this.post = rollupRuleShorthand.post || '';
1344
+ this.alternateCommand = rollupRuleShorthand.alternateCommand;
1345
+ this.args = rollupRuleShorthand.args || [];
1346
+
1347
+ if (rollupRuleShorthand.commandMatchers) {
1348
+ // construct the rule from the list of CommandMatchers
1349
+ this.commandMatchers = [];
1350
+ var matchers = rollupRuleShorthand.commandMatchers;
1351
+ for (var i = 0; i < matchers.length; ++i) {
1352
+ if (matchers[i].updateArgs && this.args.length == 0) {
1353
+ // enforce metadata for arguments
1354
+ var msg = "RollupRule validation error:\n"
1355
+ + print_r(rollupRuleShorthand)
1356
+ + 'no argument metadata provided!';
1357
+ throw new RollupRuleException(msg);
1358
+ }
1359
+ this.commandMatchers.push(new CommandMatcher(matchers[i]));
1360
+ }
1361
+
1362
+ // returns false if the rollup doesn't match, or a rollup command
1363
+ // if it does. If returned, the command contains the
1364
+ // replacementIndexes property, which indicates which commands it
1365
+ // substitutes for.
1366
+ this.getRollup = function(commands) {
1367
+ // this is a greedy matching algorithm
1368
+ var replacementIndexes = [];
1369
+ var commandMatcherQueue = this.commandMatchers;
1370
+ var matchCount = 0;
1371
+ var args = {};
1372
+ for (var i = 0, j = 0; i < commandMatcherQueue.length;) {
1373
+ var matcher = commandMatcherQueue[i];
1374
+ if (j >= commands.length) {
1375
+ // we've run out of commands! If the remaining matchers
1376
+ // do not have minMatches requirements, this is a
1377
+ // match. Otherwise, it's not.
1378
+ if (matcher.minMatches > 0) {
1379
+ return false;
1380
+ }
1381
+ ++i;
1382
+ matchCount = 0; // unnecessary, but let's be consistent
1383
+ }
1384
+ else {
1385
+ if (matcher.isMatch(commands[j])) {
1386
+ ++matchCount;
1387
+ if (matchCount == matcher.maxMatches) {
1388
+ // exhausted this matcher's matches ... move on
1389
+ // to next matcher
1390
+ ++i;
1391
+ matchCount = 0;
1392
+ }
1393
+ args = matcher.updateArgs(commands[j], args);
1394
+ replacementIndexes.push(j);
1395
+ ++j; // move on to next command
1396
+ }
1397
+ else {
1398
+ //alert(matchCount + ', ' + matcher.minMatches);
1399
+ if (matchCount < matcher.minMatches) {
1400
+ return false;
1401
+ }
1402
+ // didn't match this time, but we've satisfied the
1403
+ // requirements already ... move on to next matcher
1404
+ ++i;
1405
+ matchCount = 0;
1406
+ // still gonna look at same command
1407
+ }
1408
+ }
1409
+ }
1410
+
1411
+ var rollup;
1412
+ if (this.alternateCommand) {
1413
+ rollup = new Command(this.alternateCommand,
1414
+ commands[0].target, commands[0].value);
1415
+ }
1416
+ else {
1417
+ rollup = new Command('rollup', this.name);
1418
+ rollup.value = to_kwargs(args);
1419
+ }
1420
+ rollup.replacementIndexes = replacementIndexes;
1421
+ return rollup;
1422
+ };
1423
+ }
1424
+ else {
1425
+ this.getRollup = function(commands) {
1426
+ var result = rollupRuleShorthand.getRollup(commands);
1427
+ if (result) {
1428
+ var rollup = new Command(
1429
+ result.command
1430
+ , result.target
1431
+ , result.value
1432
+ );
1433
+ rollup.replacementIndexes = result.replacementIndexes;
1434
+ return rollup;
1435
+ }
1436
+ return false;
1437
+ };
1438
+ }
1439
+
1440
+ this.getExpandedCommands = function(kwargs) {
1441
+ var commands = [];
1442
+ var expandedCommands = (rollupRuleShorthand.expandedCommands
1443
+ ? rollupRuleShorthand.expandedCommands
1444
+ : rollupRuleShorthand.getExpandedCommands(
1445
+ parse_kwargs(kwargs)));
1446
+ for (var i = 0; i < expandedCommands.length; ++i) {
1447
+ var command = expandedCommands[i];
1448
+ commands.push(new Command(
1449
+ command.command
1450
+ , command.target
1451
+ , command.value
1452
+ ));
1453
+ }
1454
+ return commands;
1455
+ };
1456
+ };
1457
+
1458
+ this.init(rollupRuleShorthand);
1459
+ }
1460
+
1461
+
1462
+
1463
+ /**
1464
+ *
1465
+ */
1466
+ function RollupManager()
1467
+ {
1468
+ // singleton pattern
1469
+ RollupManager.self = this;
1470
+
1471
+ this.init = function()
1472
+ {
1473
+ this.rollupRules = {};
1474
+ if (is_IDE()) {
1475
+ Editor.rollupManager = this;
1476
+ }
1477
+ };
1478
+
1479
+ /**
1480
+ * Adds a new RollupRule to the repository. Returns true on success, or
1481
+ * false if the rule couldn't be added.
1482
+ *
1483
+ * @param rollupRuleShorthand shorthand JSON specification of the new
1484
+ * RollupRule, possibly including CommandMatcher
1485
+ * shorthand too.
1486
+ * @return true if the rule was added successfully,
1487
+ * false otherwise.
1488
+ */
1489
+ this.addRollupRule = function(rollupRuleShorthand)
1490
+ {
1491
+ try {
1492
+ var rule = new RollupRule(rollupRuleShorthand);
1493
+ this.rollupRules[rule.name] = rule;
1494
+ }
1495
+ catch(e) {
1496
+ smart_alert("Could not create RollupRule from shorthand:\n\n"
1497
+ + e.message);
1498
+ return false;
1499
+ }
1500
+ return true;
1501
+ };
1502
+
1503
+ /**
1504
+ * Returns a RollupRule by name.
1505
+ *
1506
+ * @param rollupName the name of the rule to fetch
1507
+ * @return the RollupRule, or null if it isn't found.
1508
+ */
1509
+ this.getRollupRule = function(rollupName)
1510
+ {
1511
+ return (this.rollupRules[rollupName] || null);
1512
+ };
1513
+
1514
+ /**
1515
+ * Returns a list of name-description pairs for use in populating the
1516
+ * auto-populated target dropdown in the IDE. Rules that have an alternate
1517
+ * command defined are not included in the list, as they are not bona-fide
1518
+ * rollups.
1519
+ *
1520
+ * @return a list of name-description pairs
1521
+ */
1522
+ this.getRollupRulesForDropdown = function()
1523
+ {
1524
+ var targets = [];
1525
+ var names = keys(this.rollupRules).sort();
1526
+ for (var i = 0; i < names.length; ++i) {
1527
+ var name = names[i];
1528
+ if (this.rollupRules[name].alternateCommand) {
1529
+ continue;
1530
+ }
1531
+ targets.push([ name, this.rollupRules[name].description ]);
1532
+ }
1533
+ return targets;
1534
+ };
1535
+
1536
+ /**
1537
+ * Applies all rules to the current editor commands, asking the user in
1538
+ * each case if it's okay to perform the replacement. The rules are applied
1539
+ * repeatedly until there are no more matches. The algorithm should
1540
+ * remember when the user has declined a replacement, and not ask to do it
1541
+ * again.
1542
+ *
1543
+ * @return the list of commands with rollup replacements performed
1544
+ */
1545
+ this.applyRollupRules = function()
1546
+ {
1547
+ var commands = editor.getTestCase().commands;
1548
+ var blacklistedRollups = {};
1549
+
1550
+ // so long as rollups were performed, we need to keep iterating through
1551
+ // the commands starting at the beginning, because further rollups may
1552
+ // potentially be applied on the newly created ones.
1553
+ while (true) {
1554
+ var performedRollup = false;
1555
+ for (var i = 0; i < commands.length; ++i) {
1556
+ // iterate through commands
1557
+ for (var rollupName in this.rollupRules) {
1558
+ var rule = this.rollupRules[rollupName];
1559
+ var rollup = rule.getRollup(commands.slice(i));
1560
+ if (rollup) {
1561
+ // since we passed in a sliced version of the commands
1562
+ // array to the getRollup() method, we need to re-add
1563
+ // the offset to the replacementIndexes
1564
+ var k = 0;
1565
+ for (; k < rollup.replacementIndexes.length; ++k) {
1566
+ rollup.replacementIndexes[k] += i;
1567
+ }
1568
+
1569
+ // build the confirmation message
1570
+ var msg = "Perform the following command rollup?\n\n";
1571
+ for (k = 0; k < rollup.replacementIndexes.length; ++k) {
1572
+ var replacementIndex = rollup.replacementIndexes[k];
1573
+ var command = commands[replacementIndex];
1574
+ msg += '[' + replacementIndex + ']: ';
1575
+ msg += command + "\n";
1576
+ }
1577
+ msg += "\n";
1578
+ msg += rollup;
1579
+
1580
+ // check against blacklisted rollups
1581
+ if (blacklistedRollups[msg]) {
1582
+ continue;
1583
+ }
1584
+
1585
+ // highlight the potentially replaced rows
1586
+ for (k = 0; k < commands.length; ++k) {
1587
+ var command = commands[k];
1588
+ command.result = '';
1589
+ if (rollup.replacementIndexes.indexOf(k) != -1) {
1590
+ command.selectedForReplacement = true;
1591
+ }
1592
+ editor.view.rowUpdated(replacementIndex);
1593
+ }
1594
+
1595
+ // get confirmation from user
1596
+ if (confirm(msg)) {
1597
+ // perform rollup
1598
+ var deleteRanges = [];
1599
+ var replacementIndexes = rollup.replacementIndexes;
1600
+ for (k = 0; k < replacementIndexes.length; ++k) {
1601
+ // this is expected to be list of ranges. A
1602
+ // range has a start, and a list of commands.
1603
+ // The deletion only checks the length of the
1604
+ // command list.
1605
+ deleteRanges.push({
1606
+ start: replacementIndexes[k]
1607
+ , commands: [ 1 ]
1608
+ });
1609
+ }
1610
+ editor.view.executeAction(new TreeView
1611
+ .DeleteCommandAction(editor.view,deleteRanges));
1612
+ editor.view.insertAt(i, rollup);
1613
+
1614
+ performedRollup = true;
1615
+ }
1616
+ else {
1617
+ // cleverly remember not to try this rollup again
1618
+ blacklistedRollups[msg] = true;
1619
+ }
1620
+
1621
+ // unhighlight
1622
+ for (k = 0; k < commands.length; ++k) {
1623
+ commands[k].selectedForReplacement = false;
1624
+ editor.view.rowUpdated(k);
1625
+ }
1626
+ }
1627
+ }
1628
+ }
1629
+ if (!performedRollup) {
1630
+ break;
1631
+ }
1632
+ }
1633
+ return commands;
1634
+ };
1635
+
1636
+ this.init();
1637
+ }
1638
+
1639
+ RollupManager.getInstance = function() {
1640
+ return (RollupManager.self == null)
1641
+ ? new RollupManager()
1642
+ : RollupManager.self;
1643
+ }
1644
+