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.
- checksums.yaml +4 -4
- data/Rakefile +17 -8
- data/android_tests/Gemfile +1 -0
- data/android_tests/LICENSE-2.0.txt +202 -0
- data/android_tests/Rakefile +61 -0
- data/android_tests/api.apk +0 -0
- data/android_tests/appium.txt +3 -0
- data/android_tests/flaky.txt +1 -0
- data/android_tests/lib/android/specs/android/dynamic.rb +5 -0
- data/android_tests/lib/android/specs/android/element/alert.rb +41 -0
- data/android_tests/lib/android/specs/android/element/button.rb +55 -0
- data/android_tests/lib/android/specs/android/element/generic.rb +48 -0
- data/android_tests/lib/android/specs/android/element/text.rb +39 -0
- data/android_tests/lib/android/specs/android/element/textfield.rb +60 -0
- data/android_tests/lib/android/specs/android/helper.rb +80 -0
- data/android_tests/lib/android/specs/android/patch.rb +14 -0
- data/android_tests/lib/android/specs/common/device.rb +117 -0
- data/android_tests/lib/android/specs/common/element/window.rb +9 -0
- data/android_tests/lib/android/specs/common/helper.rb +112 -0
- data/android_tests/lib/android/specs/common/patch.rb +69 -0
- data/android_tests/lib/android/specs/common/version.rb +9 -0
- data/android_tests/lib/android/specs/driver.rb +174 -0
- data/android_tests/lib/format.rb +49 -0
- data/android_tests/lib/run.rb +72 -0
- data/android_tests/readme.md +27 -0
- data/appium_lib.gemspec +8 -5
- data/docs/android_docs.md +1052 -716
- data/docs/ios_docs.md +657 -834
- data/docs_gen/make_docs.rb +1 -3
- data/ios_tests/Gemfile +1 -0
- data/ios_tests/LICENSE-2.0.txt +202 -0
- data/ios_tests/Rakefile +47 -0
- data/ios_tests/UICatalog.app.zip +0 -0
- data/ios_tests/UICatalog.app/12-6AM.png +0 -0
- data/ios_tests/UICatalog.app/12-6PM.png +0 -0
- data/ios_tests/UICatalog.app/6-12AM.png +0 -0
- data/ios_tests/UICatalog.app/6-12PM.png +0 -0
- data/ios_tests/UICatalog.app/Default-568h@2x.png +0 -0
- data/ios_tests/UICatalog.app/Default@2x.png +0 -0
- data/ios_tests/UICatalog.app/Info.plist +0 -0
- data/ios_tests/UICatalog.app/PkgInfo +1 -0
- data/ios_tests/UICatalog.app/UIButton_custom.png +0 -0
- data/ios_tests/UICatalog.app/UICatalog +0 -0
- data/ios_tests/UICatalog.app/blueButton.png +0 -0
- data/ios_tests/UICatalog.app/bookmarkImage.png +0 -0
- data/ios_tests/UICatalog.app/bookmarkImageHighlighted.png +0 -0
- data/ios_tests/UICatalog.app/divider.png +0 -0
- data/ios_tests/UICatalog.app/en.lproj/AlertsViewController.nib +0 -0
- data/ios_tests/UICatalog.app/en.lproj/ButtonsViewController.nib +0 -0
- data/ios_tests/UICatalog.app/en.lproj/ControlsViewController.nib +0 -0
- data/ios_tests/UICatalog.app/en.lproj/ImagesViewController.nib +0 -0
- data/ios_tests/UICatalog.app/en.lproj/Localizable.strings +0 -0
- data/ios_tests/UICatalog.app/en.lproj/MainWindow.nib +0 -0
- data/ios_tests/UICatalog.app/en.lproj/PickerViewController.nib +0 -0
- data/ios_tests/UICatalog.app/en.lproj/SearchBarController.nib +0 -0
- data/ios_tests/UICatalog.app/en.lproj/SegmentViewController.nib +0 -0
- data/ios_tests/UICatalog.app/en.lproj/TextFieldController.nib +0 -0
- data/ios_tests/UICatalog.app/en.lproj/TextViewController.nib +0 -0
- data/ios_tests/UICatalog.app/en.lproj/ToolbarViewController.nib +0 -0
- data/ios_tests/UICatalog.app/en.lproj/TransitionViewController.nib +0 -0
- data/ios_tests/UICatalog.app/en.lproj/WebViewController.nib +0 -0
- data/ios_tests/UICatalog.app/orangeslide.png +0 -0
- data/ios_tests/UICatalog.app/scene1.jpg +0 -0
- data/ios_tests/UICatalog.app/scene2.jpg +0 -0
- data/ios_tests/UICatalog.app/scene3.jpg +0 -0
- data/ios_tests/UICatalog.app/scene4.jpg +0 -0
- data/ios_tests/UICatalog.app/scene5.jpg +0 -0
- data/ios_tests/UICatalog.app/searchBarBackground.png +0 -0
- data/ios_tests/UICatalog.app/segment_check.png +0 -0
- data/ios_tests/UICatalog.app/segment_search.png +0 -0
- data/ios_tests/UICatalog.app/segment_tools.png +0 -0
- data/ios_tests/UICatalog.app/segmentedBackground.png +0 -0
- data/ios_tests/UICatalog.app/slider_ball.png +0 -0
- data/ios_tests/UICatalog.app/toolbarBackground.png +0 -0
- data/ios_tests/UICatalog.app/whiteButton.png +0 -0
- data/ios_tests/UICatalog.app/yellowslide.png +0 -0
- data/ios_tests/appium.txt +3 -0
- data/ios_tests/flaky.txt +1 -0
- data/ios_tests/lib/format.rb +25 -0
- data/ios_tests/lib/ios/specs/common/element/window.rb +15 -0
- data/ios_tests/lib/ios/specs/common/helper.rb +204 -0
- data/ios_tests/lib/ios/specs/common/patch.rb +50 -0
- data/ios_tests/lib/ios/specs/common/version.rb +17 -0
- data/ios_tests/lib/ios/specs/device/device.rb +82 -0
- data/ios_tests/lib/ios/specs/device/multi_touch.rb +12 -0
- data/ios_tests/lib/ios/specs/device/touch_actions.rb +15 -0
- data/ios_tests/lib/ios/specs/driver.rb +203 -0
- data/ios_tests/lib/ios/specs/ios/element/alert.rb +48 -0
- data/ios_tests/lib/ios/specs/ios/element/button.rb +58 -0
- data/ios_tests/lib/ios/specs/ios/element/generic.rb +35 -0
- data/ios_tests/lib/ios/specs/ios/element/text.rb +54 -0
- data/ios_tests/lib/ios/specs/ios/element/textfield.rb +123 -0
- data/ios_tests/lib/ios/specs/ios/helper.rb +27 -0
- data/ios_tests/lib/ios/specs/ios/patch.rb +30 -0
- data/ios_tests/lib/run.rb +106 -0
- data/ios_tests/readme.md +30 -0
- data/ios_tests/upload/sauce_storage.rb +64 -0
- data/ios_tests/upload/upload.rb +6 -0
- data/lib/appium_lib.rb +4 -14
- data/lib/appium_lib/android/dynamic.rb +30 -32
- data/lib/appium_lib/android/element/alert.rb +34 -33
- data/lib/appium_lib/android/element/button.rb +91 -0
- data/lib/appium_lib/android/element/generic.rb +51 -146
- data/lib/appium_lib/android/element/text.rb +54 -0
- data/lib/appium_lib/android/element/textfield.rb +46 -41
- data/lib/appium_lib/android/helper.rb +248 -417
- data/lib/appium_lib/android/mobile_methods.rb +17 -0
- data/lib/appium_lib/android/patch.rb +9 -8
- data/lib/appium_lib/awesome_print/ostruct.rb +33 -0
- data/lib/appium_lib/common/element/window.rb +9 -8
- data/lib/appium_lib/common/helper.rb +182 -243
- data/lib/appium_lib/common/patch.rb +65 -79
- data/lib/appium_lib/common/version.rb +2 -3
- data/lib/appium_lib/device/device.rb +339 -0
- data/lib/appium_lib/device/multi_touch.rb +94 -0
- data/lib/appium_lib/device/touch_actions.rb +142 -0
- data/lib/appium_lib/driver.rb +217 -306
- data/lib/appium_lib/ios/element/alert.rb +16 -92
- data/lib/appium_lib/ios/element/button.rb +55 -0
- data/lib/appium_lib/ios/element/generic.rb +27 -160
- data/lib/appium_lib/ios/element/text.rb +54 -0
- data/lib/appium_lib/ios/element/textfield.rb +78 -65
- data/lib/appium_lib/ios/helper.rb +300 -190
- data/lib/appium_lib/ios/mobile_methods.rb +17 -0
- data/lib/appium_lib/ios/patch.rb +55 -41
- data/lib/appium_lib/logger.rb +13 -0
- data/lib/appium_lib/rails/duplicable.rb +116 -0
- data/readme.md +6 -1
- data/release_notes.md +118 -0
- metadata +170 -12
- data/lib/appium_lib/common/element/button.rb +0 -83
- 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
|
-
|
|
2
|
-
module
|
|
3
|
-
|
|
1
|
+
module Appium
|
|
2
|
+
module Android
|
|
3
|
+
EditText = 'android.widget.EditText'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
# Find the first EditText.
|
|
22
|
+
# @return [EditText]
|
|
23
|
+
def first_textfield
|
|
24
|
+
first_ele EditText
|
|
25
|
+
end
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
# Find the last EditText.
|
|
28
|
+
# @return [EditText]
|
|
29
|
+
def last_textfield
|
|
30
|
+
last_ele EditText
|
|
31
|
+
end
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
#
|
|
36
|
-
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
end
|
|
48
|
-
end # module Appium
|
|
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
|
-
|
|
2
|
-
module
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
215
|
-
|
|
9
|
+
def filter
|
|
10
|
+
@filter
|
|
11
|
+
end
|
|
216
12
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
232
|
-
|
|
42
|
+
string = ''
|
|
43
|
+
text = attributes['text']
|
|
44
|
+
desc = attributes['content-desc']
|
|
45
|
+
id = attributes['resource-id']
|
|
233
46
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
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
|
-
|
|
248
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
124
|
+
# @private
|
|
125
|
+
def string_attr_exact class_name, attr, value
|
|
126
|
+
%Q(//#{class_name}[@#{attr}='#{value}'])
|
|
127
|
+
end
|
|
293
128
|
|
|
294
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
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
|
|
322
|
-
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
|
|
356
|
-
|
|
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
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
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
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
@
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
#
|
|
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
|