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