appium_lib 0.24.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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