appium_lib 0.0.21
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 +15 -0
- data/Gemfile +2 -0
- data/LICENSE-2.0.txt +202 -0
- data/Rakefile +78 -0
- data/appium_lib.gemspec +33 -0
- data/lib/appium_lib.rb +10 -0
- data/lib/appium_lib/console.rb +207 -0
- data/lib/appium_lib/element/alert.rb +47 -0
- data/lib/appium_lib/element/android/textfield.rb +45 -0
- data/lib/appium_lib/element/button.rb +115 -0
- data/lib/appium_lib/element/ios/textfield.rb +87 -0
- data/lib/appium_lib/element/text.rb +41 -0
- data/lib/appium_lib/element/window.rb +12 -0
- data/lib/appium_lib/helper.rb +176 -0
- data/lib/appium_lib/patch.rb +93 -0
- data/lib/appium_lib/version.rb +4 -0
- data/readme.md +30 -0
- metadata +103 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
!binary "U0hBMQ==":
|
|
3
|
+
metadata.gz: !binary |-
|
|
4
|
+
OTY2ZDQ0ZGExZTAzZjk4ZTU2YzBkMjBlYTdjZTE1MDk4NDJmZmZmZg==
|
|
5
|
+
data.tar.gz: !binary |-
|
|
6
|
+
ZTBjMjc1NzZhY2UzMTQxNjZkZTJhODM3NTE4ZDFlNGEyYmEzY2RkMg==
|
|
7
|
+
!binary "U0hBNTEy":
|
|
8
|
+
metadata.gz: !binary |-
|
|
9
|
+
ZjU1ODExZDgwZWFhMmNhNjYwOWU0ZGRjMjk2YjAzMmM4NzllZWY0OTMzNDQy
|
|
10
|
+
ODg4ODI3NDQ4OWJlNzgwNzlhYWM1YzllNWM5ZWIyZjM1OTJmYWIwYzI4YTdh
|
|
11
|
+
ZjhkZTQ0NzI2NGMwNjRjZThmMmZmMGFjNWZiY2E2YWY3NzdlNGQ=
|
|
12
|
+
data.tar.gz: !binary |-
|
|
13
|
+
NGIxOWI1MTUzY2IxM2Q3OWFiZmEyNmFkYWI5ODA4M2M5ZTlkYmMwYmU0OTA4
|
|
14
|
+
OWYxNDBhODVlOWEyZjQ0YzA0MmRjMDQ2ZDg1NGZiYmYzNGMwOWYxZjAyYzU2
|
|
15
|
+
N2NjNjQ2NmJmMjMyNTU3NWJiYzJmN2E3NzI0Yzc1N2VkZWQ5YWQ=
|
data/Gemfile
ADDED
data/LICENSE-2.0.txt
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'rake'
|
|
3
|
+
require 'date'
|
|
4
|
+
|
|
5
|
+
# Defines gem name.
|
|
6
|
+
def repo_name; 'appium_lib'; end # ruby_lib published as appium_lib
|
|
7
|
+
def version_file; "lib/#{repo_name}/version.rb"; end
|
|
8
|
+
def version_rgx; /VERSION = '([^']+)'/m; end
|
|
9
|
+
|
|
10
|
+
def version
|
|
11
|
+
@version = @version || File.read(version_file).match(version_rgx)[1]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def bump
|
|
15
|
+
data = File.read version_file
|
|
16
|
+
|
|
17
|
+
v_line = data.match version_rgx
|
|
18
|
+
d_line = data.match /DATE = '([^']+)'/m
|
|
19
|
+
|
|
20
|
+
old_v = v_line[0]
|
|
21
|
+
old_d = d_line[0]
|
|
22
|
+
|
|
23
|
+
old_num = v_line[1]
|
|
24
|
+
new_num = old_num.split('.')
|
|
25
|
+
new_num[-1] = new_num[-1].to_i + 1
|
|
26
|
+
new_num = new_num.join '.'
|
|
27
|
+
|
|
28
|
+
new_v = old_v.sub old_num, new_num
|
|
29
|
+
puts "#{old_num} -> #{new_num}"
|
|
30
|
+
|
|
31
|
+
old_date = d_line[1]
|
|
32
|
+
new_date = Date.today.to_s
|
|
33
|
+
new_d = old_d.sub old_date, new_date
|
|
34
|
+
puts "#{old_date} -> #{new_date}" unless old_date == new_date
|
|
35
|
+
|
|
36
|
+
data.sub! old_v, new_v
|
|
37
|
+
data.sub! old_d, new_d
|
|
38
|
+
|
|
39
|
+
File.write version_file, data
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
desc 'Bump the version number and update the date.'
|
|
43
|
+
task :bump do
|
|
44
|
+
bump
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Inspired by Gollum's Rakefile
|
|
48
|
+
desc 'Build and release a new gem to rubygems.org'
|
|
49
|
+
task :release => :gem do
|
|
50
|
+
unless `git branch`.include? '* master'
|
|
51
|
+
puts 'Master branch required to release.'
|
|
52
|
+
exit!
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Commit then pull before pushing.
|
|
56
|
+
sh "git commit --allow-empty -am 'Release #{version}'"
|
|
57
|
+
sh 'git pull'
|
|
58
|
+
sh "git tag v#{version}"
|
|
59
|
+
sh 'git push origin master'
|
|
60
|
+
sh "git push origin v#{version}"
|
|
61
|
+
sh "gem push #{repo_name}-#{version}.gem"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
desc 'Build a new gem'
|
|
65
|
+
task :gem do
|
|
66
|
+
`chmod 0600 ~/.gem/credentials`
|
|
67
|
+
sh "gem build #{repo_name}.gemspec"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
desc 'Build a new gem (same as gem task)'
|
|
71
|
+
task :build => :gem do
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
desc 'Install gem'
|
|
75
|
+
task :install => :gem do
|
|
76
|
+
`gem uninstall -aIx #{repo_name}`
|
|
77
|
+
sh "gem install --no-rdoc --no-ri #{repo_name}-#{version}.gem"
|
|
78
|
+
end
|
data/appium_lib.gemspec
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
def self.add_to_path path
|
|
4
|
+
path = File.expand_path "../#{path}/", __FILE__
|
|
5
|
+
|
|
6
|
+
$:.unshift path unless $:.include? path
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
add_to_path 'lib'
|
|
10
|
+
|
|
11
|
+
require 'appium_lib/version'
|
|
12
|
+
|
|
13
|
+
Gem::Specification.new do |s|
|
|
14
|
+
# 1.8.x is not supported
|
|
15
|
+
s.required_ruby_version = '>= 1.9.3'
|
|
16
|
+
|
|
17
|
+
s.name = 'appium_lib'
|
|
18
|
+
s.version = AppiumLib::VERSION
|
|
19
|
+
s.date = AppiumLib::DATE
|
|
20
|
+
s.license = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
|
21
|
+
s.description = s.summary = 'Ruby lib for use with Appium'
|
|
22
|
+
s.description += '.' # avoid identical warning
|
|
23
|
+
s.authors = s.email = [ 'code@bootstraponline.com' ]
|
|
24
|
+
s.homepage = 'https://github.com/appium/ruby_lib' # published as appium_lib
|
|
25
|
+
s.require_paths = [ 'lib' ]
|
|
26
|
+
|
|
27
|
+
s.add_runtime_dependency 'selenium-webdriver', '~> 2.31.0'
|
|
28
|
+
s.add_runtime_dependency 'awesome_print', '~> 1.1.0'
|
|
29
|
+
|
|
30
|
+
s.add_development_dependency 'rake', '~> 10.0.3'
|
|
31
|
+
|
|
32
|
+
s.files = `git ls-files`.split "\n"
|
|
33
|
+
end
|
data/lib/appium_lib.rb
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
=begin
|
|
3
|
+
Run with:
|
|
4
|
+
pry -r ./console.rb
|
|
5
|
+
|
|
6
|
+
Based on simple_test.rb
|
|
7
|
+
https://github.com/appium/appium/blob/82995f47408530c80c3376f4e07a1f649d96ba22/sample-code/examples/ruby/simple_test.rb
|
|
8
|
+
https://github.com/appium/appium/blob/c58eeb66f2d6fa3b9a89d188a2e657cca7cb300f/LICENSE
|
|
9
|
+
=end
|
|
10
|
+
|
|
11
|
+
add_to_path __FILE__
|
|
12
|
+
add_to_path __FILE__, 'element'
|
|
13
|
+
|
|
14
|
+
require 'selenium-webdriver'
|
|
15
|
+
|
|
16
|
+
# Path to the .app or .app.zip.
|
|
17
|
+
# The path can be local or remote for Sauce.
|
|
18
|
+
APP_PATH = ENV['APP_PATH'] unless defined?(APP_PATH)
|
|
19
|
+
raise "APP_PATH must be set." if APP_PATH.nil?
|
|
20
|
+
|
|
21
|
+
# The name to use for the test run on Sauce.
|
|
22
|
+
APP_NAME = ENV['APP_NAME'] unless defined?(APP_NAME)
|
|
23
|
+
|
|
24
|
+
# Android app package
|
|
25
|
+
APP_PACKAGE = ENV['APP_PACKAGE'] unless defined?(APP_PACKAGE)
|
|
26
|
+
|
|
27
|
+
# Android app starting activity.
|
|
28
|
+
APP_ACTIVITY = ENV['APP_ACTIVITY'] unless defined?(APP_ACTIVITY)
|
|
29
|
+
|
|
30
|
+
# Sauce Username
|
|
31
|
+
SAUCE_USERNAME = ENV['SAUCE_USERNAME'] unless defined?(SAUCE_USERNAME)
|
|
32
|
+
|
|
33
|
+
# Sauce Key
|
|
34
|
+
SAUCE_ACCESS_KEY = ENV['SAUCE_ACCESS_KEY'] unless defined?(SAUCE_ACCESS_KEY)
|
|
35
|
+
|
|
36
|
+
PORT = ENV['PORT'] || 4723 unless defined?(PORT)
|
|
37
|
+
|
|
38
|
+
$os = nil
|
|
39
|
+
|
|
40
|
+
if $os.nil?
|
|
41
|
+
$os = :ios
|
|
42
|
+
$os = :android if APP_PATH.end_with?('.apk') || APP_PATH.end_with?('.apk.zip')
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
puts "OS is: #{$os}" if defined?(Pry)
|
|
46
|
+
|
|
47
|
+
if $os == :android
|
|
48
|
+
raise "APP_PACKAGE must be set." if APP_PACKAGE.nil?
|
|
49
|
+
raise "APP_ACTIVITY must be set." if APP_ACTIVITY.nil?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# ruby_console files load depending on OS
|
|
53
|
+
require 'helper'
|
|
54
|
+
require 'button'
|
|
55
|
+
require 'text'
|
|
56
|
+
require 'window'
|
|
57
|
+
require 'patch'
|
|
58
|
+
require 'alert'
|
|
59
|
+
|
|
60
|
+
# Android combines secure and textfield.
|
|
61
|
+
# iOS differentiates between secure and textfield.
|
|
62
|
+
# combine secure & textfield on iOS to match Android behavior.
|
|
63
|
+
$os == :ios ? require('ios/textfield') :
|
|
64
|
+
require('android/textfield')
|
|
65
|
+
|
|
66
|
+
# WebDriver capabilities. Must be valid for Sauce to work.
|
|
67
|
+
# https://github.com/jlipps/appium/blob/master/app/android.js
|
|
68
|
+
def android_capabilities
|
|
69
|
+
{
|
|
70
|
+
browserName: 'Android',
|
|
71
|
+
platform: 'LINUX',
|
|
72
|
+
version: '4.1',
|
|
73
|
+
device: 'Android',
|
|
74
|
+
name: APP_NAME || 'Ruby Console Android Appium',
|
|
75
|
+
app: absolute_app_path,
|
|
76
|
+
:'app-package' => APP_PACKAGE,
|
|
77
|
+
:'app-activity' => APP_ACTIVITY
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# WebDriver capabilities. Must be valid for Sauce to work.
|
|
82
|
+
def ios_capabilities
|
|
83
|
+
{
|
|
84
|
+
browserName: 'iOS 6.0',
|
|
85
|
+
platform: 'Mac 10.8',
|
|
86
|
+
version: '6.0',
|
|
87
|
+
device: 'iPhone Simulator',
|
|
88
|
+
name: APP_NAME || 'Ruby Console iOS Appium',
|
|
89
|
+
app: absolute_app_path
|
|
90
|
+
}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def capabilities
|
|
94
|
+
$os == :ios ? ios_capabilities : android_capabilities
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Converts environment variable APP_PATH to an absolute path.
|
|
98
|
+
# @return [String] APP_PATH as an absolute path
|
|
99
|
+
def absolute_app_path
|
|
100
|
+
raise 'APP_PATH environment variable not set!' if APP_PATH.nil?
|
|
101
|
+
return APP_PATH if APP_PATH.match(/^http/) # public URL for Sauce
|
|
102
|
+
return APP_PATH if APP_PATH.match(/^\//) # absolute file path
|
|
103
|
+
file = File.join(File.dirname(__FILE__), APP_PATH)
|
|
104
|
+
raise "App doesn't exist #{file}" unless File.exist? file
|
|
105
|
+
file
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Get the server url for sauce or local based on env vars.
|
|
109
|
+
# @return [String] the server url
|
|
110
|
+
def server_url
|
|
111
|
+
if !SAUCE_USERNAME.nil? && !SAUCE_ACCESS_KEY.nil?
|
|
112
|
+
"http://#{SAUCE_USERNAME}:#{SAUCE_ACCESS_KEY}@ondemand.saucelabs.com:80/wd/hub"
|
|
113
|
+
else
|
|
114
|
+
"http://127.0.0.1:#{PORT}/wd/hub"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Quits the driver
|
|
119
|
+
# @return [void]
|
|
120
|
+
def driver_quit
|
|
121
|
+
# rescue NoSuchDriverError
|
|
122
|
+
begin; $driver.quit unless $driver.nil?; rescue; end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Creates a new global driver and quits the old one if it exists.
|
|
126
|
+
# @return [Selenium::WebDriver] the new global driver
|
|
127
|
+
def start_driver
|
|
128
|
+
@client = @client || Selenium::WebDriver::Remote::Http::Default.new
|
|
129
|
+
@client.timeout = 999999
|
|
130
|
+
|
|
131
|
+
# If the driver already exists, quit before creating a new driver.
|
|
132
|
+
driver_quit
|
|
133
|
+
|
|
134
|
+
begin
|
|
135
|
+
$driver = Selenium::WebDriver.for(:remote, http_client: @client, desired_capabilities: capabilities, url: server_url)
|
|
136
|
+
rescue Errno::ECONNREFUSED
|
|
137
|
+
puts 'ERROR: Unable to connect to Appium. Is the server running?'
|
|
138
|
+
exit
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Set timeout to a large number so that Appium doesn't quit
|
|
142
|
+
# when no commands are entered after 60 seconds.
|
|
143
|
+
$driver.execute_script 'mobile: setCommandTimeout', timeout: 9999
|
|
144
|
+
|
|
145
|
+
# Must set implicit_wait to zero or $ commands will fail.
|
|
146
|
+
# execute_script "$('button')"
|
|
147
|
+
# $ commands fail anyway now so set implicit wait.
|
|
148
|
+
# https://github.com/appium/appium/issues/214
|
|
149
|
+
$driver.manage.timeouts.implicit_wait = 30
|
|
150
|
+
|
|
151
|
+
$driver
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Set implicit wait to zero.
|
|
155
|
+
def no_wait
|
|
156
|
+
$driver.manage.timeouts.implicit_wait = 0
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Set implicit wait to timeout, defaults to 30.
|
|
160
|
+
# @param timeout [Integer] the timeout in seconds
|
|
161
|
+
# @return [void]
|
|
162
|
+
def set_wait timeout=30
|
|
163
|
+
$driver.manage.timeouts.implicit_wait = timeout
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# The same as $driver.execute_script
|
|
167
|
+
# @return the object returned by execute_script
|
|
168
|
+
def execute_script script, *args
|
|
169
|
+
$driver.execute_script script, *args
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Helper method for mobile gestures
|
|
173
|
+
#
|
|
174
|
+
# https://github.com/appium/appium/wiki/Automating-mobile-gestures
|
|
175
|
+
#
|
|
176
|
+
# $driver.execute_script 'mobile: swipe', endX: 100, endY: 100, duration: 0.01
|
|
177
|
+
#
|
|
178
|
+
# becomes
|
|
179
|
+
#
|
|
180
|
+
# mobile :swipe, endX: 100, endY: 100, duration: 0.01
|
|
181
|
+
def mobile method, *args
|
|
182
|
+
raise "Method must not be nil" if method.nil?
|
|
183
|
+
raise "Method must have .to_s" unless method.respond_to? :to_s
|
|
184
|
+
$driver.execute_script "mobile: #{method.to_s}", *args
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Calls $driver.find_elements
|
|
188
|
+
def find_elements *args
|
|
189
|
+
$driver.find_elements *args
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Calls $driver.find_elements
|
|
193
|
+
def find_element *args
|
|
194
|
+
$driver.find_element *args
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Quit the driver and Pry.
|
|
198
|
+
# quit and exit are reserved by Pry.
|
|
199
|
+
def x
|
|
200
|
+
driver_quit
|
|
201
|
+
exit # exit pry
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Paging in Pry is annoying :q required to exit.
|
|
205
|
+
# With pager disabled, the output is similar to IRB
|
|
206
|
+
# Only set if Pry is defined.
|
|
207
|
+
Pry.config.pager = false if defined?(Pry)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
# iOS only
|
|
4
|
+
# Tap the alert button identified by value.
|
|
5
|
+
# @param value [Integer, String] either an integer index of the button or the button's name
|
|
6
|
+
# @return [void]
|
|
7
|
+
def alert_click value
|
|
8
|
+
$driver.execute_script "UIATarget.localTarget().frontMostApp().alert().buttons()[#{value}].tap();"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Get the alert message text.
|
|
12
|
+
# @return [String]
|
|
13
|
+
def alert_text
|
|
14
|
+
$driver.switch_to.alert.text
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Accept the alert.
|
|
18
|
+
# @return [void]
|
|
19
|
+
def alert_accept
|
|
20
|
+
$driver.switch_to.alert.accept
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Get the text of the alert's accept button.
|
|
24
|
+
# The last button is considered "accept."
|
|
25
|
+
# @return [String]
|
|
26
|
+
def alert_accept_text
|
|
27
|
+
a = $driver.find_element(:tag_name, :alert)
|
|
28
|
+
return if a.nil?
|
|
29
|
+
b = a.find_elements(:tag_name, :button)
|
|
30
|
+
b.last.text if b && b.size >= 1
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Dismiss the alert.
|
|
34
|
+
# @return [void]
|
|
35
|
+
def alert_dismiss
|
|
36
|
+
$driver.switch_to.alert.dismiss
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Get the text of the alert's dismiss button.
|
|
40
|
+
# The first button is considered "dismiss."
|
|
41
|
+
# @return [String]
|
|
42
|
+
def alert_dismiss_text
|
|
43
|
+
a = $driver.find_element(:tag_name, :alert)
|
|
44
|
+
return if a.nil?
|
|
45
|
+
b = a.find_elements(:tag_name, :button)
|
|
46
|
+
b.first.text if b && b.size >= 1
|
|
47
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# UIATextField methods
|
|
3
|
+
|
|
4
|
+
if $os == :android
|
|
5
|
+
|
|
6
|
+
# Get an array of textfield texts.
|
|
7
|
+
# @return [Array<String>]
|
|
8
|
+
def textfields
|
|
9
|
+
find_eles_attr :textfield, :text
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Get an array of textfield elements.
|
|
13
|
+
# @return [Array<Textfield>]
|
|
14
|
+
def e_textfields
|
|
15
|
+
find_eles :textfield
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Get the first textfield element.
|
|
19
|
+
# @return [Textfield]
|
|
20
|
+
def first_textfield
|
|
21
|
+
first_ele :textfield
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Get the last textfield element.
|
|
25
|
+
# @return [Textfield]
|
|
26
|
+
def last_textfield
|
|
27
|
+
last_ele :textfield
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Get the first textfield that matches text.
|
|
31
|
+
# @param text [String, Integer] the text to match exactly. If int then the textfield at that index is returned.
|
|
32
|
+
# @return [Textfield]
|
|
33
|
+
def textfield text
|
|
34
|
+
return ele_index :textfield, text if text.is_a? Numeric
|
|
35
|
+
find_ele_by_text :textfield, text
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Get the first textfield that includes text.
|
|
39
|
+
# @param text [String] the text the textfield must include
|
|
40
|
+
# @return [Textfield]
|
|
41
|
+
def textfield_include text
|
|
42
|
+
find_ele_by_text_include :textfield, text
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end # if $os
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# UIAButton methods
|
|
3
|
+
|
|
4
|
+
=begin
|
|
5
|
+
Method Signatures:
|
|
6
|
+
|
|
7
|
+
button( text, number = -1 )
|
|
8
|
+
buttons( text = nil )
|
|
9
|
+
button_include( text )
|
|
10
|
+
buttons_include( text )
|
|
11
|
+
first_button
|
|
12
|
+
last_button
|
|
13
|
+
|
|
14
|
+
Examples:
|
|
15
|
+
|
|
16
|
+
button 'text' # 1st button exactly matching text
|
|
17
|
+
button 'text', 2 # 2nd button exactly matching text
|
|
18
|
+
|
|
19
|
+
buttons # text of all buttons.
|
|
20
|
+
buttons 'text' # all buttons exactly matching text
|
|
21
|
+
|
|
22
|
+
button_include 'text' # the first button that includes text
|
|
23
|
+
buttons_include 'text' # all buttons that include text
|
|
24
|
+
|
|
25
|
+
first_button # the first button
|
|
26
|
+
last_button # the last button
|
|
27
|
+
=end
|
|
28
|
+
|
|
29
|
+
# Find a button by text and optionally number.
|
|
30
|
+
# @param text [String, Integer] the text to exactly match. If int then the button at that index is returned.
|
|
31
|
+
# @param number [Integer] the occurance of the button matching text. Defaults to the first button.
|
|
32
|
+
# @return [Button] the button found with text and matching number
|
|
33
|
+
def button text, number=0
|
|
34
|
+
return ele_index :button, text if text.is_a? Numeric
|
|
35
|
+
|
|
36
|
+
number >= 1 ? button_text_num( text, number ) :
|
|
37
|
+
button_text( text )
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Get an array of button texts or button elements if text is provided.
|
|
41
|
+
# @param text [String] the text to exactly match
|
|
42
|
+
# @return [Array<String>, Array<Buttons>] either an array of button texts or an array of button elements if text is provided.
|
|
43
|
+
def buttons text=nil
|
|
44
|
+
text == nil ? find_eles_attr(:button, :text) :
|
|
45
|
+
find_ele_by_text(:button, text)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Get the first button that includes text.
|
|
49
|
+
# @param text [String] the text that the element must include
|
|
50
|
+
# @return [Button]
|
|
51
|
+
def button_include text
|
|
52
|
+
find_ele_by_text_include :button, text
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Get all buttons that include text.
|
|
56
|
+
# @param text [String] the text that the element must include
|
|
57
|
+
# @return [Array<Button>]
|
|
58
|
+
def buttons_include text
|
|
59
|
+
find_eles_by_text_include :button, text
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Get the first button element.
|
|
63
|
+
# @return [Button]
|
|
64
|
+
def first_button
|
|
65
|
+
first_ele :button
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Get the last button element.
|
|
69
|
+
# @return [Button]
|
|
70
|
+
def last_button
|
|
71
|
+
last_ele :button
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# -- prefer above methods before using these.
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
# Get the first button element that exactly matches text.
|
|
78
|
+
# @param text [String] the text to match exactly
|
|
79
|
+
# @return [Button]
|
|
80
|
+
def button_text text
|
|
81
|
+
find_ele_by_text :button, text
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Get the button element exactly matching text and
|
|
85
|
+
# occurrence. number=2 means the 2nd occurrence.
|
|
86
|
+
#
|
|
87
|
+
# find the second Sign In button
|
|
88
|
+
#
|
|
89
|
+
# b = e_button 'Sign In', 2
|
|
90
|
+
#
|
|
91
|
+
# Button order will change in iOS vs Android
|
|
92
|
+
# so if there's no button found at number then
|
|
93
|
+
# return the first button.
|
|
94
|
+
#
|
|
95
|
+
# @param text [String] the text to match exactly
|
|
96
|
+
# @param number [Integer] the button occurance to return. 1 = first button
|
|
97
|
+
# @return [Button] the button that exactly matches text and number
|
|
98
|
+
def button_text_num text, number=1
|
|
99
|
+
raise "Number must be >= 1" if number <= 0
|
|
100
|
+
number = number - 1 # zero indexed
|
|
101
|
+
|
|
102
|
+
result = nil
|
|
103
|
+
|
|
104
|
+
elements = find_eles_by_text :button, text
|
|
105
|
+
elements.size > number ? result = elements[number]
|
|
106
|
+
: result = elements.first
|
|
107
|
+
|
|
108
|
+
result
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Get an array of button elements.
|
|
112
|
+
# @return [Array<Button>]
|
|
113
|
+
def e_buttons
|
|
114
|
+
find_eles :button
|
|
115
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# UIATextField & UIASecureTextField methods
|
|
3
|
+
#
|
|
4
|
+
# Find textfield and then secure elements in one server call
|
|
5
|
+
# to match Android.
|
|
6
|
+
|
|
7
|
+
if $os == :ios
|
|
8
|
+
|
|
9
|
+
# Get an array of textfield texts.
|
|
10
|
+
# @return [Array<String>]
|
|
11
|
+
def textfields
|
|
12
|
+
find_2_eles_attr :textfield, :secure, :text
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Get an array of textfield elements.
|
|
16
|
+
# @return [Array<Textfield>]
|
|
17
|
+
def e_textfields
|
|
18
|
+
execute_script textfield_js
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Get the first textfield element.
|
|
22
|
+
# @return [Textfield]
|
|
23
|
+
def first_textfield
|
|
24
|
+
js = textfield_js 'r = r.length > 0 ? $(r[0]) : r;'
|
|
25
|
+
execute_script(js).first
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Get the last textfield element.
|
|
29
|
+
# @return [Textfield]
|
|
30
|
+
def last_textfield
|
|
31
|
+
js = textfield_js 'r = r.length > 0 ? $(r[r.length - 1]) : r;'
|
|
32
|
+
execute_script(js).first
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Get the first textfield that matches text.
|
|
36
|
+
# @param text [String, Integer] the text to match exactly. If int then the textfield at that index is returned.
|
|
37
|
+
# @return [Textfield]
|
|
38
|
+
def textfield text
|
|
39
|
+
# Don't use ele_index because that only works on one element type.
|
|
40
|
+
# iOS needs to combine textfield and secure to match Android.
|
|
41
|
+
if text.is_a? Numeric
|
|
42
|
+
js = textfield_js 'r = r.length > 0 ? $(r[#{text}]) : r;'
|
|
43
|
+
return execute_script(js).first
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# find_ele_by_text :textfield, text
|
|
47
|
+
js = %Q(
|
|
48
|
+
var t = au.getElementsByXpath('textfield[@text="#{text}"]').value;
|
|
49
|
+
var s = au.getElementsByXpath('secure[@text="#{text}"]').value;
|
|
50
|
+
t.concat(s)[0];
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
puts js if defined?(Pry)
|
|
54
|
+
|
|
55
|
+
execute_script js
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Get the first textfield that includes text.
|
|
59
|
+
# @param text [String] the text the textfield must include
|
|
60
|
+
# @return [Textfield]
|
|
61
|
+
def textfield_include text
|
|
62
|
+
js = %Q(
|
|
63
|
+
var t = au.getElementsByXpath('textfield[contains(@text, "#{text}")]').value;
|
|
64
|
+
var s = au.getElementsByXpath('secure[contains(@text, "#{text}")]').value;
|
|
65
|
+
t.concat(s)[0];
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
puts js if defined?(Pry)
|
|
69
|
+
|
|
70
|
+
execute_script js
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
# Return combined lookup of textfield and secure
|
|
76
|
+
# with an optional filter. $() wrap is required for .each
|
|
77
|
+
def textfield_js filter=''
|
|
78
|
+
%Q(
|
|
79
|
+
var t = au.lookup('textfield');
|
|
80
|
+
var s = au.lookup('secure');
|
|
81
|
+
var r = $(t.concat(s));
|
|
82
|
+
#{filter}
|
|
83
|
+
au._returnElems(r);
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
end # if $os
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# UIAStaticText methods
|
|
3
|
+
|
|
4
|
+
# Get an array of text texts.
|
|
5
|
+
# @return [Array<String>]
|
|
6
|
+
def texts
|
|
7
|
+
find_eles_attr :text, :text
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Get an array of text elements.
|
|
11
|
+
# @return [Array<Text>]
|
|
12
|
+
def e_texts
|
|
13
|
+
find_eles :text
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Get the first text element.
|
|
17
|
+
# @return [Text]
|
|
18
|
+
def first_text
|
|
19
|
+
first_ele :text
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Get the last text element
|
|
23
|
+
# @return [Text]
|
|
24
|
+
def last_text
|
|
25
|
+
last_ele :text
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Get the first element that matches text.
|
|
29
|
+
# @param text [String, Integer] the text to find exactly. If int then the text at that index is returned.
|
|
30
|
+
# @return [Text]
|
|
31
|
+
def text text
|
|
32
|
+
return ele_index :text, text if text.is_a? Numeric
|
|
33
|
+
find_ele_by_text :text, text
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Get the first textfield that includes text.
|
|
37
|
+
# @param text [String] the text that the tag must include
|
|
38
|
+
# @return [Text]
|
|
39
|
+
def text_include text
|
|
40
|
+
find_ele_by_text_include :text, text
|
|
41
|
+
end
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# Generic helper methods not specific
|
|
3
|
+
# to a particular tag name
|
|
4
|
+
|
|
5
|
+
# json and ap are required for the source method.
|
|
6
|
+
require 'json'
|
|
7
|
+
require 'ap' # awesome print
|
|
8
|
+
require 'timeout' # for wait
|
|
9
|
+
|
|
10
|
+
# iOS .name returns the accessibility attribute if it's set. if not set, the string value is used.
|
|
11
|
+
# Android .name returns the accessibility attribute and nothing if it's not set.
|
|
12
|
+
#
|
|
13
|
+
# .text should be cross platform so prefer that over name, unless both
|
|
14
|
+
# Android and iOS have proper accessibility attributes.
|
|
15
|
+
# .text and .value should be the same so use .text over .value.
|
|
16
|
+
#
|
|
17
|
+
# secure tag_name is iOS only because it can't be implemented using uiautomator for Android.
|
|
18
|
+
#
|
|
19
|
+
# find_element :text doesn't work so use XPath to find by text.
|
|
20
|
+
|
|
21
|
+
# Check every 0.5 seconds to see if block.call is true.
|
|
22
|
+
# Give up after 30 seconds.
|
|
23
|
+
# @param block [Block] the block to call
|
|
24
|
+
# @return [Object] the result of block.call
|
|
25
|
+
def wait &block
|
|
26
|
+
# Rescue Timeout::Error: execution expired
|
|
27
|
+
result = nil
|
|
28
|
+
timeout(30) { while(!(result = begin;block.call;rescue;end)) do; sleep(0.5) end }
|
|
29
|
+
result
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Presses the back button on Android.
|
|
33
|
+
# @return [void]
|
|
34
|
+
def back
|
|
35
|
+
$driver.navigate.back
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Get the element of type tag_name at matching index.
|
|
39
|
+
# @param tag_name [String] the tag name to find
|
|
40
|
+
# @param index [Integer] the index
|
|
41
|
+
# @return [Element] the found element of type tag_name
|
|
42
|
+
def ele_index tag_name, index
|
|
43
|
+
find_eles(tag_name)[index]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Get all elements exactly matching tag name
|
|
47
|
+
# @param tag_name [String] the tag name to find
|
|
48
|
+
# @return [Array<Element>] the found elements of type tag_name
|
|
49
|
+
def find_eles tag_name
|
|
50
|
+
$driver.find_elements :tag_name, tag_name
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# iOS only. Android uses uiautomator instead of uiautomation.
|
|
54
|
+
# Get an array of attribute values from elements exactly matching tag name.
|
|
55
|
+
# @param tag_name [String] the tag name to find
|
|
56
|
+
# @param attribute [String] the attribute to collect
|
|
57
|
+
# @result [Array<String>] an array of strings containing the attribute from found elements of type tag_name.
|
|
58
|
+
def find_eles_attr tag_name, attribute
|
|
59
|
+
# Use au.lookup(tag_name) instead of $(tag_name)
|
|
60
|
+
# See https://github.com/appium/appium/issues/214
|
|
61
|
+
js = %Q(
|
|
62
|
+
var eles = au.lookup('#{tag_name}');
|
|
63
|
+
var result = [];
|
|
64
|
+
for (var a = 0, length = eles.length; a < length; a++) {
|
|
65
|
+
result.push(eles[a].#{attribute}());
|
|
66
|
+
}
|
|
67
|
+
result
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
$driver.execute_script js
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# iOS only. Android uses uiautomator instead of uiautomation.
|
|
74
|
+
# Get an array of attribute values from elements exactly matching tag name.
|
|
75
|
+
# @param tag_name_1 [String] the 1st tag name to find
|
|
76
|
+
# @param tag_name_2 [String] the 2nd tag name to find
|
|
77
|
+
# @param attribute [String] the attribute to collect
|
|
78
|
+
# @result [Array<String>] an array of strings containing the attribute from found elements of type tag_name.
|
|
79
|
+
def find_2_eles_attr tag_name_1, tag_name_2, attribute
|
|
80
|
+
# Use au.lookup(tag_name) instead of $(tag_name)
|
|
81
|
+
# See https://github.com/appium/appium/issues/214
|
|
82
|
+
js = %Q(
|
|
83
|
+
var eles = au.lookup('#{tag_name_1}');
|
|
84
|
+
eles = $(eles.concat(au.lookup('#{tag_name_2}')));
|
|
85
|
+
var result = [];
|
|
86
|
+
for (var a = 0, length = eles.length; a < length; a++) {
|
|
87
|
+
result.push(eles[a].#{attribute}());
|
|
88
|
+
}
|
|
89
|
+
result
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
$driver.execute_script js
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Get the first tag that exactly matches tag and text.
|
|
96
|
+
# @param tag [String] the tag name to match
|
|
97
|
+
# @param text [String] the text to exactly match
|
|
98
|
+
# @return [Element] the element of type tag exactly matching text
|
|
99
|
+
def find_ele_by_text tag, text
|
|
100
|
+
$driver.find_element :xpath, %Q(#{tag}[@text='#{text}'])
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Get all tags that exactly match tag and text.
|
|
104
|
+
# @param tag [String] the tag name to match
|
|
105
|
+
# @param text [String] the text to exactly match
|
|
106
|
+
# @return [Array<Element>] the elements of type tag exactly matching text
|
|
107
|
+
def find_eles_by_text tag, text
|
|
108
|
+
$driver.find_elements :xpath, %Q(#{tag}[@text='#{text}'])
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Get the first tag by attribute that exactly matches value.
|
|
112
|
+
# @param tag [String] the tag name to match
|
|
113
|
+
# @param attr [String] the attribute to compare
|
|
114
|
+
# @param value [String] the value of the attribute that the element must include
|
|
115
|
+
# @return [Element] the element of type tag who's attribute includes value
|
|
116
|
+
def find_ele_by_attr_include tag, attr, value
|
|
117
|
+
$driver.find_element :xpath, %Q(#{tag}[contains(@#{attr}, '#{value}')])
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Get tags by attribute that include value.
|
|
121
|
+
# @param tag [String] the tag name to match
|
|
122
|
+
# @param attr [String] the attribute to compare
|
|
123
|
+
# @param value [String] the value of the attribute that the element must include
|
|
124
|
+
# @return [Array<Element>] the elements of type tag who's attribute includes value
|
|
125
|
+
def find_eles_by_attr_include tag, attr, value
|
|
126
|
+
$driver.find_elements :xpath, %Q(#{tag}[contains(@#{attr}, '#{value}')])
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Get the first tag that includes text.
|
|
130
|
+
# @param tag [String] the tag name to match
|
|
131
|
+
# @param text [String] the text the element must include
|
|
132
|
+
# @return [Element] the element of type tag that includes text
|
|
133
|
+
# element.attribute(:text).include? text
|
|
134
|
+
def find_ele_by_text_include tag, text
|
|
135
|
+
find_ele_by_attr_include tag, :text, text
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Get the tags that include text.
|
|
139
|
+
# @param tag [String] the tag name to match
|
|
140
|
+
# @param text [String] the text the element must include
|
|
141
|
+
# @return [Array<Element>] the elements of type tag that includes text
|
|
142
|
+
# element.attribute(:text).include? text
|
|
143
|
+
def find_eles_by_text_include tag, text
|
|
144
|
+
find_eles_by_attr_include tag, :text, text
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Get the first tag that matches tag_name
|
|
148
|
+
# @param tag_name [String] the tag to match
|
|
149
|
+
# @return [Element]
|
|
150
|
+
def first_ele tag_name
|
|
151
|
+
tag = find_eles tag_name
|
|
152
|
+
tag = tag.first unless tag.nil?
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Get the last tag that matches tag_name
|
|
156
|
+
# @param tag_name [String] the tag to match
|
|
157
|
+
# @return [Element]
|
|
158
|
+
def last_ele tag_name
|
|
159
|
+
tag = find_eles tag_name
|
|
160
|
+
tag = tag.last unless tag.nil?
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Prints a JSON view of the current page
|
|
164
|
+
# @return [void]
|
|
165
|
+
def source
|
|
166
|
+
ap JSON.parse($driver.page_source)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# iOS only. On Android uiautomator always returns an empty string for EditText password.
|
|
170
|
+
#
|
|
171
|
+
# Password character returned from value of UIASecureTextField
|
|
172
|
+
# @param length [Integer] the length of the password to generate
|
|
173
|
+
# @return [String] the returned string is of size length
|
|
174
|
+
def password length=1
|
|
175
|
+
'•' * length
|
|
176
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Implement useful features for element.
|
|
2
|
+
class Selenium::WebDriver::Element
|
|
3
|
+
# Note: For testing .text should be used over value, and name.
|
|
4
|
+
|
|
5
|
+
# Fixes NoMethodError: undefined method `value' for #<Selenium::WebDriver::Element:0x..fa4a9148235390a44 id="1">
|
|
6
|
+
def value
|
|
7
|
+
self.attribute :value
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Fixes NoMethodError: undefined method `name' for #<Selenium::WebDriver::Element
|
|
11
|
+
def name
|
|
12
|
+
self.attribute :name
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Fixes NoMethodError: undefined method `type' for #<Selenium::WebDriver::Element
|
|
16
|
+
def type
|
|
17
|
+
self.attribute :type
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Tag name appears to be the same as type.
|
|
21
|
+
#
|
|
22
|
+
# Fixes Selenium::WebDriver::Error::UnknownError: Not yet implemented
|
|
23
|
+
def tag_name
|
|
24
|
+
type
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Cross platform way of entering text into a textfield
|
|
28
|
+
def set_value text
|
|
29
|
+
# enter text then tap window to hide the keyboard.
|
|
30
|
+
js = %Q(
|
|
31
|
+
au.getElement('#{self.ref}').setValue('#{text}');
|
|
32
|
+
au.lookup('window')[0].tap()
|
|
33
|
+
)
|
|
34
|
+
$driver.execute_script js
|
|
35
|
+
end if $os == :ios
|
|
36
|
+
|
|
37
|
+
# Cross platform way of entering text into a textfield
|
|
38
|
+
def set_value text
|
|
39
|
+
self.send_keys text
|
|
40
|
+
end if $os == :android
|
|
41
|
+
|
|
42
|
+
# For use with mobile tap.
|
|
43
|
+
#
|
|
44
|
+
# $driver.execute_script 'mobile: tap', :x => 0.0, :y => 0.98
|
|
45
|
+
#
|
|
46
|
+
# https://github.com/appium/appium/wiki/Automating-mobile-gestures
|
|
47
|
+
# @return [OpenStruct] the relative x, y in a struct. ex: { x: 0.50, y: 0.20 }
|
|
48
|
+
def location_rel
|
|
49
|
+
xy = self.location
|
|
50
|
+
w = window_size
|
|
51
|
+
OpenStruct.new( x: xy.x.to_f / w.width.to_f,
|
|
52
|
+
y: xy.y.to_f / w.height.to_f )
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Print JSON posted to Appium
|
|
57
|
+
#
|
|
58
|
+
# Requires from lib/selenium/webdriver/remote.rb
|
|
59
|
+
require 'selenium/webdriver/remote/capabilities'
|
|
60
|
+
require 'selenium/webdriver/remote/bridge'
|
|
61
|
+
require 'selenium/webdriver/remote/server_error'
|
|
62
|
+
require 'selenium/webdriver/remote/response'
|
|
63
|
+
require 'selenium/webdriver/remote/commands'
|
|
64
|
+
require 'selenium/webdriver/remote/http/common'
|
|
65
|
+
require 'selenium/webdriver/remote/http/default'
|
|
66
|
+
|
|
67
|
+
# Show http calls to the Selenium server.
|
|
68
|
+
#
|
|
69
|
+
# Invaluable for debugging.
|
|
70
|
+
module Selenium::WebDriver::Remote
|
|
71
|
+
class Bridge
|
|
72
|
+
# Code from lib/selenium/webdriver/remote/bridge.rb
|
|
73
|
+
def raw_execute(command, opts = {}, command_hash = nil)
|
|
74
|
+
verb, path = COMMANDS[command] || raise(ArgumentError, "unknown command: #{command.inspect}")
|
|
75
|
+
path = path.dup
|
|
76
|
+
|
|
77
|
+
path[':session_id'] = @session_id if path.include?(":session_id")
|
|
78
|
+
|
|
79
|
+
begin
|
|
80
|
+
opts.each { |key, value| path[key.inspect] = escaper.escape(value.to_s) }
|
|
81
|
+
rescue IndexError
|
|
82
|
+
raise ArgumentError, "#{opts.inspect} invalid for #{command.inspect}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
puts verb
|
|
86
|
+
puts path
|
|
87
|
+
puts command_hash.to_json
|
|
88
|
+
|
|
89
|
+
puts "-> #{verb.to_s.upcase} #{path}" if $DEBUG
|
|
90
|
+
http.call verb, path, command_hash
|
|
91
|
+
end # def
|
|
92
|
+
end # class
|
|
93
|
+
end if defined?(Pry)# module
|
data/readme.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#### appium_lib [](https://gemnasium.com/appium/ruby_lib)
|
|
2
|
+
|
|
3
|
+
- [appium_lib on RubyGems](https://rubygems.org/gems/appium_lib)
|
|
4
|
+
- [Documentation for appium_lib](http://www.rubydoc.info/github/appium/ruby_lib/master/frames)
|
|
5
|
+
|
|
6
|
+
Helper methods for writing cross platform (iPad, iPhone, Android) tests in Ruby using Appium.
|
|
7
|
+
|
|
8
|
+
Make sure you're using Ruby 1.9.3+ with upgraded rubygems and bundler.
|
|
9
|
+
|
|
10
|
+
#### Install / Upgrade
|
|
11
|
+
|
|
12
|
+
Update rubygems and bundler.
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
gem update --system ;\
|
|
16
|
+
gem update bundler
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Install the latest gem release.
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
gem uninstall -aIx appium_lib ;\
|
|
23
|
+
gem install --no-rdoc --no-ri appium_lib
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
#### Run from Source
|
|
27
|
+
|
|
28
|
+
`pry -r ./lib/appium_lib.rb`
|
|
29
|
+
|
|
30
|
+
Then `start_driver`
|
metadata
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: appium_lib
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.21
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- code@bootstraponline.com
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2013-03-22 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: selenium-webdriver
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ~>
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 2.31.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ~>
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 2.31.0
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: awesome_print
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ~>
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 1.1.0
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ~>
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 1.1.0
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ~>
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: 10.0.3
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ~>
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: 10.0.3
|
|
55
|
+
description: Ruby lib for use with Appium.
|
|
56
|
+
email:
|
|
57
|
+
- code@bootstraponline.com
|
|
58
|
+
executables: []
|
|
59
|
+
extensions: []
|
|
60
|
+
extra_rdoc_files: []
|
|
61
|
+
files:
|
|
62
|
+
- .gitignore
|
|
63
|
+
- Gemfile
|
|
64
|
+
- LICENSE-2.0.txt
|
|
65
|
+
- Rakefile
|
|
66
|
+
- appium_lib.gemspec
|
|
67
|
+
- lib/appium_lib.rb
|
|
68
|
+
- lib/appium_lib/console.rb
|
|
69
|
+
- lib/appium_lib/element/alert.rb
|
|
70
|
+
- lib/appium_lib/element/android/textfield.rb
|
|
71
|
+
- lib/appium_lib/element/button.rb
|
|
72
|
+
- lib/appium_lib/element/ios/textfield.rb
|
|
73
|
+
- lib/appium_lib/element/text.rb
|
|
74
|
+
- lib/appium_lib/element/window.rb
|
|
75
|
+
- lib/appium_lib/helper.rb
|
|
76
|
+
- lib/appium_lib/patch.rb
|
|
77
|
+
- lib/appium_lib/version.rb
|
|
78
|
+
- readme.md
|
|
79
|
+
homepage: https://github.com/appium/ruby_lib
|
|
80
|
+
licenses:
|
|
81
|
+
- http://www.apache.org/licenses/LICENSE-2.0.txt
|
|
82
|
+
metadata: {}
|
|
83
|
+
post_install_message:
|
|
84
|
+
rdoc_options: []
|
|
85
|
+
require_paths:
|
|
86
|
+
- lib
|
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
88
|
+
requirements:
|
|
89
|
+
- - ! '>='
|
|
90
|
+
- !ruby/object:Gem::Version
|
|
91
|
+
version: 1.9.3
|
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ! '>='
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
97
|
+
requirements: []
|
|
98
|
+
rubyforge_project:
|
|
99
|
+
rubygems_version: 2.0.3
|
|
100
|
+
signing_key:
|
|
101
|
+
specification_version: 4
|
|
102
|
+
summary: Ruby lib for use with Appium
|
|
103
|
+
test_files: []
|