testa_appium_driver 0.1.4 → 0.1.8

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/.gitignore +2 -2
  3. data/.idea/deployment.xml +9 -1
  4. data/.idea/inspectionProfiles/Project_Default.xml +8 -8
  5. data/.idea/runConfigurations/Android_Test.xml +41 -41
  6. data/.idea/runConfigurations.xml +10 -0
  7. data/.idea/sshConfigs.xml +3 -0
  8. data/.idea/webServers.xml +7 -0
  9. data/.rspec +3 -3
  10. data/.rubocop.yml +13 -13
  11. data/CHANGELOG.md +5 -5
  12. data/CODE_OF_CONDUCT.md +102 -102
  13. data/Gemfile +12 -12
  14. data/LICENSE.txt +21 -21
  15. data/README.md +18 -5
  16. data/Rakefile +12 -12
  17. data/bin/console +17 -17
  18. data/bin/setup +8 -8
  19. data/lib/testa_appium_driver/android/class_selectors.rb +53 -18
  20. data/lib/testa_appium_driver/android/driver.rb +17 -0
  21. data/lib/testa_appium_driver/android/locator/attributes.rb +1 -3
  22. data/lib/testa_appium_driver/android/locator.rb +22 -7
  23. data/lib/testa_appium_driver/android/scroll_actions/uiautomator_scroll_actions.rb +1 -14
  24. data/lib/testa_appium_driver/common/constants.rb +2 -0
  25. data/lib/testa_appium_driver/common/helpers.rb +32 -1
  26. data/lib/testa_appium_driver/common/locator/scroll_actions.rb +20 -4
  27. data/lib/testa_appium_driver/common/locator.rb +191 -31
  28. data/lib/testa_appium_driver/common/scroll_actions/w3c_scroll_actions.rb +4 -35
  29. data/lib/testa_appium_driver/common/scroll_actions.rb +5 -14
  30. data/lib/testa_appium_driver/driver.rb +92 -40
  31. data/lib/testa_appium_driver/ios/driver.rb +13 -0
  32. data/lib/testa_appium_driver/ios/locator.rb +22 -4
  33. data/lib/testa_appium_driver/ios/type_selectors.rb +36 -15
  34. data/lib/testa_appium_driver/version.rb +5 -5
  35. data/testa_appium_driver.iml +36 -2
  36. metadata +3 -2
@@ -29,7 +29,6 @@ module TestaAppiumDriver
29
29
  def initialize(opts = {})
30
30
  @testa_opts = opts[:testa_appium_driver] || {}
31
31
 
32
-
33
32
  core = Appium::Core.for(opts)
34
33
  extend_for(core.device, core.automation_name)
35
34
  @device = core.device
@@ -41,6 +40,9 @@ module TestaAppiumDriver
41
40
  invalidate_cache
42
41
 
43
42
 
43
+ disable_wait_for_idle
44
+ disable_implicit_wait
45
+
44
46
  Selenium::WebDriver::Element.set_driver(self, opts[:caps][:udid])
45
47
  end
46
48
 
@@ -63,42 +65,55 @@ module TestaAppiumDriver
63
65
  # Cache stores last executed find_element with given selector, strategy and from_element. If given values are the same within
64
66
  # last 5 seconds element is retrieved from cache.
65
67
  # @param [TestaAppiumDriver::Locator, TestaAppiumDriver::Driver] from_element element from which start the search
66
- # @param [String] selector resolved string of a [TestaAppiumDriver::Locator] selector xpath for xpath strategy, java UiSelectors for uiautomator or id for ID strategy
67
68
  # @param [Boolean] single fetch single or multiple results
68
- # @param [Symbol, nil] strategy [TestaAppiumDriver::FIND_STRATEGY_UIAUTOMATOR], [TestaAppiumDriver::FIND_STRATEGY_XPATH] or [TestaAppiumDriver::FIND_STRATEGY_ID]
69
- # @param [Symbol] default_strategy if strategy is not enforced, default can be used
69
+ # @param [Array<Hash>] strategies_and_selectors array of usable strategies and selectors
70
70
  # @param [Boolean] skip_cache to skip checking and storing cache
71
71
  # @return [Selenium::WebDriver::Element, Array] element is returned if single is true, array otherwise
72
- def execute(from_element, selector, single, strategy, default_strategy, skip_cache = false)
72
+ def execute(from_element, single, strategies_and_selectors, skip_cache: false, ignore_implicit_wait: false)
73
73
 
74
74
  # if user wants to wait for element to exist, he can use wait_until_present
75
- disable_wait_for_idle
75
+ start_time = Time.now.to_f
76
+ ss_index = 0
77
+
76
78
 
77
79
 
78
- # if not restricted to a strategy, use the default one
79
- strategy = default_strategy if strategy.nil?
80
80
 
81
81
  # resolve from_element unique id, so that we can cache it properly
82
- from_element_id = from_element.kind_of?(TestaAppiumDriver::Locator) ? from_element.strategy_and_selector[1] : nil
82
+ from_element_id = from_element.instance_of?(TestaAppiumDriver::Locator) ? from_element.strategies_and_selectors : nil
83
83
 
84
- puts "Executing #{from_element_id ? "from #{from_element.strategy}: #{from_element.strategy_and_selector} => " : ""}#{strategy}: #{selector}"
85
84
  begin
86
- if @cache[:selector] != selector || # cache miss, selector is different
85
+ ss = strategies_and_selectors[ss_index % strategies_and_selectors.count]
86
+ ss_index +=1
87
+
88
+ puts "Executing #{from_element_id ? "from #{from_element.strategy}: #{from_element.strategies_and_selectors} => " : ""}#{ss.keys[0]}: #{ss.values[0]}"
89
+
90
+ if @cache[:selector] != ss.values[0] || # cache miss, selector is different
87
91
  @cache[:time] + 5 <= Time.now || # cache miss, older than 5 seconds
88
- @cache[:strategy] != strategy || # cache miss, different find strategy
92
+ @cache[:strategy] != ss.keys[0] || # cache miss, different find strategy
89
93
  @cache[:from_element_id] != from_element_id || # cache miss, search is started from different element
90
94
  skip_cache # cache is skipped
91
95
 
92
- if single
93
- execute_result = from_element.find_element("#{strategy}": selector)
96
+ if ss.keys[0] == FIND_STRATEGY_IMAGE
97
+ set_find_by_image_settings(ss.values[0].dup)
98
+ if single
99
+ execute_result = from_element.find_element_by_image(ss.values[0][:image])
100
+ else
101
+ execute_result = from_element.find_elements_by_image(ss.values[0][:image])
102
+ end
103
+ restore_set_by_image_settings
94
104
  else
95
- execute_result = from_element.find_elements("#{strategy}": selector)
105
+ if single
106
+ execute_result = from_element.find_element(ss)
107
+ else
108
+ execute_result = from_element.find_elements(ss)
109
+ end
96
110
  end
97
111
 
98
112
 
113
+
99
114
  unless skip_cache
100
- @cache[:selector] = selector
101
- @cache[:strategy] = strategy
115
+ @cache[:selector] = ss.values[0]
116
+ @cache[:strategy] = ss.keys[0]
102
117
  @cache[:time] = Time.now
103
118
  @cache[:from_element_id] = from_element_id
104
119
  @cache[:element] = execute_result
@@ -109,9 +124,12 @@ module TestaAppiumDriver
109
124
  puts "Using cache from #{@cache[:time].strftime("%H:%M:%S.%L")}, strategy: #{@cache[:strategy]}"
110
125
  end
111
126
  rescue => e
112
- raise e
113
- ensure
114
- enable_wait_for_idle
127
+ if (start_time + @implicit_wait_ms/1000 < Time.now.to_f && !ignore_implicit_wait) || ss_index < strategies_and_selectors.count
128
+ sleep EXISTS_WAIT if ss_index >= strategies_and_selectors.count
129
+ retry
130
+ else
131
+ raise e
132
+ end
115
133
  end
116
134
 
117
135
  execute_result
@@ -121,37 +139,49 @@ module TestaAppiumDriver
121
139
  # method missing is used to forward methods to the actual appium driver
122
140
  # after the method is executed, find element cache is invalidated
123
141
  def method_missing(method, *args, &block)
124
- @driver.send(method, *args, &block)
142
+ r = @driver.send(method, *args, &block)
125
143
  invalidate_cache
144
+ r
126
145
  end
127
146
 
128
147
  # disables implicit wait
129
148
  def disable_implicit_wait
130
- @implicit_wait_ms = @driver.get_timeouts["implicit"]
131
- @driver.manage.timeouts.implicit_wait = 0
149
+ @implicit_wait_ms = @driver.get_timeouts["implicit"].to_i
150
+ @implicit_wait_ms = @implicit_wait_ms/1000 if @implicit_wait_ms > 100000
151
+ @implicit_wait_uiautomator_ms = @driver.get_settings["waitForSelectorTimeout"]
152
+ @driver.manage.timeouts.implicit_wait = 0
153
+ @driver.update_settings({waitForSelectorTimeout: 1})
132
154
  end
133
155
 
134
- # enables implicit wait, can be called only after disabling implicit wait
135
- def enable_implicit_wait
136
- raise "Implicit wait is not disabled" if @implicit_wait_ms.nil?
137
- # get_timeouts always returns in milliseconds, but we should set in seconds
138
- @driver.manage.timeouts.implicit_wait = @implicit_wait_ms / 1000
139
- end
140
156
 
141
157
  # disables wait for idle, only executed for android devices
142
158
  def disable_wait_for_idle
143
159
  if @device == :android
144
160
  @wait_for_idle_timeout = @driver.settings.get["waitForIdleTimeout"]
145
- @driver.update_settings({waitForIdleTimeout: 0})
161
+ @driver.update_settings({waitForIdleTimeout: 1})
146
162
  end
147
163
  end
148
164
 
149
- # enables wait for idle, only executed for android devices
150
- def enable_wait_for_idle
151
- if @device == :android
152
- raise "Wait for idle is not disabled" if @wait_for_idle_timeout.nil?
153
- @driver.update_settings({waitForIdleTimeout: @wait_for_idle_timeout})
154
- end
165
+
166
+ def set_find_by_image_settings(settings)
167
+ settings.delete(:image)
168
+ @default_find_image_settings = {}
169
+ old_settings = @driver.get_settings
170
+ @default_find_image_settings[:imageMatchThreshold] = old_settings["imageMatchThreshold"]
171
+ @default_find_image_settings[:fixImageFindScreenshotDims] = old_settings["fixImageFindScreenshotDims"]
172
+ @default_find_image_settings[:fixImageTemplateSize] = old_settings["fixImageTemplateSize"]
173
+ @default_find_image_settings[:fixImageTemplateScale] = old_settings["fixImageTemplateScale"]
174
+ @default_find_image_settings[:defaultImageTemplateScale] = old_settings["defaultImageTemplateScale"]
175
+ @default_find_image_settings[:checkForImageElementStaleness] = old_settings["checkForImageElementStaleness"]
176
+ @default_find_image_settings[:autoUpdateImageElementPosition] = old_settings["autoUpdateImageElementPosition"]
177
+ @default_find_image_settings[:imageElementTapStrategy] = old_settings["imageElementTapStrategy"]
178
+ @default_find_image_settings[:getMatchedImageResult] = old_settings["getMatchedImageResult"]
179
+
180
+ @driver.update_settings(settings)
181
+ end
182
+
183
+ def restore_set_by_image_settings
184
+ @driver.update_settings(@default_find_image_settings) if @default_find_image_settings
155
185
  end
156
186
 
157
187
 
@@ -178,6 +208,30 @@ module TestaAppiumDriver
178
208
  @driver.hide_keyboard
179
209
  end
180
210
 
211
+ def tab_key
212
+ @driver.press_keycode(61)
213
+ end
214
+
215
+ def dpad_up_key
216
+ @driver.press_keycode(19)
217
+ end
218
+
219
+ def dpad_down_key
220
+ @driver.press_keycode(20)
221
+ end
222
+
223
+ def dpad_right_key
224
+ @driver.press_keycode(22)
225
+ end
226
+
227
+ def dpad_left_key
228
+ @driver.press_keycode(23)
229
+ end
230
+
231
+ def enter_key
232
+ @driver.press_keycode(66)
233
+ end
234
+
181
235
  def press_keycode(code)
182
236
  @driver.press_keycode(code)
183
237
  end
@@ -187,13 +241,11 @@ module TestaAppiumDriver
187
241
  end
188
242
 
189
243
 
244
+
245
+
190
246
  # @return [Array<Selenium::WebDriver::Element] array of 2 elements, the first element without children and the last element without children in the current page
191
247
  def first_and_last_leaf(from_element = @driver)
192
- disable_wait_for_idle
193
- disable_implicit_wait
194
248
  elements = from_element.find_elements(xpath: "//*[not(*)]")
195
- enable_implicit_wait
196
- enable_wait_for_idle
197
249
  return nil if elements.count == 0
198
250
  [elements[0], elements[-1]]
199
251
  end
@@ -7,6 +7,19 @@ module TestaAppiumDriver
7
7
  include TypeSelectors
8
8
 
9
9
 
10
+
11
+ # @param params [Hash]
12
+ # @return [TestaAppiumDriver::Locator] first scrollable element
13
+ def scrollable(params = {})
14
+ scroll_view(params)
15
+ end
16
+
17
+ # @param params [Hash]
18
+ # @return [TestaAppiumDriver::Locator] first scrollable element
19
+ def scrollables(params = {})
20
+ scroll_views(params)
21
+ end
22
+
10
23
  private
11
24
  def handle_testa_opts
12
25
  if @testa_opts[:default_find_strategy].nil?
@@ -3,6 +3,7 @@ require_relative 'locator/attributes'
3
3
  module TestaAppiumDriver
4
4
  class Locator
5
5
  include TypeSelectors
6
+ attr_accessor :class_chain_selector
6
7
 
7
8
  def init(params, selectors, single)
8
9
  if is_scrollable_selector?(selectors, single)
@@ -19,16 +20,23 @@ module TestaAppiumDriver
19
20
  params[:scrollable_locator] = self.dup
20
21
  end
21
22
 
23
+ @class_chain_selector = hash_to_class_chain(selectors, single)
24
+
25
+
22
26
  @scrollable_locator = params[:scrollable_locator] if params[:scrollable_locator]
23
27
  end
24
28
 
25
29
 
26
30
  # @return [Array] returns 2 elements. The first is the resolved find element strategy and the second is the resolved selector
27
- def strategy_and_selector
31
+ def strategies_and_selectors
32
+ ss = []
28
33
  if @can_use_id_strategy
29
- return FIND_STRATEGY_NAME, @can_use_id_strategy
34
+ ss.push({"#{FIND_STRATEGY_NAME}": @can_use_id_strategy})
30
35
  end
31
- [FIND_STRATEGY_XPATH, @xpath_selector]
36
+ ss.push({"#{FIND_STRATEGY_CLASS_CHAIN}": @class_chain_selector}) if @strategy.nil? || @strategy == FIND_STRATEGY_CLASS_CHAIN
37
+ ss.push({"#{FIND_STRATEGY_XPATH}": @xpath_selector}) if @strategy.nil? || @strategy == FIND_STRATEGY_XPATH
38
+ ss.push({"#{FIND_STRATEGY_IMAGE}": @image_selector}) if @strategy == FIND_STRATEGY_IMAGE
39
+ ss
32
40
  end
33
41
 
34
42
 
@@ -41,13 +49,23 @@ module TestaAppiumDriver
41
49
 
42
50
  locator = self.dup
43
51
  add_xpath_child_selectors(locator, selectors, single)
52
+ if @strategy.nil? || @strategy == FIND_STRATEGY_CLASS_CHAIN
53
+ add_class_chain_child_selectors(locator, selectors, single)
54
+ end
55
+
44
56
  if is_scrollable_selector?(selectors, single)
45
57
  locator.scrollable_locator.scroll_orientation = :vertical
46
- locator.scrollable_locator = self.dup
58
+ locator.scrollable_locator = locator
47
59
  end
48
60
 
49
61
  locator.last_selector_adjacent = false
50
62
  locator
51
63
  end
64
+
65
+
66
+ def add_class_chain_child_selectors(locator, selectors, single)
67
+ locator.single = false unless single # switching from single result to multiple
68
+ locator.class_chain_selector += "/" + hash_to_class_chain(selectors, single)
69
+ end
52
70
  end
53
71
  end
@@ -5,10 +5,15 @@ module TestaAppiumDriver
5
5
  # @return [TestaAppiumDriver::Locator]
6
6
  def add_selector(*args, &block)
7
7
  # if class selector is executed from driver, create new locator instance
8
- if self.kind_of?(TestaAppiumDriver::Driver) || self.kind_of(Selenium::WebDriver::Element)
8
+ if self.kind_of?(TestaAppiumDriver::Driver) || self.instance_of?(Selenium::WebDriver::Element)
9
9
  args.last[:default_find_strategy] = @default_find_strategy
10
10
  args.last[:default_scroll_strategy] = @default_scroll_strategy
11
- Locator.new(self, self, *args)
11
+ if self.instance_of?(Selenium::WebDriver::Element)
12
+ driver = self.get_driver
13
+ else
14
+ driver = self
15
+ end
16
+ Locator.new(driver, self, *args)
12
17
  else
13
18
  # class selector is executed from locator, just add child selector criteria
14
19
  self.add_child_selector(*args)
@@ -111,19 +116,6 @@ module TestaAppiumDriver
111
116
  end
112
117
 
113
118
 
114
- # @param params [Hash]
115
- # @return [TestaAppiumDriver::Locator] first scrollable element
116
- def scrollable(params = {})
117
- scroll_view(params)
118
- end
119
-
120
- # @param params [Hash]
121
- # @return [TestaAppiumDriver::Locator] first scrollable element
122
- def scrollables(params = {})
123
- scroll_views(params)
124
- end
125
-
126
-
127
119
  # @return [TestaAppiumDriver::Locator]
128
120
  def scroll_view(params = {})
129
121
  params[:type] = "XCUIElementTypeScrollView"
@@ -163,5 +155,34 @@ module TestaAppiumDriver
163
155
  params[:single] = false
164
156
  add_selector(params)
165
157
  end
158
+
159
+
160
+
161
+ # @return [TestaAppiumDriver::Locator]
162
+ def text_field(params = {})
163
+ params[:type] = "XCUIElementTypeTextField"
164
+ add_selector(params)
165
+ end
166
+
167
+ # @return [TestaAppiumDriver::Locator]
168
+ def text_fields(params = {})
169
+ params[:type] = "XCUIElementTypeTextField"
170
+ params[:single] = false
171
+ add_selector(params)
172
+ end
173
+
174
+
175
+ # @return [TestaAppiumDriver::Locator]
176
+ def secure_text_field(params = {})
177
+ params[:type] = "XCUIElementTypeSecureTextField"
178
+ add_selector(params)
179
+ end
180
+
181
+ # @return [TestaAppiumDriver::Locator]
182
+ def secure_text_fields(params = {})
183
+ params[:type] = "XCUIElementTypeSecureTextField"
184
+ params[:single] = false
185
+ add_selector(params)
186
+ end
166
187
  end
167
188
  end
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
-
3
- module TestaAppiumDriver
4
- VERSION = "0.1.4"
5
- end
1
+ # frozen_string_literal: true
2
+
3
+ module TestaAppiumDriver
4
+ VERSION = "0.1.8"
5
+ end
@@ -11,7 +11,7 @@
11
11
  <orderEntry type="sourceFolder" forTests="false" />
12
12
  <orderEntry type="library" scope="PROVIDED" name="appium_lib_core (v4.7.0, ruby-2.6.5-p114) [gem]" level="application" />
13
13
  <orderEntry type="library" scope="PROVIDED" name="ast (v2.4.2, ruby-2.6.5-p114) [gem]" level="application" />
14
- <orderEntry type="library" scope="PROVIDED" name="bundler (v2.2.25, ruby-2.6.5-p114) [gem]" level="application" />
14
+ <orderEntry type="library" scope="PROVIDED" name="bundler (v2.1.4, ruby-2.6.5-p114) [gem]" level="application" />
15
15
  <orderEntry type="library" scope="PROVIDED" name="childprocess (v3.0.0, ruby-2.6.5-p114) [gem]" level="application" />
16
16
  <orderEntry type="library" scope="PROVIDED" name="diff-lcs (v1.4.4, ruby-2.6.5-p114) [gem]" level="application" />
17
17
  <orderEntry type="library" scope="PROVIDED" name="eventmachine (v1.2.7, ruby-2.6.5-p114) [gem]" level="application" />
@@ -39,7 +39,41 @@
39
39
  </component>
40
40
  <component name="RakeTasksCache">
41
41
  <option name="myRootTask">
42
- <RakeTaskImpl id="rake" />
42
+ <RakeTaskImpl id="rake">
43
+ <subtasks>
44
+ <RakeTaskImpl description="Build testa_appium_driver-0.1.5.gem into the pkg directory" fullCommand="build" id="build" />
45
+ <RakeTaskImpl id="build">
46
+ <subtasks>
47
+ <RakeTaskImpl description="Generate SHA512 checksum if testa_appium_driver-0.1.5.gem into the checksums directory" fullCommand="build:checksum" id="checksum" />
48
+ </subtasks>
49
+ </RakeTaskImpl>
50
+ <RakeTaskImpl description="Remove any temporary products" fullCommand="clean" id="clean" />
51
+ <RakeTaskImpl description="Remove any generated files" fullCommand="clobber" id="clobber" />
52
+ <RakeTaskImpl description="Build and install testa_appium_driver-0.1.5.gem into system gems" fullCommand="install" id="install" />
53
+ <RakeTaskImpl id="install">
54
+ <subtasks>
55
+ <RakeTaskImpl description="Build and install testa_appium_driver-0.1.5.gem into system gems without network access" fullCommand="install:local" id="local" />
56
+ </subtasks>
57
+ </RakeTaskImpl>
58
+ <RakeTaskImpl description="Create tag v0.1.5 and build and push testa_appium_driver-0.1.5.gem to rubygems.org" fullCommand="release[remote]" id="release[remote]" />
59
+ <RakeTaskImpl description="Run RuboCop" fullCommand="rubocop" id="rubocop" />
60
+ <RakeTaskImpl id="rubocop">
61
+ <subtasks>
62
+ <RakeTaskImpl description="Auto-correct RuboCop offenses" fullCommand="rubocop:auto_correct" id="auto_correct" />
63
+ </subtasks>
64
+ </RakeTaskImpl>
65
+ <RakeTaskImpl description="Run RSpec code examples" fullCommand="spec" id="spec" />
66
+ <RakeTaskImpl description="" fullCommand="default" id="default" />
67
+ <RakeTaskImpl description="" fullCommand="release" id="release" />
68
+ <RakeTaskImpl id="release">
69
+ <subtasks>
70
+ <RakeTaskImpl description="" fullCommand="release:guard_clean" id="guard_clean" />
71
+ <RakeTaskImpl description="" fullCommand="release:rubygem_push" id="rubygem_push" />
72
+ <RakeTaskImpl description="" fullCommand="release:source_control_push" id="source_control_push" />
73
+ </subtasks>
74
+ </RakeTaskImpl>
75
+ </subtasks>
76
+ </RakeTaskImpl>
43
77
  </option>
44
78
  </component>
45
79
  </module>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: testa_appium_driver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - karlo.razumovic
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-08-23 00:00:00.000000000 Z
11
+ date: 2021-11-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: appium_lib_core
@@ -80,6 +80,7 @@ files:
80
80
  - ".idea/inspectionProfiles/Project_Default.xml"
81
81
  - ".idea/misc.xml"
82
82
  - ".idea/modules.xml"
83
+ - ".idea/runConfigurations.xml"
83
84
  - ".idea/runConfigurations/Android_Test.xml"
84
85
  - ".idea/sshConfigs.xml"
85
86
  - ".idea/vcs.xml"