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
@@ -0,0 +1,54 @@
1
+ # TextView methods
2
+ module Appium
3
+ module Android
4
+ TextView = 'android.widget.TextView'
5
+
6
+ # Find the first TextView that contains value or by index.
7
+ # @param value [String, Integer] the value to find.
8
+ # If int then the TextView at that index is returned.
9
+ # @return [TextView]
10
+ def s_text value
11
+ return ele_index TextView, value if value.is_a? Numeric
12
+ xpath_visible_contains TextView, value
13
+ end
14
+
15
+ # Find all TextViews containing value.
16
+ # @param value [String] the value to search for
17
+ # @return [Array<TextView>]
18
+ def s_texts value
19
+ xpaths_visible_contains TextView, value
20
+ end
21
+
22
+ # Find the first TextView.
23
+ # @return [TextView]
24
+ def first_s_text
25
+ first_ele TextView
26
+ end
27
+
28
+ # Find the last TextView.
29
+ # @return [TextView]
30
+ def last_s_text
31
+ last_ele TextView
32
+ end
33
+
34
+ # Find the first TextView that exactly matches value.
35
+ # @param value [String] the value to match exactly
36
+ # @return [TextView]
37
+ def s_text_exact value
38
+ xpath_visible_exact TextView, value
39
+ end
40
+
41
+ # Find all TextViews that exactly match value.
42
+ # @param value [String] the value to match exactly
43
+ # @return [Array<TextView>]
44
+ def s_texts_exact value
45
+ xpaths_visible_exact TextView, value
46
+ end
47
+
48
+ # Find all TextViews.
49
+ # @return [Array<TextView>]
50
+ def e_s_texts
51
+ tags TextView
52
+ end
53
+ end # module Android
54
+ end # module Appium
@@ -1,48 +1,53 @@
1
- # encoding: utf-8
2
- module Appium::Android
3
- # UIATextField methods
1
+ module Appium
2
+ module Android
3
+ EditText = 'android.widget.EditText'
4
4
 
5
- # Get an array of textfield texts.
6
- # @return [Array<String>]
7
- def textfields
8
- find_eles_attr :textfield, :text
9
- end
5
+ # Find the first EditText that contains value or by index.
6
+ # @param value [String, Integer] the text to match exactly.
7
+ # If int then the EditText at that index is returned.
8
+ # @return [EditText]
9
+ def textfield value
10
+ return ele_index EditText, value if value.is_a? Numeric
11
+ xpath_visible_contains EditText, value
12
+ end
10
13
 
11
- # Get an array of textfield elements.
12
- # @return [Array<Textfield>]
13
- def e_textfields
14
- find_eles :textfield
15
- end
14
+ # Find all EditTexts containing value.
15
+ # @param value [String] the value to search for
16
+ # @return [Array<EditText>]
17
+ def textfields value
18
+ xpaths_visible_contains EditText, value
19
+ end
16
20
 
17
- # Get the first textfield element.
18
- # @return [Textfield]
19
- def first_textfield
20
- first_ele :textfield
21
- end
21
+ # Find the first EditText.
22
+ # @return [EditText]
23
+ def first_textfield
24
+ first_ele EditText
25
+ end
22
26
 
23
- # Get the last textfield element.
24
- # @return [Textfield]
25
- def last_textfield
26
- last_ele :textfield
27
- end
27
+ # Find the last EditText.
28
+ # @return [EditText]
29
+ def last_textfield
30
+ last_ele EditText
31
+ end
28
32
 
29
- # Get the first textfield that includes text or name (content description).
30
- # @param text [String, Integer] the text to search for. If int then the textfield at that index is returned.
31
- # @return [Textfield]
32
- def textfield text
33
- return ele_index :textfield, text if text.is_a? Numeric
33
+ # Find the first EditText that exactly matches value.
34
+ # @param value [String] the value to match exactly
35
+ # @return [EditText]
36
+ def textfield_exact value
37
+ xpath_visible_exact EditText, value
38
+ end
34
39
 
35
- # s.className('android.widget.EditText').descriptionContains(value);
36
- args = [ [4, 'android.widget.EditText'], [7, text] ],
37
- # s.className('android.widget.EditText').textContains(value);
38
- [ [4, 'android.widget.EditText'], [3, text] ]
39
- mobile :find, args
40
- end
40
+ # Find all EditTexts that exactly match value.
41
+ # @param value [String] the value to match exactly
42
+ # @return [Array<EditText>]
43
+ def textfields_exact value
44
+ xpaths_visible_exact EditText, value
45
+ end
41
46
 
42
- # Get the first textfield that matches text.
43
- # @param text [String] the text to match
44
- # @return [Textfield]
45
- def textfield_exact text
46
- find_ele_by_text :textfield, text
47
- end
48
- end # module Appium::Android
47
+ # Find all EditTexts.
48
+ # @return [Array<EditText>]
49
+ def e_textfields
50
+ tags EditText
51
+ end
52
+ end # module Android
53
+ end # module Appium
@@ -1,447 +1,278 @@
1
- # encoding: utf-8
2
- module Appium::Android
3
- # Returns an array of android classes that match the tag name
4
- # @param tag_name [String] the tag name to convert to an android class
5
- # @return [String]
6
- def tag_name_to_android tag_name
7
- tag_name = tag_name.to_s.downcase.strip
8
-
1
+ module Appium
2
+ module Android
9
3
  # @private
10
- def prefix *tags
11
- tags.map!{ |tag| "android.widget.#{tag}" }
12
- end
13
- # note that 'secure' is not an allowed tag name on android
14
- # because android can't tell what a secure textfield is
15
- # they're all edittexts.
16
-
17
- # must match names in AndroidElementClassMap (Appium's Java server)
18
- case tag_name
19
- when 'abslist'
20
- prefix 'AbsListView'
21
- when 'absseek'
22
- prefix 'AbsSeekBar'
23
- when 'absspinner'
24
- prefix 'AbsSpinner'
25
- when 'absolute'
26
- prefix 'AbsoluteLayout'
27
- when 'adapterview'
28
- prefix 'AdapterView'
29
- when 'adapterviewanimator'
30
- prefix 'AdapterViewAnimator'
31
- when 'adapterviewflipper'
32
- prefix 'AdapterViewFlipper'
33
- when 'analogclock'
34
- prefix 'AnalogClock'
35
- when 'appwidgethost'
36
- prefix 'AppWidgetHostView'
37
- when 'autocomplete'
38
- prefix 'AutoCompleteTextView'
39
- when 'button'
40
- prefix 'Button', 'ImageButton'
41
- when 'breadcrumbs'
42
- prefix 'FragmentBreadCrumbs'
43
- when 'calendar'
44
- prefix 'CalendarView'
45
- when 'checkbox'
46
- prefix 'CheckBox'
47
- when 'checked'
48
- prefix 'CheckedTextView'
49
- when 'chronometer'
50
- prefix 'Chronometer'
51
- when 'compound'
52
- prefix 'CompoundButton'
53
- when 'datepicker'
54
- prefix 'DatePicker'
55
- when 'dialerfilter'
56
- prefix 'DialerFilter'
57
- when 'digitalclock'
58
- prefix 'DigitalClock'
59
- when 'drawer'
60
- prefix 'SlidingDrawer'
61
- when 'expandable'
62
- prefix 'ExpandableListView'
63
- when 'extract'
64
- prefix 'ExtractEditText'
65
- when 'fragmenttabhost'
66
- prefix 'FragmentTabHost'
67
- when 'frame'
68
- prefix 'FrameLayout'
69
- when 'gallery'
70
- prefix 'Gallery'
71
- when 'gesture'
72
- prefix 'GestureOverlayView'
73
- when 'glsurface'
74
- prefix 'GLSurfaceView'
75
- when 'grid'
76
- prefix 'GridView'
77
- when 'gridlayout'
78
- prefix 'GridLayout'
79
- when 'horizontal'
80
- prefix 'HorizontalScrollView'
81
- when 'image'
82
- prefix 'ImageView'
83
- when 'imagebutton'
84
- prefix 'ImageButton'
85
- when 'imageswitcher'
86
- prefix 'ImageSwitcher'
87
- when 'keyboard'
88
- prefix 'KeyboardView'
89
- when 'linear'
90
- prefix 'LinearLayout'
91
- when 'list'
92
- prefix 'ListView'
93
- when 'media'
94
- prefix 'MediaController'
95
- when 'mediaroutebutton'
96
- prefix 'MediaRouteButton'
97
- when 'multiautocomplete'
98
- prefix 'MultiAutoCompleteTextView'
99
- when 'numberpicker'
100
- prefix 'NumberPicker'
101
- when 'pagetabstrip'
102
- prefix 'PageTabStrip'
103
- when 'pagetitlestrip'
104
- prefix 'PageTitleStrip'
105
- when 'progress'
106
- prefix 'ProgressBar'
107
- when 'quickcontactbadge'
108
- prefix 'QuickContactBadge'
109
- when 'radio'
110
- prefix 'RadioButton'
111
- when 'radiogroup'
112
- prefix 'RadioGroup'
113
- when 'rating'
114
- prefix 'RatingBar'
115
- when 'relative'
116
- prefix 'RelativeLayout'
117
- when 'row'
118
- prefix 'TableRow'
119
- when 'rssurface'
120
- prefix 'RSSurfaceView'
121
- when 'rstexture'
122
- prefix 'RSTextureView'
123
- when 'scroll'
124
- prefix 'ScrollView'
125
- when 'search'
126
- prefix 'SearchView'
127
- when 'seek'
128
- prefix 'SeekBar'
129
- when 'space'
130
- prefix 'Space'
131
- when 'spinner'
132
- prefix 'Spinner'
133
- when 'stack'
134
- prefix 'StackView'
135
- when 'surface'
136
- prefix 'SurfaceView'
137
- when 'switch'
138
- prefix 'Switch'
139
- when 'tabhost'
140
- prefix 'TabHost'
141
- when 'tabwidget'
142
- prefix 'TabWidget'
143
- when 'table'
144
- prefix 'TableLayout'
145
- when 'text'
146
- prefix 'TextView'
147
- when 'textclock'
148
- prefix 'TextClock'
149
- when 'textswitcher'
150
- prefix 'TextSwitcher'
151
- when 'texture'
152
- prefix 'TextureView'
153
- when 'textfield'
154
- prefix 'EditText'
155
- when 'timepicker'
156
- prefix 'TimePicker'
157
- when 'toggle'
158
- prefix 'ToggleButton'
159
- when 'twolinelistitem'
160
- prefix 'TwoLineListItem'
161
- when 'view'
162
- 'android.view.View'
163
- when 'video'
164
- prefix 'VideoView'
165
- when 'viewanimator'
166
- prefix 'ViewAnimator'
167
- when 'viewflipper'
168
- prefix 'ViewFlipper'
169
- when 'viewgroup'
170
- prefix 'ViewGroup'
171
- when 'viewpager'
172
- prefix 'ViewPager'
173
- when 'viewstub'
174
- prefix 'ViewStub'
175
- when 'viewswitcher'
176
- prefix 'ViewSwitcher'
177
- when 'web'
178
- 'android.webkit.WebView' # WebView is not a widget
179
- when 'window'
180
- prefix 'FrameLayout'
181
- when 'zoom'
182
- prefix 'ZoomButton'
183
- when 'zoomcontrols'
184
- prefix 'ZoomControls'
185
- else
186
- raise "Invalid tag name #{tag_name}"
187
- end # return result of case
188
- end
189
- # Find all elements matching the attribute
190
- # On android, assume the attr is name (which falls back to text).
191
- #
192
- # ```ruby
193
- # find_eles_attr :text
194
- # ```
195
- #
196
- # @param tag_name [String] the tag name to search for
197
- # @return [Element]
198
- def find_eles_attr tag_name, attribute=nil
199
- =begin
200
- sel1 = [ [4, 'android.widget.Button'], [100] ]
201
- sel2 = [ [4, 'android.widget.ImageButton'], [100] ]
202
-
203
- args = [ 'all', sel1, sel2 ]
204
-
205
- mobile :find, args
206
- =end
207
- array = ['all']
208
-
209
- tag_name_to_android(tag_name).each do |name|
210
- # sel.className(name).getStringAttribute("name")
211
- array.push [ [4, name], [100] ]
212
- end
4
+ # http://nokogiri.org/Nokogiri/XML/SAX.html
5
+ class AndroidElements < Nokogiri::XML::SAX::Document
6
+ # TODO: Support strings.xml ids
7
+ attr_reader :result, :keys
213
8
 
214
- mobile :find, array
215
- end
9
+ def filter
10
+ @filter
11
+ end
216
12
 
217
- # Selendroid only.
218
- # Returns a string containing interesting elements.
219
- # @return [String]
220
- def get_selendroid_inspect
221
- # @private
222
- def run node
223
- r = []
13
+ # convert to string to support symbols
14
+ def filter= value
15
+ # nil and false disable the filter
16
+ return @filter = false unless value
17
+ @filter = value.to_s.downcase
18
+ end
224
19
 
225
- run_internal = lambda do |node|
226
- if node.kind_of? Array
227
- node.each { |node| run_internal.call node }
228
- return
20
+ def initialize
21
+ reset
22
+ @filter = false
23
+ end
24
+
25
+ def reset
26
+ @result = ''
27
+ @keys = %w[text resource-id content-desc]
28
+ end
29
+
30
+ # http://nokogiri.org/Nokogiri/XML/SAX/Document.html
31
+ def start_element name, attrs = []
32
+ return if filter && !name.downcase.include?(filter)
33
+
34
+ attributes = {}
35
+
36
+ attrs.each do |key, value|
37
+ if keys.include?(key) && !value.empty?
38
+ attributes[key] = value
39
+ end
229
40
  end
230
41
 
231
- keys = node.keys
232
- return if keys.empty?
42
+ string = ''
43
+ text = attributes['text']
44
+ desc = attributes['content-desc']
45
+ id = attributes['resource-id']
233
46
 
234
- obj = {}
235
- # name is id
236
- obj.merge!( { id: node['name'] } ) if keys.include?('name') && !node['name'].empty?
237
- obj.merge!( { text: node['value'] } ) if keys.include?('value') && !node['value'].empty?
238
- # label is name
239
- obj.merge!( { name: node['label'] } ) if keys.include?('label') && !node['label'].empty?
240
- obj.merge!( { class: node['type'] } ) if keys.include?('type') && !obj.empty?
241
- obj.merge!( { shown: node['shown'] } ) if keys.include?('shown')
47
+ if !text.nil? && text == desc
48
+ string += " text, desc: #{text}\n"
49
+ else
50
+ string += " text: #{text}\n" unless text.nil?
51
+ string += " desc: #{desc}\n" unless desc.nil?
52
+ end
53
+ string += " id: #{id}\n" unless id.nil?
242
54
 
243
- r.push obj if !obj.empty?
244
- run_internal.call node['children'] if keys.include?('children')
55
+ @result += "\n#{name}\n#{string}" unless attributes.empty?
245
56
  end
57
+ end # class AndroidElements
58
+
59
+ # Android only.
60
+ # Returns a string containing interesting elements.
61
+ # The text, content description, and id are returned.
62
+ # @param class_name [String] the class name to filter on.
63
+ # if false (default) then all classes will be inspected
64
+ # @return [String]
65
+ def get_android_inspect class_name=false
66
+ parser = @android_elements_parser ||= Nokogiri::XML::SAX::Parser.new(AndroidElements.new)
67
+
68
+ parser.document.reset
69
+ parser.document.filter = class_name
70
+ parser.parse get_source
71
+
72
+ parser.document.result
73
+ end
74
+
75
+ # Intended for use with console.
76
+ # Inspects and prints the current page.
77
+ # @param class_name [String] the class name to filter on.
78
+ # if false (default) then all classes will be inspected
79
+ # @return [void]
80
+ def page class_name=false
81
+ puts get_android_inspect class_name
82
+ nil
83
+ end
246
84
 
247
- run_internal.call node
248
- r
85
+ # Lists package, activity, and adb shell am start -n value for current app.
86
+ # Works on local host only (not remote).
87
+ # noinspection RubyArgCount
88
+ def current_app
89
+ line = `adb shell dumpsys window windows`.each_line.grep(/mFocusedApp/).first.strip
90
+ pair = line.split(' ').last.gsub('}', '').split '/'
91
+ pkg = pair.first
92
+ act = pair.last
93
+ OpenStruct.new(line: line,
94
+ package: pkg,
95
+ activity: act,
96
+ am_start: pkg + '/' + act)
249
97
  end
250
98
 
251
- json = get_source
252
- node = json['children']
253
- results = run node
254
-
255
- out = ''
256
- results.each { |e|
257
- no_text = e[:text].nil?
258
- no_name = e[:name].nil? || e[:name] == 'null'
259
- next unless e[:shown] # skip invisible
260
- # Ignore elements with id only.
261
- next if no_text && no_name
262
-
263
- out += e[:class].split('.').last + "\n"
264
-
265
- # name is id when using selendroid.
266
- # remove id/ prefix
267
- e[:id].sub!(/^id\//, '') if e[:id]
268
-
269
- out += " class: #{e[:class]}\n"
270
- # id('back_button').click
271
- out += " id: #{e[:id]}\n" unless e[:id].nil?
272
- # find_element(:link_text, 'text')
273
- out += " text: #{e[:text]}\n" unless no_text
274
- # label is name. default is 'null'
275
- # find_element(:link_text, 'Facebook')
276
- out += " name: #{e[:name]}\n" unless no_name
277
- # out += " visible: #{e[:shown]}\n" unless e[:shown].nil?
278
- }
279
- out
280
- end
281
-
282
- def get_page_class
283
- r = []
284
- run_internal = lambda do |node|
285
- if node.kind_of? Array
286
- node.each { |node| run_internal.call node }
287
- return
99
+ # Find by id
100
+ # @param id [String] the id to search for
101
+ # @return [Element]
102
+ def id id
103
+ value = resolve_id id
104
+ # If the id doesn't resolve in strings.xml then use it as is
105
+ # It's probably a resource id which won't be in strings.xml
106
+ value = id unless value
107
+ exact = string_visible_exact '*', value
108
+ contains = string_visible_contains '*', value
109
+ xpath "#{exact} | #{contains}"
110
+ end
111
+
112
+ # Find the element of type class_name at matching index.
113
+ # @param class_name [String] the class name to find
114
+ # @param index [Integer] the index
115
+ # @return [Element] the found element of type class_name
116
+ def ele_index class_name, index
117
+ unless index == 'last()'
118
+ # XPath index starts at 1.
119
+ raise "#{index} is not a valid xpath index. Must be >= 1" if index <= 0
288
120
  end
121
+ find_element :xpath, %Q(//#{class_name}[#{index}])
122
+ end
289
123
 
290
- keys = node.keys
291
- return if keys.empty?
292
- r.push node['@class'] if keys.include?('@class')
124
+ # @private
125
+ def string_attr_exact class_name, attr, value
126
+ %Q(//#{class_name}[@#{attr}='#{value}'])
127
+ end
293
128
 
294
- run_internal.call node['node'] if keys.include?('node')
129
+ # Find the first element exactly matching class and attribute value.
130
+ # @param class_name [String] the class name to search for
131
+ # @param attr [String] the attribute to inspect
132
+ # @param value [String] the expected value of the attribute
133
+ # @return [Element]
134
+ def find_ele_by_attr class_name, attr, value
135
+ @driver.find_element :xpath, string_attr_exact(class_name, attr, value)
295
136
  end
296
- json = get_source
297
- run_internal.call json['hierarchy']
298
137
 
299
- res = []
300
- r = r.sort
301
- r.uniq.each do |ele|
302
- res.push "#{r.count(ele)}x #{ele}\n"
138
+ # Find all elements exactly matching class and attribute value.
139
+ # @param class_name [String] the class name to match
140
+ # @param attr [String] the attribute to compare
141
+ # @param value [String] the value of the attribute that the element must have
142
+ # @return [Array<Element>]
143
+ def find_eles_by_attr class_name, attr, value
144
+ @driver.find_elements :xpath, string_attr_exact(class_name, attr, value)
303
145
  end
304
- count_sort = ->(one,two) { two.match(/(\d+)x/)[1].to_i <=> one.match(/(\d+)x/)[1].to_i }
305
- res.sort(&count_sort).join ''
306
- end
307
-
308
- # Count all classes on screen and print to stdout.
309
- # Useful for appium_console.
310
- def page_class
311
- puts get_page_class
312
- nil
313
- end
314
-
315
- # Android only.
316
- # Returns a string containing interesting elements.
317
- # If an element has no content desc or text, then it's not returned by this method.
318
- # @return [String]
319
- def get_android_inspect
146
+
320
147
  # @private
321
- def run node
322
- r = []
148
+ def string_attr_include class_name, attr, value
149
+ %Q(//#{class_name}[contains(translate(@#{attr},'#{value.upcase}', '#{value}'), '#{value}')])
150
+ end
323
151
 
324
- run_internal = lambda do |node|
325
- if node.kind_of? Array
326
- node.each { |node| run_internal.call node }
327
- return
328
- end
152
+ # Find the first element by attribute that exactly matches value.
153
+ # @param class_name [String] the class name to match
154
+ # @param attr [String] the attribute to compare
155
+ # @param value [String] the value of the attribute that the element must include
156
+ # @return [Element] the element of type tag who's attribute includes value
157
+ def find_ele_by_attr_include class_name, attr, value
158
+ @driver.find_element :xpath, string_attr_include(class_name, attr, value)
159
+ end
329
160
 
330
- keys = node.keys
331
- return if keys.empty?
332
- if keys == %w(hierarchy)
333
- run_internal.call node['hierarchy']
334
- return
335
- end
161
+ # Find elements by attribute that include value.
162
+ # @param class_name [String] the tag name to match
163
+ # @param attr [String] the attribute to compare
164
+ # @param value [String] the value of the attribute that the element must include
165
+ # @return [Array<Element>] the elements of type tag who's attribute includes value
166
+ def find_eles_by_attr_include class_name, attr, value
167
+ @driver.find_elements :xpath, string_attr_include(class_name, attr, value)
168
+ end
336
169
 
337
- n_content = '@content-desc'
338
- n_text = '@text'
339
- n_class = '@class'
340
- n_resource = '@resource-id'
341
- n_node = 'node'
342
-
343
- # Store the object if it has a content description, text, or resource id.
344
- # If it only has a class, then don't save it.
345
- obj = {}
346
- obj.merge!( { desc: node[n_content] } ) if keys.include?(n_content) && !node[n_content].empty?
347
- obj.merge!( { text: node[n_text] } ) if keys.include?(n_text) && !node[n_text].empty?
348
- obj.merge!( { resource_id: node[n_resource] } ) if keys.include?(n_resource) && !node[n_resource].empty?
349
- obj.merge!( { class: node[n_class] } ) if keys.include?(n_class) && !obj.empty?
350
-
351
- r.push obj if !obj.empty?
352
- run_internal.call node[n_node] if keys.include?(n_node)
353
- end
170
+ # Find the first element that matches class_name
171
+ # @param class_name [String] the tag to match
172
+ # @return [Element]
173
+ def first_ele class_name
174
+ # XPath index starts at 1
175
+ ele_index class_name, 1
176
+ end
354
177
 
355
- run_internal.call node
356
- r
178
+ # Find the last element that matches class_name
179
+ # @param class_name [String] the tag to match
180
+ # @return [Element]
181
+ def last_ele class_name
182
+ ele_index class_name, 'last()'
357
183
  end
358
184
 
359
- lazy_load_strings
360
- json = get_source
361
- node = json['hierarchy']
362
- results = run node
363
-
364
- out = ''
365
- results.each { |e|
366
- e_desc = e[:desc]
367
- e_text = e[:text]
368
- e_class = e[:class]
369
- e_resource_id = e[:resource_id]
370
- out += e_class.split('.').last + "\n"
371
-
372
- out += " class: #{e_class}\n"
373
- if e_text == e_desc
374
- out += " text, name: #{e_text}\n" unless e_text.nil?
375
- else
376
- out += " text: #{e_text}\n" unless e_text.nil?
377
- out += " name: #{e_desc}\n" unless e_desc.nil?
378
- end
185
+ # Find the first element of type class_name
186
+ #
187
+ # @param class_name [String] the class_name to search for
188
+ # @return [Element]
189
+ def tag class_name
190
+ xpath %Q(//#{class_name})
191
+ end
379
192
 
380
- out += " resource_id: #{e_resource_id}\n" unless e_resource_id.nil? || e_resource_id.empty?
193
+ # Find all elements of type class_name
194
+ #
195
+ # @param class_name [String] the class_name to search for
196
+ # @return [Element]
197
+ def tags class_name
198
+ xpaths %Q(//#{class_name})
199
+ end
381
200
 
382
- # there may be many ids with the same value.
383
- # output all exact matches.
384
- id_matches = @strings_xml.select do |key, value|
385
- value == e_desc || value == e_text
201
+ # @private
202
+ # Returns a string xpath that matches the first element that contains value
203
+ #
204
+ # example: xpath_visible_contains 'UIATextField', 'sign in'
205
+ #
206
+ # @param element [String] the class name for the element
207
+ # @param value [String] the value to search for
208
+ # @return [String]
209
+ def string_visible_contains element, value
210
+ result = []
211
+ attributes = %w[content-desc text]
212
+
213
+ value_up = value.upcase
214
+ value_down = value.downcase
215
+
216
+ attributes.each do |attribute|
217
+ result << %Q(contains(translate(@#{attribute},"#{value_up}","#{value_down}"), "#{value_down}"))
386
218
  end
387
219
 
388
- if id_matches && id_matches.length > 0
389
- match_str = ''
390
- # [0] = key, [1] = value
391
- id_matches.each do |match|
392
- match_str += ' ' * 6 + "#{match[0]}\n"
393
- end
394
- out += " id: #{match_str.strip}\n"
220
+ # never partial match on a resource id
221
+ result << %Q(@resource-id="#{value}")
222
+
223
+ result = result.join(' or ')
224
+
225
+ "//#{element}[#{result}]"
226
+ end
227
+
228
+ # Find the first element that contains value
229
+ # @param element [String] the class name for the element
230
+ # @param value [String] the value to search for
231
+ # @return [Element]
232
+ def xpath_visible_contains element, value
233
+ xpath string_visible_contains element, value
234
+ end
235
+
236
+ # Find all elements containing value
237
+ # @param element [String] the class name for the element
238
+ # @param value [String] the value to search for
239
+ # @return [Array<Element>]
240
+ def xpaths_visible_contains element, value
241
+ xpaths string_visible_contains element, value
242
+ end
243
+
244
+ # @private
245
+ # Create an xpath string to exactly match the first element with target value
246
+ # @param element [String] the class name for the element
247
+ # @param value [String] the value to search for
248
+ # @return [String]
249
+ def string_visible_exact element, value
250
+ result = []
251
+ attributes = %w[content-desc resource-id text]
252
+
253
+ attributes.each do |attribute|
254
+ result << %Q(@#{attribute}="#{value}")
395
255
  end
396
- }
397
- out
398
- end
399
-
400
- # Automatically detects selendroid or android.
401
- # Returns a string containing interesting elements.
402
- # @return [String]
403
- def get_inspect
404
- @device == 'Selendroid' ? get_selendroid_inspect : get_android_inspect
405
- end
406
-
407
- # Intended for use with console.
408
- # Inspects and prints the current page.
409
- def page
410
- puts get_inspect
411
- nil
412
- end
413
-
414
- # JavaScript code from https://github.com/appium/appium/blob/master/app/android.js
415
- #
416
- # ```javascript
417
- # Math.round(1.0/28.0 * 28) = 1
418
- # ```
419
- #
420
- # We want steps to be exactly 1. If it's zero then a tap is used instead of a swipe.
421
- def fast_duration
422
- 0.0357 # 1.0/28.0
423
- end
424
-
425
- # Lists package, activity, and adb shell am start -n value for current app.
426
- # Works on local host only (not remote).
427
- def current_app
428
- line = `adb shell dumpsys window windows`.each_line.grep(/mFocusedApp/).first.strip
429
- pair = line.split(' ').last.gsub('}','').split '/'
430
- pkg = pair.first
431
- act = pair.last
432
- OpenStruct.new line: line,
433
- package: pkg,
434
- activity: act,
435
- am_start: pkg + '/' + act
436
- end
437
-
438
- # Find by id. Useful for selendroid
439
- # @param id [String] the id to search for
440
- # @return [Element]
441
- def id id
442
- lazy_load_strings
443
- # resource ids must include ':' and they're not contained in strings_xml
444
- raise "Invalid id `#{id}`" unless @strings_xml[id] || id.include?(':')
445
- find_element :id, id
446
- end
447
- end # module Appium::Android
256
+
257
+ result = result.join(' or ')
258
+
259
+ "//#{element}[#{result}]"
260
+ end
261
+
262
+ # Find the first element exactly matching value
263
+ # @param element [String] the class name for the element
264
+ # @param value [String] the value to search for
265
+ # @return [Element]
266
+ def xpath_visible_exact element, value
267
+ xpath string_visible_exact element, value
268
+ end
269
+
270
+ # Find all elements exactly matching value
271
+ # @param element [String] the class name for the element
272
+ # @param value [String] the value to search for
273
+ # @return [Element]
274
+ def xpaths_visible_exact element, value
275
+ xpaths string_visible_exact element, value
276
+ end
277
+ end # module Android
278
+ end # module Appium