testa_appium_driver 0.1.4 → 0.1.8

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/.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"