appium_lib 0.24.1 → 1.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 (132) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +17 -8
  3. data/android_tests/Gemfile +1 -0
  4. data/android_tests/LICENSE-2.0.txt +202 -0
  5. data/android_tests/Rakefile +61 -0
  6. data/android_tests/api.apk +0 -0
  7. data/android_tests/appium.txt +3 -0
  8. data/android_tests/flaky.txt +1 -0
  9. data/android_tests/lib/android/specs/android/dynamic.rb +5 -0
  10. data/android_tests/lib/android/specs/android/element/alert.rb +41 -0
  11. data/android_tests/lib/android/specs/android/element/button.rb +55 -0
  12. data/android_tests/lib/android/specs/android/element/generic.rb +48 -0
  13. data/android_tests/lib/android/specs/android/element/text.rb +39 -0
  14. data/android_tests/lib/android/specs/android/element/textfield.rb +60 -0
  15. data/android_tests/lib/android/specs/android/helper.rb +80 -0
  16. data/android_tests/lib/android/specs/android/patch.rb +14 -0
  17. data/android_tests/lib/android/specs/common/device.rb +117 -0
  18. data/android_tests/lib/android/specs/common/element/window.rb +9 -0
  19. data/android_tests/lib/android/specs/common/helper.rb +112 -0
  20. data/android_tests/lib/android/specs/common/patch.rb +69 -0
  21. data/android_tests/lib/android/specs/common/version.rb +9 -0
  22. data/android_tests/lib/android/specs/driver.rb +174 -0
  23. data/android_tests/lib/format.rb +49 -0
  24. data/android_tests/lib/run.rb +72 -0
  25. data/android_tests/readme.md +27 -0
  26. data/appium_lib.gemspec +8 -5
  27. data/docs/android_docs.md +1052 -716
  28. data/docs/ios_docs.md +657 -834
  29. data/docs_gen/make_docs.rb +1 -3
  30. data/ios_tests/Gemfile +1 -0
  31. data/ios_tests/LICENSE-2.0.txt +202 -0
  32. data/ios_tests/Rakefile +47 -0
  33. data/ios_tests/UICatalog.app.zip +0 -0
  34. data/ios_tests/UICatalog.app/12-6AM.png +0 -0
  35. data/ios_tests/UICatalog.app/12-6PM.png +0 -0
  36. data/ios_tests/UICatalog.app/6-12AM.png +0 -0
  37. data/ios_tests/UICatalog.app/6-12PM.png +0 -0
  38. data/ios_tests/UICatalog.app/Default-568h@2x.png +0 -0
  39. data/ios_tests/UICatalog.app/Default@2x.png +0 -0
  40. data/ios_tests/UICatalog.app/Info.plist +0 -0
  41. data/ios_tests/UICatalog.app/PkgInfo +1 -0
  42. data/ios_tests/UICatalog.app/UIButton_custom.png +0 -0
  43. data/ios_tests/UICatalog.app/UICatalog +0 -0
  44. data/ios_tests/UICatalog.app/blueButton.png +0 -0
  45. data/ios_tests/UICatalog.app/bookmarkImage.png +0 -0
  46. data/ios_tests/UICatalog.app/bookmarkImageHighlighted.png +0 -0
  47. data/ios_tests/UICatalog.app/divider.png +0 -0
  48. data/ios_tests/UICatalog.app/en.lproj/AlertsViewController.nib +0 -0
  49. data/ios_tests/UICatalog.app/en.lproj/ButtonsViewController.nib +0 -0
  50. data/ios_tests/UICatalog.app/en.lproj/ControlsViewController.nib +0 -0
  51. data/ios_tests/UICatalog.app/en.lproj/ImagesViewController.nib +0 -0
  52. data/ios_tests/UICatalog.app/en.lproj/Localizable.strings +0 -0
  53. data/ios_tests/UICatalog.app/en.lproj/MainWindow.nib +0 -0
  54. data/ios_tests/UICatalog.app/en.lproj/PickerViewController.nib +0 -0
  55. data/ios_tests/UICatalog.app/en.lproj/SearchBarController.nib +0 -0
  56. data/ios_tests/UICatalog.app/en.lproj/SegmentViewController.nib +0 -0
  57. data/ios_tests/UICatalog.app/en.lproj/TextFieldController.nib +0 -0
  58. data/ios_tests/UICatalog.app/en.lproj/TextViewController.nib +0 -0
  59. data/ios_tests/UICatalog.app/en.lproj/ToolbarViewController.nib +0 -0
  60. data/ios_tests/UICatalog.app/en.lproj/TransitionViewController.nib +0 -0
  61. data/ios_tests/UICatalog.app/en.lproj/WebViewController.nib +0 -0
  62. data/ios_tests/UICatalog.app/orangeslide.png +0 -0
  63. data/ios_tests/UICatalog.app/scene1.jpg +0 -0
  64. data/ios_tests/UICatalog.app/scene2.jpg +0 -0
  65. data/ios_tests/UICatalog.app/scene3.jpg +0 -0
  66. data/ios_tests/UICatalog.app/scene4.jpg +0 -0
  67. data/ios_tests/UICatalog.app/scene5.jpg +0 -0
  68. data/ios_tests/UICatalog.app/searchBarBackground.png +0 -0
  69. data/ios_tests/UICatalog.app/segment_check.png +0 -0
  70. data/ios_tests/UICatalog.app/segment_search.png +0 -0
  71. data/ios_tests/UICatalog.app/segment_tools.png +0 -0
  72. data/ios_tests/UICatalog.app/segmentedBackground.png +0 -0
  73. data/ios_tests/UICatalog.app/slider_ball.png +0 -0
  74. data/ios_tests/UICatalog.app/toolbarBackground.png +0 -0
  75. data/ios_tests/UICatalog.app/whiteButton.png +0 -0
  76. data/ios_tests/UICatalog.app/yellowslide.png +0 -0
  77. data/ios_tests/appium.txt +3 -0
  78. data/ios_tests/flaky.txt +1 -0
  79. data/ios_tests/lib/format.rb +25 -0
  80. data/ios_tests/lib/ios/specs/common/element/window.rb +15 -0
  81. data/ios_tests/lib/ios/specs/common/helper.rb +204 -0
  82. data/ios_tests/lib/ios/specs/common/patch.rb +50 -0
  83. data/ios_tests/lib/ios/specs/common/version.rb +17 -0
  84. data/ios_tests/lib/ios/specs/device/device.rb +82 -0
  85. data/ios_tests/lib/ios/specs/device/multi_touch.rb +12 -0
  86. data/ios_tests/lib/ios/specs/device/touch_actions.rb +15 -0
  87. data/ios_tests/lib/ios/specs/driver.rb +203 -0
  88. data/ios_tests/lib/ios/specs/ios/element/alert.rb +48 -0
  89. data/ios_tests/lib/ios/specs/ios/element/button.rb +58 -0
  90. data/ios_tests/lib/ios/specs/ios/element/generic.rb +35 -0
  91. data/ios_tests/lib/ios/specs/ios/element/text.rb +54 -0
  92. data/ios_tests/lib/ios/specs/ios/element/textfield.rb +123 -0
  93. data/ios_tests/lib/ios/specs/ios/helper.rb +27 -0
  94. data/ios_tests/lib/ios/specs/ios/patch.rb +30 -0
  95. data/ios_tests/lib/run.rb +106 -0
  96. data/ios_tests/readme.md +30 -0
  97. data/ios_tests/upload/sauce_storage.rb +64 -0
  98. data/ios_tests/upload/upload.rb +6 -0
  99. data/lib/appium_lib.rb +4 -14
  100. data/lib/appium_lib/android/dynamic.rb +30 -32
  101. data/lib/appium_lib/android/element/alert.rb +34 -33
  102. data/lib/appium_lib/android/element/button.rb +91 -0
  103. data/lib/appium_lib/android/element/generic.rb +51 -146
  104. data/lib/appium_lib/android/element/text.rb +54 -0
  105. data/lib/appium_lib/android/element/textfield.rb +46 -41
  106. data/lib/appium_lib/android/helper.rb +248 -417
  107. data/lib/appium_lib/android/mobile_methods.rb +17 -0
  108. data/lib/appium_lib/android/patch.rb +9 -8
  109. data/lib/appium_lib/awesome_print/ostruct.rb +33 -0
  110. data/lib/appium_lib/common/element/window.rb +9 -8
  111. data/lib/appium_lib/common/helper.rb +182 -243
  112. data/lib/appium_lib/common/patch.rb +65 -79
  113. data/lib/appium_lib/common/version.rb +2 -3
  114. data/lib/appium_lib/device/device.rb +339 -0
  115. data/lib/appium_lib/device/multi_touch.rb +94 -0
  116. data/lib/appium_lib/device/touch_actions.rb +142 -0
  117. data/lib/appium_lib/driver.rb +217 -306
  118. data/lib/appium_lib/ios/element/alert.rb +16 -92
  119. data/lib/appium_lib/ios/element/button.rb +55 -0
  120. data/lib/appium_lib/ios/element/generic.rb +27 -160
  121. data/lib/appium_lib/ios/element/text.rb +54 -0
  122. data/lib/appium_lib/ios/element/textfield.rb +78 -65
  123. data/lib/appium_lib/ios/helper.rb +300 -190
  124. data/lib/appium_lib/ios/mobile_methods.rb +17 -0
  125. data/lib/appium_lib/ios/patch.rb +55 -41
  126. data/lib/appium_lib/logger.rb +13 -0
  127. data/lib/appium_lib/rails/duplicable.rb +116 -0
  128. data/readme.md +6 -1
  129. data/release_notes.md +118 -0
  130. metadata +170 -12
  131. data/lib/appium_lib/common/element/button.rb +0 -83
  132. data/lib/appium_lib/common/element/text.rb +0 -61
@@ -1,209 +1,319 @@
1
- # encoding: utf-8
2
- module Appium::Ios
3
- # iOS only. Android uses uiautomator instead of uiautomation.
4
- # Get an array of attribute values from elements exactly matching tag name.
5
- # @param tag_name [String] the tag name to find
6
- # @param attribute [String] the attribute to collect
7
- # @return [Array<String>] an array of strings containing the attribute from found elements of type tag_name.
8
- def find_eles_attr tag_name, attribute
9
- # Use au.lookup(tag_name) instead of $(tag_name)
10
- # See https://github.com/appium/appium/issues/214
11
- js = %Q(
12
- var eles = au.lookup('#{tag_name}');
13
- var result = [];
14
- for (var a = 0, length = eles.length; a < length; a++) {
15
- result.push(eles[a].#{attribute}());
16
- }
17
- result
18
- )
19
-
20
- @driver.execute_script js
21
- end
22
-
23
- # iOS only. Android doesn't use find_2_eles_attr.
24
- # Get an array of attribute values from elements exactly matching tag name.
25
- # @param tag_name_1 [String] the 1st tag name to find
26
- # @param tag_name_2 [String] the 2nd tag name to find
27
- # @param attribute [String] the attribute to collect
28
- # @return [Array<String>] an array of strings containing the attribute from found elements of type tag_name.
29
- def find_2_eles_attr tag_name_1, tag_name_2, attribute
30
- # Use au.lookup(tag_name) instead of $(tag_name)
31
- # See https://github.com/appium/appium/issues/214
32
- js = %Q(
33
- var eles = au.lookup('#{tag_name_1}');
34
- eles = $(eles.concat(au.lookup('#{tag_name_2}')));
35
- var result = [];
36
- for (var a = 0, length = eles.length; a < length; a++) {
37
- result.push(eles[a].#{attribute}());
38
- }
39
- result
40
- )
41
-
42
- @driver.execute_script js
43
- end
44
-
45
- # iOS only. On Android uiautomator always returns an empty string for EditText password.
46
- #
47
- # Password character returned from value of UIASecureTextField
48
- # @param length [Integer] the length of the password to generate
49
- # @return [String] the returned string is of size length
50
- def ios_password length=1
51
- '•' * length
52
- end
53
-
54
- # Returns a string of class counts.
55
- def get_page_class
56
- r = []
57
- run_internal = lambda do |node|
58
- if node.kind_of? Array
59
- node.each { |node| run_internal.call node }
60
- return
1
+ module Appium
2
+ module Ios
3
+ # iOS only. On Android uiautomator always returns an empty string for EditText password.
4
+ #
5
+ # Password character returned from value of UIASecureTextField
6
+ # @param length [Integer] the length of the password to generate
7
+ # @return [String] the returned string is of size length
8
+ def ios_password length=1
9
+ '•' * length
10
+ end
11
+
12
+ # Returns a string of interesting elements. iOS only.
13
+ #
14
+ # Defaults to inspecting the 1st windows source only.
15
+ # use get_page(get_source) for all window sources
16
+ #
17
+ # @param element [Object] the element to search. omit to search everything
18
+ # @return [String]
19
+ def get_page element=source_window(0), class_name=nil
20
+ lazy_load_strings # populate @strings_xml
21
+
22
+ # @private
23
+ def empty ele
24
+ (ele['name'] || ele['label'] || ele['value']) == nil
25
+ end
26
+
27
+ # @private
28
+ def fix_space s
29
+ # if s is an int, we can't call .empty
30
+ return nil if s.nil? || (s.respond_to?(:empty) && s.empty?)
31
+ # ints don't respond to force encoding
32
+ # ensure we're converting to a string
33
+ unless s.respond_to? :force_encoding
34
+ s_s = s.to_s
35
+ return s_s.empty? ? nil : s_s
36
+ end
37
+ # char code 160 (name, label) vs 32 (value) will break comparison.
38
+ # convert string to binary and remove 160.
39
+ # \xC2\xA0
40
+ s = s.force_encoding('binary').gsub("\xC2\xA0".force_encoding('binary'), ' ') if s
41
+ s.empty? ? nil : s
61
42
  end
62
43
 
63
- keys = node.keys
64
- return if keys.empty?
65
- r.push node['type'] if keys.include?('type')
44
+ unless empty(element) || element['visible'] == false
45
+ name = fix_space element['name']
46
+ label = fix_space element['label']
47
+ value = fix_space element['value']
48
+ hint = fix_space element['hint']
49
+ visible = fix_space element['visible']
50
+ type = fix_space element['type']
51
+
52
+ # if class_name is set, mark non-matches as invisible
53
+ visible = (type == class_name).to_s if class_name
54
+
55
+ if name == label && name == value
56
+ puts "#{type}" if name || label || value || hint
57
+ puts " name, label, value: #{name}" if name
58
+ puts " hint: #{hint}" if hint
59
+ elsif name == label
60
+ puts "#{type}" if name || label || value || hint
61
+ puts " name, label: #{name}" if name
62
+ puts " value: #{value}" if value
63
+ puts " hint: #{hint}" if hint
64
+ elsif name == value
65
+ puts "#{type}" if name || label || value || hint
66
+ puts " name, value: #{name}" if name
67
+ puts " label: #{label}" if label
68
+ puts " hint: #{hint}" if hint
69
+ else
70
+ puts "#{type}" if name || label || value || hint
71
+ puts " name: #{name}" if name
72
+ puts " label: #{label}" if label
73
+ puts " value: #{value}" if value
74
+ puts " hint: #{hint}" if hint
75
+ end if visible && visible == 'true'
66
76
 
67
- run_internal.call node['children'] if keys.include?('children')
77
+ # there may be many ids with the same value.
78
+ # output all exact matches.
79
+ id_matches = @strings_xml.select do |key, val|
80
+ val == name || val == label || val == value
81
+ end
82
+
83
+ if id_matches && id_matches.length > 0
84
+ match_str = ''
85
+ # [0] = key, [1] = value
86
+ id_matches.each do |match|
87
+ match_str += ' ' * 7 + "#{match[0]}\n"
88
+ end
89
+ puts " id: #{match_str.strip}\n"
90
+ end
91
+ end
92
+
93
+ children = element['children']
94
+ children.each { |c| get_page c, class_name } if children
95
+ nil
68
96
  end
69
- json = get_source
70
- run_internal.call json['children']
71
97
 
72
- res = []
73
- r = r.sort
74
- r.uniq.each do |ele|
75
- res.push "#{r.count(ele)}x #{ele}\n"
98
+ # Prints a string of interesting elements to the console.
99
+ # @return [void]
100
+ def page opts={}
101
+ window_number = opts.fetch :window, -1
102
+ class_name = opts.fetch :class, nil
103
+
104
+ if window_number == -1
105
+ # if the 0th window has no children, find the next window that does.
106
+ target_window = source_window 0
107
+ target_window = source_window 1 if target_window['children'].empty?
108
+ get_page target_window, class_name
109
+ else
110
+ get_page source_window(window_number || 0), class_name
111
+ end
112
+ nil
76
113
  end
77
- count_sort = ->(one, two) { two.match(/(\d+)x/)[1].to_i <=> one.match(/(\d+)x/)[1].to_i }
78
- res.sort(&count_sort).join ''
79
- end
80
114
 
81
- def page_class
82
- puts get_page_class
83
- nil
84
- end
115
+ # Gets the JSON source of window number
116
+ # @param window_number [Integer] the int index of the target window
117
+ # @return [JSON]
118
+ def source_window window_number=0
119
+ # appium 1.0 still returns JSON when getTree() is invoked so this
120
+ # doesn't need to change to XML. If getTree() is removed then
121
+ # source_window will need to parse the results of getTreeForXML()\
122
+ # https://github.com/appium/appium-uiauto/blob/247eb71383fa1a087ff8f8fc96fac25025731f3f/uiauto/appium/element.js#L145
123
+ execute_script "UIATarget.localTarget().frontMostApp().windows()[#{window_number}].getTree()"
124
+ end
125
+
126
+ # Prints parsed page source to console.
127
+ #
128
+ # example: page_window 0
129
+ #
130
+ # @param window_number [Integer] the int index of the target window
131
+ # @return [void]
132
+ def page_window window_number=0
133
+ get_page source_window window_number
134
+ nil
135
+ end
136
+
137
+ # Find by id
138
+ # @param id [String] the id to search for
139
+ # @return [Element]
140
+ def id id
141
+ value = resolve_id id
142
+ raise "Invalid id `#{id}`" unless value
143
+ exact = string_visible_exact '*', value
144
+ contains = string_visible_contains '*', value
145
+ xpath "#{exact} | #{contains}"
146
+ end
85
147
 
86
- # Returns a string of interesting elements. iOS only.
87
- #
88
- # Defaults to inspecting the 1st windows source only.
89
- # use get_page(get_source) for all window sources
90
- #
91
- # @param element [Object] the element to search. omit to search everything
92
- # @return [String]
93
- def get_page element=source_window(0)
94
- lazy_load_strings
148
+ # Return the iOS version as an array of integers
149
+ # @return [Array<Integer>]
150
+ def ios_version
151
+ ios_version = execute_script 'UIATarget.localTarget().systemVersion()'
152
+ ios_version.split('.').map { |e| e.to_i }
153
+ end
154
+
155
+ # Get the element of type class_name at matching index.
156
+ # @param class_name [String] the class name to find
157
+ # @param index [Integer] the index
158
+ # @return [Element]
159
+ def ele_index class_name, index
160
+ unless index == 'last()'
161
+ # XPath index starts at 1.
162
+ raise "#{index} is not a valid xpath index. Must be >= 1" if index <= 0
163
+ end
164
+ find_element :xpath, %Q(//#{class_name}[@visible="true"][#{index}])
165
+ end
95
166
 
96
167
  # @private
97
- def empty ele
98
- (ele['name'] || ele['label'] || ele['value']) == nil
168
+ def string_attr_exact class_name, attr, value
169
+ %Q(//#{class_name}[@visible="true" and @#{attr}='#{value}'])
170
+ end
171
+
172
+ # Find the first element exactly matching class and attribute value.
173
+ # @param class_name [String] the class name to search for
174
+ # @param attr [String] the attribute to inspect
175
+ # @param value [String] the expected value of the attribute
176
+ # @return [Element]
177
+ def find_ele_by_attr class_name, attr, value
178
+ @driver.find_element :xpath, string_attr_exact(class_name, attr, value)
179
+ end
180
+
181
+ # Find all elements exactly matching class and attribute value.
182
+ # @param class_name [String] the class name to match
183
+ # @param attr [String] the attribute to compare
184
+ # @param value [String] the value of the attribute that the element must have
185
+ # @return [Array<Element>]
186
+ def find_eles_by_attr class_name, attr, value
187
+ @driver.find_elements :xpath, string_attr_exact(class_name, attr, value)
99
188
  end
100
189
 
101
190
  # @private
102
- def fix_space s
103
- # if s is an int, we can't call .empty
104
- return nil if s.nil? || (s.respond_to?(:empty) && s.empty?)
105
- # ints don't respond to force encoding
106
- # ensure we're converting to a string
107
- unless s.respond_to? :force_encoding
108
- s_s = s.to_s
109
- return s_s.empty? ? nil : s_s
110
- end
111
- # char code 160 (name, label) vs 32 (value) will break comparison.
112
- # convert string to binary and remove 160.
113
- # \xC2\xA0
114
- s = s.force_encoding('binary').gsub("\xC2\xA0".force_encoding('binary'), ' ') if s
115
- s.empty? ? nil : s
116
- end
117
-
118
- unless empty(element) || element['visible'] == false
119
- puts "#{element['type']}"
120
- name = fix_space element['name']
121
- label = fix_space element['label']
122
- value = fix_space element['value']
123
-
124
- if name == label && name == value
125
- puts " name, label, value: #{name}" if name
126
- elsif name == label
127
- puts " name, label: #{name}" if name
128
- puts " value: #{value}" if value
129
- elsif name == value
130
- puts " name, value: #{name}" if name
131
- puts " label: #{label}" if label
132
- else
133
- puts " name: #{name}" if name
134
- puts " label: #{label}" if label
135
- puts " value: #{value}" if value
136
- end
191
+ def string_attr_include class_name, attr, value
192
+ %Q(//#{class_name}[@visible="true" and contains(translate(@#{attr},'#{value.upcase}', '#{value}'), '#{value}')])
193
+ end
194
+
195
+ # Get the first tag by attribute that exactly matches value.
196
+ # @param class_name [String] the tag name to match
197
+ # @param attr [String] the attribute to compare
198
+ # @param value [String] the value of the attribute that the element must include
199
+ # @return [Element] the element of type tag who's attribute includes value
200
+ def find_ele_by_attr_include class_name, attr, value
201
+ @driver.find_element :xpath, string_attr_include(class_name, attr, value)
202
+ end
203
+
204
+ # Get tags by attribute that include value.
205
+ # @param class_name [String] the tag name to match
206
+ # @param attr [String] the attribute to compare
207
+ # @param value [String] the value of the attribute that the element must include
208
+ # @return [Array<Element>] the elements of type tag who's attribute includes value
209
+ def find_eles_by_attr_include class_name, attr, value
210
+ @driver.find_elements :xpath, string_attr_include(class_name, attr, value)
211
+ end
212
+
213
+ # Get the first tag that matches class_name
214
+ # @param class_name [String] the tag to match
215
+ # @return [Element]
216
+ def first_ele class_name
217
+ # XPath index starts at 1
218
+ ele_index class_name, 1
219
+ end
220
+
221
+ # Get the last tag that matches class_name
222
+ # @param class_name [String] the tag to match
223
+ # @return [Element]
224
+ def last_ele class_name
225
+ ele_index class_name, 'last()'
226
+ end
227
+
228
+ # Returns the first element matching class_name
229
+ #
230
+ # @param class_name [String] the class_name to search for
231
+ # @return [Element]
232
+ def tag class_name
233
+ xpath %Q(//#{class_name}[@visible="true"])
234
+ end
235
+
236
+ # Returns all elements matching class_name
237
+ #
238
+ # @param class_name [String] the class_name to search for
239
+ # @return [Element]
240
+ def tags class_name
241
+ xpaths %Q(//#{class_name}[@visible="true"])
242
+ end
243
+
244
+ # @private
245
+ # Returns a string xpath that matches the first element that contains value
246
+ #
247
+ # example: xpath_visible_contains 'UIATextField', 'sign in'
248
+ #
249
+ # @param element [String] the class name for the element
250
+ # @param value [String] the value to search for
251
+ # @return [String]
252
+ def string_visible_contains element, value
253
+ result = []
254
+ attributes = %w[name hint label value]
137
255
 
138
- # there may be many ids with the same value.
139
- # output all exact matches.
140
- id_matches = @strings_xml.select do |key, val|
141
- val == name || val == label || val == value
256
+ value_up = value.upcase
257
+ value_down = value.downcase
258
+
259
+ attributes.each do |attribute|
260
+ result << %Q(contains(translate(@#{attribute},"#{value_up}","#{value_down}"), "#{value_down}"))
142
261
  end
143
262
 
144
- if id_matches && id_matches.length > 0
145
- match_str = ''
146
- # [0] = key, [1] = value
147
- id_matches.each do |match|
148
- match_str += ' ' * 7 + "#{match[0]}\n"
149
- end
150
- puts " id: #{match_str.strip}\n"
263
+ result = result.join(' or ')
264
+ result = %Q(@visible="true" and (#{result}))
265
+ "//#{element}[#{result}]"
266
+ end
267
+
268
+ # Find the first element that contains value
269
+ # @param element [String] the class name for the element
270
+ # @param value [String] the value to search for
271
+ # @return [Element]
272
+ def xpath_visible_contains element, value
273
+ xpath string_visible_contains element, value
274
+ end
275
+
276
+ # Find all elements containing value
277
+ # @param element [String] the class name for the element
278
+ # @param value [String] the value to search for
279
+ # @return [Array<Element>]
280
+ def xpaths_visible_contains element, value
281
+ xpaths string_visible_contains element, value
282
+ end
283
+
284
+ # @private
285
+ # Create an xpath string to exactly match the first element with target value
286
+ # @param element [String] the class name for the element
287
+ # @param value [String] the value to search for
288
+ # @return [String]
289
+ def string_visible_exact element, value
290
+ result = []
291
+ attributes = %w[name hint label value]
292
+
293
+ attributes.each do |attribute|
294
+ result << %Q(@#{attribute}="#{value}")
151
295
  end
296
+
297
+ result = result.join(' or ')
298
+ result = %Q(@visible="true" and (#{result}))
299
+
300
+ "//#{element}[#{result}]"
152
301
  end
153
302
 
154
- children = element['children']
155
- children.each { |c| get_page c } if children
156
- nil
157
- end
158
-
159
- # Prints a string of interesting elements to the console.
160
- # @return [void]
161
- def page window_number = -1
162
- if window_number == -1
163
- # if the 0th window has no children, find the next window that does.
164
- target_window = source_window 0
165
- target_window = source_window 1 if target_window['children'].empty?
166
- get_page target_window
167
- else
168
- get_page source_window window_number || 0
169
- end
170
- nil
171
- end
172
-
173
- # Gets the JSON source of window number
174
- # @param window_number [Integer] the int index of the target window
175
- # @return [JSON]
176
- def source_window window_number=0
177
- execute_script "UIATarget.localTarget().frontMostApp().windows()[#{window_number}].getTree()"
178
- end
179
-
180
- # Prints parsed page source to console.
181
- # @param window_number [Integer] the int index of the target window
182
- # example: page_window 0
183
- def page_window window_number=0
184
- get_page source_window window_number
185
- nil
186
- end
187
-
188
- # The fastest duration that can be used on iOS.
189
- # @return [Float]
190
- def fast_duration
191
- 0.5
192
- end
193
-
194
- # Find by id. Useful for selendroid
195
- # @param id [String] the id to search for
196
- # @return [Element]
197
- def id id
198
- lazy_load_strings
199
- raise "Invalid id `#{id}`" unless @strings_xml[id]
200
- find_element :id, id
201
- end
202
-
203
- # Return the iOS version as an array of integers
204
- # @return [Array<Integer>]
205
- def ios_version
206
- ios_version = execute_script 'UIATarget.localTarget().systemVersion()'
207
- ios_version.split('.').map { |e| e.to_i }
208
- end
209
- end # module Appium::Ios
303
+ # Find the first element exactly matching value
304
+ # @param element [String] the class name for the element
305
+ # @param value [String] the value to search for
306
+ # @return [Element]
307
+ def xpath_visible_exact element, value
308
+ xpath string_visible_exact element, value
309
+ end
310
+
311
+ # Find all elements exactly matching value
312
+ # @param element [String] the class name for the element
313
+ # @param value [String] the value to search for
314
+ # @return [Element]
315
+ def xpaths_visible_exact element, value
316
+ xpaths string_visible_exact element, value
317
+ end
318
+ end # module Ios
319
+ end # module Appium