testa_appium_driver 0.1.18 → 0.1.20

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -0
  3. data/Gemfile.lock +5 -1
  4. data/appium-driver.iml +79 -0
  5. data/testa_appium_driver.gemspec +4 -8
  6. metadata +11 -40
  7. data/.gitattributes +0 -23
  8. data/.gitignore +0 -17
  9. data/.rspec +0 -3
  10. data/.rubocop.yml +0 -5
  11. data/bin/console +0 -17
  12. data/bin/setup +0 -8
  13. data/lib/testa_appium_driver/android/class_selectors.rb +0 -438
  14. data/lib/testa_appium_driver/android/driver.rb +0 -71
  15. data/lib/testa_appium_driver/android/locator/attributes.rb +0 -118
  16. data/lib/testa_appium_driver/android/locator.rb +0 -142
  17. data/lib/testa_appium_driver/android/scroll_actions/uiautomator_scroll_actions.rb +0 -62
  18. data/lib/testa_appium_driver/android/selenium_element.rb +0 -12
  19. data/lib/testa_appium_driver/common/bounds.rb +0 -150
  20. data/lib/testa_appium_driver/common/constants.rb +0 -38
  21. data/lib/testa_appium_driver/common/exceptions/strategy_mix_exception.rb +0 -12
  22. data/lib/testa_appium_driver/common/helpers.rb +0 -271
  23. data/lib/testa_appium_driver/common/locator/scroll_actions.rb +0 -398
  24. data/lib/testa_appium_driver/common/locator.rb +0 -599
  25. data/lib/testa_appium_driver/common/scroll_actions/json_wire_scroll_actions.rb +0 -4
  26. data/lib/testa_appium_driver/common/scroll_actions/w3c_scroll_actions.rb +0 -305
  27. data/lib/testa_appium_driver/common/scroll_actions.rb +0 -267
  28. data/lib/testa_appium_driver/common/selenium_element.rb +0 -19
  29. data/lib/testa_appium_driver/driver.rb +0 -335
  30. data/lib/testa_appium_driver/ios/driver.rb +0 -49
  31. data/lib/testa_appium_driver/ios/locator/attributes.rb +0 -85
  32. data/lib/testa_appium_driver/ios/locator.rb +0 -72
  33. data/lib/testa_appium_driver/ios/selenium_element.rb +0 -8
  34. data/lib/testa_appium_driver/ios/type_selectors.rb +0 -188
  35. data/lib/testa_appium_driver/version.rb +0 -5
  36. data/lib/testa_appium_driver.rb +0 -6
@@ -1,142 +0,0 @@
1
- require_relative 'locator/attributes'
2
-
3
- module TestaAppiumDriver
4
- #noinspection RubyTooManyInstanceVariablesInspection
5
- class Locator
6
- attr_accessor :closing_parenthesis
7
- include ClassSelectors
8
- include Attributes
9
-
10
- def init(params, selectors, single)
11
- @closing_parenthesis = 0
12
-
13
-
14
- @ui_selector = hash_to_uiautomator(selectors, single)
15
-
16
- if is_scrollable_selector?(selectors, single)
17
- if selectors[:class] == "android.widget.HorizontalScrollView"
18
- @scroll_orientation = :horizontal
19
- else
20
- @scroll_orientation = :vertical
21
- end
22
-
23
- if !params[:top].nil? || !params[:bottom].nil? || !params[:right].nil? || !params[:left].nil?
24
- @scroll_deadzone = {}
25
- @scroll_deadzone[:top] = params[:top].to_f unless params[:top].nil?
26
- @scroll_deadzone[:bottom] = params[:bottom].to_f unless params[:bottom].nil?
27
- @scroll_deadzone[:right] = params[:right].to_f unless params[:right].nil?
28
- @scroll_deadzone[:left] = params[:left].to_f unless params[:left].nil?
29
- end
30
-
31
- params[:scrollable_locator] = self.dup
32
- end
33
-
34
- @scrollable_locator = params[:scrollable_locator] if params[:scrollable_locator]
35
- end
36
-
37
-
38
- # resolve selector which will be used for finding element
39
- def strategies_and_selectors
40
- ss = []
41
- if @can_use_id_strategy
42
- ss.push({"#{FIND_STRATEGY_ID}": @can_use_id_strategy})
43
- end
44
- if @strategy.nil? || @strategy == FIND_STRATEGY_UIAUTOMATOR
45
- ss.push({"#{FIND_STRATEGY_UIAUTOMATOR}": ui_selector})
46
- end
47
-
48
- if @strategy.nil? || @strategy == FIND_STRATEGY_XPATH
49
- ss.push({"#{FIND_STRATEGY_XPATH}": @xpath_selector})
50
- end
51
-
52
- if @strategy == FIND_STRATEGY_IMAGE
53
- ss.push({"#{FIND_STRATEGY_IMAGE}": @image_selector})
54
- end
55
- ss
56
- end
57
-
58
-
59
- # @param [Boolean] include_semicolon should the semicolon be included at the end
60
- # @return ui_selector for uiautomator find strategy
61
- def ui_selector(include_semicolon = true)
62
- @ui_selector + ")" * @closing_parenthesis + (include_semicolon ? ";" : "");
63
- end
64
-
65
- def ui_selector=(value)
66
- @ui_selector = value
67
- end
68
-
69
-
70
-
71
-
72
- # @return [TestaAppiumDriver::Locator]
73
- def from_parent(selectors = {})
74
- raise "Cannot add from_parent selector to array" unless @single
75
- raise StrategyMixException.new(@strategy, @strategy_reason, FIND_STRATEGY_UIAUTOMATOR, "from_parent") if @strategy != FIND_STRATEGY_UIAUTOMATOR
76
-
77
- locator = self.dup
78
- locator.strategy = FIND_STRATEGY_UIAUTOMATOR
79
- locator.strategy_reason = "from_parent"
80
- locator.closing_parenthesis += 1
81
- locator.ui_selector = "#{locator.ui_selector}.fromParent(#{hash_to_uiautomator(selectors)}"
82
- locator
83
- end
84
-
85
-
86
- # @return [Locator] new child locator element
87
- def add_child_selector(params)
88
- params, selectors = extract_selectors_from_params(params)
89
- single = params[:single]
90
- raise "Cannot add child selector to Array" if single && !@single
91
-
92
- locator = self.dup
93
- locator.can_use_id_strategy = false
94
- if (@strategy.nil? && !single) || @strategy == FIND_STRATEGY_XPATH
95
- locator.strategy = FIND_STRATEGY_XPATH
96
- locator.strategy_reason = "multiple child selector"
97
- add_xpath_child_selectors(locator, selectors, single)
98
- elsif @strategy == FIND_STRATEGY_UIAUTOMATOR
99
- locator = add_uiautomator_child_selector(locator, selectors, single)
100
- elsif @strategy == FIND_STRATEGY_IMAGE
101
- locator = add_image_child_selector(locator, selectors, single)
102
- else
103
- # both paths are valid
104
- add_xpath_child_selectors(locator, selectors, single)
105
- locator = add_uiautomator_child_selector(locator, selectors, single)
106
- end
107
-
108
- if is_scrollable_selector?(selectors, single)
109
- locator.scrollable_locator = locator
110
- if selectors[:class] == "android.widget.HorizontalScrollView"
111
- locator.scrollable_locator.scroll_orientation = :horizontal
112
- else
113
- locator.scrollable_locator.scroll_orientation = :vertical
114
- end
115
- end
116
-
117
- locator.last_selector_adjacent = false
118
- locator
119
- end
120
-
121
-
122
- private
123
- def add_uiautomator_child_selector(locator, selectors, single)
124
- if locator.single && !single
125
- # current locator stays single, the child locator looks for multiple
126
- params = selectors.merge({single: single, scrollable_locator: locator.scrollable_locator})
127
- params[:default_find_strategy] = locator.default_find_strategy
128
- params[:default_scroll_strategy] = locator.default_scroll_strategy
129
- Locator.new(@driver, self, params)
130
- else
131
- locator.single = true
132
- locator.ui_selector = "#{locator.ui_selector(false)}.childSelector(#{hash_to_uiautomator(selectors, single)})"
133
- locator
134
- end
135
- end
136
-
137
- def add_image_child_selector(locator, selectors, single)
138
- params = selectors.merge({single: single, scrollable_locator: locator.scrollable_locator})
139
- Locator.new(@driver, self, params)
140
- end
141
- end
142
- end
@@ -1,62 +0,0 @@
1
- module TestaAppiumDriver
2
- #noinspection RubyInstanceMethodNamingConvention
3
- class ScrollActions
4
- private
5
-
6
- def uiautomator_scroll_to_start_or_end(type)
7
-
8
- scrollable_selector = @scrollable.ui_selector(false)
9
- orientation = @scrollable.scroll_orientation == :vertical ? ".setAsVerticalList()" : ".setAsHorizontalList()"
10
- scroll_command = type == :start ? ".scrollToBeginning(#{DEFAULT_UIAUTOMATOR_MAX_SWIPES})" : ".scrollToEnd(#{DEFAULT_UIAUTOMATOR_MAX_SWIPES})"
11
- cmd = "new UiScrollable(#{scrollable_selector})#{orientation}#{scroll_command};"
12
- begin
13
- puts "Scroll execute[uiautomator_#{type}]: #{cmd}"
14
- @driver.find_element(uiautomator: cmd)
15
- rescue
16
- # Ignored
17
- end
18
-
19
-
20
- end
21
-
22
-
23
- def uiautomator_scroll_to
24
- raise "UiAutomator scroll cannot work with specified direction" unless @direction.nil?
25
-
26
- scrollable_selector = @scrollable.ui_selector(false)
27
- element_selector = @locator.ui_selector(false)
28
- orientation_command = @scrollable.scroll_orientation == :vertical ? ".setAsVerticalList()" : ".setAsHorizontalList()"
29
- cmd = "new UiScrollable(#{scrollable_selector})#{orientation_command}.scrollIntoView(#{element_selector});"
30
- begin
31
- puts "Scroll execute[uiautomator_scroll_to]: #{cmd}"
32
- @driver.find_element(uiautomator: cmd)
33
- rescue
34
- # Ignored
35
- ensure
36
- end
37
- end
38
-
39
-
40
- def uiautomator_page_or_fling(type, direction)
41
- scrollable_selector = @scrollable.ui_selector(false)
42
- orientation = direction == :up || direction == :down ? ".setAsVerticalList()" : ".setAsHorizontalList()"
43
- if type == SCROLL_ACTION_TYPE_SCROLL
44
- direction_command = direction == :down || direction == :right ? ".scrollForward()" : ".scrollBackward()"
45
- elsif type == SCROLL_ACTION_TYPE_FLING
46
- direction_command = direction == :down || direction == :right ? ".flingForward()" : ".flingBackward()"
47
- else
48
- raise "Unknown scroll action type #{type}"
49
- end
50
- cmd = "new UiScrollable(#{scrollable_selector})#{orientation}#{direction_command};"
51
- begin
52
- puts "Scroll execute[uiautomator_#{type}]: #{cmd}"
53
- @driver.find_element(uiautomator: cmd)
54
- rescue
55
- # Ignored
56
- end
57
- end
58
-
59
-
60
- end
61
-
62
- end
@@ -1,12 +0,0 @@
1
- module ::Appium
2
- module Core
3
- class Element
4
- include TestaAppiumDriver::ClassSelectors
5
- include TestaAppiumDriver::Attributes
6
-
7
- def parent
8
- self.find_element(xpath: "./..")
9
- end
10
- end
11
- end
12
- end
@@ -1,150 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
- module TestaAppiumDriver
5
- class Bounds
6
-
7
- attr_reader :width
8
- attr_reader :height
9
- attr_reader :offset
10
-
11
-
12
- # @param top_left [Coordinates]
13
- # @param bottom_right [Coordinates]
14
- # @param window_width [Integer]
15
- # @param window_height [Integer]
16
- def initialize(top_left, bottom_right, window_width, window_height)
17
- @top_left = top_left
18
- @bottom_right = bottom_right
19
- @width = bottom_right.x - top_left.x
20
- @height = bottom_right.y - top_left.y
21
- @offset = Offset.new(self, window_width, window_height)
22
- @center = TestaAppiumDriver::Coordinates.new(@top_left.x + @width/2, @top_left.y + @height / 2)
23
- end
24
-
25
- def as_json
26
- {
27
- width: @width,
28
- height: @height,
29
- top_left: @top_left.as_json,
30
- bottom_right: @bottom_right.as_json,
31
- offset: @offset.as_json
32
- }
33
- end
34
-
35
- # @return [TestaAppiumDriver::Offset]
36
- def offset
37
- @offset
38
- end
39
-
40
- # @return [TestaAppiumDriver::Coordinates]
41
- def top_left
42
- @top_left
43
- end
44
- # @return [TestaAppiumDriver::Coordinates]
45
- def bottom_right
46
- @bottom_right
47
- end
48
-
49
- # @return [TestaAppiumDriver::Coordinates]
50
- def center
51
- @center
52
- end
53
-
54
- def to_s
55
- JSON.dump(as_json)
56
- end
57
-
58
- # @param bounds [String] bounds that driver.attribute("bounds") return
59
- # @param driver [TestaAppiumDriver::Driver]
60
- def self.from_android(bounds, driver)
61
- matches = bounds.match(/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/)
62
- raise "Unexpected bounds: #{bounds}" unless matches
63
-
64
- captures = matches.captures
65
- top_left = Coordinates.new(captures[0], captures[1])
66
- bottom_right = Coordinates.new(captures[2], captures[3])
67
- ws = driver.window_size
68
- window_width = ws.width.to_i
69
- window_height = ws.height.to_i
70
- Bounds.new(top_left, bottom_right, window_width, window_height)
71
- end
72
-
73
- def self.from_ios(rect, driver)
74
- rect = JSON.parse(rect)
75
- top_left = Coordinates.new(rect["x"], rect["y"])
76
- bottom_right = Coordinates.new(top_left.x + rect["width"].to_i, top_left.y + rect["height"].to_i)
77
- ws = driver.window_size
78
- window_width = ws.width.to_i
79
- window_height = ws.height.to_i
80
- Bounds.new(top_left, bottom_right, window_width, window_height)
81
- end
82
- end
83
-
84
- #noinspection ALL
85
- class Coordinates
86
- def initialize(x, y)
87
- @x = x.to_i
88
- @y = y.to_i
89
- end
90
-
91
- def as_json
92
- {
93
- x: @x,
94
- y: @y
95
- }
96
- end
97
-
98
-
99
- # @return [Integer]
100
- def x
101
- @x
102
- end
103
-
104
- # @return [Integer]
105
- def y
106
- @y
107
- end
108
- end
109
-
110
-
111
- class Offset
112
- def initialize(bounds, window_width, window_height)
113
- @top = bounds.top_left.y
114
- @right = window_width - bounds.bottom_right.x
115
- @bottom = window_height - bounds.bottom_right.y
116
- @left = bounds.top_left.x
117
- end
118
-
119
- def as_json
120
- {
121
- top: @top,
122
- right: @right,
123
- bottom: @bottom,
124
- left: @left
125
- }
126
- end
127
-
128
-
129
- # @return [Integer]
130
- def top
131
- @top
132
- end
133
-
134
- # @return [Integer]
135
- def right
136
- @right
137
- end
138
-
139
- # @return [Integer]
140
- def bottom
141
- @bottom
142
- end
143
-
144
- # @return [Integer]
145
- def left
146
- @left
147
- end
148
-
149
- end
150
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #noinspection ALL
4
- module TestaAppiumDriver
5
- FIND_STRATEGY_UIAUTOMATOR = :uiautomator
6
- FIND_STRATEGY_XPATH = :xpath
7
- FIND_STRATEGY_ID = :id
8
- FIND_STRATEGY_NAME = :name
9
- FIND_STRATEGY_IMAGE = :image
10
- FIND_STRATEGY_CLASS_CHAIN = :class_chain
11
-
12
- SCROLL_STRATEGY_UIAUTOMATOR = :uiautomator
13
- SCROLL_STRATEGY_W3C = :w3c
14
-
15
-
16
- SCROLL_CORRECTION_W3C = 30
17
- SCROLL_ALIGNMENT_THRESHOLD = 25
18
-
19
- SCROLL_ACTION_TYPE_SCROLL = :scroll
20
- SCROLL_ACTION_TYPE_FLING = :fling
21
- SCROLL_ACTION_TYPE_DRAG = :drag
22
-
23
-
24
- DEFAULT_UIAUTOMATOR_MAX_SWIPES = 20
25
-
26
- DEFAULT_ANDROID_FIND_STRATEGY = FIND_STRATEGY_UIAUTOMATOR
27
- #DEFAULT_ANDROID_SCROLL_STRATEGY = SCROLL_STRATEGY_UIAUTOMATOR
28
- DEFAULT_ANDROID_SCROLL_STRATEGY = SCROLL_STRATEGY_W3C
29
-
30
-
31
- DEFAULT_IOS_FIND_STRATEGY = FIND_STRATEGY_XPATH
32
- DEFAULT_IOS_SCROLL_STRATEGY = SCROLL_STRATEGY_W3C
33
-
34
- DEFAULT_W3C_MAX_SCROLLS = 7
35
-
36
- EXISTS_WAIT = 0.5
37
- LONG_TAP_DURATION = 1.5
38
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module TestaAppiumDriver
4
- class StrategyMixException < Exception
5
- def initialize(strategy, strategy_reason, mixed_strategy, mixed_reason)
6
-
7
- # example: parent is only available in xpath strategy and cannot be used with from_element which is only available in uiautomator strategy
8
- msg = "strategy mix exception: '#{strategy_reason}' is only available in #{strategy} strategy and cannot be used with '#{mixed_reason}' which is only available in #{mixed_strategy} strategy"
9
- super(msg)
10
- end
11
- end
12
- end
@@ -1,271 +0,0 @@
1
- module TestaAppiumDriver
2
- module Helpers
3
-
4
- # supported selectors
5
- # id: "com.my.package:id/myId"
6
- # id: "myId" => will be converted to "com.my.package:id/myId"
7
- # id: /my/ will find all elements with ids that contain my
8
- # desc: "element description"
9
- # desc: /ription/ will find all elements that contains ription
10
- # class: "android.widget.Button"
11
- # class: /Button/ will find all elements with classes that contain Button
12
- # text: "Hello world"
13
- # text: /ello/ will find all elements with text that contain ello
14
- # package: "com.my.package"
15
- # package: /my/ will find all elements with package that contains my
16
- # long_clickable: true or false
17
- # checkable: true or false
18
- # checked: true or false
19
- # clickable: true or false
20
- # enabled: true or false
21
- # focusable: true or false
22
- # focused: true or false
23
- # index: child index inside of a parent element, index starts from 0
24
- # selected: true or false
25
- # scrollable: true or false
26
- # @param [Hash] hash selectors for finding elements
27
- # @param [Boolean] single should the command return first instance or all of matched elements
28
- # @return [String] hash selectors converted to uiautomator command
29
- def hash_to_uiautomator(hash, single = true)
30
- command = "new UiSelector()"
31
-
32
- id = resolve_id(hash[:id])
33
- command = "#{ command }.resourceId(\"#{ %(#{ id }) }\")" if id && id.kind_of?(String)
34
- command = "#{ command }.resourceIdMatches(\".*#{ %(#{ id.source }) }.*\")" if id && id.kind_of?(Regexp)
35
- command = "#{ command }.description(\"#{ %(#{ hash[:desc] }) }\")" if hash[:desc] && hash[:desc].kind_of?(String)
36
- command = "#{ command }.descriptionMatches(\".*#{ %(#{ hash[:desc].source }) }.*\")" if hash[:desc] && hash[:desc].kind_of?(Regexp)
37
- command = "#{ command }.className(\"#{ %(#{ hash[:class] }) }\")" if hash[:class] && hash[:class].kind_of?(String)
38
- command = "#{ command }.classNameMatches(\".*#{ %(#{ hash[:class].source }) }.*\")" if hash[:class] && hash[:class].kind_of?(Regexp)
39
- command = "#{ command }.text(\"#{ %(#{ hash[:text] }) }\")" if hash[:text] && hash[:text].kind_of?(String)
40
- command = "#{ command }.textMatches(\".*#{ %(#{ hash[:text].source }) }.*\")" if hash[:text] && hash[:text].kind_of?(Regexp)
41
- command = "#{ command }.packageName(\"#{ %(#{ hash[:package] }) }\")" if hash[:package] && hash[:package].kind_of?(String)
42
- command = "#{ command }.packageNameMatches(\".*#{ %(#{ hash[:package].source }) }.*\")" if hash[:package] && hash[:package].kind_of?(Regexp)
43
-
44
- command = "#{ command }.longClickable(#{ hash[:long_clickable] })" if hash[:long_clickable]
45
- command = "#{ command }.checkable(#{ hash[:checkable] })" unless hash[:checkable].nil?
46
- command = "#{ command }.checked(#{ hash[:checked] })" unless hash[:checked].nil?
47
- command = "#{ command }.clickable(#{ hash[:clickable] })" unless hash[:clickable].nil?
48
- command = "#{ command }.enabled(#{ hash[:enabled] })" unless hash[:enabled].nil?
49
- command = "#{ command }.focusable(#{ hash[:focusable] })" unless hash[:focusable].nil?
50
- command = "#{ command }.focused(#{ hash[:focused] })" unless hash[:focused].nil?
51
- command = "#{ command }.index(#{ hash[:index].to_s })" unless hash[:index].nil?
52
- command = "#{ command }.selected(#{ hash[:selected] })" unless hash[:selected].nil?
53
- command = "#{ command }.scrollable(#{ hash[:scrollable] })" unless hash[:scrollable].nil?
54
-
55
- command += ".instance(0)" if single
56
-
57
- command
58
- end
59
-
60
-
61
- # supported selectors
62
- # id: "com.my.package:id/myId"
63
- # id: "myId" => will be converted to "com.my.package:id/myId"
64
- # id: /my/ will find all elements with ids that contain my
65
- # desc: "element description"
66
- # desc: /ription/ will find all elements that contains ription
67
- # class: "android.widget.Button"
68
- # class: /Button/ will find all elements with classes that contain Button
69
- # text: "Hello world"
70
- # text: /ello/ will find all elements with text that contain ello
71
- # package: "com.my.package"
72
- # package: /my/ will find all elements with package that contains my
73
- # long_clickable: true or false
74
- # checkable: true or false
75
- # checked: true or false
76
- # clickable: true or false
77
- # enabled: true or false
78
- # focusable: true or false
79
- # focused: true or false
80
- # index: child index inside of a parent element, index starts from 0
81
- # selected: true or false
82
- # scrollable: true or false
83
- # @param [Hash] hash selectors for finding elements
84
- # @param [Boolean] single should the command return first instance or all of matched elements
85
- # @return [String] hash selectors converted to xpath command
86
- def hash_to_xpath(device, hash, single = true)
87
- for_android = device == :android
88
-
89
-
90
- command = "//"
91
-
92
-
93
-
94
- if for_android
95
- id = resolve_id(hash[:id])
96
- if hash[:class] && hash[:class].kind_of?(String)
97
- command = "#{ command }#{hash[:class] }"
98
- elsif hash[:class] && hash[:class].kind_of?(Regexp)
99
- command = "#{ command}*[contains(@class, \"#{ %(#{hash[:class].source }) }\")]"
100
- else
101
- command = "#{command}*"
102
- end
103
-
104
- # TODO: with new uiautomator sever you can use matches to look with regex instead of contains
105
- command = "#{ command }[@resource-id=\"#{ %(#{ id }) }\"]" if id && id.kind_of?(String)
106
- command = "#{ command }[contains(@resource-id, \"#{ %(#{ id.source }) }\")]" if id && id.kind_of?(Regexp)
107
- command = "#{ command }[@content-desc=\"#{ %(#{hash[:desc] }) }\"]" if hash[:desc] && hash[:desc].kind_of?(String)
108
- command = "#{ command }[contains(@content-desc, \"#{ %(#{hash[:desc].source }) }\")]" if hash[:desc] && hash[:desc].kind_of?(Regexp)
109
- command = "#{ command }[contains(@class, \"#{ %(#{hash[:class].source }) }\")]" if hash[:class] && hash[:class].kind_of?(Regexp)
110
- command = "#{ command }[@text=\"#{ %(#{hash[:text] }) }\"]" if hash[:text] && hash[:text].kind_of?(String)
111
- command = "#{ command }[contains(@text, \"#{ %(#{hash[:text].source }) }\")]" if hash[:text] && hash[:text].kind_of?(Regexp)
112
- command = "#{ command }[@package=\"#{ %(#{hash[:package] }) }\"]" if hash[:package] && hash[:package].kind_of?(String)
113
- command = "#{ command }[contains=(@package, \"#{ %(#{hash[:package].source }) }\")]" if hash[:package] && hash[:package].kind_of?(Regexp)
114
- command = "#{ command }[@long-clickable=\"#{ hash[:long_clickable] }\"]" if hash[:long_clickable]
115
- command = "#{ command }[@checkable=\"#{ hash[:checkable] }\"]" unless hash[:checkable].nil?
116
- command = "#{ command }[@checked=\"#{ hash[:checked] }\"]" unless hash[:checked].nil?
117
- command = "#{ command }[@clickable=\"#{ hash[:clickable] }\"]" unless hash[:clickable].nil?
118
- command = "#{ command }[@enabled=\"#{ hash[:enabled] }\"]" unless hash[:enabled].nil?
119
- command = "#{ command }[@focusable=\"#{ hash[:focusable] }\"]" unless hash[:focusable].nil?
120
- command = "#{ command }[@focused=\"#{ hash[:focused] }\"]" unless hash[:focused].nil?
121
- command = "#{ command }[@index=\"#{ hash[:index] }\"]" unless hash[:index].nil?
122
- command = "#{ command }[@selected=\"#{ hash[:selected] }\"]" unless hash[:selected].nil?
123
-
124
- # it seems like you cannot query by scrollable
125
- # command = "#{ command }[@scrollable=\"#{ hash[:scrollable] }\"]" unless hash[:scrollable].nil?
126
- else
127
-
128
- hash[:type] = hash[:class] unless hash[:class].nil?
129
- if hash[:type] && hash[:type].kind_of?(String)
130
- command = "#{ command }#{hash[:type] }"
131
- elsif hash[:type] && hash[:type].kind_of?(Regexp)
132
- command = "#{ command}*[contains(@type, \"#{ %(#{hash[:type].source }) }\")]"
133
- else
134
- command = "#{command}*"
135
- end
136
-
137
-
138
- # # ios specific
139
- hash[:label] = hash[:text] unless hash[:text].nil?
140
- hash[:name] = hash[:id] unless hash[:id].nil?
141
-
142
- command = "#{ command }[@enabled=\"#{ hash[:enabled] }\"]" unless hash[:enabled].nil?
143
- command = "#{ command }[@label=\"#{ %(#{hash[:label] }) }\"]" if hash[:label] && hash[:label].kind_of?(String)
144
- command = "#{ command }[contains(@label, \"#{ %(#{hash[:label].source }) }\")]" if hash[:label] && hash[:label].kind_of?(Regexp)
145
- command = "#{ command }[@name=\"#{ %(#{hash[:name] }) }\"]" if hash[:name] && hash[:name].kind_of?(String)
146
- command = "#{ command }[contains(@name, \"#{ %(#{hash[:name].source }) }\")]" if hash[:name] && hash[:name].kind_of?(Regexp)
147
- command = "#{ command }[@value=\"#{ %(#{hash[:value] }) }\"]" if hash[:value] && hash[:value].kind_of?(String)
148
- command = "#{ command }[contains(@value, \"#{ %(#{hash[:value].source }) }\")]" if hash[:value] && hash[:value].kind_of?(Regexp)
149
- command = "#{ command }[@width=\"#{ hash[:width] }\"]" unless hash[:width].nil?
150
- command = "#{ command }[@height=\"#{ hash[:height] }\"]" unless hash[:height].nil?
151
- command = "#{ command }[@visible=\"#{ hash[:visible] }\"]" unless hash[:visible].nil?
152
- end
153
-
154
-
155
- command += "[1]" if single
156
-
157
- command
158
- end
159
-
160
-
161
- def hash_to_class_chain(hash, single = true)
162
- command = "**/"
163
-
164
- hash[:type] = hash[:class] unless hash[:class].nil?
165
- hash[:label] = hash[:text] unless hash[:text].nil?
166
- hash[:name] = hash[:id] unless hash[:id].nil?
167
- if hash[:type] && hash[:type].kind_of?(String)
168
- command = "#{ command }#{hash[:type] }"
169
- else
170
- command = "#{command}*"
171
- end
172
-
173
- command = "#{ command }[`enabled == #{ hash[:enabled] }`]" unless hash[:enabled].nil?
174
- command = "#{ command }[`label == \"#{ %(#{hash[:label] }) }\"`]" if hash[:label] && hash[:label].kind_of?(String)
175
- command = "#{ command }[`label CONTAINS \"#{ %(#{hash[:label].source }) }\"`]" if hash[:label] && hash[:label].kind_of?(Regexp)
176
- command = "#{ command }[`name == \"#{ %(#{hash[:name] }) }\"`]" if hash[:name] && hash[:name].kind_of?(String)
177
- command = "#{ command }[`name CONTAINS \"#{ %(#{hash[:name].source }) }\"`]" if hash[:name] && hash[:name].kind_of?(Regexp)
178
- command = "#{ command }[`value == \"#{ %(#{hash[:value] }) }\"`]" if hash[:value] && hash[:value].kind_of?(String)
179
- command = "#{ command }[`value CONTAINS \"#{ %(#{hash[:value].source }) }\"`]" if hash[:value] && hash[:value].kind_of?(Regexp)
180
- command = "#{ command }[`visible == #{ hash[:visible] }`]" unless hash[:visible].nil?
181
-
182
- command += "[1]" if single
183
-
184
- command
185
- end
186
-
187
- # check if selectors are for a scrollable element
188
- # @param [Boolean] single should the command return first instance or all of matched elements
189
- # @param [Hash] selectors for fetching elements
190
- # @return [Boolean] true if element has scrollable attribute true or is class one of (RecyclerView, HorizontalScrollView, ScrollView, ListView)
191
- def is_scrollable_selector?(selectors, single)
192
- return false unless single
193
- return true if selectors[:scrollable]
194
- if selectors[:class] == "androidx.recyclerview.widget.RecyclerView" ||
195
- selectors[:class] == "android.widget.HorizontalScrollView" ||
196
- selectors[:class] == "android.widget.ScrollView" ||
197
- selectors[:class] == "android.widget.ListView"
198
- return true
199
- elsif selectors[:type] == "XCUIElementTypeScrollView"
200
- return true
201
- end
202
- false
203
- end
204
-
205
- #noinspection RubyUnnecessaryReturnStatement,RubyUnusedLocalVariable
206
- # separate selectors from given hash parameters
207
- # @param [Hash] params
208
- # @return [Array] first element is params, second are selectors
209
- def extract_selectors_from_params(params = {})
210
- selectors = params.select { |key, value| [
211
- :id,
212
- :long_clickable,
213
- :desc,
214
- :class,
215
- :text,
216
- :package,
217
- :checkable,
218
- :checked,
219
- :clickable,
220
- :enabled,
221
- :focusable,
222
- :focused,
223
- :index,
224
- :selected,
225
- :scrollable,
226
-
227
- # ios specific
228
- :type,
229
- :label,
230
- :x,
231
- :y,
232
- :width,
233
- :height,
234
- :visible,
235
- :name,
236
- :value,
237
-
238
- :image
239
- ].include?(key) }
240
- params = Hash[params.to_a - selectors.to_a]
241
-
242
- # default params
243
- params[:single] = true if params[:single].nil?
244
- params[:scrollable_locator] = nil if params[:scrollable_locator].nil?
245
- if params[:default_find_strategy].nil?
246
- params[:default_find_strategy] = DEFAULT_ANDROID_FIND_STRATEGY if @driver.device == :android
247
- params[:default_find_strategy] = DEFAULT_IOS_FIND_STRATEGY if @driver.device == :ios || @driver.device == :tvos
248
- end
249
- if params[:default_scroll_strategy].nil?
250
- params[:default_scroll_strategy] = DEFAULT_ANDROID_SCROLL_STRATEGY if @driver.device == :android
251
- params[:default_scroll_strategy] = DEFAULT_IOS_SCROLL_STRATEGY if @driver.device == :ios || @driver.device == :tvos
252
- end
253
-
254
- return params, selectors
255
- end
256
-
257
-
258
- def resolve_id(id)
259
- if id && id.kind_of?(String) && !id.match?(/.*:id\//)
260
- # shorthand ids like myId make full ids => my.app.package:id/myId
261
- if id[0] == "="
262
- return id[1..-1]
263
- else
264
- return "#{@driver.current_package}:id/#{id}"
265
- end
266
- else
267
- id
268
- end
269
- end
270
- end
271
- end