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
@@ -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