testa_appium_driver 0.1.18 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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