lebowski 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ };