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.
@@ -8,7 +8,7 @@ describe 'common/helper.rb' do
8
8
  before_first
9
9
  end
10
10
 
11
- wait_time = [0.2, 0.2] # max_wait, interval
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(*wait_time) { true }
25
- wait(*wait_time) { false }
26
- wait(*wait_time) { nil }
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(*wait_time) { raise } }.must_raise Timeout::Error
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(*wait_time) { raise NoMemoryError } }.must_raise Timeout::Error
34
- proc { wait(0.2, 0.0) { raise NoMemoryError } }.must_raise Timeout::Error
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(*wait_time) { true }
52
+ wait_true(wait_opts) { true }
50
53
 
51
54
  # failed wait should error
52
- proc { wait_true(*wait_time) { false } }.must_raise Timeout::Error
53
- proc { wait_true(*wait_time) { nil } }.must_raise Timeout::Error
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(*wait_time) { raise } }.must_raise Timeout::Error
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(*wait_time) { raise NoMemoryError } }.must_raise Timeout::Error
61
- proc { wait_true(0.2, 0.0) { raise NoMemoryError } }.must_raise Timeout::Error
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.0.0' unless defined? ::Appium::VERSION
4
- DATE = '2014-07-05' unless defined? ::Appium::DATE
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.
@@ -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[:caps][:app]
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
- def self.promote_singleton_appium_methods main_module
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
- main_module.constants.each do |sub_module|
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
- unless !@caps || @caps[:app].nil? || @caps[:app].empty?
268
- @caps[:app] = self.class.absolute_app_path @caps[:app]
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 app_path
361
- raise 'APP_PATH not set!' if app_path.nil? || app_path.empty?
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 @custom_url
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.0.0
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-05 00:00:00.000000000 Z
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