appium_lib 9.15.1 → 9.15.2

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +13 -15
  3. data/CHANGELOG.md +8 -0
  4. data/appium_lib.gemspec +7 -10
  5. data/docs/android_docs.md +259 -181
  6. data/docs/ios_docs.md +322 -244
  7. data/docs/ios_xcuitest.md +1 -1
  8. data/docs/w3c.md +21 -0
  9. data/lib/appium_lib/android/common/helper.rb +13 -13
  10. data/lib/appium_lib/android/element/button.rb +2 -0
  11. data/lib/appium_lib/android/element/generic.rb +2 -2
  12. data/lib/appium_lib/android/element/text.rb +2 -0
  13. data/lib/appium_lib/android/element/textfield.rb +2 -0
  14. data/lib/appium_lib/android/uiautomator2/element/button.rb +3 -0
  15. data/lib/appium_lib/android/uiautomator2/helper.rb +10 -8
  16. data/lib/appium_lib/appium.rb +4 -0
  17. data/lib/appium_lib/common/helper.rb +9 -8
  18. data/lib/appium_lib/common/multi_touch.rb +2 -0
  19. data/lib/appium_lib/common/touch_actions.rb +6 -5
  20. data/lib/appium_lib/driver.rb +72 -19
  21. data/lib/appium_lib/ios/common/helper.rb +36 -30
  22. data/lib/appium_lib/ios/element/button.rb +2 -0
  23. data/lib/appium_lib/ios/element/generic.rb +1 -0
  24. data/lib/appium_lib/ios/element/text.rb +2 -0
  25. data/lib/appium_lib/ios/element/textfield.rb +5 -2
  26. data/lib/appium_lib/ios/xcuitest/command/gestures.rb +4 -4
  27. data/lib/appium_lib/ios/xcuitest/element/button.rb +2 -0
  28. data/lib/appium_lib/ios/xcuitest/element/generic.rb +1 -0
  29. data/lib/appium_lib/ios/xcuitest/element/text.rb +2 -0
  30. data/lib/appium_lib/ios/xcuitest/element/textfield.rb +3 -0
  31. data/lib/appium_lib/ios/xcuitest/helper.rb +3 -4
  32. data/lib/appium_lib/version.rb +2 -2
  33. data/release_notes.md +10 -0
  34. metadata +39 -53
@@ -5,7 +5,7 @@
5
5
  - How to migrate XCUITest from UIAutomation
6
6
  - [Migrating your iOS tests from UIAutomation](https://github.com/appium/appium/blob/v1.6.2/docs/en/advanced-concepts/migrating-to-xcuitest.md)
7
7
  - Mobile gestures for XCUITest
8
- - [ios-xctest-mobile-gestures](https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/ios-xctest-mobile-gestures.md)
8
+ - [ios-xctest-mobile-gestures](https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/ios/ios-xctest-mobile-gestures.md)
9
9
  - Required Appium1.6.4+
10
10
 
11
11
  ## find elements
@@ -15,6 +15,27 @@ driver.action
15
15
  .move_to_location(500, 500).pointer_down(:left)
16
16
  .move_to_location(0, 700)
17
17
  .release.perform
18
+
19
+ # multiple action chains
20
+ f1 = driver.action.add_pointer_input(:touch, 'finger1')
21
+ f1.create_pointer_move(duration: 1, x: 200, y: 500,
22
+ origin: ::Selenium::WebDriver::Interactions::PointerMove::VIEWPORT)
23
+ f1.create_pointer_down(:left)
24
+ f1.create_pause(0.5)
25
+ f1.create_pointer_move(duration: 1, x: 200, y: 200,
26
+ origin: ::Selenium::WebDriver::Interactions::PointerMove::VIEWPORT)
27
+ f1.create_pointer_up(:left)
28
+
29
+ f2 = driver.action.add_pointer_input(:touch, 'finger2')
30
+ f2.create_pointer_move(duration: 1, x: 200, y: 500,
31
+ origin: ::Selenium::WebDriver::Interactions::PointerMove::VIEWPORT)
32
+ f2.create_pointer_down(:left)
33
+ f2.create_pause(0.5)
34
+ f2.create_pointer_move(duration: 1, x: 200, y: 800,
35
+ origin: ::Selenium::WebDriver::Interactions::PointerMove::VIEWPORT)
36
+ f2.create_pointer_up(:left)
37
+
38
+ driver.perform_actions [f1, f2]
18
39
  ```
19
40
 
20
41
  # Note
@@ -8,7 +8,8 @@ module Appium
8
8
  # convert to string to support symbols
9
9
  def filter=(value)
10
10
  # nil and false disable the filter
11
- return @filter = false unless value
11
+ return @filter = false unless value # rubocop:disable Lint/ReturnInVoidContext
12
+
12
13
  @filter = value.to_s.downcase
13
14
  end
14
15
 
@@ -136,6 +137,7 @@ module Appium
136
137
  index -= 1 if index >= 0
137
138
  else
138
139
  raise 'Index must be >= 1' unless index >= 1
140
+
139
141
  index -= 1 if index >= 1
140
142
  end
141
143
 
@@ -218,7 +220,7 @@ module Appium
218
220
 
219
221
  if class_name == '*'
220
222
  return "//*[contains(translate(@text,'#{value.upcase}', '#{value}'), '#{value}')" \
221
- " or contains(translate(@content-desc,'#{value.upcase}', '#{value}'), '#{value}')" + r_id + ']'
223
+ " or contains(translate(@content-desc,'#{value.upcase}', '#{value}'), '#{value}')" + r_id + ']'
222
224
  end
223
225
 
224
226
  "//#{class_name}[contains(translate(@text,'#{value.upcase}', '#{value}'), '#{value}')" \
@@ -237,14 +239,14 @@ module Appium
237
239
  value = %("#{value}")
238
240
  if class_name == '*'
239
241
  return (resource_id(value, "new UiSelector().resourceId(#{value});") +
240
- "new UiSelector().descriptionContains(#{value});" \
241
- "new UiSelector().textContains(#{value});")
242
+ "new UiSelector().descriptionContains(#{value});" \
243
+ "new UiSelector().textContains(#{value});")
242
244
  end
243
245
 
244
246
  class_name = %("#{class_name}")
245
247
  resource_id(value, "new UiSelector().className(#{class_name}).resourceId(#{value});") +
246
- "new UiSelector().className(#{class_name}).descriptionContains(#{value});" \
247
- "new UiSelector().className(#{class_name}).textContains(#{value});"
248
+ "new UiSelector().className(#{class_name}).descriptionContains(#{value});" \
249
+ "new UiSelector().className(#{class_name}).textContains(#{value});"
248
250
  end
249
251
 
250
252
  # Find the first element that contains value
@@ -272,9 +274,7 @@ module Appium
272
274
  def string_visible_exact_xpath(class_name, value)
273
275
  r_id = resource_id(value, " or @resource-id='#{value}'")
274
276
 
275
- if class_name == '*'
276
- return "//*[@text='#{value}' or @content-desc='#{value}'" + r_id + ']'
277
- end
277
+ return "//*[@text='#{value}' or @content-desc='#{value}'" + r_id + ']' if class_name == '*'
278
278
 
279
279
  "//#{class_name}[@text='#{value}' or @content-desc='#{value}'" + r_id + ']'
280
280
  end
@@ -289,14 +289,14 @@ module Appium
289
289
 
290
290
  if class_name == '*'
291
291
  return (resource_id(value, "new UiSelector().resourceId(#{value});") +
292
- "new UiSelector().description(#{value});" \
293
- "new UiSelector().text(#{value});")
292
+ "new UiSelector().description(#{value});" \
293
+ "new UiSelector().text(#{value});")
294
294
  end
295
295
 
296
296
  class_name = %("#{class_name}")
297
297
  resource_id(value, "new UiSelector().className(#{class_name}).resourceId(#{value});") +
298
- "new UiSelector().className(#{class_name}).description(#{value});" \
299
- "new UiSelector().className(#{class_name}).text(#{value});"
298
+ "new UiSelector().className(#{class_name}).description(#{value});" \
299
+ "new UiSelector().className(#{class_name}).text(#{value});"
300
300
  end
301
301
 
302
302
  # Find the first element exactly matching value
@@ -26,6 +26,7 @@ module Appium
26
26
  # @return [Array<Button>]
27
27
  def buttons(value = false)
28
28
  return find_elements :uiautomator, _button_visible_selectors unless value
29
+
29
30
  find_elements :uiautomator, _button_contains_string(value)
30
31
  end
31
32
 
@@ -69,6 +70,7 @@ module Appium
69
70
  # @private
70
71
  def raise_no_such_element_if_empty(elements)
71
72
  raise _no_such_element if elements.empty?
73
+
72
74
  elements.first
73
75
  end
74
76
 
@@ -45,7 +45,7 @@ module Appium
45
45
  begin
46
46
  elem = find_element :uiautomator, scroll_uiselector(arg, scrollable_index)
47
47
  return elem
48
- rescue => e
48
+ rescue StandardError => e
49
49
  raise e if index == args.size - 1
50
50
  end
51
51
  end
@@ -63,7 +63,7 @@ module Appium
63
63
  begin
64
64
  elem = find_element :uiautomator, scroll_uiselector(arg, scrollable_index)
65
65
  return elem
66
- rescue => e
66
+ rescue StandardError => e
67
67
  raise e if index == args.size - 1
68
68
  end
69
69
  end
@@ -9,6 +9,7 @@ module Appium
9
9
  # @return [TextView]
10
10
  def text(value)
11
11
  return ele_index TextView, value if value.is_a? Numeric
12
+
12
13
  complex_find_contains TextView, value
13
14
  end
14
15
 
@@ -18,6 +19,7 @@ module Appium
18
19
  # @return [Array<TextView>]
19
20
  def texts(value = false)
20
21
  return tags TextView unless value
22
+
21
23
  complex_finds_contains TextView, value
22
24
  end
23
25
 
@@ -8,6 +8,7 @@ module Appium
8
8
  # @return [EditText]
9
9
  def textfield(value)
10
10
  return ele_index EditText, value if value.is_a? Numeric
11
+
11
12
  complex_find_contains EditText, value
12
13
  end
13
14
 
@@ -17,6 +18,7 @@ module Appium
17
18
  # @return [Array<EditText>]
18
19
  def textfields(value = false)
19
20
  return tags EditText unless value
21
+
20
22
  complex_finds_contains EditText, value
21
23
  end
22
24
 
@@ -15,6 +15,7 @@ module Appium
15
15
 
16
16
  result = find_elements :uiautomator, _button_visible_selectors(index: index)
17
17
  raise _no_such_element if result.empty?
18
+
18
19
  return result[value - 1]
19
20
  end
20
21
 
@@ -28,6 +29,7 @@ module Appium
28
29
  # @return [Array<Button>]
29
30
  def buttons(value = false)
30
31
  return find_elements :uiautomator, _button_visible_selectors unless value
32
+
31
33
  find_elements :uiautomator, _button_contains_string(value)
32
34
  end
33
35
 
@@ -74,6 +76,7 @@ module Appium
74
76
  # @private
75
77
  def raise_no_such_element_if_empty(elements)
76
78
  raise _no_such_element if elements.empty?
79
+
77
80
  elements.first
78
81
  end
79
82
 
@@ -14,14 +14,14 @@ module Appium
14
14
  value = %("#{value}")
15
15
  if class_name == '*'
16
16
  return (resource_id(value, "new UiSelector().resourceId(#{value});") +
17
- "new UiSelector().descriptionContains(#{value});" \
18
- "new UiSelector().textContains(#{value});")
17
+ "new UiSelector().descriptionContains(#{value});" \
18
+ "new UiSelector().textContains(#{value});")
19
19
  end
20
20
 
21
21
  class_name = %("#{class_name}")
22
22
  resource_id(value, "new UiSelector().className(#{class_name}).resourceId(#{value});") +
23
- "new UiSelector().className(#{class_name}).descriptionContains(#{value});" \
24
- "new UiSelector().className(#{class_name}).textContains(#{value});"
23
+ "new UiSelector().className(#{class_name}).descriptionContains(#{value});" \
24
+ "new UiSelector().className(#{class_name}).textContains(#{value});"
25
25
  end
26
26
 
27
27
  # Find the first element that contains value
@@ -31,6 +31,7 @@ module Appium
31
31
  def complex_find_contains(class_name, value)
32
32
  elements = find_elements :uiautomator, string_visible_contains(class_name, value)
33
33
  raise _no_such_element if elements.empty?
34
+
34
35
  elements.first
35
36
  end
36
37
 
@@ -52,14 +53,14 @@ module Appium
52
53
 
53
54
  if class_name == '*'
54
55
  return (resource_id(value, "new UiSelector().resourceId(#{value});") +
55
- "new UiSelector().description(#{value});" \
56
- "new UiSelector().text(#{value});")
56
+ "new UiSelector().description(#{value});" \
57
+ "new UiSelector().text(#{value});")
57
58
  end
58
59
 
59
60
  class_name = %("#{class_name}")
60
61
  resource_id(value, "new UiSelector().className(#{class_name}).resourceId(#{value});") +
61
- "new UiSelector().className(#{class_name}).description(#{value});" \
62
- "new UiSelector().className(#{class_name}).text(#{value});"
62
+ "new UiSelector().className(#{class_name}).description(#{value});" \
63
+ "new UiSelector().className(#{class_name}).text(#{value});"
63
64
  end
64
65
 
65
66
  # Find the first element exactly matching value
@@ -69,6 +70,7 @@ module Appium
69
70
  def complex_find_exact(class_name, value)
70
71
  elements = find_elements :uiautomator, string_visible_exact(class_name, value)
71
72
  raise _no_such_element if elements.empty?
73
+
72
74
  elements.first
73
75
  end
74
76
 
@@ -49,6 +49,7 @@ module Appium
49
49
 
50
50
  toml = opts[:file]
51
51
  raise 'Must pass a capability file which has [caps] and [appium_lib]' unless toml
52
+
52
53
  verbose = opts.fetch :verbose, false
53
54
 
54
55
  Appium::Logger.info "appium settings path: #{toml}" if verbose
@@ -57,6 +58,7 @@ module Appium
57
58
  Appium::Logger.info "Exists? #{toml_exists}" if verbose
58
59
 
59
60
  raise "toml doesn't exist #{toml}" unless toml_exists
61
+
60
62
  require 'tomlrb'
61
63
  Appium::Logger.info "Loading #{toml}" if verbose
62
64
 
@@ -135,6 +137,7 @@ module Appium
135
137
  end
136
138
  else
137
139
  raise 'modules must be a module or an array' unless modules.is_a? Array
140
+
138
141
  target_modules = modules
139
142
  end
140
143
 
@@ -181,6 +184,7 @@ module Appium
181
184
  #
182
185
  def promote_appium_methods(class_array, driver = $driver)
183
186
  raise 'Driver is nil' if driver.nil?
187
+
184
188
  # Wrap single class into an array
185
189
  class_array = [class_array] unless class_array.class == Array
186
190
  # Promote Appium driver methods to class instance methods.
@@ -194,7 +194,8 @@ module Appium
194
194
  # convert to string to support symbols
195
195
  def filter=(value)
196
196
  # nil and false disable the filter
197
- return @filter = false unless value
197
+ return @filter = false unless value # rubocop:disable Lint/ReturnInVoidContext
198
+
198
199
  @filter = value.to_s.downcase
199
200
  end
200
201
 
@@ -211,16 +212,13 @@ module Appium
211
212
 
212
213
  def result
213
214
  @elements_in_order.reduce('') do |r, e|
214
- name = e.delete :name
215
+ name = e.delete :name
215
216
  attr_string = e.reduce('') do |string, attr|
216
- attr_1 = attr[1]
217
- attr_1 = attr_1 ? attr_1.strip : attr_1
218
- string + " #{attr[0]}: #{attr_1}\n"
217
+ attr1 = attr[1] ? attr[1].strip : attr[1]
218
+ "#{string} #{attr[0]}: #{attr1}\n"
219
219
  end
220
220
 
221
- unless attr_string.nil? || attr_string.empty?
222
- r += "\n#{name}\n#{attr_string}"
223
- end
221
+ r.concat "\n#{name}\n#{attr_string}" unless attr_string.nil? || attr_string.empty?
224
222
  r
225
223
  end
226
224
  end
@@ -228,6 +226,7 @@ module Appium
228
226
  def start_element(name, attrs = [])
229
227
  @skip_element = filter && !filter.include?(name.downcase)
230
228
  return if @skip_element
229
+
231
230
  element = { name: name }
232
231
  attrs.each { |a| element[a[0]] = a[1] }
233
232
  @element_stack.push element
@@ -236,12 +235,14 @@ module Appium
236
235
 
237
236
  def end_element(name)
238
237
  return if filter && !filter.include?(name.downcase)
238
+
239
239
  element_index = @element_stack.rindex { |e| e[:name] == name }
240
240
  @element_stack.delete_at element_index
241
241
  end
242
242
 
243
243
  def characters(chars)
244
244
  return if @skip_element
245
+
245
246
  element = @element_stack.last
246
247
  element[:text] = chars
247
248
  end
@@ -63,6 +63,7 @@ module Appium
63
63
  pinch.add top
64
64
  pinch.add bottom
65
65
  return pinch unless auto_perform
66
+
66
67
  pinch.perform
67
68
  end
68
69
 
@@ -105,6 +106,7 @@ module Appium
105
106
  zoom.add top
106
107
  zoom.add bottom
107
108
  return zoom unless auto_perform
109
+
108
110
  zoom.perform
109
111
  end
110
112
 
@@ -36,9 +36,10 @@ module Appium
36
36
  COMPLEX_ACTIONS.each do |action|
37
37
  define_method(action) do |opts|
38
38
  auto_perform = opts.delete(:auto_perform) { |_k| true }
39
- ta = ::Appium::TouchAction.new($driver)
39
+ ta = ::Appium::TouchAction.new($driver)
40
40
  ta.send(action, opts)
41
41
  return ta unless auto_perform
42
+
42
43
  ta.perform
43
44
  end
44
45
  end
@@ -49,10 +50,10 @@ module Appium
49
50
  end
50
51
 
51
52
  def swipe(opts)
52
- start_x = opts.fetch :start_x, 0
53
- start_y = opts.fetch :start_y, 0
54
- end_x = opts.fetch :end_x, 0
55
- end_y = opts.fetch :end_y, 0
53
+ start_x = opts.fetch :start_x, 0
54
+ start_y = opts.fetch :start_y, 0
55
+ end_x = opts.fetch :end_x, 0
56
+ end_y = opts.fetch :end_y, 0
56
57
  duration = opts.fetch :duration, 200
57
58
 
58
59
  if opts[:offset_x]
@@ -199,7 +199,7 @@ module Appium
199
199
  # Save global reference to last created Appium driver for top level methods.
200
200
  $driver = self if global_driver
201
201
 
202
- self # return newly created driver
202
+ self # rubocop:disable Lint/Void # return newly created driver
203
203
  end
204
204
 
205
205
  private
@@ -254,23 +254,25 @@ module Appium
254
254
 
255
255
  # Returns a hash of the driver attributes
256
256
  def driver_attributes
257
+ # rubocop:disable Layout/AlignHash
257
258
  {
258
- caps: @core.caps,
259
- automation_name: @core.automation_name,
260
- custom_url: @core.custom_url,
261
- export_session: @core.export_session,
262
- export_session_path: @core.export_session_path,
263
- default_wait: @core.default_wait,
264
- sauce_username: @sauce.username,
265
- sauce_access_key: @sauce.access_key,
266
- sauce_endpoint: @sauce.endpoint,
267
- port: @core.port,
268
- device: @core.device,
269
- debug: @appium_debug,
270
- listener: @listener,
271
- wait_timeout: @core.wait_timeout,
272
- wait_interval: @core.wait_interval
259
+ caps: @core.caps,
260
+ automation_name: @core.automation_name,
261
+ custom_url: @core.custom_url,
262
+ export_session: @core.export_session,
263
+ export_session_path: @core.export_session_path,
264
+ default_wait: @core.default_wait,
265
+ sauce_username: @sauce.username,
266
+ sauce_access_key: @sauce.access_key,
267
+ sauce_endpoint: @sauce.endpoint,
268
+ port: @core.port,
269
+ device: @core.device,
270
+ debug: @appium_debug,
271
+ listener: @listener,
272
+ wait_timeout: @core.wait_timeout,
273
+ wait_interval: @core.wait_interval
273
274
  }
275
+ # rubocop:enable Layout/AlignHash
274
276
  end
275
277
 
276
278
  def device_is_android?
@@ -328,8 +330,9 @@ module Appium
328
330
  # @return [Boolean]
329
331
  def check_server_version_xcuitest
330
332
  if automation_name_is_xcuitest? &&
331
- !@appium_server_status.empty? &&
332
- (@appium_server_status['build']['version'] < REQUIRED_VERSION_XCUITEST)
333
+ !@appium_server_status.empty? &&
334
+ (@appium_server_status['build']['version'] < REQUIRED_VERSION_XCUITEST)
335
+
333
336
  raise(Appium::Core::Error::NotSupportedAppiumServer,
334
337
  "XCUITest requires Appium version >= #{REQUIRED_VERSION_XCUITEST}")
335
338
  end
@@ -351,6 +354,7 @@ module Appium
351
354
  @core.appium_server_version
352
355
  rescue Selenium::WebDriver::Error::WebDriverError => ex
353
356
  raise ::Appium::Core::Error::ServerError unless ex.message.include?('content-type=""')
357
+
354
358
  # server (TestObject for instance) does not respond to status call
355
359
  {}
356
360
  end
@@ -385,6 +389,7 @@ module Appium
385
389
  # @return [String] APP_PATH as an absolute path
386
390
  def self.absolute_app_path(opts)
387
391
  raise 'opts must be a hash' unless opts.is_a? Hash
392
+
388
393
  caps = opts[:caps] || {}
389
394
  app_path = caps[:app]
390
395
  raise 'absolute_app_path invoked and app is not set!' if app_path.nil? || app_path.empty?
@@ -403,6 +408,7 @@ module Appium
403
408
  def server_url
404
409
  return @core.custom_url if @core.custom_url
405
410
  return @sauce.server_url if @sauce.sauce_server_url?
411
+
406
412
  "http://127.0.0.1:#{@core.port}/wd/hub"
407
413
  end
408
414
 
@@ -517,6 +523,7 @@ module Appium
517
523
  unless e.message.include?('The operation requested is not yet implemented by Espresso driver')
518
524
  raise ::Appium::Core::Error::ServerError
519
525
  end
526
+
520
527
  {}
521
528
  end
522
529
 
@@ -562,7 +569,7 @@ module Appium
562
569
 
563
570
  begin
564
571
  yield # search for element
565
- rescue
572
+ rescue StandardError
566
573
  exists = false # error means it's not there
567
574
  end
568
575
 
@@ -580,6 +587,52 @@ module Appium
580
587
  @driver.execute_script script, *args
581
588
  end
582
589
 
590
+ ###
591
+ # Wrap calling selenium webdrier APIs via ruby_core
592
+ ###
593
+ # Get the window handles of open browser windows
594
+ def execute_async_script(script, *args)
595
+ @driver.execute_async_script script, *args
596
+ end
597
+
598
+ def window_handles
599
+ @driver.window_handles
600
+ end
601
+
602
+ # Get the current window handle
603
+ def window_handle
604
+ @driver.window_handle
605
+ end
606
+
607
+ def navigate
608
+ @driver.navigate
609
+ end
610
+
611
+ def manage
612
+ @driver.manage
613
+ end
614
+
615
+ def get(url)
616
+ @driver.get(url)
617
+ end
618
+
619
+ def current_url
620
+ @driver.current_url
621
+ end
622
+
623
+ def title
624
+ @driver.title
625
+ end
626
+
627
+ # @return [TargetLocator]
628
+ # @see TargetLocator
629
+ def switch_to
630
+ @driver.switch_to
631
+ end
632
+ ###
633
+ # End core
634
+ ###
635
+
583
636
  # Calls @driver.find_elements_with_appium
584
637
  #
585
638
  # @example