appium_lib 4.0.0 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/android_tests/lib/android/specs/common/helper.rb +26 -13
- data/android_tests/lib/android/specs/driver.rb +4 -4
- data/docs/android_docs.md +249 -203
- data/docs/docs.md +2 -12
- data/docs/ios_docs.md +210 -196
- data/ios_tests/lib/ios/specs/common/helper.rb +19 -13
- data/lib/appium_lib/android/client_xpath.rb +47 -0
- data/lib/appium_lib/common/helper.rb +0 -58
- data/lib/appium_lib/common/version.rb +2 -2
- data/lib/appium_lib/common/wait.rb +99 -0
- data/lib/appium_lib/device/touch_actions.rb +3 -0
- data/lib/appium_lib/driver.rb +38 -10
- data/release_notes.md +13 -0
- metadata +4 -2
@@ -8,7 +8,7 @@ describe 'common/helper.rb' do
|
|
8
8
|
before_first
|
9
9
|
end
|
10
10
|
|
11
|
-
|
11
|
+
wait_opts = { timeout: 0.2, interval: 0.2 } # max_wait, interval
|
12
12
|
|
13
13
|
=begin
|
14
14
|
There's no `must_not_raise` as the opposite of must_raise
|
@@ -21,17 +21,20 @@ must_not_raise is a no-op.
|
|
21
21
|
# max_wait=0 is infinity to use 0.1
|
22
22
|
t 'wait' do
|
23
23
|
# successful wait should not raise error
|
24
|
-
wait(
|
25
|
-
wait(
|
26
|
-
wait(
|
24
|
+
wait(wait_opts) { true }
|
25
|
+
wait(wait_opts) { false }
|
26
|
+
wait(wait_opts) { nil }
|
27
27
|
|
28
28
|
# failed wait should error
|
29
|
-
proc { wait(
|
29
|
+
proc { wait(wait_opts) { raise } }.must_raise Selenium::WebDriver::Error::TimeOutError
|
30
30
|
|
31
31
|
# regular rescue will not handle exceptions outside of StandardError hierarchy
|
32
32
|
# must rescue Exception explicitly to rescue everything
|
33
|
-
proc { wait(
|
34
|
-
proc { wait(0.2, 0.0) { raise NoMemoryError } }.must_raise
|
33
|
+
proc { wait(wait_opts) { raise NoMemoryError } }.must_raise Selenium::WebDriver::Error::TimeOutError
|
34
|
+
proc { wait(timeout: 0.2, interval: 0.0) { raise NoMemoryError } }.must_raise Selenium::WebDriver::Error::TimeOutError
|
35
|
+
|
36
|
+
# invalid keys are rejected
|
37
|
+
proc { wait(invalidkey: 2) { true } }.must_raise RuntimeError
|
35
38
|
end
|
36
39
|
|
37
40
|
t 'ignore' do
|
@@ -46,19 +49,22 @@ must_not_raise is a no-op.
|
|
46
49
|
# wait_true is a success unless the value is not true
|
47
50
|
t 'wait_true' do
|
48
51
|
# successful wait should not error
|
49
|
-
wait_true(
|
52
|
+
wait_true(wait_opts) { true }
|
50
53
|
|
51
54
|
# failed wait should error
|
52
|
-
proc { wait_true(
|
53
|
-
proc { wait_true(
|
55
|
+
proc { wait_true(wait_opts) { false } }.must_raise Selenium::WebDriver::Error::TimeOutError
|
56
|
+
proc { wait_true(wait_opts) { nil } }.must_raise Selenium::WebDriver::Error::TimeOutError
|
54
57
|
|
55
58
|
# raise should error
|
56
|
-
proc { wait_true(
|
59
|
+
proc { wait_true(wait_opts) { raise } }.must_raise Selenium::WebDriver::Error::TimeOutError
|
57
60
|
|
58
61
|
# regular rescue will not handle exceptions outside of StandardError hierarchy
|
59
62
|
# must rescue Exception explicitly to rescue everything
|
60
|
-
proc { wait_true(
|
61
|
-
proc { wait_true(0.2, 0.0) { raise NoMemoryError } }.must_raise
|
63
|
+
proc { wait_true(wait_opts) { raise NoMemoryError } }.must_raise Selenium::WebDriver::Error::TimeOutError
|
64
|
+
proc { wait_true(timeout: 0.2, interval: 0.0) { raise NoMemoryError } }.must_raise Selenium::WebDriver::Error::TimeOutError
|
65
|
+
|
66
|
+
# invalid keys are rejected
|
67
|
+
proc { wait_true(invalidkey: 2) { true } }.must_raise RuntimeError
|
62
68
|
end
|
63
69
|
|
64
70
|
# t 'id' # id is for Selendroid
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module Appium
|
4
|
+
module Android
|
5
|
+
def _nodeset_to_uiselector opts={}
|
6
|
+
results = ''
|
7
|
+
|
8
|
+
nodes = opts[:nodes]
|
9
|
+
first = opts[:first]
|
10
|
+
|
11
|
+
nodes = [nodes[0]] if first
|
12
|
+
|
13
|
+
nodes.each do |node|
|
14
|
+
results += %Q(new UiSelector().className("#{node.name}").instance(#{node.attr('instance')});)
|
15
|
+
end
|
16
|
+
|
17
|
+
results.strip
|
18
|
+
end
|
19
|
+
|
20
|
+
def _client_xpath opts={}
|
21
|
+
root_node = Nokogiri::XML(get_source).children.first
|
22
|
+
|
23
|
+
instance = Hash.new -1
|
24
|
+
|
25
|
+
root_node.traverse do |node|
|
26
|
+
number = instance[node.name] += 1
|
27
|
+
node.set_attribute 'instance', number
|
28
|
+
end
|
29
|
+
|
30
|
+
nodes = root_node.xpath(opts[:xpath])
|
31
|
+
first = opts[:first]
|
32
|
+
|
33
|
+
_nodeset_to_uiselector nodes: nodes, first: first
|
34
|
+
end
|
35
|
+
|
36
|
+
def client_xpath xpath
|
37
|
+
find_element :uiautomator, _client_xpath(xpath: xpath, first: true)
|
38
|
+
end
|
39
|
+
|
40
|
+
def client_xpaths xpath
|
41
|
+
find_elements :uiautomator, _client_xpath(xpath: xpath, first: false)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# http://stackoverflow.com/questions/9199415/getting-first-node-in-xpath-result-set
|
47
|
+
# '(//android.widget.TextView)[1]' not '//android.widget.TextView[1]'
|
@@ -18,38 +18,6 @@ module Appium
|
|
18
18
|
#
|
19
19
|
# find_element :text doesn't work so use XPath to find by text.
|
20
20
|
|
21
|
-
# Check every 0.5 seconds to see if block.call doesn't raise an exception.
|
22
|
-
# if .call raises an exception then it will be tried again.
|
23
|
-
# if .call doesn't raise an exception then it will stop waiting.
|
24
|
-
#
|
25
|
-
# Example: wait { name('back').click }
|
26
|
-
#
|
27
|
-
# Give up after 30 seconds.
|
28
|
-
# @param max_wait [Integer] the maximum time in seconds to wait for.
|
29
|
-
# Note that max wait 0 means infinity.
|
30
|
-
# @param interval [Float] the time in seconds to wait after calling the block
|
31
|
-
# @param block [Block] the block to call
|
32
|
-
# @return [Object] the result of block.call
|
33
|
-
def wait max_wait=30, interval=0.5, &block
|
34
|
-
max_wait = 1 if max_wait <= 0
|
35
|
-
result = nil
|
36
|
-
timeout max_wait do
|
37
|
-
until (
|
38
|
-
begin
|
39
|
-
result = block.call || true
|
40
|
-
rescue Errno::ECONNREFUSED => e
|
41
|
-
raise e
|
42
|
-
rescue Exception
|
43
|
-
sleep interval
|
44
|
-
# sleep returns truthy value which breaks out of until
|
45
|
-
# must return false value
|
46
|
-
false
|
47
|
-
end)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
result
|
51
|
-
end
|
52
|
-
|
53
21
|
# Return block.call and ignore any exceptions.
|
54
22
|
def ignore &block
|
55
23
|
begin
|
@@ -58,32 +26,6 @@ module Appium
|
|
58
26
|
end
|
59
27
|
end
|
60
28
|
|
61
|
-
# Check every 0.5 seconds to see if block.call returns a truthy value.
|
62
|
-
# Note this isn't a strict boolean true, any truthy value is accepted.
|
63
|
-
# false and nil are considered failures.
|
64
|
-
# Give up after 30 seconds.
|
65
|
-
# @param max_wait [Integer] the maximum time in seconds to wait for
|
66
|
-
# @param interval [Float] the time in seconds to wait after calling the block
|
67
|
-
# @param block [Block] the block to call
|
68
|
-
# @return [Object] the result of block.call
|
69
|
-
def wait_true max_wait=30, interval=0.5, &block
|
70
|
-
max_wait = 1 if max_wait <= 0
|
71
|
-
result = nil
|
72
|
-
timeout max_wait do
|
73
|
-
until (
|
74
|
-
begin
|
75
|
-
result = block.call
|
76
|
-
rescue Errno::ECONNREFUSED => e
|
77
|
-
raise e
|
78
|
-
rescue Exception
|
79
|
-
ensure
|
80
|
-
sleep interval unless result
|
81
|
-
end)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
result
|
85
|
-
end
|
86
|
-
|
87
29
|
# Navigate back.
|
88
30
|
# @return [void]
|
89
31
|
def back
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Appium
|
2
2
|
# Version and Date are defined on the 'Appium' module, not 'Appium::Common'
|
3
|
-
VERSION = '4.
|
4
|
-
DATE = '2014-07-
|
3
|
+
VERSION = '4.1.0' unless defined? ::Appium::VERSION
|
4
|
+
DATE = '2014-07-21' unless defined? ::Appium::DATE
|
5
5
|
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Appium
|
2
|
+
module Common
|
3
|
+
|
4
|
+
# http://mudge.name/2011/01/26/passing-blocks-in-ruby-without-block.html
|
5
|
+
# Note that the Ruby timeout module is avoided. timeout has problems.
|
6
|
+
# https://coderwall.com/p/1novga
|
7
|
+
|
8
|
+
# Wait code from the selenium Ruby gem
|
9
|
+
# https://github.com/SeleniumHQ/selenium/blob/cf501dda3f0ed12233de51ce8170c0e8090f0c20/rb/lib/selenium/webdriver/common/wait.rb
|
10
|
+
def _generic_wait opts={}, &block
|
11
|
+
valid_keys = [:timeout, :interval, :message, :ignore, :return_if_true]
|
12
|
+
invalid_keys = []
|
13
|
+
opts.keys.each { |key| invalid_keys << key unless valid_keys.include?(key) }
|
14
|
+
# [:one, :two] => :one, :two
|
15
|
+
raise "Invalid keys #{invalid_keys.to_s[1..-2]}. Valid keys are #{valid_keys.to_s[1..-2]}" unless invalid_keys.empty?
|
16
|
+
|
17
|
+
timeout = opts.fetch(:timeout, 30)
|
18
|
+
interval = opts.fetch(:interval, 0.5)
|
19
|
+
message = opts[:message]
|
20
|
+
ignored = Array(opts[:ignore] || ::Exception)
|
21
|
+
return_if_true = opts[:return_if_true]
|
22
|
+
|
23
|
+
end_time = Time.now + timeout
|
24
|
+
last_error = nil
|
25
|
+
|
26
|
+
until Time.now > end_time
|
27
|
+
begin
|
28
|
+
if return_if_true
|
29
|
+
result = block.call
|
30
|
+
return result if result
|
31
|
+
else
|
32
|
+
return block.call
|
33
|
+
end
|
34
|
+
rescue ::Errno::ECONNREFUSED => e
|
35
|
+
raise e
|
36
|
+
rescue *ignored => last_error
|
37
|
+
# swallowed
|
38
|
+
end
|
39
|
+
|
40
|
+
sleep interval
|
41
|
+
end
|
42
|
+
|
43
|
+
if message
|
44
|
+
msg = message.dup
|
45
|
+
else
|
46
|
+
msg = "timed out after #{timeout} seconds"
|
47
|
+
end
|
48
|
+
|
49
|
+
msg << " (#{last_error.message})" if last_error
|
50
|
+
|
51
|
+
raise Selenium::WebDriver::Error::TimeOutError, msg
|
52
|
+
end
|
53
|
+
|
54
|
+
# process opts before calling _generic_wait
|
55
|
+
def _process_wait_opts opts
|
56
|
+
opts = { timeout: opts } if opts.is_a?(Numeric)
|
57
|
+
raise 'opts must be a hash' unless opts.is_a? Hash
|
58
|
+
opts
|
59
|
+
end
|
60
|
+
|
61
|
+
# Check every interval seconds to see if block.call returns a truthy value.
|
62
|
+
# Note this isn't a strict boolean true, any truthy value is accepted.
|
63
|
+
# false and nil are considered failures.
|
64
|
+
# Give up after timeout seconds.
|
65
|
+
#
|
66
|
+
# Wait code from the selenium Ruby gem
|
67
|
+
# https://github.com/SeleniumHQ/selenium/blob/cf501dda3f0ed12233de51ce8170c0e8090f0c20/rb/lib/selenium/webdriver/common/wait.rb
|
68
|
+
#
|
69
|
+
# If only a number is provided then it's treated as the timeout value.
|
70
|
+
#
|
71
|
+
# @param [Hash] opts Options
|
72
|
+
# @option opts [Numeric] :timeout (30) Seconds to wait before timing out.
|
73
|
+
# @option opts [Numeric] :interval (0.5) Seconds to sleep between polls.
|
74
|
+
# @option opts [String] :message Exception message if timed out.
|
75
|
+
# @option opts [Array, Exception] :ignore Exceptions to ignore while polling (default: Exception)
|
76
|
+
def wait_true opts={}, &block
|
77
|
+
opts = _process_wait_opts(opts).merge(return_if_true: true)
|
78
|
+
_generic_wait opts, &block
|
79
|
+
end
|
80
|
+
|
81
|
+
# Check every interval seconds to see if block.call doesn't raise an exception.
|
82
|
+
# Give up after timeout seconds.
|
83
|
+
#
|
84
|
+
# Wait code from the selenium Ruby gem
|
85
|
+
# https://github.com/SeleniumHQ/selenium/blob/cf501dda3f0ed12233de51ce8170c0e8090f0c20/rb/lib/selenium/webdriver/common/wait.rb
|
86
|
+
#
|
87
|
+
# If only a number is provided then it's treated as the timeout value.
|
88
|
+
#
|
89
|
+
# @param [Hash] opts Options
|
90
|
+
# @option opts [Numeric] :timeout (30) Seconds to wait before timing out.
|
91
|
+
# @option opts [Numeric] :interval (0.5) Seconds to sleep between polls.
|
92
|
+
# @option opts [String] :message Exception message if timed out.
|
93
|
+
# @option opts [Array, Exception] :ignore Exceptions to ignore while polling (default: Exception)
|
94
|
+
def wait opts={}, &block
|
95
|
+
opts = _process_wait_opts(opts).merge(return_if_true: false)
|
96
|
+
_generic_wait opts, &block
|
97
|
+
end
|
98
|
+
end # module Common
|
99
|
+
end # module Appium
|
@@ -93,6 +93,9 @@ module Appium
|
|
93
93
|
end
|
94
94
|
|
95
95
|
# Convenience method to peform a swipe.
|
96
|
+
#
|
97
|
+
# Note that iOS 7 simulators have broken swipe.
|
98
|
+
#
|
96
99
|
# @option opts [int] :start_x Where to start swiping, on the x axis. Default 0.
|
97
100
|
# @option opts [int] :start_y Where to start swiping, on the y axis. Default 0.
|
98
101
|
# @option opts [int] :end_x Where to end swiping, on the x axis. Default 0.
|
data/lib/appium_lib/driver.rb
CHANGED
@@ -8,6 +8,7 @@ require_relative 'awesome_print/ostruct'
|
|
8
8
|
|
9
9
|
# common
|
10
10
|
require_relative 'common/helper'
|
11
|
+
require_relative 'common/wait'
|
11
12
|
require_relative 'common/patch'
|
12
13
|
require_relative 'common/version'
|
13
14
|
require_relative 'common/element/window'
|
@@ -26,6 +27,7 @@ require_relative 'ios/mobile_methods'
|
|
26
27
|
# android
|
27
28
|
require_relative 'android/helper'
|
28
29
|
require_relative 'android/patch'
|
30
|
+
require_relative 'android/client_xpath'
|
29
31
|
require_relative 'android/element/alert'
|
30
32
|
require_relative 'android/element/button'
|
31
33
|
require_relative 'android/element/generic'
|
@@ -92,8 +94,8 @@ module Appium
|
|
92
94
|
data = Appium::symbolize_keys data
|
93
95
|
ap data unless data.empty? if verbose
|
94
96
|
|
95
|
-
if data && data[:caps] && data[:caps][:app]
|
96
|
-
data[:caps][:app] = Appium::Driver.absolute_app_path data
|
97
|
+
if data && data[:caps] && data[:caps][:app] && !data[:caps][:app].empty?
|
98
|
+
data[:caps][:app] = Appium::Driver.absolute_app_path data
|
97
99
|
end
|
98
100
|
|
99
101
|
# return list of require files as an array
|
@@ -147,12 +149,26 @@ module Appium
|
|
147
149
|
result
|
148
150
|
end
|
149
151
|
|
150
|
-
|
152
|
+
# if modules is a module instead of an array, then the constants of
|
153
|
+
# that module are promoted on.
|
154
|
+
# otherwise, the array of modules will be used as the promotion target.
|
155
|
+
def self.promote_singleton_appium_methods modules
|
151
156
|
raise 'Driver is nil' if $driver.nil?
|
152
|
-
|
157
|
+
|
158
|
+
target_modules = []
|
159
|
+
|
160
|
+
if modules.is_a? Module
|
161
|
+
modules.constants.each do |sub_module|
|
162
|
+
target_modules << modules.const_get(sub_module)
|
163
|
+
end
|
164
|
+
else
|
165
|
+
raise 'modules must be a module or an array' unless modules.is_a? Array
|
166
|
+
target_modules = modules
|
167
|
+
end
|
168
|
+
|
169
|
+
target_modules.each do |const|
|
153
170
|
#noinspection RubyResolve
|
154
171
|
$driver.public_methods(false).each do |m|
|
155
|
-
const = main_module.const_get(sub_module)
|
156
172
|
const.send(:define_singleton_method, m) do |*args, &block|
|
157
173
|
begin
|
158
174
|
super(*args, &block) # promote.rb
|
@@ -264,8 +280,8 @@ module Appium
|
|
264
280
|
|
265
281
|
# Path to the .apk, .app or .app.zip.
|
266
282
|
# The path can be local or remote for Sauce.
|
267
|
-
|
268
|
-
@caps[:app] = self.class.absolute_app_path
|
283
|
+
if @caps && @caps[:app] && ! @caps[:app].empty?
|
284
|
+
@caps[:app] = self.class.absolute_app_path opts
|
269
285
|
end
|
270
286
|
|
271
287
|
# https://code.google.com/p/selenium/source/browse/spec-draft.md?repo=mobile
|
@@ -356,12 +372,24 @@ module Appium
|
|
356
372
|
end
|
357
373
|
|
358
374
|
# Converts app_path to an absolute path.
|
375
|
+
#
|
376
|
+
# opts is the full options hash (caps and appium_lib). If server_url is set
|
377
|
+
# then the app path is used as is.
|
378
|
+
#
|
379
|
+
# if app isn't set then an error is raised.
|
380
|
+
#
|
359
381
|
# @return [String] APP_PATH as an absolute path
|
360
|
-
def self.absolute_app_path
|
361
|
-
raise '
|
382
|
+
def self.absolute_app_path opts
|
383
|
+
raise 'opts must be a hash' unless opts.is_a? Hash
|
384
|
+
caps = opts[:caps] || {}
|
385
|
+
appium_lib_opts = opts[:appium_lib] || {}
|
386
|
+
server_url = appium_lib_opts.fetch :server_url, false
|
387
|
+
|
388
|
+
app_path = caps[:app]
|
389
|
+
raise 'absolute_app_path invoked and app is not set!' if app_path.nil? || app_path.empty?
|
362
390
|
# may be absolute path to file on remote server.
|
363
391
|
# if the file is on the remote server then we can't check if it exists
|
364
|
-
return app_path if
|
392
|
+
return app_path if server_url
|
365
393
|
# Sauce storage API. http://saucelabs.com/docs/rest#storage
|
366
394
|
return app_path if app_path.start_with? 'sauce-storage:'
|
367
395
|
return app_path if app_path.match(/^http/) # public URL for Sauce
|
data/release_notes.md
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
#### v4.1.0 2014-07-21
|
2
|
+
|
3
|
+
- [a13158f](https://github.com/appium/ruby_lib/commit/a13158fb926212d84f26120c3bc5355c8cd34baf) Release 4.1.0
|
4
|
+
- [be1c710](https://github.com/appium/ruby_lib/commit/be1c710134ee9a001b8e71c0ce5cbb5786bebf1e) Update android specs
|
5
|
+
- [4edd949](https://github.com/appium/ruby_lib/commit/4edd94989519ec14acf9c694d9ed3ae6a5939b10) Update self.promote_singleton_appium_methods
|
6
|
+
- [82a236a](https://github.com/appium/ruby_lib/commit/82a236a84742f1eb6d55acab00d8f222d721bd9a) Update docs.md
|
7
|
+
- [a724d5d](https://github.com/appium/ruby_lib/commit/a724d5d3711cdfa3ea420663949c06017189bf02) Fix #224
|
8
|
+
- [d05bfe8](https://github.com/appium/ruby_lib/commit/d05bfe85d6c356a8ea6a0f151aaf21dfef979736) Fix docs
|
9
|
+
- [c04d6f0](https://github.com/appium/ruby_lib/commit/c04d6f048ee4e24c66bef662e31a0d957360e7a7) Fix wait / wait_true by using selenium wait method
|
10
|
+
- [da19c8c](https://github.com/appium/ruby_lib/commit/da19c8cca5753f06576b82bbbb6e77e7e36bcb9c) Add iOS 7 note to swipe
|
11
|
+
- [4f4d800](https://github.com/appium/ruby_lib/commit/4f4d80094eac5a4fbc2c11b8050155b2f767839c) Add client side xpath support
|
12
|
+
|
13
|
+
|
1
14
|
#### v4.0.0 2014-07-05
|
2
15
|
|
3
16
|
- [8cc004a](https://github.com/appium/ruby_lib/commit/8cc004ad04ec087a8a11c06ca0749a5e2c6586a7) Release 4.0.0
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: appium_lib
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- code@bootstraponline.com
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-07-
|
11
|
+
date: 2014-07-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: selenium-webdriver
|
@@ -322,6 +322,7 @@ files:
|
|
322
322
|
- ios_tests/upload/sauce_storage.rb
|
323
323
|
- ios_tests/upload/upload.rb
|
324
324
|
- lib/appium_lib.rb
|
325
|
+
- lib/appium_lib/android/client_xpath.rb
|
325
326
|
- lib/appium_lib/android/element/alert.rb
|
326
327
|
- lib/appium_lib/android/element/button.rb
|
327
328
|
- lib/appium_lib/android/element/generic.rb
|
@@ -335,6 +336,7 @@ files:
|
|
335
336
|
- lib/appium_lib/common/helper.rb
|
336
337
|
- lib/appium_lib/common/patch.rb
|
337
338
|
- lib/appium_lib/common/version.rb
|
339
|
+
- lib/appium_lib/common/wait.rb
|
338
340
|
- lib/appium_lib/device/device.rb
|
339
341
|
- lib/appium_lib/device/multi_touch.rb
|
340
342
|
- lib/appium_lib/device/touch_actions.rb
|