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.
- checksums.yaml +4 -4
- data/android_tests/api.apk +0 -0
- data/android_tests/lib/android/specs/android/element/text.rb +2 -2
- data/android_tests/lib/android/specs/android/helper.rb +2 -2
- data/android_tests/lib/android/specs/common/device.rb +10 -28
- data/android_tests/lib/android/specs/common/device_touchaction.rb +29 -0
- data/android_tests/lib/android/specs/common/helper.rb +4 -4
- data/android_tests/lib/android/specs/common/patch.rb +2 -2
- data/android_tests/lib/android/specs/driver.rb +11 -1
- data/android_tests/lib/android/specs/install.rb +7 -6
- data/appium_lib.gemspec +2 -10
- data/contributing.md +23 -0
- data/docs/android_docs.md +274 -217
- data/docs/docs.md +28 -0
- data/docs/ios_docs.md +372 -212
- data/ios_tests/UICatalog.app/Info.plist +0 -0
- data/ios_tests/appium.txt +2 -1
- data/ios_tests/lib/ios/specs/common/helper.rb +12 -21
- data/ios_tests/lib/ios/specs/common/web_context.rb +7 -2
- data/ios_tests/lib/ios/specs/device/device.rb +16 -10
- data/ios_tests/lib/ios/specs/driver.rb +13 -2
- data/ios_tests/lib/ios/specs/ios/element/button.rb +5 -4
- data/ios_tests/lib/ios/specs/ios/element/generic.rb +2 -2
- data/ios_tests/lib/ios/specs/ios/element/text.rb +11 -7
- data/ios_tests/lib/ios/specs/ios/element/textfield.rb +9 -9
- data/lib/appium_lib.rb +1 -17
- data/lib/appium_lib/android/helper.rb +55 -5
- data/lib/appium_lib/common/helper.rb +26 -30
- data/lib/appium_lib/common/patch.rb +12 -0
- data/lib/appium_lib/common/version.rb +2 -2
- data/lib/appium_lib/device/device.rb +83 -12
- data/lib/appium_lib/driver.rb +26 -14
- data/lib/appium_lib/ios/element/button.rb +4 -4
- data/lib/appium_lib/ios/element/generic.rb +4 -4
- data/lib/appium_lib/ios/element/text.rb +4 -4
- data/lib/appium_lib/ios/element/textfield.rb +41 -22
- data/lib/appium_lib/ios/helper.rb +216 -47
- data/lib/appium_lib/ios/patch.rb +0 -19
- data/readme.md +1 -0
- data/release_notes.md +64 -0
- metadata +7 -5
@@ -6,27 +6,41 @@ module Appium
|
|
6
6
|
private
|
7
7
|
|
8
8
|
# @private
|
9
|
-
def
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
57
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
201
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
291
|
+
eles_by_json({
|
292
|
+
typeArray: [class_name],
|
293
|
+
onlyVisible: true,
|
294
|
+
})
|
281
295
|
end
|
282
296
|
|
283
297
|
# @private
|
284
|
-
# Returns
|
298
|
+
# Returns an object that matches the first element that contains value
|
285
299
|
#
|
286
|
-
# example:
|
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
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
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
|
-
|
303
|
-
|
304
|
-
|
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
|
312
|
-
|
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
|
320
|
-
|
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
|
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
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
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
|
-
|
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
|
347
|
-
|
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
|
355
|
-
|
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
|