Skip to content

Commit 0199159

Browse files
authored
Search with predicate (#504)
* add find elements with predicate * use predicate strategy in find/s and find/s_exact * fix rubocop * remove elements_include and elements_exact * add tests and update find elements * fix text field * fix rubocop * add some documentations * remove redundant lines * remove redundant find elemenets * fix lint
1 parent dbd8762 commit 0199159

File tree

9 files changed

+163
-75
lines changed

9 files changed

+163
-75
lines changed

.rubocop.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ Style/IndentHash:
3838
Enabled: false
3939
Style/VariableNumber:
4040
EnforcedStyle: 'snake_case'
41+
Style/MultilineOperationIndentation:
42+
Enabled: false
4143
Lint/NestedMethodDefinition:
4244
Enabled: false
4345
# Should enable and fix for Ruby3

docs/ios_xcuitest.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
- Mapping
1414
- https://github.com/facebook/WebDriverAgent/blob/master/WebDriverAgentLib/Utilities/FBElementTypeTransformer.m#L19
1515

16-
### with except for XPath
16+
### with except for XPath and Predicate
1717
#### examples
1818
- [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.
1919
- 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
3636
buttons(value) # Return button elements include `value` as its name attributes.
3737
```
3838

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+
```
3955

4056
### with XPath
4157
- It is better to avoid XPath strategy.
@@ -48,11 +64,9 @@ buttons(value) # Return button elements include `value` as its name attributes.
4864
- https://github.com/facebook/WebDriverAgent/blob/2158a8d0f305549532f1338fe1e4628cfbd53cd9/WebDriverAgentLib/Categories/XCElementSnapshot%2BFBHelpers.m#L57
4965

5066
#### 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.
5267

5368
```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")
5670
```
5771

5872
## Other actions

ios_tests/lib/ios/specs/common/helper.rb

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,22 @@ def uibutton_text
125125
set_wait
126126
end
127127

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+
128144
# TODO: 'string_attr_include'
129145

130146
t 'find_ele_by_attr_include' do
@@ -141,6 +157,18 @@ def uibutton_text
141157
ele_count.must_equal expected
142158
end
143159

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+
144172
t 'first_ele' do
145173
first_ele(UI::Inventory.static_text).name.must_equal 'UICatalog'
146174
end
@@ -174,7 +202,7 @@ def uibutton_text
174202
tags(UI::Inventory.table_cell).length.must_equal 12
175203
end
176204

177-
t 'find_eles_by_attr_include' do
205+
t 'find_eles_by_attr_include_length' do
178206
find_eles_by_attr_include(UI::Inventory.static_text, 'name', 'Use').length.must_equal 7
179207
end
180208

ios_tests/lib/ios/specs/ios/helper.rb

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,28 @@ def before_first
3232
end
3333

3434
t 'tags_include' do
35+
elements = tags_include class_names: %w(XCUIElementTypeTextView)
36+
elements.length.must_equal 0
37+
3538
elements = tags_include class_names: %w(XCUIElementTypeTextView XCUIElementTypeStaticText)
36-
elements.length.must_be 24
39+
elements.length.must_equal 24
3740

3841
elements = tags_include class_names: %w(XCUIElementTypeTextView XCUIElementTypeStaticText), value: 'u'
39-
elements.length.must_be 13
42+
elements.length.must_equal 13
4043
end
4144

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+
4352
elements = tags_exact class_names: %w(XCUIElementTypeTextView XCUIElementTypeStaticText)
44-
elements.length.must_be 24
53+
elements.length.must_equal 24
4554

4655
elements = tags_exact class_names: %w(XCUIElementTypeTextView XCUIElementTypeStaticText), value: 'Buttons'
47-
elements.length.must_be 1
56+
elements.length.must_equal 1
4857
elements.first.value.must_equal 'Buttons'
4958
end
5059
end

lib/appium_lib/ios/element/button.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ def buttons(value = false)
3232
return tags button_class unless value
3333

3434
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
3737
else
3838
eles_by_json_visible_contains button_class, value
3939
end
@@ -69,8 +69,8 @@ def button_exact(value)
6969
# @return [Array<UIAButton|XCUIElementTypeButton>]
7070
def buttons_exact(value)
7171
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
7474
else
7575
eles_by_json_visible_exact button_class, value
7676
end

lib/appium_lib/ios/element/generic.rb

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def find(value)
1616
# @return [Array<Element>]
1717
def finds(value)
1818
if automation_name_is_xcuitest?
19-
elements = find_eles_by_attr_include '*', '*', value
19+
elements = find_eles_by_predicate_include value: value
2020
select_visible_elements elements
2121
else
2222
eles_by_json_visible_contains '*', value
@@ -39,7 +39,7 @@ def find_exact(value)
3939
# @return [Array<Element>]
4040
def finds_exact(value)
4141
if automation_name_is_xcuitest?
42-
elements = find_eles_by_attr '*', '*', value
42+
elements = find_eles_by_predicate value: value
4343
select_visible_elements elements
4444
else
4545
eles_by_json_visible_exact '*', value
@@ -54,34 +54,6 @@ def raise_error_if_no_element(element)
5454
element
5555
end
5656

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-
8557
# Return visible elements.
8658
def select_visible_elements(elements)
8759
elements.select(&:displayed?)

lib/appium_lib/ios/element/text.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ def texts(value = false)
3131
return tags static_text_class unless value
3232

3333
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
3636
else
3737
eles_by_json_visible_contains static_text_class, value
3838
end
@@ -66,8 +66,8 @@ def text_exact(value)
6666
# @return [Array<UIAStaticText|XCUIElementTypeStaticText>]
6767
def texts_exact(value)
6868
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
7171
else
7272
eles_by_json_visible_exact static_text_class, value
7373
end

lib/appium_lib/ios/element/textfield.rb

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,14 @@ def secure_text_field_class
2020

2121
# @private
2222
# 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
2525
end
2626

2727
# @private
2828
# 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])
3731
select_visible_elements elements
3832
end
3933

@@ -67,9 +61,9 @@ def textfield(value)
6761
if value.is_a? Numeric
6862
index = value
6963
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.
7165
result = if automation_name_is_xcuitest?
72-
_textfields_with_xpath[index]
66+
_textfields_with_predicate[index]
7367
else
7468
eles_by_json(_textfield_visible)[index]
7569
end
@@ -91,8 +85,9 @@ def textfield(value)
9185
# @return [Array<TextField>]
9286
def textfields(value = false)
9387
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
9691
select_visible_elements elements
9792
else
9893
return eles_by_json _textfield_visible unless value
@@ -104,7 +99,7 @@ def textfields(value = false)
10499
# @return [TextField]
105100
def first_textfield
106101
if automation_name_is_xcuitest?
107-
_textfield_with_xpath
102+
_textfield_with_predicate
108103
else
109104
ele_by_json _textfield_visible
110105
end
@@ -114,7 +109,7 @@ def first_textfield
114109
# @return [TextField]
115110
def last_textfield
116111
result = if automation_name_is_xcuitest?
117-
_textfields_with_xpath.last
112+
_textfields_with_predicate.last
118113
else
119114
eles_by_json(_textfield_visible).last
120115
end
@@ -138,7 +133,7 @@ def textfield_exact(value)
138133
# @return [Array<TextField>]
139134
def textfields_exact(value)
140135
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
142137
select_visible_elements elements
143138
else
144139
eles_by_json _textfield_exact_string value

0 commit comments

Comments
 (0)