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.
- data/History.md +3 -0
- data/License.txt +20 -0
- data/Manifest.txt +84 -0
- data/README.md +146 -0
- data/Rakefile +42 -0
- data/bin/lebowski +26 -0
- data/bin/lebowski-spec +29 -0
- data/bin/lebowski-start-server +24 -0
- data/lib/lebowski.rb +15 -0
- data/lib/lebowski/core.rb +35 -0
- data/lib/lebowski/foundation.rb +52 -0
- data/lib/lebowski/foundation/application.rb +315 -0
- data/lib/lebowski/foundation/core.rb +61 -0
- data/lib/lebowski/foundation/core_query.rb +231 -0
- data/lib/lebowski/foundation/dom_element.rb +114 -0
- data/lib/lebowski/foundation/errors/argument_invalid_type.rb +31 -0
- data/lib/lebowski/foundation/errors/unexpected_type.rb +30 -0
- data/lib/lebowski/foundation/mixins/collection_item_view_support.rb +87 -0
- data/lib/lebowski/foundation/mixins/delegate_support.rb +22 -0
- data/lib/lebowski/foundation/mixins/inline_text_field_support.rb +29 -0
- data/lib/lebowski/foundation/mixins/key_check.rb +35 -0
- data/lib/lebowski/foundation/mixins/list_item_view_support.rb +36 -0
- data/lib/lebowski/foundation/mixins/positioned_element.rb +20 -0
- data/lib/lebowski/foundation/mixins/stall_support.rb +79 -0
- data/lib/lebowski/foundation/mixins/user_actions.rb +302 -0
- data/lib/lebowski/foundation/mixins/wait_actions.rb +44 -0
- data/lib/lebowski/foundation/object_array.rb +305 -0
- data/lib/lebowski/foundation/panes/alert.rb +117 -0
- data/lib/lebowski/foundation/panes/main.rb +20 -0
- data/lib/lebowski/foundation/panes/menu.rb +100 -0
- data/lib/lebowski/foundation/panes/modal.rb +21 -0
- data/lib/lebowski/foundation/panes/palette.rb +21 -0
- data/lib/lebowski/foundation/panes/pane.rb +24 -0
- data/lib/lebowski/foundation/panes/panel.rb +25 -0
- data/lib/lebowski/foundation/panes/picker.rb +43 -0
- data/lib/lebowski/foundation/panes/sheet.rb +21 -0
- data/lib/lebowski/foundation/proxy_factory.rb +87 -0
- data/lib/lebowski/foundation/proxy_object.rb +670 -0
- data/lib/lebowski/foundation/sc_object.rb +38 -0
- data/lib/lebowski/foundation/views/button.rb +20 -0
- data/lib/lebowski/foundation/views/checkbox.rb +63 -0
- data/lib/lebowski/foundation/views/collection.rb +304 -0
- data/lib/lebowski/foundation/views/container.rb +32 -0
- data/lib/lebowski/foundation/views/disclosure.rb +59 -0
- data/lib/lebowski/foundation/views/grid.rb +21 -0
- data/lib/lebowski/foundation/views/label.rb +30 -0
- data/lib/lebowski/foundation/views/list.rb +67 -0
- data/lib/lebowski/foundation/views/list_item.rb +280 -0
- data/lib/lebowski/foundation/views/menu_item.rb +27 -0
- data/lib/lebowski/foundation/views/radio.rb +32 -0
- data/lib/lebowski/foundation/views/segmented.rb +97 -0
- data/lib/lebowski/foundation/views/select_field.rb +139 -0
- data/lib/lebowski/foundation/views/support/simple_item_array.rb +249 -0
- data/lib/lebowski/foundation/views/text_field.rb +108 -0
- data/lib/lebowski/foundation/views/view.rb +108 -0
- data/lib/lebowski/runtime.rb +7 -0
- data/lib/lebowski/runtime/errors/remote_control_command_execution_error.rb +9 -0
- data/lib/lebowski/runtime/errors/remote_control_command_timeout_error.rb +9 -0
- data/lib/lebowski/runtime/errors/remote_control_error.rb +9 -0
- data/lib/lebowski/runtime/errors/selenium_server_error.rb +9 -0
- data/lib/lebowski/runtime/object_encoder.rb +123 -0
- data/lib/lebowski/runtime/sprout_core_driver.rb +14 -0
- data/lib/lebowski/runtime/sprout_core_extensions.rb +600 -0
- data/lib/lebowski/scui.rb +18 -0
- data/lib/lebowski/scui/mixins/node_item_view_support.rb +136 -0
- data/lib/lebowski/scui/mixins/terminal_view_support.rb +25 -0
- data/lib/lebowski/scui/views/combo_box.rb +119 -0
- data/lib/lebowski/scui/views/date_picker.rb +148 -0
- data/lib/lebowski/scui/views/linkit.rb +36 -0
- data/lib/lebowski/spec.rb +17 -0
- data/lib/lebowski/spec/core.rb +21 -0
- data/lib/lebowski/spec/matchers/be.rb +63 -0
- data/lib/lebowski/spec/matchers/has.rb +40 -0
- data/lib/lebowski/spec/matchers/match_supporters/has_object_function.rb +67 -0
- data/lib/lebowski/spec/matchers/match_supporters/has_predicate_with_no_prefix.rb +50 -0
- data/lib/lebowski/spec/matchers/match_supporters/has_predicate_with_prefix_has.rb +50 -0
- data/lib/lebowski/spec/matchers/match_supporters/match_supporter.rb +29 -0
- data/lib/lebowski/spec/matchers/method_missing.rb +24 -0
- data/lib/lebowski/spec/operators/operator.rb +20 -0
- data/lib/lebowski/spec/operators/that.rb +116 -0
- data/lib/lebowski/spec/util.rb +26 -0
- data/lib/lebowski/version.rb +17 -0
- data/resources/selenium-server.jar +0 -0
- data/resources/user-extensions.js +1421 -0
- metadata +198 -0
@@ -0,0 +1,108 @@
|
|
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 Foundation
|
8
|
+
module Views
|
9
|
+
|
10
|
+
#
|
11
|
+
# Represents a proxy to a SproutCore text field view (SC.TextFieldView)
|
12
|
+
#
|
13
|
+
class TextFieldView < Lebowski::Foundation::Views::View
|
14
|
+
|
15
|
+
representing_sc_class 'SC.TextFieldView'
|
16
|
+
|
17
|
+
SELECTOR_INPUT_FIELD = "input"
|
18
|
+
SELECTOR_TEXT_AREA = "textarea"
|
19
|
+
|
20
|
+
#
|
21
|
+
# Used to check if this view is empty
|
22
|
+
#
|
23
|
+
def empty?()
|
24
|
+
val = self['value']
|
25
|
+
return (val.nil? or val.empty?)
|
26
|
+
end
|
27
|
+
|
28
|
+
def clear()
|
29
|
+
type ""
|
30
|
+
end
|
31
|
+
|
32
|
+
def key_down(char)
|
33
|
+
cq = core_query(text_field_selector)
|
34
|
+
input = cq[0]
|
35
|
+
input.key_down char
|
36
|
+
cq.done
|
37
|
+
end
|
38
|
+
|
39
|
+
def key_up(char)
|
40
|
+
cq = core_query(text_field_selector)
|
41
|
+
input = cq[0]
|
42
|
+
input.key_up char
|
43
|
+
cq.done
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Used to type text into this view. This will directly insert the text
|
48
|
+
# into the input field. There will be no simulated key up and key down
|
49
|
+
# events
|
50
|
+
#
|
51
|
+
def type(text)
|
52
|
+
cq = core_query(text_field_selector)
|
53
|
+
input = cq[0]
|
54
|
+
input.type text
|
55
|
+
cq.done
|
56
|
+
end
|
57
|
+
|
58
|
+
def type_append(text)
|
59
|
+
val = self['value']
|
60
|
+
type "#{val}#{text}"
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# Used to simulate the typing of a single key. This will cause a simulated
|
65
|
+
# key up and key down event
|
66
|
+
#
|
67
|
+
def type_key(text)
|
68
|
+
clear
|
69
|
+
type_key_append text
|
70
|
+
end
|
71
|
+
|
72
|
+
def type_key_append(text)
|
73
|
+
cq = core_query(text_field_selector)
|
74
|
+
input = cq[0]
|
75
|
+
input.type_key text
|
76
|
+
cq.done
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# Used to simulate the typing of some given text. Each character from the
|
81
|
+
# given text will "typed" meaning that a simulated key up and key down event
|
82
|
+
# will occur for each character. This is useful when you have something
|
83
|
+
# that reacts to each character being entered.
|
84
|
+
#
|
85
|
+
def type_keys(text)
|
86
|
+
clear
|
87
|
+
type_keys_append text
|
88
|
+
end
|
89
|
+
|
90
|
+
def type_keys_append(text)
|
91
|
+
cq = core_query(text_field_selector)
|
92
|
+
input = cq[0]
|
93
|
+
text.chars.each do |x|
|
94
|
+
input.type_key x
|
95
|
+
end
|
96
|
+
cq.done
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def text_field_selector()
|
102
|
+
return (self['isTextArea'] == true) ? SELECTOR_TEXT_AREA : SELECTOR_INPUT_FIELD
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,108 @@
|
|
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 Foundation
|
8
|
+
|
9
|
+
#
|
10
|
+
# Represents a proxy to a generic SproutCore view (SC.View).
|
11
|
+
#
|
12
|
+
# All other views in this framework derive from this in order to acquire base
|
13
|
+
# functionality. If you intended to create your own view proxy object then you
|
14
|
+
# must inherit from this class as well.
|
15
|
+
#
|
16
|
+
module Views
|
17
|
+
class View < Lebowski::Foundation::SCObject
|
18
|
+
include Lebowski::Foundation
|
19
|
+
include Lebowski::Foundation::Views
|
20
|
+
include Lebowski::Foundation::Mixins::UserActions
|
21
|
+
include Lebowski::Foundation::Mixins::DelegateSupport
|
22
|
+
|
23
|
+
representing_sc_class 'SC.View'
|
24
|
+
|
25
|
+
def assigned_layer_id?()
|
26
|
+
return (not (rel_path =~ /^#/).nil?)
|
27
|
+
end
|
28
|
+
|
29
|
+
def abs_path()
|
30
|
+
return rel_path if assigned_layer_id?
|
31
|
+
return super
|
32
|
+
end
|
33
|
+
|
34
|
+
# @override Actions#action_locator_args
|
35
|
+
def action_locator_args()
|
36
|
+
return [abs_path]
|
37
|
+
end
|
38
|
+
|
39
|
+
# @override Actions#action_target
|
40
|
+
def action_target()
|
41
|
+
return :view
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# Gets the remote layer ID for this view
|
46
|
+
#
|
47
|
+
# @see SC.View.layerId
|
48
|
+
#
|
49
|
+
def layer_id()
|
50
|
+
# We only need to fetch the layer ID once since it never changes for a given instance
|
51
|
+
@layer_id = @driver.get_sc_property_string_value(abs_path, "layerId") if @layer_id.nil?
|
52
|
+
return @layer_id
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Gets the frame for this view.
|
57
|
+
#
|
58
|
+
# @return {Lebowski::Rect}
|
59
|
+
#
|
60
|
+
def frame()
|
61
|
+
return @driver.get_sc_view_frame(abs_path)
|
62
|
+
end
|
63
|
+
|
64
|
+
# @override Lebowski::Foundation::Mixins::PositionedElement#position
|
65
|
+
def position()
|
66
|
+
return @driver.get_sc_element_window_position(action_target, *action_locator_args)
|
67
|
+
end
|
68
|
+
|
69
|
+
# @override Lebowski::Foundation::Mixins::PositionedElement#scroll_to_visible
|
70
|
+
def scroll_to_visible()
|
71
|
+
@driver.sc_view_scroll_to_visible(abs_path)
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Gets the string representing of the view's layer. The layer in SproutCore is
|
76
|
+
# the root DOM element of the view. This method will return an HTML string
|
77
|
+
# representation of the entire layer that is equivalent to the following:
|
78
|
+
#
|
79
|
+
# view.get('layer').outerHTML
|
80
|
+
#
|
81
|
+
def layer()
|
82
|
+
value = @driver.get_sc_view_layer(abs_path)
|
83
|
+
return value
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# Obtain a core query object from the view.
|
88
|
+
#
|
89
|
+
# @param selector {String} a CSS selector that the core query object will use
|
90
|
+
# @return returns an instance of Lebowski::Foundation::CoreQuery
|
91
|
+
#
|
92
|
+
def core_query(selector=nil)
|
93
|
+
cq = Lebowski::Foundation::CoreQuery.new abs_path, selector, @driver
|
94
|
+
return cq
|
95
|
+
end
|
96
|
+
|
97
|
+
def child_views()
|
98
|
+
if @child_views.nil?
|
99
|
+
@child_views = ObjectArray.new self, 'childViews', 'length'
|
100
|
+
end
|
101
|
+
return @child_views
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
@@ -0,0 +1,7 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/runtime/errors/remote_control_error')
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/runtime/errors/remote_control_command_execution_error')
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + '/runtime/errors/remote_control_command_timeout_error')
|
4
|
+
require File.expand_path(File.dirname(__FILE__) + '/runtime/errors/selenium_server_error')
|
5
|
+
require File.expand_path(File.dirname(__FILE__) + '/runtime/object_encoder')
|
6
|
+
require File.expand_path(File.dirname(__FILE__) + '/runtime/sprout_core_extensions')
|
7
|
+
require File.expand_path(File.dirname(__FILE__) + '/runtime/sprout_core_driver')
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Lebowski
|
2
|
+
module Runtime
|
3
|
+
|
4
|
+
#
|
5
|
+
# Used to encode Ruby values into an encoded string that can be sent over
|
6
|
+
# to the browser to then be decoded and used.
|
7
|
+
#
|
8
|
+
# TODO: This needs to be completely changed. Instead of using a custom encoding,
|
9
|
+
# should instead use a standard encoding scheme, like JSON. Got too
|
10
|
+
# experimental in this case.
|
11
|
+
#
|
12
|
+
module ObjectEncoder
|
13
|
+
|
14
|
+
COMMA_CHAR = ','
|
15
|
+
COLON_CHAR = ':'
|
16
|
+
EQUAL_CHAR = '='
|
17
|
+
LEFT_SQUARE_BRACKET_CHAR = '['
|
18
|
+
RIGHT_SQUARE_BRACKET_CHAR = ']'
|
19
|
+
|
20
|
+
ENCODED_COMMA = "[cma]"
|
21
|
+
ENCODED_COLON = "[cln]"
|
22
|
+
ENCODED_EQUAL = "[eql]"
|
23
|
+
ENCODED_LEFT_SQUARE_BRACKET = "[lsb]"
|
24
|
+
ENCODED_RIGHT_SQUARE_BRACKET = "[rsb]"
|
25
|
+
|
26
|
+
def self.encode(value)
|
27
|
+
return encode_hash(value) if value.kind_of? Hash
|
28
|
+
return encode_array(value) if value.kind_of? Array
|
29
|
+
return nil
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Encodes a given hash into a string representation that can be sent over
|
34
|
+
# to the remote browser via Selenium. Examples of encoding:
|
35
|
+
#
|
36
|
+
# { 'foo' => "bar"} # => foo=bar
|
37
|
+
# { :foo => "bar"} # => foo=bar
|
38
|
+
# { 'foo' => "cat", bar => "dog"} # => foo=cat,bar=dog
|
39
|
+
# { 'foo' => /bar/ } # => foo=regexp:bar
|
40
|
+
# { 'foo' => /bar/i } # => foo=regexpi:bar
|
41
|
+
# { 'foo.bar' => "cat" } # => foo.bar=cat
|
42
|
+
# { 'foo' => "Acme, Inc."} # => foo=Acme[comma] Inc.
|
43
|
+
# { 'foo' => 100 } # => foo=int:100
|
44
|
+
# { 'foo' => true } # => foo=bool:true
|
45
|
+
# { 'foo' => false } # => foo=bool:false
|
46
|
+
#
|
47
|
+
def self.encode_hash(hash)
|
48
|
+
str = ""
|
49
|
+
counter = 1
|
50
|
+
hash.each do |key, value|
|
51
|
+
str << key.to_s << "=" << encode_value(value)
|
52
|
+
str << "," if counter < hash.length
|
53
|
+
counter = counter.next
|
54
|
+
end
|
55
|
+
return str
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# Encodes a given array into a string representation that can be sent over
|
60
|
+
# to the remote browser via Selenium
|
61
|
+
#
|
62
|
+
def self.encode_array(array)
|
63
|
+
str = ""
|
64
|
+
counter = 1
|
65
|
+
array.each do |value|
|
66
|
+
str << encode_value(value)
|
67
|
+
str << "," if counter < array.length
|
68
|
+
counter = counter.next
|
69
|
+
end
|
70
|
+
return str
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.encode_value(value)
|
74
|
+
str = ""
|
75
|
+
if value.kind_of? Regexp
|
76
|
+
str << "regexp"
|
77
|
+
str << "i" if (value.options & Regexp::IGNORECASE) == Regexp::IGNORECASE
|
78
|
+
str << ":" << encode_string(value.source)
|
79
|
+
elsif value.kind_of? Integer
|
80
|
+
str << "int:" << value.to_s
|
81
|
+
elsif value.kind_of? TrueClass or value.kind_of? FalseClass
|
82
|
+
str << "bool:" << value.to_s
|
83
|
+
elsif value.kind_of? Hash
|
84
|
+
str << "hash:" << encode_string(encode_hash(value))
|
85
|
+
elsif value.kind_of? Array
|
86
|
+
str << "array:" << encode_string(encode_array(value))
|
87
|
+
elsif value.nil?
|
88
|
+
str << "null:null"
|
89
|
+
elsif value == :undefined
|
90
|
+
str << "undefined:undefined"
|
91
|
+
else
|
92
|
+
str << encode_string(value.to_s)
|
93
|
+
end
|
94
|
+
return str
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
# Encodes special characters in a string. Characters encoded are:
|
99
|
+
#
|
100
|
+
# , : = [ ]
|
101
|
+
#
|
102
|
+
def self.encode_string(string)
|
103
|
+
new_string = string.gsub(/[,=:\[\]]/) do |c|
|
104
|
+
case c
|
105
|
+
when COMMA_CHAR
|
106
|
+
ENCODED_COMMA
|
107
|
+
when COLON_CHAR
|
108
|
+
ENCODED_COLON
|
109
|
+
when EQUAL_CHAR
|
110
|
+
ENCODED_EQUAL
|
111
|
+
when LEFT_SQUARE_BRACKET_CHAR
|
112
|
+
ENCODED_LEFT_SQUARE_BRACKET
|
113
|
+
when RIGHT_SQUARE_BRACKET_CHAR
|
114
|
+
ENCODED_RIGHT_SQUARE_BRACKET
|
115
|
+
end
|
116
|
+
end
|
117
|
+
return new_string
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,600 @@
|
|
1
|
+
module Lebowski
|
2
|
+
module Runtime
|
3
|
+
|
4
|
+
module SproutCoreExtensions
|
5
|
+
include Lebowski
|
6
|
+
|
7
|
+
HTTP_HEADERS = { 'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8' }
|
8
|
+
|
9
|
+
REMOTE_CONTROL_COMMAND_TIMEOUT = /^timed out after/i
|
10
|
+
|
11
|
+
# SC Application Setup Selenium Calls
|
12
|
+
|
13
|
+
def set_application_name(name)
|
14
|
+
__remote_control_command("setScApplicationName", [name,])
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize_sc_selenium_extension(timeout)
|
18
|
+
__remote_control_command("initializeScSeleniumExtension", [timeout,])
|
19
|
+
end
|
20
|
+
|
21
|
+
def open_sc_application(app_root_path, timeout=nil)
|
22
|
+
__remote_control_command("openScApplication", [app_root_path, timeout])
|
23
|
+
end
|
24
|
+
|
25
|
+
# SC Object Foundation Selenium Calls
|
26
|
+
|
27
|
+
def get_sc_guid(scpath)
|
28
|
+
return __string_command("getScGuid", [scpath])
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_sc_object_class_name(scpath)
|
32
|
+
return __string_command("getScObjectClassName", [scpath])
|
33
|
+
end
|
34
|
+
|
35
|
+
def is_sc_object_kind_of_class(scpath, klass)
|
36
|
+
return __boolean_command("isScObjectKindOfClass", [scpath, klass])
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_sc_type_of(scpath)
|
40
|
+
return __string_command("getScTypeOf", [scpath])
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_sc_type_of_array_content(scpath)
|
44
|
+
return __string_command("getScTypeOfArrayContent", [scpath])
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_sc_object_class_names(scpath)
|
48
|
+
return __string_array_command("getScObjectClassNames", [scpath])
|
49
|
+
end
|
50
|
+
|
51
|
+
# SC Object Property Selenium Calls
|
52
|
+
|
53
|
+
def get_sc_path_string_value(scpath)
|
54
|
+
return __string_command("getScPropertyValue", [scpath])
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_sc_path_number_value(scpath)
|
58
|
+
return __number_command("getScPropertyValue", [scpath])
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_sc_path_boolean_value(scpath)
|
62
|
+
return __boolean_command("getScPropertyValue", [scpath])
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_sc_path_string_array_value(scpath)
|
66
|
+
return __string_array_command("getScPropertyValue", [scpath])
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_sc_path_number_array_value(scpath)
|
70
|
+
return __number_array_command("getScPropertyValue", [scpath])
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_sc_path_boolean_array_value(scpath)
|
74
|
+
return __boolean_array_command("getScPropertyValue", [scpath])
|
75
|
+
end
|
76
|
+
|
77
|
+
def get_sc_property_value(scpath, property)
|
78
|
+
return get_sc_property_string_value(scpath, property)
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_sc_property_string_value(scpath, property)
|
82
|
+
full_path = "#{scpath}.#{property}"
|
83
|
+
return __string_command("getScPropertyValue", [full_path,])
|
84
|
+
end
|
85
|
+
|
86
|
+
def get_sc_property_boolean_value(scpath, property)
|
87
|
+
full_path = "#{scpath}.#{property}"
|
88
|
+
return __boolean_command("getScPropertyValue", [full_path,])
|
89
|
+
end
|
90
|
+
|
91
|
+
def get_sc_property_number_value(scpath, property)
|
92
|
+
full_path = "#{scpath}.#{property}"
|
93
|
+
return __number_command("getScPropertyValue", [full_path,])
|
94
|
+
end
|
95
|
+
|
96
|
+
def get_sc_property_array_value(scpath, property)
|
97
|
+
return get_sc_property_string_array_value(scpath, property)
|
98
|
+
end
|
99
|
+
|
100
|
+
def get_sc_property_string_array_value(scpath, property)
|
101
|
+
full_path = "#{scpath}.#{property}"
|
102
|
+
return __string_array_command("getScPropertyValue", [full_path,])
|
103
|
+
end
|
104
|
+
|
105
|
+
def get_sc_property_number_array_value(scpath, property)
|
106
|
+
full_path = "#{scpath}.#{property}"
|
107
|
+
return __number_array_command("getScPropertyValue", [full_path,])
|
108
|
+
end
|
109
|
+
|
110
|
+
def get_sc_property_boolean_array_value(scpath, property)
|
111
|
+
full_path = "#{scpath}.#{property}"
|
112
|
+
return __boolean_array_command("getScPropertyValue", [full_path,])
|
113
|
+
end
|
114
|
+
|
115
|
+
# SC View Object Foundation Selenium Calls
|
116
|
+
|
117
|
+
def get_sc_view_layer(scpath)
|
118
|
+
return string_command("getScViewLayer", [scpath, ])
|
119
|
+
end
|
120
|
+
|
121
|
+
# @return {Lebowski::Rect}
|
122
|
+
def get_sc_view_frame(scpath)
|
123
|
+
value = __number_array_command("getScViewFrame", [scpath])
|
124
|
+
return nil if value.nil?
|
125
|
+
return Rect.new(value[0], value[1], value[2], value[3])
|
126
|
+
end
|
127
|
+
|
128
|
+
# @return {Lebowski::Coords}
|
129
|
+
def get_sc_element_window_position(type, *params)
|
130
|
+
value = __number_array_command("getScElementWindowPosition", [__locator(type, *params)])
|
131
|
+
return nil if value.nil?
|
132
|
+
return Coords.new(value[0], value[1])
|
133
|
+
end
|
134
|
+
|
135
|
+
def get_sc_view_child_view_count(scpath)
|
136
|
+
full_path = "#{scpath}.childViews.length"
|
137
|
+
return __number_command("getScPropertyValue", [full_path, ])
|
138
|
+
end
|
139
|
+
|
140
|
+
# SC View Object Event Selenium Calls
|
141
|
+
|
142
|
+
def sc_type(type, text, *params)
|
143
|
+
__remote_control_command("type", [__locator(type, *params), text, ])
|
144
|
+
end
|
145
|
+
|
146
|
+
def sc_select(type, optionLocator, *params)
|
147
|
+
__remote_control_command("select", [__locator(type, *params), optionLocator, ])
|
148
|
+
end
|
149
|
+
|
150
|
+
def sc_mouse_move(type, *params)
|
151
|
+
__remote_control_command("mouseMove", [__locator(type, *params),])
|
152
|
+
end
|
153
|
+
|
154
|
+
alias_method :sc_mouse_enter, :sc_mouse_move
|
155
|
+
alias_method :sc_mouse_exit, :sc_mouse_move
|
156
|
+
|
157
|
+
def sc_mouse_move_at(type, x, y, *params)
|
158
|
+
coords = "#{x},#{y}"
|
159
|
+
__remote_control_command("mouseMoveAt", [__locator(type, *params), coords])
|
160
|
+
end
|
161
|
+
|
162
|
+
def sc_mouse_down(type, *params)
|
163
|
+
__remote_control_command("scMouseDown", [__locator(type, *params),])
|
164
|
+
end
|
165
|
+
|
166
|
+
def sc_mouse_up(type, *params)
|
167
|
+
__remote_control_command("scMouseUp", [__locator(type, *params), ])
|
168
|
+
end
|
169
|
+
|
170
|
+
def sc_mouse_down_at(type, x, y, *params)
|
171
|
+
coords = "#{x},#{y}"
|
172
|
+
__remote_control_command("mouseDownAt", [__locator(type, *params), coords])
|
173
|
+
end
|
174
|
+
|
175
|
+
def sc_mouse_up_at(type, x, y, *params)
|
176
|
+
coords = "#{x},#{y}"
|
177
|
+
__remote_control_command("mouseUpAt", [__locator(type, *params), coords])
|
178
|
+
end
|
179
|
+
|
180
|
+
def sc_right_mouse_down(type, *params)
|
181
|
+
__remote_control_command("scMouseDownRight", [__locator(type, *params), ])
|
182
|
+
end
|
183
|
+
|
184
|
+
def sc_right_mouse_up(type, *params)
|
185
|
+
__remote_control_command("scMouseUpRight", [__locator(type, *params), ])
|
186
|
+
end
|
187
|
+
|
188
|
+
def sc_right_mouse_down_at(type, x, y, *params)
|
189
|
+
coords = "#{x},#{y}"
|
190
|
+
__remote_control_command("mouseDownRightAt", [__locator(type, *params), coords])
|
191
|
+
end
|
192
|
+
|
193
|
+
def sc_right_mouse_up_at(type, x, y, *params)
|
194
|
+
coords = "#{x},#{y}"
|
195
|
+
__remote_control_command("mouseUpRightAt", [__locator(type, *params), coords])
|
196
|
+
end
|
197
|
+
|
198
|
+
def sc_basic_click(type, *params)
|
199
|
+
__remote_control_command("click", [__locator(type, *params), ])
|
200
|
+
end
|
201
|
+
|
202
|
+
def sc_click(type, *params)
|
203
|
+
__remote_control_command("scClick", [__locator(type, *params), ])
|
204
|
+
end
|
205
|
+
|
206
|
+
def sc_right_click(type, *params)
|
207
|
+
__remote_control_command("scRightClick", [__locator(type, *params), ])
|
208
|
+
end
|
209
|
+
|
210
|
+
def sc_double_click(type, *params)
|
211
|
+
__remote_control_command("scDoubleClick", [__locator(type, *params), ])
|
212
|
+
end
|
213
|
+
|
214
|
+
def sc_focus(type, *params)
|
215
|
+
__remote_control_command("focus", [__locator(type, *params), ])
|
216
|
+
end
|
217
|
+
|
218
|
+
def sc_key_down(type, key, *params)
|
219
|
+
if key == :meta_key
|
220
|
+
meta_key_down
|
221
|
+
elsif key == :alt_key
|
222
|
+
alt_key_down
|
223
|
+
elsif key == :ctrl_key
|
224
|
+
control_key_down
|
225
|
+
elsif key == :shift_key
|
226
|
+
shift_key_down
|
227
|
+
elsif key.kind_of? Symbol
|
228
|
+
__remote_control_command("scFunctionKeyDown", [__locator(type, *params), key.to_s])
|
229
|
+
else
|
230
|
+
__remote_control_command("scKeyDown", [__locator(type, *params), key])
|
231
|
+
end
|
232
|
+
__register_key_as_down(key)
|
233
|
+
end
|
234
|
+
|
235
|
+
def sc_key_up(type, key, *params)
|
236
|
+
if key == :meta_key
|
237
|
+
meta_key_up
|
238
|
+
elsif key == :alt_key
|
239
|
+
alt_key_up
|
240
|
+
elsif key == :ctrl_key
|
241
|
+
control_key_up
|
242
|
+
elsif key == :shift_key
|
243
|
+
shift_key_up
|
244
|
+
elsif key.kind_of? Symbol
|
245
|
+
__remote_control_command("scFunctionKeyUp", [__locator(type, *params), key.to_s])
|
246
|
+
else
|
247
|
+
__remote_control_command("scKeyUp", [__locator(type, *params), key])
|
248
|
+
end
|
249
|
+
__register_key_as_up(key)
|
250
|
+
end
|
251
|
+
|
252
|
+
def key_down?(key)
|
253
|
+
return __key_pressed?(key)
|
254
|
+
end
|
255
|
+
|
256
|
+
def key_up?(key)
|
257
|
+
return (not __key_pressed?(key))
|
258
|
+
end
|
259
|
+
|
260
|
+
def sc_type_key(type, key, *params)
|
261
|
+
if key.kind_of? Symbol
|
262
|
+
__remote_control_command("scTypeFunctionKey", [__locator(type, *params), key.to_s, ])
|
263
|
+
else
|
264
|
+
__remote_control_command("scTypeKey", [__locator(type, *params), key, ])
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def sc_view_scroll_to_visible(scpath)
|
269
|
+
__remote_control_command("scViewScrollToVisible", [scpath])
|
270
|
+
end
|
271
|
+
|
272
|
+
def sc_wait_until(root_scpath, join, conditions, timeout=nil)
|
273
|
+
params = {
|
274
|
+
:rootPath => root_scpath,
|
275
|
+
:join => join,
|
276
|
+
:conditions => conditions,
|
277
|
+
:timeout => timeout
|
278
|
+
}
|
279
|
+
|
280
|
+
encoded_params = ObjectEncoder.encode_hash(params)
|
281
|
+
|
282
|
+
__remote_control_command("scWaitUntil", [encoded_params])
|
283
|
+
end
|
284
|
+
|
285
|
+
# SC Core Query Selenium Calls
|
286
|
+
|
287
|
+
def sc_core_query_done(handle)
|
288
|
+
__remote_control_command("scCoreQueryDone", [handle,])
|
289
|
+
end
|
290
|
+
|
291
|
+
def get_sc_core_query(scpath, selector)
|
292
|
+
return __number_command("getScCoreQuery", [scpath, selector])
|
293
|
+
end
|
294
|
+
|
295
|
+
def get_sc_core_query_size(handle)
|
296
|
+
return __number_command("getScCoreQuerySize", [handle,])
|
297
|
+
end
|
298
|
+
|
299
|
+
def get_sc_core_query_element_classes(handle, elemIndex)
|
300
|
+
return __string_command("getScCoreQueryElementClasses", [handle, elemIndex,])
|
301
|
+
end
|
302
|
+
|
303
|
+
def get_sc_core_query_element_html(handle, elemIndex)
|
304
|
+
return __string_command("getScCoreQueryElementHTML", [handle, elemIndex,])
|
305
|
+
end
|
306
|
+
|
307
|
+
def get_sc_core_query_element_attribute(handle, elemIndex, attribute)
|
308
|
+
return __string_command("getScCoreQueryElementAttribute", [handle, "#{elemIndex}:#{attribute}",])
|
309
|
+
end
|
310
|
+
|
311
|
+
def get_sc_core_query_element_text(handle, elemIndex)
|
312
|
+
return __string_command("getScCoreQueryElementText", [handle, elemIndex,])
|
313
|
+
end
|
314
|
+
|
315
|
+
def get_sc_core_query_element_tag(handle, elemIndex)
|
316
|
+
return __string_command("getScCoreQueryElementTag", [handle, elemIndex,])
|
317
|
+
end
|
318
|
+
|
319
|
+
# SC Collection View Selenium Calls
|
320
|
+
|
321
|
+
def get_sc_collection_view_content_group_indexes(scpath)
|
322
|
+
return __number_array_command("getScCollectionViewContentGroupIndexes", [scpath,])
|
323
|
+
end
|
324
|
+
|
325
|
+
def get_sc_collection_view_content_selected_indexes(scpath)
|
326
|
+
return __number_array_command("getScCollectionViewContentSelectedIndexes", [scpath,])
|
327
|
+
end
|
328
|
+
|
329
|
+
def get_sc_collection_view_content_now_showing_indexes(scpath)
|
330
|
+
return __number_array_command("getScCollectionViewContentNowShowingIndexes", [scpath,])
|
331
|
+
end
|
332
|
+
|
333
|
+
def get_sc_collection_view_content_is_selected(scpath, index)
|
334
|
+
return __boolean_command("getScCollectionViewContentIsSelected", [scpath, index,])
|
335
|
+
end
|
336
|
+
|
337
|
+
def get_sc_collection_view_content_is_group(scpath, index)
|
338
|
+
return __boolean_command("getScCollectionViewContentIsGroup", [scpath, index,])
|
339
|
+
end
|
340
|
+
|
341
|
+
def get_sc_collection_view_content_disclosure_state(scpath, index)
|
342
|
+
return __number_command("getScCollectionViewContentDisclosureState", [scpath, index,])
|
343
|
+
end
|
344
|
+
|
345
|
+
def get_sc_collection_view_content_outline_level(scpath, index)
|
346
|
+
return __number_command("getScCollectionViewContentOutlineLevel", [scpath, index,])
|
347
|
+
end
|
348
|
+
|
349
|
+
# Selenium User Extension Utility Function Selenium Calls
|
350
|
+
|
351
|
+
def get_sc_object_array_index_lookup(scpath, lookup_params)
|
352
|
+
encoded_params = ObjectEncoder.encode_hash(lookup_params)
|
353
|
+
return __number_array_command("getScObjectArrayIndexLookup", [scpath, encoded_params])
|
354
|
+
end
|
355
|
+
|
356
|
+
def get_sc_selection_set_indexes(scpath)
|
357
|
+
return __number_array_command("getScSelectSetIndexes", [scpath, ])
|
358
|
+
end
|
359
|
+
|
360
|
+
def get_sc_localized_string(str)
|
361
|
+
if @localized_string_cache.nil?
|
362
|
+
@localized_string_cache = {}
|
363
|
+
end
|
364
|
+
|
365
|
+
if @localized_string_cache.has_key?(str)
|
366
|
+
return @localized_string_cache[str]
|
367
|
+
else
|
368
|
+
val = __string_command("getScLocalizedString", [str, ])
|
369
|
+
@localized_string_cache[str] = val
|
370
|
+
return val
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
def sc_window_move_to(x, y)
|
375
|
+
__remote_control_command("scWindowMoveTo", [x, y])
|
376
|
+
end
|
377
|
+
|
378
|
+
def sc_window_resize_to(width, height)
|
379
|
+
__remote_control_command("scWindowResizeTo", [width, height])
|
380
|
+
end
|
381
|
+
|
382
|
+
def sc_window_maximize()
|
383
|
+
__remote_control_command("scWindowMaximize")
|
384
|
+
end
|
385
|
+
|
386
|
+
# Selenium User Extensions Testing/Debugging Calls
|
387
|
+
|
388
|
+
def __sc_test_computing_property_path(key, path)
|
389
|
+
__remote_control_command("scTestComputePropertyPath", [key.to_s, path,])
|
390
|
+
end
|
391
|
+
|
392
|
+
def __sc_test_sending_encoded_hash(key, hash)
|
393
|
+
str = ObjectEncoder.encode_hash(hash)
|
394
|
+
__remote_control_command("scTestDecodingEncodedHash", [key.to_s, str,])
|
395
|
+
end
|
396
|
+
|
397
|
+
def __sc_test_sending_encoded_array(key, array)
|
398
|
+
str = ObjectEncoder.encode_array(array)
|
399
|
+
__remote_control_command("scTestDecodingEncodedArray", [key.to_s, str,])
|
400
|
+
end
|
401
|
+
|
402
|
+
def __sc_test_object_array_lookup(key, path, lookup)
|
403
|
+
params = ObjectEncoder.encode_hash({
|
404
|
+
:key => key.to_s,
|
405
|
+
:path => path,
|
406
|
+
:lookup => lookup,
|
407
|
+
})
|
408
|
+
__remote_control_command("scTestObjectArrayLookup", [params,])
|
409
|
+
end
|
410
|
+
|
411
|
+
protected
|
412
|
+
|
413
|
+
#
|
414
|
+
# @private
|
415
|
+
#
|
416
|
+
# Will convert the given type and parameters into a locator recognized by Selenium
|
417
|
+
#
|
418
|
+
def __locator(type, *params)
|
419
|
+
loc = ""
|
420
|
+
case type
|
421
|
+
when :view
|
422
|
+
loc = "scPath=#{params[0]}"
|
423
|
+
when :core_query_element
|
424
|
+
loc = "scCoreQuery=#{params[0]}:#{params[1]}"
|
425
|
+
else
|
426
|
+
loc = "scPath=#{params[0]}"
|
427
|
+
end
|
428
|
+
return loc
|
429
|
+
end
|
430
|
+
|
431
|
+
def __remote_control_command(verb, args=[])
|
432
|
+
if session_id.nil?
|
433
|
+
raise RemoteControlError.new "Unable to execute remote control command: A session ID is required"
|
434
|
+
end
|
435
|
+
|
436
|
+
timeout(default_timeout_in_seconds) do
|
437
|
+
data = http_request_for(verb, args)
|
438
|
+
status, response = __http_post(data)
|
439
|
+
|
440
|
+
if status != "OK"
|
441
|
+
if response =~ REMOTE_CONTROL_COMMAND_TIMEOUT
|
442
|
+
raise RemoteControlCommandTimeoutError, response
|
443
|
+
else
|
444
|
+
called_from = caller.detect{|line| line !~ /(selenium-client|vendor|usr\/lib\/ruby|\(eval\))/i}
|
445
|
+
err_message = "Received error from server while trying to execute command:\n"
|
446
|
+
err_message << "requested:\n"
|
447
|
+
err_message << "\t" + CGI::unescape(data.split('&').join("\n\t")) + "\n"
|
448
|
+
err_message << "received:\n"
|
449
|
+
err_message << "\t#{response}\n"
|
450
|
+
raise RemoteControlCommandExecutionError, err_message
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
return response[3..-1] # strip "OK," from response
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
def __http_post(data)
|
459
|
+
http = Net::HTTP.new(@host, @port)
|
460
|
+
http.open_timeout = default_timeout_in_seconds
|
461
|
+
http.read_timeout = default_timeout_in_seconds
|
462
|
+
begin
|
463
|
+
response = http.post('/selenium-server/driver/', data, HTTP_HEADERS)
|
464
|
+
rescue Exception => ex
|
465
|
+
err_message = "Error communicating with selenium server: #{ex.message}\n"
|
466
|
+
err_message << "Confirm that selenium server is running"
|
467
|
+
raise SeleniumServerError.new err_message
|
468
|
+
end
|
469
|
+
[ response.body[0..1], response.body ]
|
470
|
+
end
|
471
|
+
|
472
|
+
def __string_command(verb, args=[])
|
473
|
+
__remote_control_command(verb, args)
|
474
|
+
end
|
475
|
+
|
476
|
+
#
|
477
|
+
# @private
|
478
|
+
#
|
479
|
+
# Use this method instead of the number_command method. The number_command method
|
480
|
+
# in the selenium-client does not actually convert a string into an int where this
|
481
|
+
# method will
|
482
|
+
#
|
483
|
+
def __number_command(verb, args)
|
484
|
+
return __string_command(verb, args).to_i
|
485
|
+
end
|
486
|
+
|
487
|
+
#
|
488
|
+
# @private
|
489
|
+
#
|
490
|
+
# User this method instead of the boolean_command method. This method will handle
|
491
|
+
# cases when the returned value from the server is neither "true" not "false". In
|
492
|
+
# such a case the raw value is returned. The reflects cases when a returned value
|
493
|
+
# is typically expected to be a true or false boolean value, but in certain
|
494
|
+
# situations can be a none boolean value.
|
495
|
+
#
|
496
|
+
def __boolean_command(verb, args)
|
497
|
+
val = __string_command(verb, args)
|
498
|
+
return true if (val == "true")
|
499
|
+
return false if (val == "false")
|
500
|
+
return val
|
501
|
+
end
|
502
|
+
|
503
|
+
#
|
504
|
+
# @private
|
505
|
+
#
|
506
|
+
# Use this method instead of the string_array_command method. The
|
507
|
+
# string_array_command method does not handle the case when the
|
508
|
+
# returned value from the server is null where as this method will
|
509
|
+
#
|
510
|
+
def __string_array_command(verb, args)
|
511
|
+
csv = string_command(verb, args)
|
512
|
+
return [] if csv.nil? or csv.empty?
|
513
|
+
token = ""
|
514
|
+
tokens = []
|
515
|
+
escape = false
|
516
|
+
csv.split(//).each do |letter|
|
517
|
+
if escape
|
518
|
+
token += letter
|
519
|
+
escape = false
|
520
|
+
next
|
521
|
+
end
|
522
|
+
case letter
|
523
|
+
when '\\'
|
524
|
+
escape = true
|
525
|
+
when ','
|
526
|
+
tokens << token
|
527
|
+
token = ""
|
528
|
+
else
|
529
|
+
token += letter
|
530
|
+
end
|
531
|
+
end
|
532
|
+
tokens << token
|
533
|
+
return tokens
|
534
|
+
end
|
535
|
+
|
536
|
+
#
|
537
|
+
# @private
|
538
|
+
#
|
539
|
+
# Use this method instead of the number_array_command method
|
540
|
+
#
|
541
|
+
def __number_array_command(verb, args)
|
542
|
+
val = __string_array_command(verb, args)
|
543
|
+
val.collect! { |x| x.to_i }
|
544
|
+
return val
|
545
|
+
end
|
546
|
+
|
547
|
+
#
|
548
|
+
# @private
|
549
|
+
#
|
550
|
+
# Use this method instead of the boolean_array_command method. This
|
551
|
+
# method will handle cases when the value is neither true nor false. In
|
552
|
+
# such a case the raw value is returned. In done in cases when a return
|
553
|
+
# value is typicallys a true or false boolean value, but in particular
|
554
|
+
# situations can be a none boolean value.
|
555
|
+
#
|
556
|
+
def __boolean_array_command(verb, args)
|
557
|
+
val = __string_array_command(verb, args)
|
558
|
+
val.collect! do |x|
|
559
|
+
if ("true" == x)
|
560
|
+
true
|
561
|
+
elsif ("false" == x)
|
562
|
+
false
|
563
|
+
else
|
564
|
+
x
|
565
|
+
end
|
566
|
+
end
|
567
|
+
return val
|
568
|
+
end
|
569
|
+
|
570
|
+
#
|
571
|
+
# @private
|
572
|
+
#
|
573
|
+
def __pressed_keys
|
574
|
+
@pressed_keys = {} if @pressed_keys.nil?
|
575
|
+
return @pressed_keys
|
576
|
+
end
|
577
|
+
|
578
|
+
#
|
579
|
+
# @private
|
580
|
+
#
|
581
|
+
def __register_key_as_down(key)
|
582
|
+
keys = __pressed_keys
|
583
|
+
keys[key] = key
|
584
|
+
end
|
585
|
+
|
586
|
+
#
|
587
|
+
# @private
|
588
|
+
#
|
589
|
+
def __register_key_as_up(key)
|
590
|
+
keys = __pressed_keys
|
591
|
+
keys.delete key
|
592
|
+
end
|
593
|
+
|
594
|
+
def __key_pressed?(key)
|
595
|
+
return _pressed_keys.has_key?(key)
|
596
|
+
end
|
597
|
+
|
598
|
+
end
|
599
|
+
end
|
600
|
+
end
|