appium_lib 0.0.30 → 0.3.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 +8 -8
- data/Rakefile +15 -7
- data/appium_lib.gemspec +3 -3
- data/docs.md +7 -5
- data/lib/appium_lib.rb +34 -6
- data/lib/appium_lib/android/element/alert.rb +43 -0
- data/lib/appium_lib/android/element/generic.rb +94 -0
- data/lib/appium_lib/android/element/textfield.rb +43 -0
- data/lib/appium_lib/android/helper.rb +120 -0
- data/lib/appium_lib/android/patch.rb +10 -0
- data/lib/appium_lib/common/element/button.rb +83 -0
- data/lib/appium_lib/common/element/text.rb +44 -0
- data/lib/appium_lib/common/element/window.rb +9 -0
- data/lib/appium_lib/common/helper.rb +140 -0
- data/lib/appium_lib/common/patch.rb +83 -0
- data/lib/appium_lib/common/version.rb +6 -0
- data/lib/appium_lib/driver.rb +265 -0
- data/lib/appium_lib/ios/element/alert.rb +56 -0
- data/lib/appium_lib/ios/element/generic.rb +170 -0
- data/lib/appium_lib/ios/element/textfield.rb +90 -0
- data/lib/appium_lib/ios/helper.rb +103 -0
- data/lib/appium_lib/ios/patch.rb +15 -0
- data/readme.md +10 -3
- data/release_notes.md +8 -0
- metadata +19 -15
- data/lib/appium_lib/console.rb +0 -254
- data/lib/appium_lib/element/android/alert.rb +0 -45
- data/lib/appium_lib/element/android/generic.rb +0 -88
- data/lib/appium_lib/element/android/textfield.rb +0 -44
- data/lib/appium_lib/element/button.rb +0 -83
- data/lib/appium_lib/element/ios/alert.rb +0 -49
- data/lib/appium_lib/element/ios/generic.rb +0 -140
- data/lib/appium_lib/element/ios/textfield.rb +0 -93
- data/lib/appium_lib/element/text.rb +0 -43
- data/lib/appium_lib/element/window.rb +0 -12
- data/lib/appium_lib/helper.rb +0 -278
- data/lib/appium_lib/patch.rb +0 -90
- data/lib/appium_lib/version.rb +0 -4
@@ -0,0 +1,56 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Appium::Ios
|
3
|
+
# iOS only
|
4
|
+
# Tap the alert button identified by value.
|
5
|
+
#
|
6
|
+
# Click the ok button:
|
7
|
+
# alert_click 'OK'
|
8
|
+
#
|
9
|
+
# Click the first button:
|
10
|
+
# alert_click 0
|
11
|
+
#
|
12
|
+
# @param value [Integer, String] either an integer index of the button or the button's name
|
13
|
+
# @return [void]
|
14
|
+
def alert_click value
|
15
|
+
value = "'#{value}'" if value.is_a?(String)
|
16
|
+
@driver.execute_script "UIATarget.localTarget().frontMostApp().alert().buttons()[#{value}].tap();"
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get the alert message text.
|
20
|
+
# @return [String]
|
21
|
+
def alert_text
|
22
|
+
@driver.switch_to.alert.text
|
23
|
+
end
|
24
|
+
|
25
|
+
# Accept the alert.
|
26
|
+
# @return [void]
|
27
|
+
def alert_accept
|
28
|
+
@driver.switch_to.alert.accept
|
29
|
+
end
|
30
|
+
|
31
|
+
# Get the text of the alert's accept button.
|
32
|
+
# The last button is considered "accept."
|
33
|
+
# @return [String]
|
34
|
+
def alert_accept_text
|
35
|
+
a = @driver.find_element(:tag_name, :alert)
|
36
|
+
return if a.nil?
|
37
|
+
b = a.find_elements(:tag_name, :button)
|
38
|
+
b.last.text if b && b.size >= 1
|
39
|
+
end
|
40
|
+
|
41
|
+
# Dismiss the alert.
|
42
|
+
# @return [void]
|
43
|
+
def alert_dismiss
|
44
|
+
@driver.switch_to.alert.dismiss
|
45
|
+
end
|
46
|
+
|
47
|
+
# Get the text of the alert's dismiss button.
|
48
|
+
# The first button is considered "dismiss."
|
49
|
+
# @return [String]
|
50
|
+
def alert_dismiss_text
|
51
|
+
a = @driver.find_element(:tag_name, :alert)
|
52
|
+
return if a.nil?
|
53
|
+
b = a.find_elements(:tag_name, :button)
|
54
|
+
b.first.text if b && b.size >= 1
|
55
|
+
end
|
56
|
+
end # module Appium::Ios
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Appium::Ios
|
3
|
+
=begin
|
4
|
+
name, names, text, text should match substring and case insensitive.
|
5
|
+
|
6
|
+
iOS .name() is the accessibility attribute. If not defined, then .label() is used instead.
|
7
|
+
This differs from Android where name (the content description) is empty when not set.
|
8
|
+
|
9
|
+
name defaults to label when undefined. value is never a default so that must be
|
10
|
+
included in a new search.
|
11
|
+
|
12
|
+
Find - search everything.
|
13
|
+
|
14
|
+
The search order is:
|
15
|
+
1. name
|
16
|
+
2. label (implied by name)
|
17
|
+
3. value
|
18
|
+
|
19
|
+
Android name = iOS name & label
|
20
|
+
Android text = iOS value
|
21
|
+
=end
|
22
|
+
|
23
|
+
# returnElems requires a wrapped $(element).
|
24
|
+
# set to empty array when length is zero to prevent hang.
|
25
|
+
#
|
26
|
+
# UIAElementNil when not matched
|
27
|
+
#
|
28
|
+
# 1. secureTextFields
|
29
|
+
# 2. textFields
|
30
|
+
# 3. buttons
|
31
|
+
# 4. elements
|
32
|
+
#
|
33
|
+
# search takes the window to search.
|
34
|
+
# start searching webview first.
|
35
|
+
# window 0 is the main window.
|
36
|
+
# window 1 is the 1st webview (if it exists)
|
37
|
+
# must break instead of return because it's not a function.
|
38
|
+
#
|
39
|
+
# single element length is undefined when found and 0 when not found.
|
40
|
+
def first_ele_js predicate
|
41
|
+
%Q(
|
42
|
+
function isNil( a ) {
|
43
|
+
return a.type() === 'UIAElementNil';
|
44
|
+
}
|
45
|
+
|
46
|
+
function search( w ) {
|
47
|
+
var search = "#{predicate}";
|
48
|
+
var a = w.secureTextFields().firstWithPredicate(search);
|
49
|
+
if ( isNil(a) ) {
|
50
|
+
a = w.textFields().firstWithPredicate(search);
|
51
|
+
if ( isNil(a) ) {
|
52
|
+
a = w.buttons().firstWithPredicate(search);
|
53
|
+
if ( isNil(a) ) {
|
54
|
+
a = w.elements().firstWithPredicate(search);
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
return a;
|
60
|
+
}
|
61
|
+
|
62
|
+
function search_web( windowIndex ) {
|
63
|
+
var a = undefined;
|
64
|
+
|
65
|
+
try {
|
66
|
+
a = UIATarget.localTarget().frontMostApp().windows()[windowIndex].scrollViews()[0].webViews()[0].elements().firstWithPredicate("#{predicate}");
|
67
|
+
} catch(e) {}
|
68
|
+
|
69
|
+
return a;
|
70
|
+
}
|
71
|
+
|
72
|
+
function run() {
|
73
|
+
var windows = au.mainApp.windows();
|
74
|
+
for (var i = 0, len = windows.length; i < len; i++) {
|
75
|
+
var result = search_web( i );
|
76
|
+
if ( isNil( result ) ) {
|
77
|
+
result = search( windows[ i ] );
|
78
|
+
}
|
79
|
+
if ( ! isNil( result ) ) {
|
80
|
+
return au._returnElems( $( [ result ] ) );
|
81
|
+
}
|
82
|
+
}
|
83
|
+
return au._returnElems( $( [] ) );
|
84
|
+
}
|
85
|
+
|
86
|
+
run();
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
def all_ele_js predicate
|
91
|
+
%Q(
|
92
|
+
var w = au.mainWindow;
|
93
|
+
var search = "#{predicate}";
|
94
|
+
var a = w.elements().withPredicate(search).toArray();
|
95
|
+
|
96
|
+
if ( a.length === 0 ) {
|
97
|
+
a = [];
|
98
|
+
}
|
99
|
+
|
100
|
+
au._returnElems($(a));
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Return the first element matching text.
|
105
|
+
# @param text [String] the text to search for
|
106
|
+
# @return [Element] the first matching element
|
107
|
+
def find text
|
108
|
+
js = first_ele_js "name contains[c] '#{text}' || label contains[c] '#{text}' || value contains[c] '#{text}'"
|
109
|
+
|
110
|
+
ele = execute_script(js).first
|
111
|
+
raise Selenium::WebDriver::Error::NoSuchElementError, '' if ele.nil?
|
112
|
+
ele
|
113
|
+
end
|
114
|
+
|
115
|
+
# Return all elements matching text.
|
116
|
+
# @param text [String] the text to search for
|
117
|
+
# @return [Array<Element>] all matching elements
|
118
|
+
def finds text
|
119
|
+
# returnElems requires a wrapped $(element).
|
120
|
+
# must call toArray when using withPredicate instead of firstWithPredicate.
|
121
|
+
js = all_ele_js "name contains[c] '#{text}' || label contains[c] '#{text}' || value contains[c] '#{text}'"
|
122
|
+
|
123
|
+
execute_script js
|
124
|
+
end
|
125
|
+
|
126
|
+
# Return the first element matching text.
|
127
|
+
# @param text [String] the text to search for
|
128
|
+
# @return [Element] the first matching element
|
129
|
+
def text text
|
130
|
+
js = first_ele_js "value contains[c] '#{text}'"
|
131
|
+
|
132
|
+
execute_script(js).first
|
133
|
+
end
|
134
|
+
|
135
|
+
# Return all elements matching text.
|
136
|
+
# @param text [String] the text to search for
|
137
|
+
# @return [Array<Element>] all matching elements
|
138
|
+
def texts text
|
139
|
+
# XPath //* is not implemented on iOS
|
140
|
+
# https://github.com/appium/appium/issues/430
|
141
|
+
js = all_ele_js "value contains[c] '#{text}'"
|
142
|
+
|
143
|
+
execute_script js
|
144
|
+
end
|
145
|
+
|
146
|
+
# Return the first element matching name.
|
147
|
+
# on Android name is content description
|
148
|
+
# on iOS name is the accessibility label or the text.
|
149
|
+
# @param name [String] the name to search for
|
150
|
+
# @return [Element] the first matching element
|
151
|
+
def name name
|
152
|
+
js = first_ele_js "name contains[c] '#{name}' || label contains[c] '#{name}'"
|
153
|
+
|
154
|
+
execute_script(js).first
|
155
|
+
end
|
156
|
+
|
157
|
+
# Return all elements matching name.
|
158
|
+
# on Android name is content description
|
159
|
+
# on iOS name is the accessibility label or the text.
|
160
|
+
# @param name [String] the name to search for
|
161
|
+
# @return [Array<Element>] all matching elements
|
162
|
+
def names name
|
163
|
+
# find_elements :name is not the same as on Android.
|
164
|
+
# it's case sensitive and exact on iOS and not on Android.
|
165
|
+
# https://github.com/appium/appium/issues/379
|
166
|
+
js = all_ele_js "name contains[c] '#{name}' || label contains[c] '#{name}''"
|
167
|
+
|
168
|
+
execute_script js
|
169
|
+
end
|
170
|
+
end # module Appium::Ios
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Appium::Ios
|
3
|
+
# UIATextField & UIASecureTextField methods
|
4
|
+
#
|
5
|
+
# Find textfield and then secure elements in one server call
|
6
|
+
# to match Android.
|
7
|
+
|
8
|
+
# Get an array of textfield texts.
|
9
|
+
# @return [Array<String>]
|
10
|
+
def textfields
|
11
|
+
find_2_eles_attr :textfield, :secure, :text
|
12
|
+
end
|
13
|
+
|
14
|
+
# Get an array of textfield elements.
|
15
|
+
# @return [Array<Textfield>]
|
16
|
+
def e_textfields
|
17
|
+
execute_script textfield_js
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get the first textfield element.
|
21
|
+
# @return [Textfield]
|
22
|
+
def first_textfield
|
23
|
+
js = textfield_js 'r = r.length > 0 ? $(r[0]) : r;'
|
24
|
+
execute_script(js).first
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get the last textfield element.
|
28
|
+
# @return [Textfield]
|
29
|
+
def last_textfield
|
30
|
+
js = textfield_js 'r = r.length > 0 ? $(r[r.length - 1]) : r;'
|
31
|
+
execute_script(js).first
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get the first textfield that matches text.
|
35
|
+
# @param text [String, Integer] the text to match exactly. If int then the textfield at that index is returned.
|
36
|
+
# @return [Textfield]
|
37
|
+
def textfield text
|
38
|
+
# Don't use ele_index because that only works on one element type.
|
39
|
+
# iOS needs to combine textfield and secure to match Android.
|
40
|
+
if text.is_a? Numeric
|
41
|
+
js = textfield_js "r = r.length > 0 ? $(r[#{text}]) : r;"
|
42
|
+
return execute_script(js).first
|
43
|
+
end
|
44
|
+
|
45
|
+
textfield_include text
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get the first textfield that includes text.
|
49
|
+
# @param text [String] the text the textfield must include
|
50
|
+
# @return [Textfield]
|
51
|
+
def textfield_include text
|
52
|
+
js = %Q(
|
53
|
+
var t = au.getElementsByXpath('textfield[contains(@text, "#{text}")]').value;
|
54
|
+
var s = au.getElementsByXpath('secure[contains(@text, "#{text}")]').value;
|
55
|
+
t.concat(s)[0];
|
56
|
+
)
|
57
|
+
|
58
|
+
puts js if defined? Pry
|
59
|
+
|
60
|
+
execute_script js
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get the first textfield that exactly matches text.
|
64
|
+
# @param text [String] the text the textfield must exactly match
|
65
|
+
# @return [Textfield]
|
66
|
+
def textfield_exact text
|
67
|
+
# find_ele_by_text :textfield, text
|
68
|
+
js = %Q(
|
69
|
+
var t = au.getElementsByXpath('textfield[@text="#{text}"]').value;
|
70
|
+
var s = au.getElementsByXpath('secure[@text="#{text}"]').value;
|
71
|
+
t.concat(s)[0];
|
72
|
+
)
|
73
|
+
|
74
|
+
puts js if defined? Pry
|
75
|
+
|
76
|
+
execute_script js
|
77
|
+
end
|
78
|
+
|
79
|
+
# Return combined lookup of textfield and secure
|
80
|
+
# with an optional filter. $() wrap is required for .each
|
81
|
+
def textfield_js filter=''
|
82
|
+
%Q(
|
83
|
+
var t = au.lookup('textfield');
|
84
|
+
var s = au.lookup('secure');
|
85
|
+
var r = $(t.concat(s));
|
86
|
+
#{filter}
|
87
|
+
au._returnElems(r);
|
88
|
+
)
|
89
|
+
end
|
90
|
+
end # module Appium::Ios
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Appium::Ios
|
3
|
+
# iOS only. Android uses uiautomator instead of uiautomation.
|
4
|
+
# Get an array of attribute values from elements exactly matching tag name.
|
5
|
+
# @param tag_name [String] the tag name to find
|
6
|
+
# @param attribute [String] the attribute to collect
|
7
|
+
# @result [Array<String>] an array of strings containing the attribute from found elements of type tag_name.
|
8
|
+
def find_eles_attr tag_name, attribute
|
9
|
+
# Use au.lookup(tag_name) instead of $(tag_name)
|
10
|
+
# See https://github.com/appium/appium/issues/214
|
11
|
+
js = %Q(
|
12
|
+
var eles = au.lookup('#{tag_name}');
|
13
|
+
var result = [];
|
14
|
+
for (var a = 0, length = eles.length; a < length; a++) {
|
15
|
+
result.push(eles[a].#{attribute}());
|
16
|
+
}
|
17
|
+
result
|
18
|
+
)
|
19
|
+
|
20
|
+
@driver.execute_script js
|
21
|
+
end
|
22
|
+
|
23
|
+
# iOS only. Android doesn't use find_2_eles_attr.
|
24
|
+
# Get an array of attribute values from elements exactly matching tag name.
|
25
|
+
# @param tag_name_1 [String] the 1st tag name to find
|
26
|
+
# @param tag_name_2 [String] the 2nd tag name to find
|
27
|
+
# @param attribute [String] the attribute to collect
|
28
|
+
# @result [Array<String>] an array of strings containing the attribute from found elements of type tag_name.
|
29
|
+
def find_2_eles_attr tag_name_1, tag_name_2, attribute
|
30
|
+
# Use au.lookup(tag_name) instead of $(tag_name)
|
31
|
+
# See https://github.com/appium/appium/issues/214
|
32
|
+
js = %Q(
|
33
|
+
var eles = au.lookup('#{tag_name_1}');
|
34
|
+
eles = $(eles.concat(au.lookup('#{tag_name_2}')));
|
35
|
+
var result = [];
|
36
|
+
for (var a = 0, length = eles.length; a < length; a++) {
|
37
|
+
result.push(eles[a].#{attribute}());
|
38
|
+
}
|
39
|
+
result
|
40
|
+
)
|
41
|
+
|
42
|
+
@driver.execute_script js
|
43
|
+
end
|
44
|
+
|
45
|
+
# iOS only. On Android uiautomator always returns an empty string for EditText password.
|
46
|
+
#
|
47
|
+
# Password character returned from value of UIASecureTextField
|
48
|
+
# @param length [Integer] the length of the password to generate
|
49
|
+
# @return [String] the returned string is of size length
|
50
|
+
def password length=1
|
51
|
+
'•' * length
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_page element
|
55
|
+
|
56
|
+
def empty ele
|
57
|
+
(ele['name'] || ele['label'] || ele['value']) == nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def fix_space s
|
61
|
+
# ints don't respond to force encoding
|
62
|
+
return s unless s.respond_to? :force_encoding
|
63
|
+
# char code 160 (name, label) vs 32 (value) will break comparison.
|
64
|
+
# convert string to binary and remove 160.
|
65
|
+
# \xC2\xA0
|
66
|
+
s.force_encoding('binary').gsub("\xC2\xA0".force_encoding('binary'), ' ') if s
|
67
|
+
end
|
68
|
+
|
69
|
+
unless empty(element)
|
70
|
+
puts "#{element['type']}"
|
71
|
+
name = fix_space element['name']
|
72
|
+
label = fix_space element['label']
|
73
|
+
value = fix_space element['value']
|
74
|
+
|
75
|
+
if name == label && name == value
|
76
|
+
puts " name, label, value: #{name}" if name
|
77
|
+
elsif name == label
|
78
|
+
puts " name, label: #{name}" if name
|
79
|
+
puts " value: #{value}" if value
|
80
|
+
elsif name == value
|
81
|
+
puts " name, value: #{name}" if name
|
82
|
+
puts " label: #{label}" if label
|
83
|
+
else
|
84
|
+
puts " name: #{name}" if name
|
85
|
+
puts " label: #{label}" if label
|
86
|
+
puts " value: #{value}" if value
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
children = element['children']
|
91
|
+
children.each { |c| get_page c } if children
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def page
|
96
|
+
get_page get_source
|
97
|
+
nil
|
98
|
+
end
|
99
|
+
|
100
|
+
def fast_duration
|
101
|
+
0.5
|
102
|
+
end
|
103
|
+
end # module Appium::Ios
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Appium::Ios
|
3
|
+
# Implement useful features for element.
|
4
|
+
class Selenium::WebDriver::Element
|
5
|
+
# Cross platform way of entering text into a textfield
|
6
|
+
def type text
|
7
|
+
# enter text then tap window to hide the keyboard.
|
8
|
+
js = %Q(
|
9
|
+
au.getElement('#{self.ref}').setValue('#{text}');
|
10
|
+
au.lookup('window')[0].tap()
|
11
|
+
)
|
12
|
+
@driver.execute_script js
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/readme.md
CHANGED
@@ -24,11 +24,18 @@ gem uninstall -aIx appium_lib ;\
|
|
24
24
|
gem install --no-rdoc --no-ri appium_lib
|
25
25
|
```
|
26
26
|
|
27
|
-
####
|
27
|
+
#### Simple Usage
|
28
28
|
|
29
|
-
|
29
|
+
```ruby
|
30
|
+
require 'appium_lib'
|
31
|
+
|
32
|
+
# Start a driver based on APP_PATH
|
33
|
+
Appium::Driver.new.start_driver
|
30
34
|
|
31
|
-
|
35
|
+
# Start an Android driver
|
36
|
+
apk = { 'APP_PATH' => ENV['APP_APK'] }
|
37
|
+
Appium::Driver.new(apk).start_driver
|
38
|
+
```
|
32
39
|
|
33
40
|
#### Documentation
|
34
41
|
|