lebowski 0.1.0

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 (85) hide show
  1. data/History.md +3 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +84 -0
  4. data/README.md +146 -0
  5. data/Rakefile +42 -0
  6. data/bin/lebowski +26 -0
  7. data/bin/lebowski-spec +29 -0
  8. data/bin/lebowski-start-server +24 -0
  9. data/lib/lebowski.rb +15 -0
  10. data/lib/lebowski/core.rb +35 -0
  11. data/lib/lebowski/foundation.rb +52 -0
  12. data/lib/lebowski/foundation/application.rb +315 -0
  13. data/lib/lebowski/foundation/core.rb +61 -0
  14. data/lib/lebowski/foundation/core_query.rb +231 -0
  15. data/lib/lebowski/foundation/dom_element.rb +114 -0
  16. data/lib/lebowski/foundation/errors/argument_invalid_type.rb +31 -0
  17. data/lib/lebowski/foundation/errors/unexpected_type.rb +30 -0
  18. data/lib/lebowski/foundation/mixins/collection_item_view_support.rb +87 -0
  19. data/lib/lebowski/foundation/mixins/delegate_support.rb +22 -0
  20. data/lib/lebowski/foundation/mixins/inline_text_field_support.rb +29 -0
  21. data/lib/lebowski/foundation/mixins/key_check.rb +35 -0
  22. data/lib/lebowski/foundation/mixins/list_item_view_support.rb +36 -0
  23. data/lib/lebowski/foundation/mixins/positioned_element.rb +20 -0
  24. data/lib/lebowski/foundation/mixins/stall_support.rb +79 -0
  25. data/lib/lebowski/foundation/mixins/user_actions.rb +302 -0
  26. data/lib/lebowski/foundation/mixins/wait_actions.rb +44 -0
  27. data/lib/lebowski/foundation/object_array.rb +305 -0
  28. data/lib/lebowski/foundation/panes/alert.rb +117 -0
  29. data/lib/lebowski/foundation/panes/main.rb +20 -0
  30. data/lib/lebowski/foundation/panes/menu.rb +100 -0
  31. data/lib/lebowski/foundation/panes/modal.rb +21 -0
  32. data/lib/lebowski/foundation/panes/palette.rb +21 -0
  33. data/lib/lebowski/foundation/panes/pane.rb +24 -0
  34. data/lib/lebowski/foundation/panes/panel.rb +25 -0
  35. data/lib/lebowski/foundation/panes/picker.rb +43 -0
  36. data/lib/lebowski/foundation/panes/sheet.rb +21 -0
  37. data/lib/lebowski/foundation/proxy_factory.rb +87 -0
  38. data/lib/lebowski/foundation/proxy_object.rb +670 -0
  39. data/lib/lebowski/foundation/sc_object.rb +38 -0
  40. data/lib/lebowski/foundation/views/button.rb +20 -0
  41. data/lib/lebowski/foundation/views/checkbox.rb +63 -0
  42. data/lib/lebowski/foundation/views/collection.rb +304 -0
  43. data/lib/lebowski/foundation/views/container.rb +32 -0
  44. data/lib/lebowski/foundation/views/disclosure.rb +59 -0
  45. data/lib/lebowski/foundation/views/grid.rb +21 -0
  46. data/lib/lebowski/foundation/views/label.rb +30 -0
  47. data/lib/lebowski/foundation/views/list.rb +67 -0
  48. data/lib/lebowski/foundation/views/list_item.rb +280 -0
  49. data/lib/lebowski/foundation/views/menu_item.rb +27 -0
  50. data/lib/lebowski/foundation/views/radio.rb +32 -0
  51. data/lib/lebowski/foundation/views/segmented.rb +97 -0
  52. data/lib/lebowski/foundation/views/select_field.rb +139 -0
  53. data/lib/lebowski/foundation/views/support/simple_item_array.rb +249 -0
  54. data/lib/lebowski/foundation/views/text_field.rb +108 -0
  55. data/lib/lebowski/foundation/views/view.rb +108 -0
  56. data/lib/lebowski/runtime.rb +7 -0
  57. data/lib/lebowski/runtime/errors/remote_control_command_execution_error.rb +9 -0
  58. data/lib/lebowski/runtime/errors/remote_control_command_timeout_error.rb +9 -0
  59. data/lib/lebowski/runtime/errors/remote_control_error.rb +9 -0
  60. data/lib/lebowski/runtime/errors/selenium_server_error.rb +9 -0
  61. data/lib/lebowski/runtime/object_encoder.rb +123 -0
  62. data/lib/lebowski/runtime/sprout_core_driver.rb +14 -0
  63. data/lib/lebowski/runtime/sprout_core_extensions.rb +600 -0
  64. data/lib/lebowski/scui.rb +18 -0
  65. data/lib/lebowski/scui/mixins/node_item_view_support.rb +136 -0
  66. data/lib/lebowski/scui/mixins/terminal_view_support.rb +25 -0
  67. data/lib/lebowski/scui/views/combo_box.rb +119 -0
  68. data/lib/lebowski/scui/views/date_picker.rb +148 -0
  69. data/lib/lebowski/scui/views/linkit.rb +36 -0
  70. data/lib/lebowski/spec.rb +17 -0
  71. data/lib/lebowski/spec/core.rb +21 -0
  72. data/lib/lebowski/spec/matchers/be.rb +63 -0
  73. data/lib/lebowski/spec/matchers/has.rb +40 -0
  74. data/lib/lebowski/spec/matchers/match_supporters/has_object_function.rb +67 -0
  75. data/lib/lebowski/spec/matchers/match_supporters/has_predicate_with_no_prefix.rb +50 -0
  76. data/lib/lebowski/spec/matchers/match_supporters/has_predicate_with_prefix_has.rb +50 -0
  77. data/lib/lebowski/spec/matchers/match_supporters/match_supporter.rb +29 -0
  78. data/lib/lebowski/spec/matchers/method_missing.rb +24 -0
  79. data/lib/lebowski/spec/operators/operator.rb +20 -0
  80. data/lib/lebowski/spec/operators/that.rb +116 -0
  81. data/lib/lebowski/spec/util.rb +26 -0
  82. data/lib/lebowski/version.rb +17 -0
  83. data/resources/selenium-server.jar +0 -0
  84. data/resources/user-extensions.js +1421 -0
  85. metadata +198 -0
@@ -0,0 +1,26 @@
1
+ # ==========================================================================
2
+ # Project: Lebowski Framework - The SproutCore Test Automation Framework
3
+ # License: Licensed under MIT license (see License.txt)
4
+ # ==========================================================================
5
+
6
+ module Lebowski
7
+ module Spec
8
+ module Util
9
+
10
+ #
11
+ # Will determine if the given two values match each other. If neither val1 or val2
12
+ # are regular expression then then will be compared using the standard == operator.
13
+ # Otherwise, if val1 or val2 is a regular expression, then it will be compared
14
+ # against the other value that must be a string
15
+ #
16
+ def self.match?(val1, val2)
17
+ if val1.kind_of? Regexp or val2.kind_of? Regexp
18
+ return (val1 =~ val2).nil? ? false : true
19
+ else
20
+ return val1 == val2
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ # ==========================================================================
2
+ # Project: Lebowski Framework - The SproutCore Test Automation Framework
3
+ # License: Licensed under MIT license (see License.txt)
4
+ # ==========================================================================
5
+
6
+ module Lebowski # :nodoc:
7
+ module VERSION # :nodoc:
8
+ MAJOR = 0
9
+ MINOR = 1
10
+ TINY = 0
11
+ PRE = nil
12
+
13
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
14
+
15
+ SUMMARY = "lebowski #{STRING}"
16
+ end
17
+ end
@@ -0,0 +1,1421 @@
1
+ // ==========================================================================
2
+ // Project: Lebowski Framework - The SproutCore Test Automation Framework
3
+ // License: Licensed under MIT license (see License.txt)
4
+ // ==========================================================================
5
+ /*globals Selenium PageBot selenium */
6
+
7
+ /**
8
+ This file contains all Lebowski framework extensions for the Selenium Core framework.
9
+
10
+ When starting up the selenium server, this file must be included in order for the
11
+ Lebowski framework to properly communicate with the SproutCore framework and application.
12
+ Example:
13
+
14
+ java -jar selenium_server -userExtensions user-extensions.js
15
+
16
+ There are three global variables used throughout this extension, which are:
17
+
18
+ $SC - References the SproutCore root object in the application window
19
+ $App - References the application's root object in the application window
20
+ $ScPath - The path parser used to access values for a given path using the SC
21
+ dot-path notation
22
+
23
+ In order to use selenium extensions correctly the doOpenScApplication method on the
24
+ Selenium object must be invoked. This method will assure that both the SproutCore framework
25
+ and application object are found.
26
+
27
+ */
28
+
29
+ /**
30
+ There are two core global variables that are used to make it more convienient to program
31
+ against the SproutCore framework and the SC application under test.
32
+ */
33
+ var $SC = null;
34
+ var $App = null;
35
+
36
+ /**
37
+ Root object that contains all the various helper objects for this extention
38
+ */
39
+ var ScExt = {};
40
+
41
+ /**
42
+ Checks if the given value is indeed an SC.Object
43
+ */
44
+ ScExt.isScObject = function(obj) {
45
+ return (obj && typeof(obj) === "object" && obj.kindOf && obj.kindOf($SC.Object));
46
+ };
47
+
48
+ /**
49
+ Converts an SC.IndexSet into a basic JavaScript array
50
+ */
51
+ ScExt.indexSet2Array = function(indexSet) {
52
+ if (!indexSet) return [];
53
+ var indexes = [];
54
+ indexSet.forEach(function(index) {
55
+ indexes.push(index);
56
+ }, this);
57
+ return indexes;
58
+ };
59
+
60
+ /**
61
+ Converts a class name into an actuall SC class object. For instance, providing
62
+ "SC.CollectionView" will convert into the SC.CollectionView class object. If
63
+ a convertion can not be made then null is returned.
64
+ */
65
+ ScExt.string2ScClass = function(className) {
66
+ var klassParts = className.split('.');
67
+ if (klassParts.length < 2) return null;
68
+
69
+ var klass = selenium.browserbot.getCurrentWindow();
70
+ for (var i = 0; i < klassParts.length; i++) {
71
+ var part = klassParts[i];
72
+ klass = klass[part];
73
+ var type = typeof(klass);
74
+ if (!klass || !(type === "object" || type === "function")) return null;
75
+ }
76
+
77
+ return klass;
78
+ };
79
+
80
+ /**
81
+ Will call a given view's scrollToVisible method to assure that it is indeed
82
+ visible to the user. The run loop is invoked to assure the views are all
83
+ updated.
84
+ */
85
+ ScExt.viewScrollToVisible = function(view) {
86
+ $SC.RunLoop.begin();
87
+ view.scrollToVisible();
88
+ $SC.RunLoop.end();
89
+ };
90
+
91
+ /**
92
+ Gets all the class names that the given object inherits from. For instance, if
93
+ an object is of type SC.ButtonView, then the following will be returned
94
+ in order:
95
+
96
+ ["SC.ButtonView", "SC.View", "SC.Object"]
97
+ */
98
+ ScExt.getScObjectClassNames = function(obj) {
99
+ if (!ScExt.isScObject(obj)) return [];
100
+
101
+ var classNames = [];
102
+ var superclass = obj.constructor;
103
+ while (superclass) {
104
+ var sc = superclass.toString();
105
+ if (classNames.indexOf(sc) < 0) {
106
+ classNames.push(sc);
107
+ }
108
+ superclass = superclass.superclass;
109
+ }
110
+ return classNames;
111
+ };
112
+
113
+ /**
114
+ Determines the make up of the given array. Returns an SC type constant if
115
+ all the values in the array are of the same type. If the array is a mixture
116
+ of type then "anonymous" is returned
117
+ */
118
+ ScExt.typeOfArrayContent = function(array) {
119
+
120
+ if (array.length === 0) return "empty";
121
+
122
+ var stringCount = 0;
123
+ var numberCount = 0;
124
+ var boolCount = 0;
125
+ var objectCount = 0;
126
+ var hashCount = 0;
127
+ var nullCount = 0;
128
+ var undefinedCount = 0;
129
+
130
+ for (var i = 0; i < array.length; i++ ) {
131
+ var value = array[i];
132
+ var type = $SC.typeOf(value);
133
+ if (type === $SC.T_STRING) {
134
+ stringCount++;
135
+ } else if (type === $SC.T_BOOL) {
136
+ boolCount++;
137
+ } else if (type === $SC.T_NUMBER) {
138
+ numberCount++;
139
+ } else if (type === $SC.T_OBJECT) {
140
+ objectCount++;
141
+ } else if (type === $SC.T_HASH) {
142
+ hashCount++;
143
+ }
144
+ }
145
+
146
+ if (stringCount === array.length) return $SC.T_STRING;
147
+ if (numberCount === array.length) return $SC.T_NUMBER;
148
+ if (boolCount === array.length) return $SC.T_BOOL;
149
+ if (objectCount === array.length) return $SC.T_OBJECT;
150
+ if (hashCount === array.length) return $SC.T_HASH;
151
+ return "anonymous";
152
+
153
+ };
154
+
155
+ /**
156
+ The path parser is used to parse a given property path and return a value based
157
+ on that path. If any part of the path evaluates to null then the path will stop
158
+ being parsed and null will be returned.
159
+
160
+ The standard SproutCore obj.getPath('some.path') approach is not used directly
161
+ since the property paths for this Selenium user extension has some additional
162
+ characteristics to make it easier to access values.
163
+ */
164
+ ScExt.PathParser = {
165
+
166
+ _isArrayIndex: function (val) {
167
+ return isNaN(parseInt(val, 0)) === false;
168
+ },
169
+
170
+ _isRootObject: function(value) {
171
+ return value.match(/^\$/) === null ? false : true;
172
+ },
173
+
174
+ _isViewLayerId: function(value) {
175
+ return value.match(/^#/) === null ? false : true;
176
+ },
177
+
178
+ _getViewByLayerId: function(value) {
179
+ return $SC.View.views[value.replace('#', '')];
180
+ },
181
+
182
+ _getRootObject: function(value) {
183
+ var win = selenium.browserbot.getCurrentWindow();
184
+ return win[value.replace('$', '')];
185
+ },
186
+
187
+ _getObjectFromArray: function(array, index) {
188
+ var i = parseInt(index, 0);
189
+ if (array.objectAt) return array.objectAt(i);
190
+ return array[i];
191
+ },
192
+
193
+ _getObjectFromFunction: function(target, func, index) {
194
+ return func.call(target, parseInt(index, 0));
195
+ },
196
+
197
+ /**
198
+ Computes a complete object chain from the given path. The chain
199
+ represents all the objects used to access the final value from
200
+ the property path.
201
+
202
+ Parts that make up the property paths can include functions, arrays
203
+ and index values (e.g. 0, 1, 2...). When a function or array is
204
+ included it must be followed by an index value. The index will then
205
+ be passed back into the function or array. If a funtion is used then
206
+ the function must be preceeded by an object.
207
+
208
+ Examples:
209
+
210
+ 1> 'objA.objB.someValue' // Property path not using indexing
211
+ 2> 'objA.someArray.3' // Property path using indexing via an array (i.e. objA.someArray[3])
212
+ 3> 'objA.someFunction.4' // Property path using indexing via a function (i.e. objA.someFunction(4))
213
+
214
+ In example 2, the index 3 is supplied which follows the array. In example 3,
215
+ the index 4 is supplied following a function. The function 'someFunction' will
216
+ be called against objA. In addition, it is assumed that someFunction only accepts
217
+ one value.
218
+ */
219
+ computeObjectChain: function(path) {
220
+ var parts = path.split('.');
221
+
222
+ var objPathChain = [];
223
+ var current_obj = null;
224
+
225
+ // Determine what the starting object is
226
+ if (this._isRootObject(parts[0])) {
227
+ current_obj = this._getRootObject(parts[0]);
228
+ } else if (this._isViewLayerId(parts[0])) {
229
+ current_obj = this._getViewByLayerId(parts[0]);
230
+ } else {
231
+ current_obj = $App.getPath(parts[0]);
232
+ }
233
+
234
+ objPathChain.push(current_obj);
235
+ if ($SC.none(current_obj)) return objPathChain;
236
+
237
+ for (var i = 1; i < parts.length; i++) {
238
+ if (this._isArrayIndex(parts[i])) {
239
+ if (typeof objPathChain[i - 1] === "function") {
240
+ // Last part is a function, therefore invoke the function with the index
241
+ var target = objPathChain[i - 2];
242
+ var func = objPathChain[i - 1];
243
+ current_obj = this._getObjectFromFunction(target, func, parts[i]);
244
+ } else {
245
+ // Just assume that the previous object in the object chain is an actual array
246
+ var array = objPathChain[i - 1];
247
+ current_obj = this._getObjectFromArray(array, parts[i]);
248
+ }
249
+ } else {
250
+ if (current_obj.getPath && typeof current_obj.getPath === "function") {
251
+ // Object is a SC object. Use the get method
252
+ current_obj = current_obj.getPath(parts[i]);
253
+ } else if (current_obj.get && typeof current_obj.get === "function") {
254
+ current_obj = current_obj.get(parts[i]);
255
+ } else {
256
+ // Object is just a plain old JS object
257
+ current_obj = current_obj[parts[i]];
258
+ }
259
+ }
260
+
261
+ objPathChain.push(current_obj);
262
+ if ($SC.none(current_obj)) return objPathChain;
263
+ }
264
+
265
+ return objPathChain;
266
+ },
267
+
268
+ getPath: function(path, scClass) {
269
+ var chain = this.computeObjectChain(path);
270
+ if (chain.length === 0 ) return null;
271
+ var pathValue = chain[chain.length - 1];
272
+
273
+ if (!scClass) return pathValue;
274
+
275
+ if (ScExt.isScObject(pathValue) && pathValue.kindOf(scClass)) {
276
+ return pathValue;
277
+ } else {
278
+ return null;
279
+ }
280
+ }
281
+ };
282
+
283
+ /**
284
+ Convienient global variable to access the path parser since it is used so much. Use
285
+ like so:
286
+
287
+ var value = $ScPath.getPath('some.path.to.a.value');
288
+
289
+ */
290
+ var $ScPath = ScExt.PathParser;
291
+
292
+ /**
293
+ Used to simulate a mouse event using SproutCore's SC.Event object
294
+ */
295
+ ScExt.MouseEventSimulation = {
296
+
297
+ simulateEvent: function(mouseEvent, locator, x, y, button) {
298
+ var element = selenium.browserbot.findElement(locator);
299
+ event = $SC.Event.simulateEvent(element, mouseEvent, {
300
+ screenX: 0,
301
+ screenY: 0,
302
+ clientX: $SC.none(x) ? 0 : x,
303
+ clientY: $SC.none(x) ? 0 : y,
304
+ pageX: 0,
305
+ pageY: 0,
306
+ bubbles: true,
307
+ button: $SC.none(button) ? 0 : button,
308
+ altKey: selenium.browserbot.altKeyDown,
309
+ metaKey: selenium.browserbot.metaKeyDown,
310
+ ctrlKey: selenium.browserbot.controlKeyDown,
311
+ shiftKey: selenium.browserbot.shiftKeyDown
312
+ });
313
+ $SC.Event.trigger(element, mouseEvent, event);
314
+ },
315
+
316
+ /**
317
+ Will simulate a mouse down on a function key
318
+ */
319
+ mouseDown: function(locator, x, y) {
320
+ this.simulateEvent('mousedown', locator, x, y, 0);
321
+ },
322
+
323
+ /**
324
+ Will simulate a mouse up event
325
+ */
326
+ mouseUp: function(locator, x, y) {
327
+ this.simulateEvent('mouseup', locator, x, y, 0);
328
+ },
329
+
330
+ /**
331
+ Will simulate a right mouse down event
332
+ */
333
+ mouseDownRight: function(locator, x, y) {
334
+ this.simulateEvent('mousedown', locator, x, y, Selenium.RIGHT_MOUSE_CLICK);
335
+ },
336
+
337
+ /**
338
+ Will simulate a right mouse up event
339
+ */
340
+ mouseUpRight: function(locator, x, y) {
341
+ this.simulateEvent('mouseup', locator, x, y, Selenium.RIGHT_MOUSE_CLICK);
342
+ }
343
+
344
+ };
345
+
346
+ /**
347
+ Used to simulate a key event using SproutCore's SC.Event object. This is
348
+ needed since not all browsers all you to create a key event and change the
349
+ event's property. Meaning that the keyCode and charCode may be stuck with a
350
+ 0 value implying null.
351
+ */
352
+ ScExt.KeyEventSimulation = {
353
+
354
+ _functionKeyCode: function(value) {
355
+ for (var key in $SC.FUNCTION_KEYS) {
356
+ if ($SC.FUNCTION_KEYS[key] == value) return key;
357
+ }
358
+
359
+ return null;
360
+ },
361
+
362
+ _printableKeyCode: function(value) {
363
+ for (var key in $SC.PRINTABLE_KEYS) {
364
+ if ($SC.PRINTABLE_KEYS[key] == value) return key;
365
+ }
366
+
367
+ return null;
368
+ },
369
+
370
+ /**
371
+ Simulates a key event, such as key up, key down, key pressed. This is used to generate
372
+ a key event for a SproutCore application since in some browsers it is not possible to
373
+ dispatch a real key event, like in Apple's Safari. In Safari, when you try to generate
374
+ a KeyboardEvent, the keyCode and charCode properties are read-only, and their values
375
+ are set to 0 (null).
376
+
377
+ Note that since this only simulates a key event, the web browser will not be informed
378
+ of the event and as such any type of form input field will also not pick up on the event.
379
+ Therefore if you want to show a field being updated by the key event, you will also
380
+ have to update the input field's value seperately.
381
+
382
+ @param keyEvent {String} The key event to simulate (e.g. keyup, keydown)
383
+ @param locator {String} The locator path to the DOM element that is to "receive" the key event
384
+ @param keyCode {Integer} The key code for the key event. Represents the key on the keyboard
385
+ @param charCode {Integer} The char code for the key event. Represents the logical character code
386
+ */
387
+ simulateEvent: function(keyEvent, locator, keyCode, charCode) {
388
+ if (!keyCode && !charCode) return;
389
+
390
+ var element = selenium.browserbot.findElement(locator);
391
+ event = $SC.Event.simulateEvent(element, keyEvent, {
392
+ which: $SC.none(keyCode) ? charCode : keyCode,
393
+ charCode: charCode || 0,
394
+ keyCode: keyCode || 0,
395
+ altKey: selenium.browserbot.altKeyDown,
396
+ metaKey: selenium.browserbot.metaKeyDown,
397
+ ctrlKey: selenium.browserbot.controlKeyDown,
398
+ shiftKey: selenium.browserbot.shiftKeyDown
399
+ });
400
+
401
+ $SC.Event.trigger(element, keyEvent, event);
402
+ },
403
+
404
+ /**
405
+ Will simulate a key down on a function key
406
+ */
407
+ functionKeyDown: function(locator, key) {
408
+ this.simulateEvent('keydown', locator, this._functionKeyCode(key), null);
409
+ this.simulateEvent('keypress', locator, this._functionKeyCode(key), null);
410
+ },
411
+
412
+ /**
413
+ Will simulate a key up on a function key
414
+ */
415
+ functionKeyUp: function(locator, key) {
416
+ this.simulateEvent('keyup', locator, this._functionKeyCode(key), null);
417
+ },
418
+
419
+ /**
420
+ Will simulate a key down on a printable character
421
+ */
422
+ keyDown: function(locator, key) {
423
+ this.simulateEvent('keydown', locator, null, key.charCodeAt(0));
424
+ this.simulateEvent('keypress', locator, null, key.charCodeAt(0));
425
+ },
426
+
427
+ /**
428
+ Will simulate a key up on a printable character
429
+ */
430
+ keyUp: function(locator, key) {
431
+ this.simulateEvent('keyup', locator, this._printableKeyCode(key), null);
432
+ }
433
+
434
+ };
435
+
436
+
437
+ /**
438
+ TODO: This needs to be redone. The scheme used should something like JSON, not a
439
+ custom scheme. Got too experimental here.
440
+
441
+ The object decoder is used to decode encoded hashes and arrays that have been sent to
442
+ the browser via some Selenium remote control.
443
+
444
+ The parts that make up a hash and array can have specified types that indicate how the
445
+ part should be decoded. Types accepted are the following:
446
+
447
+ int --> represent the value as an integer
448
+ bool --> represent the value as an boolean (true/false)
449
+ regexp --> represent the value as a regular expression (case sensitive)
450
+ regexpi --> represent the value as a regular expression (case insensitive)
451
+ hash --> represent the value as a hash object
452
+ array --> represent the value as an array object
453
+
454
+ If no type is specified then it is assumed the type is a string. all parts that are represented
455
+ as a string must have the characters ,, :, [, ], and = be represented as the
456
+ following: [cma], [cln], [lsb], [rsb], [eql].
457
+
458
+ Hashes (maps, dictionaries) follow a standard <key>=<value> pattern where <value> follows
459
+ the pattern [<type>:]<string> (see above). Examples of encoded hashes:
460
+
461
+ foo=bar --> { "foo": "bar" }
462
+ foo.bar=cat --> { "foo.bar": "cat"}
463
+ foo=cat,bar=dog --> { "foo": "cat", "bar": "dog" }
464
+ company=Acme[comma] Inc --> { "company": "Acme, Inc" }
465
+ foo=regexp:abc --> { "foo": /abc/ }
466
+ foo=regexpi:abc --> { "foo": /abc/i }
467
+ foo=regexp:a[eql]b --> { "foo": /a=b/ }
468
+ foo=int:100 --> { "foo": 100 }
469
+ foo=100 --> { "foo": "100" }
470
+ foo=bool:true --> { "foo": true }
471
+ foo=bool:false --> { "foo": false }
472
+ foo=true --> { "foo": "true" }
473
+
474
+ Depending on how the hash is being used, the key can be used as a SproutCore
475
+ property path. Therefore, the key can be something like "foo.bar.cat".
476
+
477
+ */
478
+ ScExt.ObjectDecoder = {
479
+
480
+ _decodeEncodedChars: function(value) {
481
+ if (value.match(/\[.*\]/)) {
482
+ var val = value.replace(/\[cma\]/g, ",");
483
+ val = val.replace(/\[cln\]/g, ":");
484
+ val = val.replace(/\[eql\]/g, "=");
485
+ val = val.replace(/\[lsb\]/g, "[");
486
+ val = val.replace(/\[rsb\]/g, "]");
487
+ return val;
488
+ }
489
+
490
+ return value;
491
+ },
492
+
493
+ _decodeValue: function(encodedValue) {
494
+ var parts = encodedValue.match(/^(.*):(.*)/);
495
+ if (parts) {
496
+ var type = parts[1];
497
+ var value = parts[2];
498
+
499
+ if (type === "int") return parseInt(value, 10);
500
+ if (type === "bool") return value === "true" ? true : false;
501
+ if (type === "hash") return this.decodeHash(this._decodeEncodedChars(value));
502
+ if (type === "array") return this.decodeArray(this._decodeEncodedChars(value));
503
+ if (type === "regexp") return new RegExp(this._decodeEncodedChars(value));
504
+ if (type === "regexpi") return new RegExp(this._decodeEncodedChars(value), "i");
505
+ if (type === "null") return null;
506
+ if (type === "undefined") return undefined;
507
+
508
+ // Assume value is just a regular string
509
+ return this._decodeEncodedChars(value);
510
+ }
511
+
512
+ return this._decodeEncodedChars(encodedValue);
513
+ },
514
+
515
+ /**
516
+ Decodes an encoded array. Returns a hash object
517
+ */
518
+ decodeArray: function(encodedArray) {
519
+ var parts = encodedArray.split(',');
520
+
521
+ var array = [];
522
+ for (var i = 0; i < parts.length; i++) {
523
+ var value = parts[i];
524
+ array.push(this._decodeValue(value));
525
+ }
526
+
527
+ return array;
528
+ },
529
+
530
+ /**
531
+ Decodes an encoded hash. Returns a hash object
532
+ */
533
+ decodeHash: function(encodedHash) {
534
+ var parts = encodedHash.split(',');
535
+
536
+ var hash = {};
537
+ for (var i = 0; i < parts.length; i++) {
538
+ var part = parts[i];
539
+ var args = part.split('=');
540
+ var key = args[0];
541
+ var value = args[1];
542
+ hash[key] = this._decodeValue(value);
543
+ }
544
+
545
+ return hash;
546
+ }
547
+
548
+ };
549
+
550
+ /**
551
+ Used to lookup objects in an enumerable type based on a set of filter
552
+ criteria. Calling the lookup method will return an array of objects that
553
+ match the given filter criteria. Calling the lookupIndexes will return
554
+ an array of indexes for the objects that match the given filter criteria.
555
+
556
+ A filter is a hash object made up of key-value pairs. The key represent
557
+ the key/property on the objects to check against. The value is what to
558
+ match again for the given key. Values can either be strings, numbers,
559
+ boolean values, or regular expressions. There are special filter criteria
560
+ keys that can also be used such as sc_guid and sc_type.
561
+
562
+ Some examples filters are the following:
563
+
564
+ { name: 'foo', age: 20 }
565
+ { company: /inc$/i }
566
+ { isEmployeed: true }
567
+ { sc_type: 'SC.ButtonView' }
568
+ { sc_guid: 'sc8934' }
569
+
570
+ A matching object are those objects that meet all of the filer criteria.
571
+ As an example, let's say we have the following object array:
572
+
573
+ [
574
+ SC.Object.create({ name: 'foo' }),
575
+ SC.Object.create({ name: 'foobar' }),
576
+ SC.Object.create({ name: 'bar' })
577
+ ]
578
+
579
+ and our filter is { name: /foo/i }, then only the first and second objects
580
+ in the array match.
581
+ */
582
+ ScExt.ObjectArrayLookup = {
583
+
584
+ LOOKUP_KEY_SC_GUID: "sc_guid",
585
+ LOOKUP_KEY_SC_TYPE: 'sc_type',
586
+
587
+ _isMatchingObject: function(object, filter) {
588
+
589
+ var klass = null;
590
+ if (!$SC.none(filter[this.LOOKUP_KEY_SC_TYPE])) {
591
+ klass = ScExt.string2ScClass(filter[this.LOOKUP_KEY_SC_TYPE]);
592
+ }
593
+
594
+ for (var key in filter) {
595
+ var value = filter[key];
596
+ if (key === this.LOOKUP_KEY_SC_GUID) {
597
+ if (value !== $SC.guidFor(object)) return false;
598
+ }
599
+ else if (key === this.LOOKUP_KEY_SC_TYPE) {
600
+ if (!object.isObject) return false;
601
+ if (!object.kindOf(klass)) return false;
602
+ }
603
+ else {
604
+ var objValue = object.get ? object.get(key) : object[key];
605
+ if (objValue === undefined) return false;
606
+ if (value instanceof RegExp) {
607
+ if (!(typeof(objValue) === "string")) return false;
608
+ if (!objValue.match(value)) return false;
609
+ } else {
610
+ if (objValue !== value) return false;
611
+ }
612
+ }
613
+ }
614
+
615
+ return true;
616
+ },
617
+
618
+ lookupIndexes: function(objects, filter) {
619
+ if (!objects || !filter) return null;
620
+
621
+ if (!(filter instanceof Object)) return null;
622
+
623
+ if (!objects.isSCArray) {
624
+ if (!objects.isEnumerable) return null;
625
+ objects = objects.toArray();
626
+ }
627
+
628
+ var matches = [];
629
+ for (var index = 0; index < objects.get('length'); index++) {
630
+ var obj = objects.objectAt(index);
631
+ var match = this._isMatchingObject(obj, filter);
632
+ if (match) {
633
+ matches.push(index);
634
+ if (!$SC.none(filter[this.LOOKUP_KEY_SC_GUID])) return matches;
635
+ }
636
+ }
637
+
638
+ return matches;
639
+ },
640
+
641
+ lookup: function(objects, filter) {
642
+ var indexes = this.lookupIndexes(objects, filter);
643
+ var objs = [];
644
+ for (var i = 0; i < indexes.length; i++) {
645
+ objs.push(objects.objectAt(indexes[i]));
646
+ }
647
+
648
+ return objs;
649
+ }
650
+
651
+ };
652
+
653
+ /**
654
+ Used specifically to check for special properties assigned to a collection
655
+ view's content objects via an assigned content delegate.
656
+ */
657
+ ScExt.CollectionView = {
658
+
659
+ _validIndex: function(collectionView, index) {
660
+ var content = collectionView.get('content');
661
+ return (typeof index === "number" && index >= 0 && index < content.get('length'));
662
+ },
663
+
664
+ getContentGroupIndexes: function(collectionView) {
665
+ var content = collectionView.get('content');
666
+ var del = collectionView.get('contentDelegate');
667
+ var suggestedGroupIndexes = del.contentGroupIndexes(collectionView, content);
668
+ if (!suggestedGroupIndexes) return [];
669
+
670
+ var confirmedGroupindexes = [];
671
+
672
+ suggestedGroupIndexes.forEach(function(index) {
673
+ if (del.contentIndexIsGroup(collectionView, content, index)) {
674
+ confirmedGroupindexes.push(index);
675
+ }
676
+ }, this);
677
+
678
+ return confirmedGroupindexes;
679
+ },
680
+
681
+ getContentIsSelected: function(collectionView, index) {
682
+ if (!this._validIndex(collectionView, index)) return false;
683
+ var content = collectionView.get('content');
684
+ var obj = content.objectAt(index);
685
+ var selection = collectionView.get('selection');
686
+ if (!selection) return false;
687
+ var value = selection.containsObject(obj);
688
+ return value;
689
+ },
690
+
691
+ getContentIsGroup: function(collectionView, index) {
692
+ if (!this._validIndex(collectionView, index)) return false;
693
+ var content = collectionView.get('content');
694
+ var del = collectionView.get('contentDelegate');
695
+ var suggestedGroupIndexes = del.contentGroupIndexes(collectionView, content);
696
+ if (!suggestedGroupIndexes.contains(index)) return false;
697
+ var value = del.contentIndexIsGroup(collectionView, content, index);
698
+ return value;
699
+ },
700
+
701
+ getContentDisclosureState: function(collectionView, index) {
702
+ if (!this._validIndex(collectionView, index)) return -1;
703
+ var content = collectionView.get('content');
704
+ var del = collectionView.get('contentDelegate');
705
+ var value = del.contentIndexDisclosureState(collectionView, content, index);
706
+ return value;
707
+ },
708
+
709
+ getContentOutlineLevel: function(collectionView, index) {
710
+ if (!this._validIndex(collectionView, index)) return -1;
711
+ var content = collectionView.get('content');
712
+ var del = collectionView.get('contentDelegate');
713
+ var value = del.contentIndexOutlineLevel(collectionView, content, index);
714
+ return value;
715
+ }
716
+
717
+ };
718
+
719
+ ///////////////////// Selenium Core API Extensions - Actions for Testing ////////////////////////////////////
720
+
721
+ /**
722
+ Private. This is intended for debugging/testing purposes only
723
+ */
724
+ Selenium.prototype.doScTestObjectArrayLookup = function(params) {
725
+ var hash = ScExt.ObjectDecoder.decodeHash(params);
726
+ var key = hash.key;
727
+ var path = hash.path;
728
+ var lookupParams = hash.lookup;
729
+
730
+ var array = $ScPath.getPath(path);
731
+ var objs = ScExt.ObjectArrayLookup.lookup(array, lookupParams);
732
+ if (!window.$__looked_up_array_objects__) window.$__looked_up_array_objects__ = {};
733
+ window.$__looked_up_array_objects__[key] = {
734
+ path: path,
735
+ array: array,
736
+ lookupParams: lookupParams,
737
+ objects: objs
738
+ };
739
+ };
740
+
741
+ /**
742
+ Private. This is intended for debugging/testing purposes only
743
+ */
744
+ Selenium.prototype.doScTestComputePropertyPath = function(key, path) {
745
+ var chain = ScExt.PathParser.computeObjectChain(path);
746
+ var value = ScExt.PathParser.getPath(path);
747
+ if (!window.$__computed_property_paths__) window.$__computed_property_paths__ = {};
748
+ window.$__computed_property_paths__[key] = {
749
+ path: path,
750
+ objChain: chain,
751
+ value: value
752
+ };
753
+ };
754
+
755
+ /**
756
+ Private. This is intended for debugging/testing purposes only
757
+ */
758
+ Selenium.prototype.doScTestDecodingEncodedHash = function(key, hash) {
759
+ var obj = ScExt.ObjectDecoder.decodeHash(hash);
760
+ if (!window.$__decoded_hashes__) window.$__decoded_hashes__ = {};
761
+ window.$__decoded_hashes__[key] = {
762
+ encoded: hash,
763
+ decoded: obj
764
+ };
765
+ };
766
+
767
+ /**
768
+ Private. This is intended for debugging/testing purposes only
769
+ */
770
+ Selenium.prototype.doScTestDecodingEncodedArray = function(key, array) {
771
+ var obj = ScExt.ObjectDecoder.decodeArray(array);
772
+ if (!window.$__decoded_arrays__) window.$__decoded_arrays__ = {};
773
+ window.$__decoded_arrays__[key] = {
774
+ encoded: array,
775
+ decoded: obj
776
+ };
777
+ };
778
+
779
+ ///////////////////// Selenium Core API Extensions - Action to Setup User Extensions File ////////////////////////////////////
780
+
781
+ /**
782
+ Sets the name of the SproutCore-based application. This is important as
783
+ it will eventually be used to initalize the $App core global variable. This
784
+ must be done before calling doInitializeScSeleniumExtension.
785
+ */
786
+ Selenium.prototype.doSetScApplicationName = function(name) {
787
+ this._sc_applicationName = name;
788
+ };
789
+
790
+ /**
791
+ Main method to be invoked before you start to test a SproutCore-based
792
+ application. This method will check to make sure the SproutCore framework
793
+ and application are indeed loaded and accessible. In addition, it is
794
+ solely responsible for setting up the core global variables to program
795
+ again the SC framework and application.
796
+ */
797
+ Selenium.prototype.doOpenScApplication = function(appRootPath, timeoutInSeconds) {
798
+
799
+ var bot = selenium.browserbot;
800
+ var win = bot.getCurrentWindow();
801
+ var loc = bot.baseUrl + appRootPath;
802
+
803
+ if (win.location.href !== loc) win.location.href = loc;
804
+
805
+ // First set up closure
806
+ var appName = this._sc_applicationName;
807
+
808
+ var timeout = timeoutInSeconds ? (parseInt(timeoutInSeconds, 10) * 1000) : Selenium.DEFAULT_TIMEOUT;
809
+
810
+ // Now run the wait function which will keep checking until
811
+ // either the SproutCore framework and application are found or
812
+ // the time out is reached. The function will also set up some
813
+ // core global variables to make it easier to program againt
814
+ // the SC framework and application
815
+ return Selenium.decorateFunctionWithTimeout(function () {
816
+
817
+ var win = selenium.browserbot.getCurrentWindow();
818
+
819
+ // First check if there is a SproutCore framework
820
+ if (!win.SC) return false;
821
+ $SC = win.SC; // Set up the first core global variable
822
+
823
+ // Found SC. Now check for the root application object
824
+ var application = win[appName];
825
+ if (!application) return false;
826
+ $App = application; // Set up the second core global variable
827
+
828
+ return true;
829
+ }, timeout, this);
830
+ };
831
+
832
+ ///////////////////// Selenium Core API Extensions - Actions ////////////////////////////////////
833
+
834
+ /**
835
+ Move the application window to a given x-y coordinate
836
+ */
837
+ Selenium.prototype.doScWindowMoveTo = function(x, y) {
838
+ var win = selenium.browserbot.getCurrentWindow();
839
+ win.moveTo(x*1, y*1);
840
+ };
841
+
842
+ /**
843
+ Resize the application window by a width and height
844
+ */
845
+ Selenium.prototype.doScWindowResizeTo = function(width, height) {
846
+ var win = selenium.browserbot.getCurrentWindow();
847
+ win.resizeTo(width*1, height*1);
848
+ };
849
+
850
+ /**
851
+ Maximizes the applicaiton window
852
+ */
853
+ Selenium.prototype.doScWindowMaximize = function() {
854
+ var win = selenium.browserbot.getCurrentWindow();
855
+ win.resizeTo(window.screen.availWidth, window.screen.availHeight);
856
+ win.moveTo(1,1); // Slight offset from origin so that Firefox will actually move the window
857
+ };
858
+
859
+ /**
860
+ Action to call a view's scrollToVisible method. The path must point
861
+ to an actual SC view object.
862
+ */
863
+ Selenium.prototype.doScViewScrollToVisible = function(path) {
864
+ var view = $ScPath.getPath(path, $SC.View);
865
+ if (!view) return;
866
+ ScExt.viewScrollToVisible(view);
867
+ };
868
+
869
+ /**
870
+ Action to raise a mouse down event
871
+ */
872
+ Selenium.prototype.doScMouseDown = function(locator) {
873
+ try {
874
+ this.doMouseDown(locator);
875
+ } catch (ex) {}
876
+ };
877
+
878
+ /**
879
+ Action to raise a mouse up event
880
+ */
881
+ Selenium.prototype.doScMouseUp = function(locator) {
882
+ try {
883
+ this.doMouseUp(locator);
884
+ } catch (ex) {}
885
+ };
886
+
887
+ /**
888
+ Action to raise a right mouse down event
889
+ */
890
+ Selenium.prototype.doScMouseDownRight = function(locator) {
891
+ try {
892
+ this.doMouseDownRight(locator);
893
+ } catch (ex) {}
894
+ };
895
+
896
+ /**
897
+ Action to raise a right mouse up event
898
+ */
899
+ Selenium.prototype.doScMouseUpRight = function(locator) {
900
+ try {
901
+ this.doMouseUpRight(locator);
902
+ } catch (ex) {}
903
+ };
904
+
905
+ /**
906
+ Action performs a single click that is recognized by the SproutCore framework.
907
+ */
908
+ Selenium.prototype.doScClick = function(locator) {
909
+ this.doScMouseDown(locator);
910
+ this.doScMouseUp(locator);
911
+ };
912
+
913
+ /**
914
+ Action performs a single right click that is recognized by the SproutCore framework.
915
+ */
916
+ Selenium.prototype.doScRightClick = function(locator) {
917
+ this.doScMouseDownRight(locator);
918
+ this.doScMouseUpRight(locator);
919
+ };
920
+
921
+ /**
922
+ Action performs a double click that is recognized by the SproutCore framework.
923
+ */
924
+ Selenium.prototype.doScDoubleClick = function(locator) {
925
+ this.doScClick(locator);
926
+ this.doScClick(locator);
927
+ };
928
+
929
+ /** @private
930
+ Check that the element is a valid text entry field
931
+ */
932
+ Selenium.prototype._validTextEntryField = function(element) {
933
+ var isTextInputField = element.tagName.toLowerCase() === 'input' && element.type.toLowerCase() === "text";
934
+ var isTextArea = element.tagName.toLowerCase() === 'textarea';
935
+ return isTextInputField || isTextArea;
936
+ };
937
+
938
+ /**
939
+ Action performs a key down on a printable character
940
+ */
941
+ Selenium.prototype.doScKeyDown = function(locator, key) {
942
+ ScExt.KeyEventSimulation.keyDown(locator, key);
943
+
944
+ var element = this.browserbot.findElement(locator);
945
+
946
+ if (this._validTextEntryField(element)) {
947
+ var value = element.value;
948
+ if (this.browserbot.shiftKeyDown) key = key.toUpperCase();
949
+ value = value + key;
950
+ element.value = value;
951
+ $SC.Event.trigger(element, 'change');
952
+ }
953
+ };
954
+
955
+ /**
956
+ Action performs a key up on a printable character
957
+ */
958
+ Selenium.prototype.doScKeyUp = function(locator, key) {
959
+ ScExt.KeyEventSimulation.keyUp(locator, key);
960
+ };
961
+
962
+ /**
963
+ Action performs a key down on a function key
964
+ */
965
+ Selenium.prototype.doScFunctionKeyDown = function(locator, key) {
966
+ ScExt.KeyEventSimulation.functionKeyDown(locator, key);
967
+
968
+ var element = this.browserbot.findElement(locator);
969
+
970
+ if (this._validTextEntryField(element)) {
971
+ // Need to simulate special function keys
972
+ var value = element.value;
973
+ if (key === 'backspace' || key === 'delete') {
974
+ if (value.length > 0) {
975
+ element.value = value.slice(0, value.length - 1);
976
+ $SC.Event.trigger(element, 'change');
977
+ }
978
+ }
979
+ else if (key === 'insert' || key === 'return') {
980
+ value = value + '\n';
981
+ element.value = value;
982
+ $SC.Event.trigger(element, 'change');
983
+ }
984
+ }
985
+ };
986
+
987
+ /**
988
+ Action performs a key up on a function key
989
+ */
990
+ Selenium.prototype.doScFunctionKeyUp = function(locator, key) {
991
+ ScExt.KeyEventSimulation.functionKeyUp(locator, key);
992
+ };
993
+
994
+ /**
995
+ Action performs a typing of an individual function key. A key down followed by
996
+ a key up event.
997
+ */
998
+ Selenium.prototype.doScTypeFunctionKey = function(locator, key) {
999
+ this.doScFunctionKeyDown(locator, key);
1000
+ this.doScFunctionKeyUp(locator, key);
1001
+ };
1002
+
1003
+ /**
1004
+ Action performs a typing of an individual printable character. A key down followed by
1005
+ a key up event.
1006
+ */
1007
+ Selenium.prototype.doScTypeKey = function(locator, key) {
1008
+ this.doScKeyDown(locator, key);
1009
+ this.doScKeyUp(locator, key);
1010
+ };
1011
+
1012
+ /**
1013
+ Used to perform clean up on a core query object when no longer used
1014
+
1015
+ @see #getScCoreQuery
1016
+ */
1017
+ Selenium.prototype.doScCoreQueryDone = function(handle) {
1018
+ this._unregisterCoreQueryObject(handle);
1019
+ };
1020
+
1021
+ ///////////////////// Selenium Core API Extensions - Accessors ////////////////////////////////////
1022
+
1023
+ /**
1024
+ Returns the SproutCore type for the given path. If the path does not
1025
+ point to a SproutCore object the null is returned.
1026
+ */
1027
+ Selenium.prototype.getScTypeOf = function(path) {
1028
+ var value = $ScPath.getPath(path);
1029
+ return $SC.typeOf(value);
1030
+ };
1031
+
1032
+ /**
1033
+ Returns the basic SproutCore type for the content that make up an
1034
+ array. If the path does not point to an array then an empty string
1035
+ is returned.
1036
+ */
1037
+ Selenium.prototype.getScTypeOfArrayContent = function(path) {
1038
+ var array = $ScPath.getPath(path);
1039
+ if ($SC.typeOf(array) !== $SC.T_ARRAY) return "";
1040
+
1041
+ return ScExt.typeOfArrayContent(array);
1042
+ };
1043
+
1044
+ /**
1045
+ Returns a SproutCore object's GUID
1046
+ */
1047
+ Selenium.prototype.getScGuid = function(path) {
1048
+ var value = $ScPath.getPath(path, $SC.Object);
1049
+ var guid = $SC.guidFor(value);
1050
+ return guid;
1051
+ };
1052
+
1053
+ /**
1054
+ Returns a SproutCore object's type
1055
+ */
1056
+ Selenium.prototype.getScObjectClassName = function(path) {
1057
+ var obj = $ScPath.getPath(path, $SC.Object);
1058
+ if (!obj) return "";
1059
+ var className = $SC._object_className(obj.constructor);
1060
+ return (className === 'Anonymous') ? "" : className;
1061
+ };
1062
+
1063
+ /**
1064
+ Checks if a SproutCore object is a kind of type
1065
+ */
1066
+ Selenium.prototype.isScObjectKindOfClass = function(path, className) {
1067
+ var obj = $ScPath.getPath(path, $SC.Object);
1068
+ if (!obj) return false;
1069
+
1070
+ var klass = ScExt.string2ScClass(className);
1071
+ if (!klass) return false;
1072
+
1073
+ return obj.kindOf(klass);
1074
+ };
1075
+
1076
+ /**
1077
+ Returns an array of strings representing all the classes a SproutCore
1078
+ object derives from.
1079
+ */
1080
+ Selenium.prototype.getScObjectClassNames = function(path) {
1081
+ var obj = $ScPath.getPath(path, $SC.Object);
1082
+ var classNames = ScExt.getScObjectClassNames(obj);
1083
+ return classNames;
1084
+ };
1085
+
1086
+ /**
1087
+ Accessor gets a value from a given property path
1088
+ */
1089
+ Selenium.prototype.getScPropertyValue = function(path) {
1090
+ var value = $ScPath.getPath(path);
1091
+ return value;
1092
+ };
1093
+
1094
+ /**
1095
+ Accessor gets a localized string from the given string provided. This is done
1096
+ through the SproutCore framework.
1097
+ */
1098
+ Selenium.prototype.getScLocalizedString = function(str) {
1099
+ var win = this.browserbot.getCurrentWindow();
1100
+ return win.eval("'" + str + "'.loc()");
1101
+ };
1102
+
1103
+ /**
1104
+ Gets the layer of a view. In order to transfer data back to server, the
1105
+ method actually returns the outer HTML of the layer instead of having translate
1106
+ DOM elements into a string.
1107
+ */
1108
+ Selenium.prototype.getScViewLayer = function(path) {
1109
+ var view = $ScPath.getPath(path, $SC.View);
1110
+ if (!view) return "";
1111
+ return view.get('layer').outerHTML;
1112
+ };
1113
+
1114
+ /**
1115
+ Gets a SproutCore view's frame
1116
+ */
1117
+ Selenium.prototype.getScViewFrame = function(path) {
1118
+ var view = $ScPath.getPath(path, $SC.View);
1119
+ if (!view) return "";
1120
+ var frame = view.get('frame');
1121
+ if (!frame) return null;
1122
+ return [frame.width, frame.height, frame.x, frame.y];
1123
+ };
1124
+
1125
+ /**
1126
+ Gets a DOM element's current window position
1127
+ */
1128
+ Selenium.prototype.getScElementWindowPosition = function(path) {
1129
+ var x = this.getElementPositionLeft(path);
1130
+ var y = this.getElementPositionTop(path);
1131
+ return [x, y];
1132
+ };
1133
+
1134
+ /**
1135
+ Gets the indexes of objects in an array that match a given lookup filter
1136
+ */
1137
+ Selenium.prototype.getScObjectArrayIndexLookup = function(path, lookupParams) {
1138
+ var params = ScExt.ObjectDecoder.decodeHash(lookupParams);
1139
+ var array = $ScPath.getPath(path);
1140
+ var indexes = ScExt.ObjectArrayLookup.lookupIndexes(array, params);
1141
+ return indexes;
1142
+ };
1143
+
1144
+ /////// SC Core Query Specific Selenium Calls /////////////////
1145
+
1146
+ /**
1147
+ Accessor is used to create a core query object based on a given CSS selector. The core query
1148
+ object is obtained through a view using a SC property path. Once the core query object is
1149
+ created an numeric handle ID is returned so that you can use subsequent commands to use
1150
+ the object.
1151
+
1152
+ Always call this method first before using any other related methods since they all require
1153
+ a handler to a core query object.
1154
+
1155
+ When finished with the core query, call doScCoreQueryDone to release the handle
1156
+
1157
+ @return a positive handle ID if the path provided points to a view object,
1158
+ otherwise -1 is returned
1159
+ */
1160
+ Selenium.prototype.getScCoreQuery = function(path, selector) {
1161
+ var view = $ScPath.getPath(path, $SC.View);
1162
+ if (!view) return -1;
1163
+
1164
+ var cq = null;
1165
+ cq = (!selector || selector === "") ? view.$() : view.$(selector);
1166
+
1167
+ var handle = this._registerCoreQueryObject(cq);
1168
+
1169
+ return handle;
1170
+ };
1171
+
1172
+ /**
1173
+ Will register a SC core query object. Once registered a handler will be
1174
+ returned in order to access the core object for subsequent use.
1175
+
1176
+ @return a numeric handler that is used to access the core query object
1177
+
1178
+ @see #_getCoreQueryObject
1179
+ */
1180
+ Selenium.prototype._registerCoreQueryObject = function(cq) {
1181
+ if (!this._registeredCoreQueries) this._registeredCoreQueries = {};
1182
+ if (!this._nextCoreQueryHandle) this._nextCoreQueryHandle = 0;
1183
+
1184
+ // Using ++ operator has a bizarre quirk in JS
1185
+ this._nextCoreQueryHandle = this._nextCoreQueryHandle + 1;
1186
+
1187
+ this._registeredCoreQueries["handle_" + this._nextCoreQueryHandle] = cq;
1188
+
1189
+ return this._nextCoreQueryHandle;
1190
+ };
1191
+
1192
+ /**
1193
+ Will unregister a registered core query object. This will help free it from
1194
+ memory.
1195
+
1196
+ @see #_registerCoreQueryObject
1197
+ */
1198
+ Selenium.prototype._unregisterCoreQueryObject = function(handle) {
1199
+ if (!this._registeredCoreQueries) return;
1200
+ delete this._registeredCoreQueries["handle_" + handle];
1201
+ };
1202
+
1203
+ /**
1204
+ Used to retrieved a registered core query object using a handler. If
1205
+ the handler does not point to a core query object then null is returned.
1206
+
1207
+ @see #_registerCoreQueryObject
1208
+ */
1209
+ Selenium.prototype._getCoreQueryObject = function(handle) {
1210
+ if (!this._registeredCoreQueries) return null;
1211
+ return this._registeredCoreQueries["handle_" + handle];
1212
+ };
1213
+
1214
+ /**
1215
+ Will get the number of elements contained in the core query object based on
1216
+ the selector used.
1217
+
1218
+ @param handle {Number} the handle to the core query object
1219
+ @see #getScCoreQuery
1220
+ */
1221
+ Selenium.prototype.getScCoreQuerySize = function(handle) {
1222
+ var cq = this._getCoreQueryObject(handle);
1223
+ return cq.size();
1224
+ };
1225
+
1226
+ /**
1227
+ Will get an element's classes. The element comes from a core query object.
1228
+
1229
+ @param handle {Number} the handle to the core query object
1230
+ @param elemIndex {Number} index to the element in the core query object
1231
+ @see #getScCoreQuery
1232
+ */
1233
+ Selenium.prototype.getScCoreQueryElementClasses = function(handle, elemIndex) {
1234
+ var cq = this._getCoreQueryObject(handle);
1235
+ var elem = cq.get(elemIndex);
1236
+ if (!elem) return "";
1237
+ return elem.className;
1238
+ };
1239
+
1240
+ /**
1241
+ Will get an element's outer HTML. The element comes from a core query object.
1242
+
1243
+ @param handle {Number} the handle to the core query object
1244
+ @param elemIndex {Number} index to the element in the core query object
1245
+ @see #getScCoreQuery
1246
+ */
1247
+ Selenium.prototype.getScCoreQueryElementHTML = function(handle, elemIndex) {
1248
+ var cq = this._getCoreQueryObject(handle);
1249
+ var elem = cq.get(elemIndex);
1250
+ if (!elem) return "";
1251
+ return elem.outerHTML;
1252
+ };
1253
+
1254
+ /**
1255
+ Will get an element's attribute. The element comes from a core query object. The
1256
+ attribute can be any attribute you find on a DOM element.
1257
+
1258
+ @param handle {Number} the handle to the core query object
1259
+ @param elemIndexPlusAttr {String} must follow the patter <elem index>:<attribute>, where
1260
+ <elem index> is the index to the DOM element in the core query object and
1261
+ <attribute> is the attribute you want to get from the element. Because of the way
1262
+ the Selenium Core works, you are only allowed to provide a command two argments,
1263
+ which is very silly. So the second and third argument had to be combined into one.
1264
+ Hence the need to follow a pattern. For more details see the following function:
1265
+
1266
+ _createCommandFromRequest
1267
+
1268
+ located in the selenium-remoterunner.js file
1269
+
1270
+ @see #getScCoreQuery
1271
+ */
1272
+ Selenium.prototype.getScCoreQueryElementAttribute = function(handle, elemIndexPlusAttr) {
1273
+ var arg2 = elemIndexPlusAttr.split(':');
1274
+ var elemIndex = arg2[0];
1275
+ var attr = arg2[1];
1276
+
1277
+ var cq = this._getCoreQueryObject(handle);
1278
+ var elem = cq.get(elemIndex);
1279
+ if (!elem) return "";
1280
+ return elem.getAttribute(attr);
1281
+ };
1282
+
1283
+ /**
1284
+ Will get an element's text. The element comes from a core query object.
1285
+
1286
+ @param handle {Number} the handle to the core query object
1287
+ @param elemIndex {Number} index to the element in the core query object
1288
+ @see #getScCoreQuery
1289
+ */
1290
+ Selenium.prototype.getScCoreQueryElementText = function(handle, elemIndex) {
1291
+ var cq = this._getCoreQueryObject(handle);
1292
+ var elem = cq.get(elemIndex);
1293
+ if (!elem) return "";
1294
+ if (elem.textContent) return elem.textContent;
1295
+ if (elem.text) return elem.text;
1296
+ return "";
1297
+ };
1298
+
1299
+ /**
1300
+ Will get an element's HTML tag. The element comes from a core query object.
1301
+
1302
+ @param handle {Number} the handle to the core query object
1303
+ @param elemIndex {Number} index to the element in the core query object
1304
+ @see #getScCoreQuery
1305
+ */
1306
+ Selenium.prototype.getScCoreQueryElementTag = function(handle, elemIndex) {
1307
+ var cq = this._getCoreQueryObject(handle);
1308
+ var elem = cq.get(elemIndex);
1309
+ if (!elem) return "";
1310
+ return elem.tagName;
1311
+ };
1312
+
1313
+ /////// SC Collection View Specific Selenium Calls /////////////////
1314
+
1315
+ Selenium.prototype.getScCollectionViewContentGroupIndexes = function(path) {
1316
+ var collectionView = $ScPath.getPath(path, $SC.CollectionView);
1317
+ if (!collectionView) return [];
1318
+ return ScExt.CollectionView.getContentGroupIndexes(collectionView);
1319
+ };
1320
+
1321
+ Selenium.prototype.getScCollectionViewContentSelectedIndexes = function(path) {
1322
+ var collectionView = $ScPath.getPath(path, $SC.CollectionView);
1323
+ if (!collectionView) return [];
1324
+ var selectionSet = collectionView.get('selection');
1325
+ if (!selectionSet) return [];
1326
+ var content = collectionView.get('content');
1327
+ return ScExt.indexSet2Array(selectionSet.indexSetForSource(content));
1328
+ };
1329
+
1330
+ Selenium.prototype.getScCollectionViewContentNowShowingIndexes = function(path) {
1331
+ var collectionView = $ScPath.getPath(path, $SC.CollectionView);
1332
+ if (!collectionView) return [];
1333
+ return ScExt.indexSet2Array(collectionView.get('nowShowing'));
1334
+ };
1335
+
1336
+ Selenium.prototype.getScCollectionViewContentIsGroup = function(path, index) {
1337
+ var collectionView = $ScPath.getPath(path, $SC.CollectionView);
1338
+ if (!collectionView) -1;
1339
+ return ScExt.CollectionView.getContentIsGroup(collectionView, parseInt(index, 0));
1340
+ };
1341
+
1342
+ Selenium.prototype.getScCollectionViewContentIsSelected = function(path, index) {
1343
+ var collectionView = $ScPath.getPath(path, $SC.CollectionView);
1344
+ if (!collectionView) -1;
1345
+ return ScExt.CollectionView.getContentIsSelected(collectionView, parseInt(index, 0));
1346
+ };
1347
+
1348
+ Selenium.prototype.getScCollectionViewContentDisclosureState = function(path, index) {
1349
+ var collectionView = $ScPath.getPath(path, $SC.CollectionView);
1350
+ if (!collectionView) -1;
1351
+ return ScExt.CollectionView.getContentDisclosureState(collectionView, parseInt(index, 0));
1352
+ };
1353
+
1354
+ Selenium.prototype.getScCollectionViewContentOutlineLevel = function(path, index) {
1355
+ var collectionView = $ScPath.getPath(path, $SC.CollectionView);
1356
+ if (!collectionView) -1;
1357
+ return ScExt.CollectionView.getContentOutlineLevel(collectionView, parseInt(index, 0));
1358
+ };
1359
+
1360
+ ///////////////////// Selenium Core API Extensions - Locators ////////////////////////////////////
1361
+
1362
+ /**
1363
+ This locator is used access a SC view's root DOM element. ONLY use this locator for actions
1364
+ that expect a DOM element.
1365
+
1366
+ A SC path follows the standard property dot-path notation, such as the following:
1367
+
1368
+ MyApp.mainPage.mainPane.someButton
1369
+
1370
+ For all paths supplied they must be relative to the root application object.
1371
+
1372
+ To use this locator via the Selenium RC, the locator text must follow the given
1373
+ pattern:
1374
+
1375
+ scPath=<your.sc.path.to.a.view.goes.here>
1376
+
1377
+ As an example:
1378
+
1379
+ clientDriver.click('scPath=mainPage.mainPane.someButton')
1380
+
1381
+ If the root application object is called MyApp, then the view will be accessed
1382
+ like so:
1383
+
1384
+ view = MyApp.getPath('mainPage.mainPane.someButton)
1385
+
1386
+ */
1387
+ PageBot.prototype.locateElementByScPath = function(path) {
1388
+
1389
+ var obj = $ScPath.getPath(path, $SC.View);
1390
+ if (!obj) return null;
1391
+
1392
+ // Return the view's layer. The layer is the root DOM element of the view
1393
+ return obj.get('layer');
1394
+
1395
+ };
1396
+
1397
+ /**
1398
+ This locator is used to access a DOM element the belongs to a core query object. ONLY use
1399
+ this locator for actions that expect a DOM element.
1400
+
1401
+ To use this locator via the Selenium RC, the locator text must follow the given pattern:
1402
+
1403
+ scCoreQuery=<core query handle>:<element index>
1404
+
1405
+ where <core query handle> is the handle to the core query object and <element index> is
1406
+ the index to the element in the CQ object. As an example:
1407
+
1408
+ clientDriver.click('scCoreQuery=2:1')
1409
+ */
1410
+ PageBot.prototype.locateElementByScCoreQuery = function(text) {
1411
+
1412
+ var args = text.split(':');
1413
+ var handle = args[0];
1414
+ var index = args[1];
1415
+
1416
+ var cq = selenium._getCoreQueryObject(handle);
1417
+ var elem = cq.get(index);
1418
+
1419
+ return elem;
1420
+
1421
+ };