appium_lib 4.1.0 → 5.0.0

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/android_tests/api.apk +0 -0
  3. data/android_tests/lib/android/specs/android/element/text.rb +2 -2
  4. data/android_tests/lib/android/specs/android/helper.rb +2 -2
  5. data/android_tests/lib/android/specs/common/device.rb +10 -28
  6. data/android_tests/lib/android/specs/common/device_touchaction.rb +29 -0
  7. data/android_tests/lib/android/specs/common/helper.rb +4 -4
  8. data/android_tests/lib/android/specs/common/patch.rb +2 -2
  9. data/android_tests/lib/android/specs/driver.rb +11 -1
  10. data/android_tests/lib/android/specs/install.rb +7 -6
  11. data/appium_lib.gemspec +2 -10
  12. data/contributing.md +23 -0
  13. data/docs/android_docs.md +274 -217
  14. data/docs/docs.md +28 -0
  15. data/docs/ios_docs.md +372 -212
  16. data/ios_tests/UICatalog.app/Info.plist +0 -0
  17. data/ios_tests/appium.txt +2 -1
  18. data/ios_tests/lib/ios/specs/common/helper.rb +12 -21
  19. data/ios_tests/lib/ios/specs/common/web_context.rb +7 -2
  20. data/ios_tests/lib/ios/specs/device/device.rb +16 -10
  21. data/ios_tests/lib/ios/specs/driver.rb +13 -2
  22. data/ios_tests/lib/ios/specs/ios/element/button.rb +5 -4
  23. data/ios_tests/lib/ios/specs/ios/element/generic.rb +2 -2
  24. data/ios_tests/lib/ios/specs/ios/element/text.rb +11 -7
  25. data/ios_tests/lib/ios/specs/ios/element/textfield.rb +9 -9
  26. data/lib/appium_lib.rb +1 -17
  27. data/lib/appium_lib/android/helper.rb +55 -5
  28. data/lib/appium_lib/common/helper.rb +26 -30
  29. data/lib/appium_lib/common/patch.rb +12 -0
  30. data/lib/appium_lib/common/version.rb +2 -2
  31. data/lib/appium_lib/device/device.rb +83 -12
  32. data/lib/appium_lib/driver.rb +26 -14
  33. data/lib/appium_lib/ios/element/button.rb +4 -4
  34. data/lib/appium_lib/ios/element/generic.rb +4 -4
  35. data/lib/appium_lib/ios/element/text.rb +4 -4
  36. data/lib/appium_lib/ios/element/textfield.rb +41 -22
  37. data/lib/appium_lib/ios/helper.rb +216 -47
  38. data/lib/appium_lib/ios/patch.rb +0 -19
  39. data/readme.md +1 -0
  40. data/release_notes.md +64 -0
  41. metadata +7 -5
@@ -6,27 +6,41 @@ module Appium
6
6
  private
7
7
 
8
8
  # @private
9
- def _textfield_visible_string opts={}
10
- index = opts.fetch :index, false
11
- if index
12
- %Q(//#{UIATextField}[@visible="true"][#{index}] | //#{UIASecureTextField}[@visible="true"][#{index}])
13
- else
14
- %Q(//#{UIATextField}[@visible="true"] | //#{UIASecureTextField}[@visible="true"])
15
- end
9
+ def _textfield_visible
10
+ {
11
+ typeArray: [UIATextField, UIASecureTextField],
12
+ onlyVisible: true,
13
+ }
16
14
  end
17
15
 
18
16
  # @private
19
17
  def _textfield_exact_string value
20
- textfield = string_visible_exact UIATextField, value
21
- secure = string_visible_exact UIASecureTextField, value
22
- "#{textfield} | #{secure}"
18
+ exact = {
19
+ target: value,
20
+ substring: false,
21
+ insensitive: false,
22
+ }
23
+ exact_obj = {
24
+ name: exact,
25
+ label: exact,
26
+ value: exact,
27
+ }
28
+ _textfield_visible.merge(exact_obj)
23
29
  end
24
30
 
25
31
  # @private
26
32
  def _textfield_contains_string value
27
- textfield = string_visible_contains UIATextField, value
28
- secure = string_visible_contains UIASecureTextField, value
29
- "#{textfield} | #{secure}"
33
+ contains = {
34
+ target: value,
35
+ substring: true,
36
+ insensitive: true,
37
+ }
38
+ contains_obj = {
39
+ name: contains,
40
+ label: contains,
41
+ value: contains,
42
+ }
43
+ _textfield_visible.merge(contains_obj)
30
44
  end
31
45
 
32
46
  public
@@ -40,12 +54,15 @@ module Appium
40
54
  # iOS needs to combine textfield and secure to match Android.
41
55
  if value.is_a? Numeric
42
56
  index = value
43
- raise "#{index} is not a valid xpath index. Must be >= 1" if index <= 0
57
+ raise "#{index} is not a valid index. Must be >= 1" if index <= 0
58
+ index -= 1 # eles_by_json is 0 indexed.
44
59
 
45
- return xpath _textfield_visible_string index: index
60
+ result = eles_by_json(_textfield_visible)[index]
61
+ raise _no_such_element if result.nil?
62
+ return result
46
63
  end
47
64
 
48
- xpath _textfield_contains_string value
65
+ ele_by_json _textfield_contains_string value
49
66
  end
50
67
 
51
68
  # Find all TextFields containing value.
@@ -53,34 +70,36 @@ module Appium
53
70
  # @param value [String] the value to search for
54
71
  # @return [Array<TextField>]
55
72
  def textfields value=false
56
- return xpaths _textfield_visible_string unless value
57
- xpaths _textfield_contains_string value
73
+ return eles_by_json _textfield_visible unless value
74
+ eles_by_json _textfield_contains_string value
58
75
  end
59
76
 
60
77
  # Find the first TextField.
61
78
  # @return [TextField]
62
79
  def first_textfield
63
- xpath _textfield_visible_string
80
+ ele_by_json _textfield_visible
64
81
  end
65
82
 
66
83
  # Find the last TextField.
67
84
  # @return [TextField]
68
85
  def last_textfield
69
- xpath _textfield_visible_string index: 'last()'
86
+ result = eles_by_json(_textfield_visible).last
87
+ raise _no_such_element if result.nil?
88
+ result
70
89
  end
71
90
 
72
91
  # Find the first TextField that exactly matches value.
73
92
  # @param value [String] the value to match exactly
74
93
  # @return [TextField]
75
94
  def textfield_exact value
76
- xpath _textfield_exact_string value
95
+ ele_by_json _textfield_exact_string value
77
96
  end
78
97
 
79
98
  # Find all TextFields that exactly match value.
80
99
  # @param value [String] the value to match exactly
81
100
  # @return [Array<TextField>]
82
101
  def textfields_exact value
83
- xpaths _textfield_exact_string value
102
+ eles_by_json _textfield_exact_string value
84
103
  end
85
104
  end # module Ios
86
105
  end # module Appium
@@ -157,7 +157,7 @@ module Appium
157
157
  def source_window window_number=0
158
158
  # appium 1.0 still returns JSON when getTree() is invoked so this
159
159
  # doesn't need to change to XML. If getTree() is removed then
160
- # source_window will need to parse the results of getTreeForXML()\
160
+ # source_window will need to parse the elements of getTreeForXML()\
161
161
  # https://github.com/appium/appium-uiauto/blob/247eb71383fa1a087ff8f8fc96fac25025731f3f/uiauto/appium/element.js#L145
162
162
  execute_script "UIATarget.localTarget().frontMostApp().windows()[#{window_number}].getTree()"
163
163
  end
@@ -177,11 +177,7 @@ module Appium
177
177
  # @param id [String] the id to search for
178
178
  # @return [Element]
179
179
  def id id
180
- value = resolve_id id
181
- raise "Invalid id `#{id}`" unless value
182
- exact = string_visible_exact '*', value
183
- contains = string_visible_contains '*', value
184
- xpath "#{exact} | #{contains}"
180
+ find_element :id, id
185
181
  end
186
182
 
187
183
  # Return the iOS version as an array of integers
@@ -196,11 +192,19 @@ module Appium
196
192
  # @param index [Integer] the index
197
193
  # @return [Element]
198
194
  def ele_index class_name, index
199
- unless index == 'last()'
200
- # XPath index starts at 1.
201
- raise "#{index} is not a valid xpath index. Must be >= 1" if index <= 0
195
+ raise 'Index must be >= 1' unless index == 'last()' || (index.is_a?(Integer) && index >= 1)
196
+ elements = tags(class_name)
197
+
198
+ if index == 'last()'
199
+ result = elements.last
200
+ else
201
+ # elements array is 0 indexed
202
+ index -= 1
203
+ result = elements[index]
202
204
  end
203
- find_element :xpath, %Q(//#{class_name}[@visible="true"][#{index}])
205
+
206
+ raise _no_such_element if result.nil?
207
+ result
204
208
  end
205
209
 
206
210
  # @private
@@ -209,6 +213,7 @@ module Appium
209
213
  end
210
214
 
211
215
  # Find the first element exactly matching class and attribute value.
216
+ # Note: Uses XPath
212
217
  # @param class_name [String] the class name to search for
213
218
  # @param attr [String] the attribute to inspect
214
219
  # @param value [String] the expected value of the attribute
@@ -218,6 +223,7 @@ module Appium
218
223
  end
219
224
 
220
225
  # Find all elements exactly matching class and attribute value.
226
+ # Note: Uses XPath
221
227
  # @param class_name [String] the class name to match
222
228
  # @param attr [String] the attribute to compare
223
229
  # @param value [String] the value of the attribute that the element must have
@@ -232,6 +238,7 @@ module Appium
232
238
  end
233
239
 
234
240
  # Get the first tag by attribute that exactly matches value.
241
+ # Note: Uses XPath
235
242
  # @param class_name [String] the tag name to match
236
243
  # @param attr [String] the attribute to compare
237
244
  # @param value [String] the value of the attribute that the element must include
@@ -241,6 +248,7 @@ module Appium
241
248
  end
242
249
 
243
250
  # Get tags by attribute that include value.
251
+ # Note: Uses XPath
244
252
  # @param class_name [String] the tag name to match
245
253
  # @param attr [String] the attribute to compare
246
254
  # @param value [String] the value of the attribute that the element must include
@@ -264,95 +272,103 @@ module Appium
264
272
  ele_index class_name, 'last()'
265
273
  end
266
274
 
267
- # Returns the first element matching class_name
275
+ # Returns the first visible element matching class_name
268
276
  #
269
277
  # @param class_name [String] the class_name to search for
270
278
  # @return [Element]
271
279
  def tag class_name
272
- xpath %Q(//#{class_name}[@visible="true"])
280
+ ele_by_json({
281
+ typeArray: [class_name],
282
+ onlyVisible: true,
283
+ })
273
284
  end
274
285
 
275
- # Returns all elements matching class_name
286
+ # Returns all visible elements matching class_name
276
287
  #
277
288
  # @param class_name [String] the class_name to search for
278
289
  # @return [Element]
279
290
  def tags class_name
280
- xpaths %Q(//#{class_name}[@visible="true"])
291
+ eles_by_json({
292
+ typeArray: [class_name],
293
+ onlyVisible: true,
294
+ })
281
295
  end
282
296
 
283
297
  # @private
284
- # Returns a string xpath that matches the first element that contains value
298
+ # Returns an object that matches the first element that contains value
285
299
  #
286
- # example: xpath_visible_contains 'UIATextField', 'sign in'
300
+ # example: ele_by_json_visible_contains 'UIATextField', 'sign in'
287
301
  #
288
302
  # @param element [String] the class name for the element
289
303
  # @param value [String] the value to search for
290
304
  # @return [String]
291
305
  def string_visible_contains element, value
292
- result = []
293
- attributes = %w[name hint label value]
294
-
295
- value_up = value.upcase
296
- value_down = value.downcase
297
-
298
- attributes.each do |attribute|
299
- result << %Q(contains(translate(@#{attribute},"#{value_up}","#{value_down}"), "#{value_down}"))
300
- end
306
+ contains = {
307
+ target: value,
308
+ substring: true,
309
+ insensitive: true,
310
+ }
301
311
 
302
- result = result.join(' or ')
303
- result = %Q(@visible="true" and (#{result}))
304
- "//#{element}[#{result}]"
312
+ {
313
+ typeArray: [element],
314
+ onlyVisible: true,
315
+ name: contains,
316
+ label: contains,
317
+ value: contains,
318
+ }
305
319
  end
306
320
 
307
321
  # Find the first element that contains value
308
322
  # @param element [String] the class name for the element
309
323
  # @param value [String] the value to search for
310
324
  # @return [Element]
311
- def xpath_visible_contains element, value
312
- xpath string_visible_contains element, value
325
+ def ele_by_json_visible_contains element, value
326
+ ele_by_json string_visible_contains element, value
313
327
  end
314
328
 
315
329
  # Find all elements containing value
316
330
  # @param element [String] the class name for the element
317
331
  # @param value [String] the value to search for
318
332
  # @return [Array<Element>]
319
- def xpaths_visible_contains element, value
320
- xpaths string_visible_contains element, value
333
+ def eles_by_json_visible_contains element, value
334
+ eles_by_json string_visible_contains element, value
321
335
  end
322
336
 
323
337
  # @private
324
- # Create an xpath string to exactly match the first element with target value
338
+ # Create an object to exactly match the first element with target value
325
339
  # @param element [String] the class name for the element
326
340
  # @param value [String] the value to search for
327
341
  # @return [String]
328
342
  def string_visible_exact element, value
329
- result = []
330
- attributes = %w[name hint label value]
331
-
332
- attributes.each do |attribute|
333
- result << %Q(@#{attribute}="#{value}")
334
- end
335
-
336
- result = result.join(' or ')
337
- result = %Q(@visible="true" and (#{result}))
343
+ exact = {
344
+ target: value,
345
+ substring: false,
346
+ insensitive: false,
347
+ }
338
348
 
339
- "//#{element}[#{result}]"
349
+ {
350
+ typeArray: [element],
351
+ onlyVisible: true,
352
+ name: exact,
353
+ label: exact,
354
+ value: exact,
355
+ }
340
356
  end
341
357
 
342
358
  # Find the first element exactly matching value
343
359
  # @param element [String] the class name for the element
344
360
  # @param value [String] the value to search for
345
361
  # @return [Element]
346
- def xpath_visible_exact element, value
347
- xpath string_visible_exact element, value
362
+ def ele_by_json_visible_exact element, value
363
+ ele_by_json string_visible_exact element, value
348
364
  end
349
365
 
350
366
  # Find all elements exactly matching value
351
367
  # @param element [String] the class name for the element
352
368
  # @param value [String] the value to search for
353
369
  # @return [Element]
354
- def xpaths_visible_exact element, value
355
- xpaths string_visible_exact element, value
370
+ def eles_by_json_visible_exact element, value
371
+ eles_by_json string_visible_exact element, value
356
372
  end
357
373
 
358
374
  # @private
@@ -363,12 +379,18 @@ module Appium
363
379
  # @return [void]
364
380
  def hide_ios_keyboard close_key='Done'
365
381
  =begin
382
+ todo: there are many various ways to hide the keyboard that work in different
383
+ app specific circumstances. webview keyboard will require a window.tap for example.
384
+
366
385
  Find the top left corner of the keyboard and move up 10 pixels (origin.y - 10)
367
386
  now swipe down until the end of the window - 10 pixels.
368
387
  -10 to ensure we're not going outside the window bounds.
369
388
 
370
389
  Swiping inside the keyboard will not dismiss it.
371
390
 
391
+ If the 'Done' key exists then that should be pressed to dismiss the keyboard
392
+ because swiping to dismiss works only if such key doesn't exist.
393
+
372
394
  Don't use window.tap. See https://github.com/appium/appium-uiauto/issues/28
373
395
  =end
374
396
  dismiss_keyboard = (<<-JS).strip
@@ -403,5 +425,152 @@ Don't use window.tap. See https://github.com/appium/appium-uiauto/issues/28
403
425
  execute_script 'au.mainApp().keyboard().isNil()'
404
426
  end
405
427
  end
428
+
429
+ #
430
+ # predicate - the predicate to evaluate on the main app
431
+ #
432
+ # visible - if true, only visible elements are returned. default true
433
+ #
434
+ def _all_pred opts
435
+ predicate = opts[:predicate]
436
+ raise 'predicate must be provided' unless predicate
437
+ visible = opts.fetch :visible, true
438
+ %Q($.mainApp().getAllWithPredicate("#{predicate}", #{visible});)
439
+ end
440
+
441
+ # returns element matching predicate contained in the main app
442
+ #
443
+ # predicate - the predicate to evaluate on the main app
444
+ #
445
+ # visible - if true, only visible elements are returned. default true
446
+ # @return [Element]
447
+ def ele_with_pred opts
448
+ # true = return only visible
449
+ find_element(:uiautomation, _all_pred(opts))
450
+ end
451
+
452
+ # returns elements matching predicate contained in the main app
453
+ #
454
+ # predicate - the predicate to evaluate on the main app
455
+ #
456
+ # visible - if true, only visible elements are returned. default true
457
+ # @return [Array<Element>]
458
+ def eles_with_pred opts
459
+ find_elements(:uiautomation, _all_pred(opts))
460
+ end
461
+
462
+ # Prints xml of the current page
463
+ # @return [void]
464
+ def source
465
+ _print_source get_source
466
+ end
467
+
468
+ def _validate_object *objects
469
+ raise 'objects must be an array' unless objects.is_a? Array
470
+ objects.each do |obj|
471
+ next unless obj # obj may be nil. if so, ignore.
472
+
473
+ valid_keys = [:target, :substring, :insensitive]
474
+ unknown_keys = obj.keys - valid_keys
475
+ raise "Unknown keys: #{unknown_keys}" unless unknown_keys.empty?
476
+
477
+ target = obj[:target]
478
+ raise 'target must be a string' unless target.is_a? String
479
+
480
+ substring = obj[:substring]
481
+ raise 'substring must be a boolean' unless [true, false].include? substring
482
+
483
+ insensitive = obj[:insensitive]
484
+ raise 'insensitive must be a boolean' unless [true, false].include? insensitive
485
+ end
486
+ end
487
+
488
+ # typeArray - array of string types to search for. Example: ["UIAStaticText"]
489
+ # onlyFirst - boolean. returns only the first result if true. Example: true
490
+ # onlyVisible - boolean. returns only visible elements if true. Example: true
491
+ # target - string. the target value to search for. Example: "Buttons, Various uses of UIButton"
492
+ # substring - boolean. matches on substrings if true otherwise an exact mathc is required. Example: true
493
+ # insensitive - boolean. ignores case sensitivity if true otherwise it's case sensitive. Example: true
494
+ #
495
+ # opts = {
496
+ # typeArray: ["UIAStaticText"],
497
+ # onlyFirst: true,
498
+ # onlyVisible: true,
499
+ # name: {
500
+ # target: "Buttons, Various uses of UIButton",
501
+ # substring: false,
502
+ # insensitive: false,
503
+ # },
504
+ # label: {
505
+ # target: "Buttons, Various uses of UIButton",
506
+ # substring: false,
507
+ # insensitive: false,
508
+ # },
509
+ # value: {
510
+ # target: "Buttons, Various uses of UIButton",
511
+ # substring: false,
512
+ # insensitive: false,
513
+ # }
514
+ # }
515
+ #
516
+ def _by_json opts
517
+ valid_keys = [:typeArray, :onlyFirst, :onlyVisible, :name, :label, :value]
518
+ unknown_keys = opts.keys - valid_keys
519
+ raise "Unknown keys: #{unknown_keys}" unless unknown_keys.empty?
520
+
521
+ typeArray = opts[:typeArray]
522
+ raise 'typeArray must be an array' unless typeArray.is_a? Array
523
+
524
+ onlyFirst = opts[:onlyFirst]
525
+ raise 'onlyFirst must be a boolean' unless [true, false].include? onlyFirst
526
+
527
+ onlyVisible = opts[:onlyVisible]
528
+ raise 'onlyVisible must be a boolean' unless [true, false].include? onlyVisible
529
+
530
+ # name/label/value are optional. when searching for class only, then none
531
+ # will be present.
532
+ _validate_object opts[:name], opts[:label], opts[:value]
533
+
534
+ element_or_elements_by_type = <<-JS
535
+ (function() {
536
+ var opts = #{opts.to_json}
537
+
538
+ return $.mainWindow()._elementOrElementsByType(opts);
539
+ })();
540
+ JS
541
+
542
+ execute_script element_or_elements_by_type
543
+ end
544
+
545
+ # example usage:
546
+ #
547
+ # eles_by_json({
548
+ # typeArray: ["UIAStaticText"],
549
+ # onlyVisible: true,
550
+ # name: {
551
+ # target: "Buttons, Various uses of UIButton",
552
+ # substring: false,
553
+ # insensitive: false,
554
+ # },
555
+ # })
556
+ def eles_by_json opts
557
+ opts[:onlyFirst] = false
558
+ return _by_json opts
559
+ end
560
+
561
+ # see eles_by_json
562
+ def ele_by_json opts
563
+ opts[:onlyFirst] = true
564
+ result = _by_json(opts).first
565
+ raise _no_such_element if result.nil?
566
+ result
567
+ end
568
+
569
+ # Returns XML string for the current page
570
+ # Same as driver.page_source
571
+ # @return [String]
572
+ def get_source
573
+ @driver.page_source
574
+ end
406
575
  end # module Ios
407
576
  end # module Appium