appium_lib 9.3.3 → 9.3.4

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.
@@ -13,7 +13,7 @@
13
13
  - Mapping
14
14
  - https://github.com/facebook/WebDriverAgent/blob/master/WebDriverAgentLib/Utilities/FBElementTypeTransformer.m#L19
15
15
 
16
- ### with except for XPath
16
+ ### with except for XPath and Predicate
17
17
  #### examples
18
18
  - [button_class](https://github.com/appium/ruby_lib/blob/master/lib/appium_lib/ios/element/button.rb#L8), [static_text_class](https://github.com/appium/ruby_lib/blob/master/lib/appium_lib/ios/element/text.rb#L8), [text_field_class](https://github.com/appium/ruby_lib/blob/master/lib/appium_lib/ios/element/textfield.rb#L10) and [secure_text_field_class](https://github.com/appium/ruby_lib/blob/master/lib/appium_lib/ios/element/textfield.rb#L15) provide class name.
19
19
  - If `automationName` is `Appium` or `nil`, then they provide `UIAxxxx`
@@ -36,6 +36,22 @@ find_element(:accessibility_id, element) # Return a element which has accessibil
36
36
  buttons(value) # Return button elements include `value` as its name attributes.
37
37
  ```
38
38
 
39
+ ### with Predicate
40
+ - We recommend to use predicate strategy instead of XPath strategy.
41
+ - e.g. `find_ele_by_predicate/find_eles_by_predicate`, `find_ele_by_predicate_include/find_eles_by_predicate_include`
42
+ - A helpful cheatsheet for predicate
43
+ - https://realm.io/news/nspredicate-cheatsheet/
44
+ - For XCUITest(WebDriverAgent), without 'wd' prefixes are supported.
45
+ - https://github.com/facebook/WebDriverAgent/wiki/Queries
46
+ - For example, `%(name ==[c] "#{value}" || label ==[c] "#{value}" || value ==[c] "#{value}")` is equal to `%(wdName ==[c] "#{value}" || wdLabel ==[c] "#{value}" || wdValue ==[c] "#{value}")` in WebDriverAgent.
47
+
48
+ #### examples
49
+ - `textfield/s(value)`, `find/s`, `find_exact/finds_exact`, `find_ele_by_predicate/find_eles_by_predicate` and `find_ele_by_predicate_include/find_eles_by_predicate_include` use predicate strategy in their method.
50
+
51
+ ```ruby
52
+ textfield(value) # Return a XCUIElementTypeSecureTextField or XCUIElementTypeTextField element which has `value` text.
53
+ finds_exact(value) # Return any elements include `value` as its name attributes.
54
+ ```
39
55
 
40
56
  ### with XPath
41
57
  - It is better to avoid XPath strategy.
@@ -48,11 +64,9 @@ buttons(value) # Return button elements include `value` as its name attributes.
48
64
  - https://github.com/facebook/WebDriverAgent/blob/2158a8d0f305549532f1338fe1e4628cfbd53cd9/WebDriverAgentLib/Categories/XCElementSnapshot%2BFBHelpers.m#L57
49
65
 
50
66
  #### examples
51
- - `textfield/s(value)`, `find/s`, `find_exact/finds_exact` uses XPath in their method. So, these methods are slower than other find_element directly.
52
67
 
53
68
  ```ruby
54
- textfield(value) # Return a XCUIElementTypeSecureTextField or XCUIElementTypeTextField element which has `value` text.
55
- finds_exact(value) # Return any elements include `value` as its name attributes.
69
+ xpaths("//some xpaths")
56
70
  ```
57
71
 
58
72
  ## Other actions
@@ -125,6 +125,22 @@ describe 'common/helper.rb' do
125
125
  set_wait
126
126
  end
127
127
 
128
+ t 'find_ele_by_predicate' do
129
+ el_text = find_ele_by_predicate(value: uibutton_text).text
130
+ el_text.must_equal uibutton_text
131
+
132
+ el_name = find_ele_by_predicate(value: uibutton_text).name
133
+ el_name.must_equal uibutton_text
134
+ end
135
+
136
+ t 'find_eles_by_predicate' do
137
+ ele_count = find_eles_by_predicate(value: uibutton_text).length
138
+ ele_count.must_equal 1
139
+
140
+ ele_count = find_eles_by_predicate(value: 'zz').length
141
+ ele_count.must_equal 0
142
+ end
143
+
128
144
  # TODO: 'string_attr_include'
129
145
 
130
146
  t 'find_ele_by_attr_include' do
@@ -141,6 +157,18 @@ describe 'common/helper.rb' do
141
157
  ele_count.must_equal expected
142
158
  end
143
159
 
160
+ t 'find_ele_by_predicate_include' do
161
+ el_text = find_ele_by_predicate_include(value: 'button').text
162
+ el_text.must_equal uibutton_text
163
+
164
+ el_name = find_ele_by_predicate_include(value: 'button').name
165
+ el_name.must_equal uibutton_text
166
+ end
167
+
168
+ t 'find_eles_by_predicate_include' do
169
+ find_eles_by_predicate_include(value: 'e').length.must_equal 21
170
+ end
171
+
144
172
  t 'first_ele' do
145
173
  first_ele(UI::Inventory.static_text).name.must_equal 'UICatalog'
146
174
  end
@@ -174,7 +202,7 @@ describe 'common/helper.rb' do
174
202
  tags(UI::Inventory.table_cell).length.must_equal 12
175
203
  end
176
204
 
177
- t 'find_eles_by_attr_include' do
205
+ t 'find_eles_by_attr_include_length' do
178
206
  find_eles_by_attr_include(UI::Inventory.static_text, 'name', 'Use').length.must_equal 7
179
207
  end
180
208
 
@@ -52,6 +52,11 @@ describe 'device/device' do
52
52
  tag(UI::Inventory.navbar).name.must_equal 'UICatalog'
53
53
  end
54
54
 
55
+ t 'background_app homescreen' do
56
+ background_app(-1) # background_app(nil) should work as same.
57
+ screen.must_raise ::Selenium::WebDriver::Error::NoSuchElementError
58
+ end
59
+
55
60
  t 'reset' do
56
61
  reset
57
62
  end
@@ -34,10 +34,13 @@ describe 'ios/element/textfield' do
34
34
  end
35
35
 
36
36
  t 'predicate textfields' do
37
- raise NotImplementedError, "XCUITest(Appium1.6.2) doesn't support UIAutomation script" if UI::Inventory.xcuitest?
37
+ textfields = if UI::Inventory.xcuitest?
38
+ find_elements(:predicate, "type contains[c] 'textfield'")
39
+ else
40
+ execute_script(%(au.mainApp().getAllWithPredicate("type contains[c] 'textfield'", true)))
41
+ end
38
42
 
39
- textfield_count = execute_script(%(au.mainApp().getAllWithPredicate("type contains[c] 'textfield'", true))).length
40
- textfield_count.must_equal 4
43
+ textfields.length.must_equal 4
41
44
  end
42
45
 
43
46
  t 'first_textfield' do
@@ -32,19 +32,28 @@ describe 'ios/helper' do
32
32
  end
33
33
 
34
34
  t 'tags_include' do
35
+ elements = tags_include class_names: %w(XCUIElementTypeTextView)
36
+ elements.length.must_equal 0
37
+
35
38
  elements = tags_include class_names: %w(XCUIElementTypeTextView XCUIElementTypeStaticText)
36
- elements.length.must_be 24
39
+ elements.length.must_equal 24
37
40
 
38
41
  elements = tags_include class_names: %w(XCUIElementTypeTextView XCUIElementTypeStaticText), value: 'u'
39
- elements.length.must_be 13
42
+ elements.length.must_equal 13
40
43
  end
41
44
 
42
- t 'tags_include' do
45
+ t 'tags_exact' do
46
+ elements = tags_exact class_names: %w()
47
+ elements.length.must_equal 0
48
+
49
+ elements = tags_exact class_names: %w(XCUIElementTypeStaticText)
50
+ elements.length.must_equal 24
51
+
43
52
  elements = tags_exact class_names: %w(XCUIElementTypeTextView XCUIElementTypeStaticText)
44
- elements.length.must_be 24
53
+ elements.length.must_equal 24
45
54
 
46
55
  elements = tags_exact class_names: %w(XCUIElementTypeTextView XCUIElementTypeStaticText), value: 'Buttons'
47
- elements.length.must_be 1
56
+ elements.length.must_equal 1
48
57
  elements.first.value.must_equal 'Buttons'
49
58
  end
50
59
  end
@@ -1,5 +1,5 @@
1
1
  module Appium
2
2
  # Version and Date are defined on the 'Appium' module, not 'Appium::Common'
3
- VERSION = '9.3.3'.freeze unless defined? ::Appium::VERSION
4
- DATE = '2017-02-18'.freeze unless defined? ::Appium::DATE
3
+ VERSION = '9.3.4'.freeze unless defined? ::Appium::VERSION
4
+ DATE = '2017-03-16'.freeze unless defined? ::Appium::DATE
5
5
  end
@@ -13,7 +13,13 @@ module Appium
13
13
  # @!method background_app
14
14
  # Backgrounds the app for a set number of seconds.
15
15
  # This is a blocking application
16
- # @param seconds (int) How many seconds to background the app for.
16
+ # @param [Integer] seconds How many seconds to background the app for.
17
+ #
18
+ # ```ruby
19
+ # background_app
20
+ # background_app(5)
21
+ # background_app(-1) #=> the app never come back. https://github.com/appium/appium/issues/7741
22
+ # ```
17
23
 
18
24
  # @!method current_activity
19
25
 
@@ -202,8 +208,14 @@ module Appium
202
208
  end
203
209
 
204
210
  add_endpoint_method(:background_app) do
205
- def background_app(duration)
206
- execute :background_app, {}, seconds: duration
211
+ def background_app(duration = 0)
212
+ # https://github.com/appium/ruby_lib/issues/500, https://github.com/appium/appium/issues/7741
213
+ if $driver.automation_name_is_xcuitest? && $driver.appium_server_status['build']['version'] >= '1.6.4'
214
+ duration_milli_sec = duration.nil? ? nil : duration * 1000
215
+ execute :background_app, {}, seconds: { timeout: duration_milli_sec }
216
+ else
217
+ execute :background_app, {}, seconds: duration
218
+ end
207
219
  end
208
220
  end
209
221
 
@@ -299,7 +299,7 @@ module Appium
299
299
  # If automation_name is nil, it is not set both client side and server side.
300
300
  attr_reader :automation_name
301
301
  # Appium's server version
302
- attr_reader :appium_server_version
302
+ attr_reader :appium_server_status
303
303
  # Boolean debug mode for the Appium Ruby bindings
304
304
  attr_accessor :appium_debug
305
305
  # instance of AbstractEventListener for logging support
@@ -476,7 +476,7 @@ module Appium
476
476
  # If the Appium server is under REQUIRED_VERSION_XCUITEST, then error is raised.
477
477
  # @return [Boolean]
478
478
  def check_server_version_xcuitest
479
- if automation_name_is_xcuitest? && (@appium_server_version['build']['version'] < REQUIRED_VERSION_XCUITEST)
479
+ if automation_name_is_xcuitest? && (@appium_server_status['build']['version'] < REQUIRED_VERSION_XCUITEST)
480
480
  raise Appium::Error::NotSupportedAppiumServer, "XCUITest requires Appium version >= #{REQUIRED_VERSION_XCUITEST}"
481
481
  end
482
482
  true
@@ -618,7 +618,7 @@ module Appium
618
618
  raise "ERROR: Unable to connect to Appium. Is the server running on #{server_url}?"
619
619
  end
620
620
 
621
- @appium_server_version = appium_server_version
621
+ @appium_server_status = appium_server_version
622
622
 
623
623
  check_server_version_xcuitest
624
624
  set_automation_name_if_nil
@@ -32,8 +32,8 @@ module Appium
32
32
  return tags button_class unless value
33
33
 
34
34
  if automation_name_is_xcuitest?
35
- visible_elements = tags button_class
36
- elements_include visible_elements, value
35
+ elements = find_eles_by_predicate_include(class_name: button_class, value: value)
36
+ select_visible_elements elements
37
37
  else
38
38
  eles_by_json_visible_contains button_class, value
39
39
  end
@@ -69,8 +69,8 @@ module Appium
69
69
  # @return [Array<UIAButton|XCUIElementTypeButton>]
70
70
  def buttons_exact(value)
71
71
  if automation_name_is_xcuitest?
72
- visible_elements = tags button_class
73
- elements_exact visible_elements, value
72
+ elements = find_eles_by_predicate(class_name: button_class, value: value)
73
+ select_visible_elements elements
74
74
  else
75
75
  eles_by_json_visible_exact button_class, value
76
76
  end
@@ -16,7 +16,7 @@ module Appium
16
16
  # @return [Array<Element>]
17
17
  def finds(value)
18
18
  if automation_name_is_xcuitest?
19
- elements = find_eles_by_attr_include '*', '*', value
19
+ elements = find_eles_by_predicate_include value: value
20
20
  select_visible_elements elements
21
21
  else
22
22
  eles_by_json_visible_contains '*', value
@@ -39,7 +39,7 @@ module Appium
39
39
  # @return [Array<Element>]
40
40
  def finds_exact(value)
41
41
  if automation_name_is_xcuitest?
42
- elements = find_eles_by_attr '*', '*', value
42
+ elements = find_eles_by_predicate value: value
43
43
  select_visible_elements elements
44
44
  else
45
45
  eles_by_json_visible_exact '*', value
@@ -54,34 +54,6 @@ module Appium
54
54
  element
55
55
  end
56
56
 
57
- # Return all elements include not displayed elements.
58
- def elements_include(elements, value)
59
- return [] if elements.empty?
60
- elements.select do |element|
61
- # `text` is equal to `value` if value is not `nil`
62
- # `text` is equal to `name` if value is `nil`
63
- name = element.name
64
- text = element.value
65
- name_result = name.nil? ? false : name.downcase.include?(value.downcase)
66
- text_result = text.nil? ? false : text.downcase.include?(value.downcase)
67
- name_result || text_result
68
- end
69
- end
70
-
71
- # Return all elements include not displayed elements.
72
- def elements_exact(elements, value)
73
- return [] if elements.empty?
74
- elements.select do |element|
75
- # `text` is equal to `value` if value is not `nil`
76
- # `text` is equal to `name` if value is `nil`
77
- name = element.name
78
- text = element.value
79
- name_result = name.nil? ? false : name.casecmp(value).zero?
80
- text_result = text.nil? ? false : text.casecmp(value).zero?
81
- name_result || text_result
82
- end
83
- end
84
-
85
57
  # Return visible elements.
86
58
  def select_visible_elements(elements)
87
59
  elements.select(&:displayed?)
@@ -31,8 +31,8 @@ module Appium
31
31
  return tags static_text_class unless value
32
32
 
33
33
  if automation_name_is_xcuitest?
34
- visible_elements = tags static_text_class
35
- elements_include visible_elements, value
34
+ elements = find_eles_by_predicate_include(class_name: static_text_class, value: value)
35
+ select_visible_elements elements
36
36
  else
37
37
  eles_by_json_visible_contains static_text_class, value
38
38
  end
@@ -66,8 +66,8 @@ module Appium
66
66
  # @return [Array<UIAStaticText|XCUIElementTypeStaticText>]
67
67
  def texts_exact(value)
68
68
  if automation_name_is_xcuitest?
69
- visible_elements = tags static_text_class
70
- elements_exact visible_elements, value
69
+ elements = find_eles_by_predicate(class_name: static_text_class, value: value)
70
+ select_visible_elements elements
71
71
  else
72
72
  eles_by_json_visible_exact static_text_class, value
73
73
  end
@@ -20,20 +20,14 @@ module Appium
20
20
 
21
21
  # @private
22
22
  # for XCUITest
23
- def _textfields
24
- %(#{text_field_class} | //#{secure_text_field_class})
23
+ def _textfield_with_predicate
24
+ raise_error_if_no_element _textfields_with_predicate.first
25
25
  end
26
26
 
27
27
  # @private
28
28
  # for XCUITest
29
- def _textfield_with_xpath
30
- raise_error_if_no_element _textfields_with_xpath.first
31
- end
32
-
33
- # @private
34
- # for XCUITest
35
- def _textfields_with_xpath
36
- elements = xpaths "//#{_textfields}"
29
+ def _textfields_with_predicate
30
+ elements = tags_include(class_names: [text_field_class, secure_text_field_class])
37
31
  select_visible_elements elements
38
32
  end
39
33
 
@@ -67,9 +61,9 @@ module Appium
67
61
  if value.is_a? Numeric
68
62
  index = value
69
63
  raise "#{index} is not a valid index. Must be >= 1" if index <= 0
70
- index -= 1 # eles_by_json and _textfields_with_xpath is 0 indexed.
64
+ index -= 1 # eles_by_json and _textfields_with_predicate is 0 indexed.
71
65
  result = if automation_name_is_xcuitest?
72
- _textfields_with_xpath[index]
66
+ _textfields_with_predicate[index]
73
67
  else
74
68
  eles_by_json(_textfield_visible)[index]
75
69
  end
@@ -91,8 +85,9 @@ module Appium
91
85
  # @return [Array<TextField>]
92
86
  def textfields(value = false)
93
87
  if automation_name_is_xcuitest?
94
- return _textfields_with_xpath unless value
95
- elements = find_eles_by_attr_include _textfields, '*', value
88
+ return tags_include(class_names: [text_field_class, secure_text_field_class]) unless value
89
+
90
+ elements = tags_include class_names: [text_field_class, secure_text_field_class], value: value
96
91
  select_visible_elements elements
97
92
  else
98
93
  return eles_by_json _textfield_visible unless value
@@ -104,7 +99,7 @@ module Appium
104
99
  # @return [TextField]
105
100
  def first_textfield
106
101
  if automation_name_is_xcuitest?
107
- _textfield_with_xpath
102
+ _textfield_with_predicate
108
103
  else
109
104
  ele_by_json _textfield_visible
110
105
  end
@@ -114,7 +109,7 @@ module Appium
114
109
  # @return [TextField]
115
110
  def last_textfield
116
111
  result = if automation_name_is_xcuitest?
117
- _textfields_with_xpath.last
112
+ _textfields_with_predicate.last
118
113
  else
119
114
  eles_by_json(_textfield_visible).last
120
115
  end
@@ -138,7 +133,7 @@ module Appium
138
133
  # @return [Array<TextField>]
139
134
  def textfields_exact(value)
140
135
  if automation_name_is_xcuitest?
141
- elements = find_eles_by_attr _textfields, '*', value
136
+ elements = tags_exact class_names: [text_field_class, secure_text_field_class], value: value
142
137
  select_visible_elements elements
143
138
  else
144
139
  eles_by_json _textfield_exact_string value
@@ -273,6 +273,31 @@ module Appium
273
273
  end
274
274
  end
275
275
 
276
+ # Find the first element exactly matching attribute case insensitive value.
277
+ # Note: Uses Predicate
278
+ # @param value [String] the expected value of the attribute
279
+ # @return [Element]
280
+ def find_ele_by_predicate(class_name: '*', value:)
281
+ elements = find_eles_by_predicate(class_name: class_name, value: value)
282
+ raise _no_such_element if elements.empty?
283
+ elements.first
284
+ end
285
+
286
+ # Find all elements exactly matching attribute case insensitive value.
287
+ # Note: Uses Predicate
288
+ # @param value [String] the value of the attribute that the element must have
289
+ # @param class_name [String] the tag name to match
290
+ # @return [Array<Element>]
291
+ def find_eles_by_predicate(class_name: '*', value:)
292
+ predicate = if class_name == '*'
293
+ %(name ==[c] "#{value}" || label ==[c] "#{value}" || value ==[c] "#{value}")
294
+ else
295
+ %(type == "#{class_name}" && ) +
296
+ %((name ==[c] "#{value}" || label ==[c] "#{value}" || value ==[c] "#{value}"))
297
+ end
298
+ @driver.find_elements_with_appium :predicate, predicate
299
+ end
300
+
276
301
  # Get the first tag by attribute that exactly matches value.
277
302
  # Note: Uses XPath
278
303
  # @param class_name [String] the tag name to match
@@ -293,6 +318,31 @@ module Appium
293
318
  @driver.find_elements :xpath, string_attr_include(class_name, attr, value)
294
319
  end
295
320
 
321
+ # Get the first elements that include insensitive value.
322
+ # Note: Uses Predicate
323
+ # @param value [String] the value of the attribute that the element must include
324
+ # @return [Element] the element of type tag who's attribute includes value
325
+ def find_ele_by_predicate_include(class_name: '*', value:)
326
+ elements = find_eles_by_predicate_include(class_name: class_name, value: value)
327
+ raise _no_such_element if elements.empty?
328
+ elements.first
329
+ end
330
+
331
+ # Get elements that include case insensitive value.
332
+ # Note: Uses Predicate
333
+ # @param value [String] the value of the attribute that the element must include
334
+ # @param class_name [String] the tag name to match
335
+ # @return [Array<Element>] the elements of type tag who's attribute includes value
336
+ def find_eles_by_predicate_include(class_name: '*', value:)
337
+ predicate = if class_name == '*'
338
+ %(name contains[c] "#{value}" || label contains[c] "#{value}" || value contains[c] "#{value}")
339
+ else
340
+ %(type == "#{class_name}" && ) +
341
+ %((name contains[c] "#{value}" || label contains[c] "#{value}" || value contains[c] "#{value}"))
342
+ end
343
+ @driver.find_elements_with_appium :predicate, predicate
344
+ end
345
+
296
346
  # Get the first tag that matches class_name
297
347
  # @param class_name [String] the tag to match
298
348
  # @return [Element]
@@ -348,11 +398,20 @@ module Appium
348
398
  def tags_include(class_names:, value: nil)
349
399
  return unless class_names.is_a? Array
350
400
 
351
- class_names.flat_map do |class_name|
352
- if automation_name_is_xcuitest?
353
- visible_elements = tags(class_name)
354
- value ? elements_include(visible_elements, value) : visible_elements
355
- else
401
+ if automation_name_is_xcuitest?
402
+ c_names = class_names.map { |class_name| %(type == "#{class_name}") }.join(' || ')
403
+
404
+ predicate = if value
405
+ %((#{c_names}) && ) +
406
+ %((name contains[c] "#{value}" || label contains[c] "#{value}" || value contains[c] "#{value}"))
407
+ else
408
+ c_names
409
+ end
410
+
411
+ elements = @driver.find_elements_with_appium :predicate, predicate
412
+ select_visible_elements elements
413
+ else
414
+ class_names.flat_map do |class_name|
356
415
  value ? eles_by_json_visible_contains(class_name, value) : tags(class_name)
357
416
  end
358
417
  end
@@ -368,11 +427,20 @@ module Appium
368
427
  def tags_exact(class_names:, value: nil)
369
428
  return unless class_names.is_a? Array
370
429
 
371
- class_names.flat_map do |class_name|
372
- if automation_name_is_xcuitest?
373
- visible_elements = tags(class_name)
374
- value ? elements_exact(visible_elements, value) : visible_elements
375
- else
430
+ if automation_name_is_xcuitest?
431
+ c_names = class_names.map { |class_name| %(type == "#{class_name}") }.join(' || ')
432
+
433
+ predicate = if value
434
+ %((#{c_names}) && ) +
435
+ %((name ==[c] "#{value}" || label ==[c] "#{value}" || value ==[c] "#{value}"))
436
+ else
437
+ c_names
438
+ end
439
+
440
+ elements = @driver.find_elements_with_appium :predicate, predicate
441
+ select_visible_elements elements
442
+ else
443
+ class_names.flat_map do |class_name|
376
444
  value ? eles_by_json_visible_exact(class_name, value) : tags(class_name)
377
445
  end
378
446
  end