appium_lib 0.0.30 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +8 -8
- data/Rakefile +15 -7
- data/appium_lib.gemspec +3 -3
- data/docs.md +7 -5
- data/lib/appium_lib.rb +34 -6
- data/lib/appium_lib/android/element/alert.rb +43 -0
- data/lib/appium_lib/android/element/generic.rb +94 -0
- data/lib/appium_lib/android/element/textfield.rb +43 -0
- data/lib/appium_lib/android/helper.rb +120 -0
- data/lib/appium_lib/android/patch.rb +10 -0
- data/lib/appium_lib/common/element/button.rb +83 -0
- data/lib/appium_lib/common/element/text.rb +44 -0
- data/lib/appium_lib/common/element/window.rb +9 -0
- data/lib/appium_lib/common/helper.rb +140 -0
- data/lib/appium_lib/common/patch.rb +83 -0
- data/lib/appium_lib/common/version.rb +6 -0
- data/lib/appium_lib/driver.rb +265 -0
- data/lib/appium_lib/ios/element/alert.rb +56 -0
- data/lib/appium_lib/ios/element/generic.rb +170 -0
- data/lib/appium_lib/ios/element/textfield.rb +90 -0
- data/lib/appium_lib/ios/helper.rb +103 -0
- data/lib/appium_lib/ios/patch.rb +15 -0
- data/readme.md +10 -3
- data/release_notes.md +8 -0
- metadata +19 -15
- data/lib/appium_lib/console.rb +0 -254
- data/lib/appium_lib/element/android/alert.rb +0 -45
- data/lib/appium_lib/element/android/generic.rb +0 -88
- data/lib/appium_lib/element/android/textfield.rb +0 -44
- data/lib/appium_lib/element/button.rb +0 -83
- data/lib/appium_lib/element/ios/alert.rb +0 -49
- data/lib/appium_lib/element/ios/generic.rb +0 -140
- data/lib/appium_lib/element/ios/textfield.rb +0 -93
- data/lib/appium_lib/element/text.rb +0 -43
- data/lib/appium_lib/element/window.rb +0 -12
- data/lib/appium_lib/helper.rb +0 -278
- data/lib/appium_lib/patch.rb +0 -90
- data/lib/appium_lib/version.rb +0 -4
@@ -0,0 +1,83 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# UIAButton methods
|
3
|
+
module Appium::Common
|
4
|
+
# Find a button by text and optionally number.
|
5
|
+
# @param text [String, Integer] the text to exactly match. If int then the button at that index is returned.
|
6
|
+
# @param number [Integer] the occurrence of the button matching text. Defaults to the first button.
|
7
|
+
# @return [Button] the button found with text and matching number
|
8
|
+
def button text, number=0
|
9
|
+
# return button at index.
|
10
|
+
return ele_index :button, text if text.is_a? Numeric
|
11
|
+
|
12
|
+
number >= 1 ? button_num( text, number ) :
|
13
|
+
find_ele_by_text_include( :button, text )
|
14
|
+
end
|
15
|
+
|
16
|
+
# Get an array of button texts or button elements if text is provided.
|
17
|
+
# @param text [String] the text to exactly match
|
18
|
+
# @return [Array<String>, Array<Buttons>] either an array of button texts or an array of button elements if text is provided.
|
19
|
+
def buttons text=nil
|
20
|
+
text == nil ? find_eles_attr( :button, :text ) :
|
21
|
+
find_eles_by_text_include( :button, text )
|
22
|
+
end
|
23
|
+
|
24
|
+
# Get the first button element.
|
25
|
+
# @return [Button]
|
26
|
+
def first_button
|
27
|
+
first_ele :button
|
28
|
+
end
|
29
|
+
|
30
|
+
# Get the last button element.
|
31
|
+
# @return [Button]
|
32
|
+
def last_button
|
33
|
+
last_ele :button
|
34
|
+
end
|
35
|
+
|
36
|
+
# Get the first button element that exactly matches text.
|
37
|
+
# @param text [String] the text to match exactly
|
38
|
+
# @return [Button]
|
39
|
+
def button_exact text
|
40
|
+
find_ele_by_text :button, text
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get all button elements that exactly match text.
|
44
|
+
# @param text [String] the text to match exactly
|
45
|
+
# @return [Array<Button>]
|
46
|
+
def buttons_exact text
|
47
|
+
find_eles_by_text :button, text
|
48
|
+
end
|
49
|
+
|
50
|
+
# Get an array of button elements.
|
51
|
+
# @return [Array<Button>]
|
52
|
+
def e_buttons
|
53
|
+
find_eles :button
|
54
|
+
end
|
55
|
+
|
56
|
+
# Expected to be called via button method.
|
57
|
+
#
|
58
|
+
# Get the button element exactly matching text and
|
59
|
+
# occurrence. number=2 means the 2nd occurrence.
|
60
|
+
#
|
61
|
+
# find the second Sign In button
|
62
|
+
#
|
63
|
+
# b = e_button 'Sign In', 2
|
64
|
+
#
|
65
|
+
# Button order will change in iOS vs Android
|
66
|
+
# so if there's no button found at number then
|
67
|
+
# return the first button.
|
68
|
+
#
|
69
|
+
# @param text [String] the text to match
|
70
|
+
# @param number [Integer] the button occurance to return. 1 = first button
|
71
|
+
# @return [Button] the button that matches text and number
|
72
|
+
def button_num text, number=1
|
73
|
+
raise 'Number must be >= 1' if number <= 0
|
74
|
+
number = number - 1 # zero indexed
|
75
|
+
result = nil
|
76
|
+
|
77
|
+
elements = buttons text
|
78
|
+
elements.size > number ? result = elements[number]
|
79
|
+
: result = elements.first
|
80
|
+
|
81
|
+
result
|
82
|
+
end
|
83
|
+
end # module Appium::Common
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# UIAStaticText methods
|
3
|
+
module Appium::Common
|
4
|
+
# s_ prefix for static_text to avoid conflict with generic text methods.
|
5
|
+
|
6
|
+
# Get an array of text texts.
|
7
|
+
# @return [Array<String>]
|
8
|
+
def s_texts
|
9
|
+
find_eles_attr :text, :text
|
10
|
+
end
|
11
|
+
|
12
|
+
# Get an array of text elements.
|
13
|
+
# @return [Array<Text>]
|
14
|
+
def e_s_texts
|
15
|
+
find_eles :text
|
16
|
+
end
|
17
|
+
|
18
|
+
# Get the first text element.
|
19
|
+
# @return [Text]
|
20
|
+
def s_first_text
|
21
|
+
first_ele :text
|
22
|
+
end
|
23
|
+
|
24
|
+
# Get the last text element
|
25
|
+
# @return [Text]
|
26
|
+
def s_last_text
|
27
|
+
last_ele :text
|
28
|
+
end
|
29
|
+
|
30
|
+
# Get the first element that includes text.
|
31
|
+
# @param text [String, Integer] the text to find. If int then the text at that index is returned.
|
32
|
+
# @return [Text]
|
33
|
+
def s_text text
|
34
|
+
return ele_index :text, text if text.is_a? Numeric
|
35
|
+
find_ele_by_text_include :text, text
|
36
|
+
end
|
37
|
+
|
38
|
+
# Get the first textfield that matches text.
|
39
|
+
# @param text [String] the text that the tag must match
|
40
|
+
# @return [Text]
|
41
|
+
def s_text_exact text
|
42
|
+
find_ele_by_text :text, text
|
43
|
+
end
|
44
|
+
end # module Appium::Common
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Generic helper methods not specific
|
3
|
+
# to a particular tag name
|
4
|
+
module Appium::Common
|
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) { until (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
|
+
def xpath xpath_str
|
39
|
+
find_element :xpath, xpath_str
|
40
|
+
end
|
41
|
+
|
42
|
+
def xpaths xpath_str
|
43
|
+
find_elements :xpath, xpath_str
|
44
|
+
end
|
45
|
+
|
46
|
+
# Get the element of type tag_name at matching index.
|
47
|
+
# @param tag_name [String] the tag name to find
|
48
|
+
# @param index [Integer] the index
|
49
|
+
# @return [Element] the found element of type tag_name
|
50
|
+
def ele_index tag_name, index
|
51
|
+
# XPath index starts at 1. ruby_lib index starts at 0
|
52
|
+
find_element :xpath, "//#{tag_name}[#{index + 1}]"
|
53
|
+
end
|
54
|
+
|
55
|
+
# Get all elements exactly matching tag name
|
56
|
+
# @param tag_name [String] the tag name to find
|
57
|
+
# @return [Array<Element>] the found elements of type tag_name
|
58
|
+
def find_eles tag_name
|
59
|
+
@driver.find_elements :tag_name, tag_name
|
60
|
+
end
|
61
|
+
|
62
|
+
# Get the first tag that exactly matches tag and text.
|
63
|
+
# @param tag [String] the tag name to match
|
64
|
+
# @param text [String] the text to exactly match
|
65
|
+
# @return [Element] the element of type tag exactly matching text
|
66
|
+
def find_ele_by_text tag, text
|
67
|
+
@driver.find_element :xpath, %Q(#{tag}[@text='#{text}'])
|
68
|
+
end
|
69
|
+
|
70
|
+
# Get all tags that exactly match tag and text.
|
71
|
+
# @param tag [String] the tag name to match
|
72
|
+
# @param text [String] the text to exactly match
|
73
|
+
# @return [Array<Element>] the elements of type tag exactly matching text
|
74
|
+
def find_eles_by_text tag, text
|
75
|
+
@driver.find_elements :xpath, %Q(#{tag}[@text='#{text}'])
|
76
|
+
end
|
77
|
+
|
78
|
+
# Get the first tag by attribute that exactly matches value.
|
79
|
+
# @param tag [String] the tag name to match
|
80
|
+
# @param attr [String] the attribute to compare
|
81
|
+
# @param value [String] the value of the attribute that the element must include
|
82
|
+
# @return [Element] the element of type tag who's attribute includes value
|
83
|
+
def find_ele_by_attr_include tag, attr, value
|
84
|
+
@driver.find_element :xpath, %Q(#{tag}[contains(@#{attr}, '#{value}')])
|
85
|
+
end
|
86
|
+
|
87
|
+
# Get tags by attribute that include value.
|
88
|
+
# @param tag [String] the tag name to match
|
89
|
+
# @param attr [String] the attribute to compare
|
90
|
+
# @param value [String] the value of the attribute that the element must include
|
91
|
+
# @return [Array<Element>] the elements of type tag who's attribute includes value
|
92
|
+
def find_eles_by_attr_include tag, attr, value
|
93
|
+
@driver.find_elements :xpath, %Q(#{tag}[contains(@#{attr}, '#{value}')])
|
94
|
+
end
|
95
|
+
|
96
|
+
# Get the first tag that includes text.
|
97
|
+
# @param tag [String] the tag name to match
|
98
|
+
# @param text [String] the text the element must include
|
99
|
+
# @return [Element] the element of type tag that includes text
|
100
|
+
# element.attribute(:text).include? text
|
101
|
+
def find_ele_by_text_include tag, text
|
102
|
+
find_ele_by_attr_include tag, :text, text
|
103
|
+
end
|
104
|
+
|
105
|
+
# Get the tags that include text.
|
106
|
+
# @param tag [String] the tag name to match
|
107
|
+
# @param text [String] the text the element must include
|
108
|
+
# @return [Array<Element>] the elements of type tag that includes text
|
109
|
+
# element.attribute(:text).include? text
|
110
|
+
def find_eles_by_text_include tag, text
|
111
|
+
find_eles_by_attr_include tag, :text, text
|
112
|
+
end
|
113
|
+
|
114
|
+
# Get the first tag that matches tag_name
|
115
|
+
# @param tag_name [String] the tag to match
|
116
|
+
# @return [Element]
|
117
|
+
def first_ele tag_name
|
118
|
+
# XPath index starts at 1
|
119
|
+
find_element :xpath, "//#{tag_name}[1]"
|
120
|
+
end
|
121
|
+
|
122
|
+
# Get the last tag that matches tag_name
|
123
|
+
# @param tag_name [String] the tag to match
|
124
|
+
# @return [Element]
|
125
|
+
def last_ele tag_name
|
126
|
+
xpath "//#{tag_name}[last()]"
|
127
|
+
end
|
128
|
+
|
129
|
+
# Prints a JSON view of the current page
|
130
|
+
# @return [void]
|
131
|
+
def source
|
132
|
+
ap get_source
|
133
|
+
end
|
134
|
+
|
135
|
+
# Gets a JSON view of the current page
|
136
|
+
# @return [JSON]
|
137
|
+
def get_source
|
138
|
+
JSON.parse(@driver.page_source)
|
139
|
+
end
|
140
|
+
end # module Appium::Common
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Appium::Common
|
3
|
+
# Implement useful features for element.
|
4
|
+
class Selenium::WebDriver::Element
|
5
|
+
# Note: For testing .text should be used over value, and name.
|
6
|
+
|
7
|
+
# Fixes NoMethodError: undefined method `value' for #<Selenium::WebDriver::Element:0x..fa4a9148235390a44 id="1">
|
8
|
+
def value
|
9
|
+
self.attribute :value
|
10
|
+
end
|
11
|
+
|
12
|
+
# Fixes NoMethodError: undefined method `name' for #<Selenium::WebDriver::Element
|
13
|
+
def name
|
14
|
+
self.attribute :name
|
15
|
+
end
|
16
|
+
|
17
|
+
# Use tag_name to get element's type.
|
18
|
+
#
|
19
|
+
# Tag name appears to be the same as type.
|
20
|
+
#
|
21
|
+
# Fixes Selenium::WebDriver::Error::UnknownError: Not yet implemented
|
22
|
+
def tag_name
|
23
|
+
self.attribute :type
|
24
|
+
end
|
25
|
+
|
26
|
+
# For use with mobile tap.
|
27
|
+
#
|
28
|
+
# execute_script 'mobile: tap', :x => 0.0, :y => 0.98
|
29
|
+
#
|
30
|
+
# https://github.com/appium/appium/wiki/Automating-mobile-gestures
|
31
|
+
# @return [OpenStruct] the relative x, y in a struct. ex: { x: 0.50, y: 0.20 }
|
32
|
+
def location_rel
|
33
|
+
xy = self.location
|
34
|
+
w = window_size
|
35
|
+
OpenStruct.new( x: xy.x.to_f / w.width.to_f,
|
36
|
+
y: xy.y.to_f / w.height.to_f )
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end # module Appium::Common
|
40
|
+
|
41
|
+
# Print JSON posted to Appium. Not scoped to an Appium module.
|
42
|
+
#
|
43
|
+
# Requires from lib/selenium/webdriver/remote.rb
|
44
|
+
require 'selenium/webdriver/remote/capabilities'
|
45
|
+
require 'selenium/webdriver/remote/bridge'
|
46
|
+
require 'selenium/webdriver/remote/server_error'
|
47
|
+
require 'selenium/webdriver/remote/response'
|
48
|
+
require 'selenium/webdriver/remote/commands'
|
49
|
+
require 'selenium/webdriver/remote/http/common'
|
50
|
+
require 'selenium/webdriver/remote/http/default'
|
51
|
+
|
52
|
+
# Show http calls to the Selenium server.
|
53
|
+
#
|
54
|
+
# Invaluable for debugging.
|
55
|
+
module Selenium::WebDriver::Remote
|
56
|
+
class Bridge
|
57
|
+
# Code from lib/selenium/webdriver/remote/bridge.rb
|
58
|
+
def raw_execute(command, opts = {}, command_hash = nil)
|
59
|
+
verb, path = COMMANDS[command] || raise(ArgumentError, "unknown command: #{command.inspect}")
|
60
|
+
path = path.dup
|
61
|
+
|
62
|
+
path[':session_id'] = @session_id if path.include?(':session_id')
|
63
|
+
|
64
|
+
begin
|
65
|
+
opts.each { |key, value| path[key.inspect] = escaper.escape(value.to_s) }
|
66
|
+
rescue IndexError
|
67
|
+
raise ArgumentError, "#{opts.inspect} invalid for #{command.inspect}"
|
68
|
+
end
|
69
|
+
|
70
|
+
# change path from session/efac972c-941a-499c-803c-d7d008749/execute
|
71
|
+
# to /execute
|
72
|
+
# path may be nil, session, or not have anything after the session_id.
|
73
|
+
path_str = ''
|
74
|
+
path_match = path.match /.*\h{8}-\h{4}-\h{4}-\h{4}-\h{12}/
|
75
|
+
path_str = path.sub(path_match[0], '') unless path_match.nil?
|
76
|
+
|
77
|
+
puts "#{verb} #{path_str}"
|
78
|
+
puts command_hash.to_json unless command_hash.to_json == 'null'
|
79
|
+
# puts "verb: #{verb}, path #{path}, command_hash #{command_hash.to_json}"
|
80
|
+
http.call verb, path, command_hash
|
81
|
+
end # def
|
82
|
+
end # class
|
83
|
+
end if defined? Pry # module Selenium::WebDriver::Remote
|
@@ -0,0 +1,265 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
=begin
|
3
|
+
Based on simple_test.rb
|
4
|
+
https://github.com/appium/appium/blob/82995f47408530c80c3376f4e07a1f649d96ba22/sample-code/examples/ruby/simple_test.rb
|
5
|
+
https://github.com/appium/appium/blob/c58eeb66f2d6fa3b9a89d188a2e657cca7cb300f/LICENSE
|
6
|
+
=end
|
7
|
+
module Appium
|
8
|
+
add_to_path __FILE__
|
9
|
+
|
10
|
+
require 'selenium-webdriver'
|
11
|
+
|
12
|
+
# common
|
13
|
+
require 'common/helper'
|
14
|
+
require 'common/patch'
|
15
|
+
require 'common/version'
|
16
|
+
require 'common/element/button'
|
17
|
+
require 'common/element/text'
|
18
|
+
require 'common/element/window'
|
19
|
+
|
20
|
+
# ios
|
21
|
+
require 'ios/helper'
|
22
|
+
require 'ios/patch'
|
23
|
+
require 'ios/element/alert'
|
24
|
+
require 'ios/element/generic'
|
25
|
+
require 'ios/element/textfield'
|
26
|
+
|
27
|
+
# android
|
28
|
+
require 'android/helper'
|
29
|
+
require 'android/patch'
|
30
|
+
require 'android/element/alert'
|
31
|
+
require 'android/element/generic'
|
32
|
+
require 'android/element/textfield'
|
33
|
+
|
34
|
+
class Driver
|
35
|
+
def initialize opts={}
|
36
|
+
opts = {} if opts.nil?
|
37
|
+
# Path to the .apk, .app or .app.zip.
|
38
|
+
# The path can be local or remote for Sauce.
|
39
|
+
@app_path = opts.fetch 'APP_PATH', ENV['APP_PATH']
|
40
|
+
raise 'APP_PATH must be set.' if @app_path.nil?
|
41
|
+
|
42
|
+
# The name to use for the test run on Sauce.
|
43
|
+
@app_name = opts.fetch 'APP_NAME', ENV['APP_NAME']
|
44
|
+
|
45
|
+
# Android app package
|
46
|
+
@app_package = opts.fetch 'APP_PACKAGE', ENV['APP_PACKAGE']
|
47
|
+
|
48
|
+
# Android app starting activity.
|
49
|
+
@app_activity = opts.fetch 'APP_ACTIVITY', ENV['APP_ACTIVITY']
|
50
|
+
|
51
|
+
# Android app waiting activity
|
52
|
+
@app_wait_activity = opts.fetch 'APP_WAIT_ACTIVITY', ENV['APP_WAIT_ACTIVITY']
|
53
|
+
|
54
|
+
# Sauce Username
|
55
|
+
@sauce_username = opts.fetch'SAUCE_USERNAME', ENV['SAUCE_USERNAME']
|
56
|
+
|
57
|
+
# Sauce Key
|
58
|
+
@sauce_access_key = opts.fetch 'SAUCE_ACCESS_KEY', ENV['SAUCE_ACCESS_KEY']
|
59
|
+
|
60
|
+
@port = opts.fetch 'PORT', ENV['PORT'] || 4723
|
61
|
+
|
62
|
+
@os = :ios
|
63
|
+
@os = :android if @app_path.end_with?('.apk') || @app_path.end_with?('.apk.zip')
|
64
|
+
puts "OS is: #{@os}" if defined?(Pry)
|
65
|
+
|
66
|
+
# load common methods
|
67
|
+
extend Appium::Common
|
68
|
+
if @os == :android
|
69
|
+
raise 'APP_ACTIVITY must be set.' if @app_activity.nil?
|
70
|
+
|
71
|
+
# load Android specific methods
|
72
|
+
extend Appium::Android
|
73
|
+
else
|
74
|
+
# load iOS specific methods
|
75
|
+
extend Appium::Ios
|
76
|
+
end
|
77
|
+
|
78
|
+
# Save global reference to last created Appium driver for top level methods.
|
79
|
+
$last_driver = self
|
80
|
+
end # def initialize
|
81
|
+
|
82
|
+
# WebDriver capabilities. Must be valid for Sauce to work.
|
83
|
+
# https://github.com/jlipps/appium/blob/master/app/android.js
|
84
|
+
def android_capabilities
|
85
|
+
{
|
86
|
+
browserName: 'Android',
|
87
|
+
platform: 'LINUX',
|
88
|
+
version: '4.1',
|
89
|
+
device: 'Android',
|
90
|
+
name: @app_name || 'Ruby Console Android Appium',
|
91
|
+
app: absolute_app_path,
|
92
|
+
:'app-package' => @app_package,
|
93
|
+
:'app-activity' => @app_activity,
|
94
|
+
:'app-wait-activity' => @app_wait_activity
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
# WebDriver capabilities. Must be valid for Sauce to work.
|
99
|
+
def ios_capabilities
|
100
|
+
{
|
101
|
+
browserName: 'iOS 6.0',
|
102
|
+
platform: 'Mac 10.8',
|
103
|
+
version: '6.0',
|
104
|
+
device: 'iPhone Simulator',
|
105
|
+
name: @app_name || 'Ruby Console iOS Appium',
|
106
|
+
app: absolute_app_path
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
def capabilities
|
111
|
+
@os == :ios ? ios_capabilities : android_capabilities
|
112
|
+
end
|
113
|
+
|
114
|
+
# Converts environment variable APP_PATH to an absolute path.
|
115
|
+
# @return [String] APP_PATH as an absolute path
|
116
|
+
def absolute_app_path
|
117
|
+
raise 'APP_PATH environment variable not set!' if @app_path.nil? || @app_path.empty?
|
118
|
+
return @app_path if @app_path.match(/^http/) # public URL for Sauce
|
119
|
+
if @app_path.match(/^\//) # absolute file path
|
120
|
+
raise "App doesn't exist. #{@app_path}" unless File.exist? @app_path
|
121
|
+
return @app_path
|
122
|
+
end
|
123
|
+
file = File.join(File.dirname(__FILE__), @app_path)
|
124
|
+
raise "App doesn't exist #{file}" unless File.exist? file
|
125
|
+
file
|
126
|
+
end
|
127
|
+
|
128
|
+
# Get the server url for sauce or local based on env vars.
|
129
|
+
# @return [String] the server url
|
130
|
+
def server_url
|
131
|
+
if !@sauce_username.nil? && !@sauce_access_key.nil?
|
132
|
+
"http://#{@sauce_username}:#{@sauce_access_key}@ondemand.saucelabs.com:80/wd/hub"
|
133
|
+
else
|
134
|
+
"http://127.0.0.1:#{@port}/wd/hub"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Restarts the driver
|
139
|
+
def restart
|
140
|
+
driver_quit
|
141
|
+
start_driver
|
142
|
+
end
|
143
|
+
|
144
|
+
# Quits the driver
|
145
|
+
# @return [void]
|
146
|
+
def driver_quit
|
147
|
+
# rescue NoSuchDriverError
|
148
|
+
begin; @driver.quit unless @driver.nil?; rescue; end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Creates a new global driver and quits the old one if it exists.
|
152
|
+
# @return [Selenium::WebDriver] the new global driver
|
153
|
+
def start_driver wait=30
|
154
|
+
@client = @client || Selenium::WebDriver::Remote::Http::Default.new
|
155
|
+
@client.timeout = 999999
|
156
|
+
|
157
|
+
@default_wait = wait
|
158
|
+
|
159
|
+
begin
|
160
|
+
@driver = Selenium::WebDriver.for :remote, http_client: @client, desired_capabilities: capabilities, url: server_url
|
161
|
+
rescue Errno::ECONNREFUSED
|
162
|
+
raise 'ERROR: Unable to connect to Appium. Is the server running?'
|
163
|
+
end
|
164
|
+
|
165
|
+
# Set timeout to a large number so that Appium doesn't quit
|
166
|
+
# when no commands are entered after 60 seconds.
|
167
|
+
mobile :setCommandTimeout, timeout: 9999
|
168
|
+
|
169
|
+
# Set implicit wait by default unless we're using Pry.
|
170
|
+
@driver.manage.timeouts.implicit_wait = @default_wait unless defined? Pry
|
171
|
+
|
172
|
+
@driver
|
173
|
+
end
|
174
|
+
|
175
|
+
# Set implicit wait to zero.
|
176
|
+
def no_wait
|
177
|
+
@driver.manage.timeouts.implicit_wait = 0
|
178
|
+
end
|
179
|
+
|
180
|
+
# Set implicit wait to timeout, defaults to 30.
|
181
|
+
# @param timeout [Integer] the timeout in seconds
|
182
|
+
# @return [void]
|
183
|
+
def set_wait timeout=@default_wait
|
184
|
+
@driver.manage.timeouts.implicit_wait = timeout
|
185
|
+
end
|
186
|
+
|
187
|
+
# Returns the default client side wait.
|
188
|
+
# This value is independent of what the server is using
|
189
|
+
# @return [Integer]
|
190
|
+
def get_wait
|
191
|
+
@default_wait
|
192
|
+
end
|
193
|
+
|
194
|
+
# Returns existence of element.
|
195
|
+
#
|
196
|
+
# Example:
|
197
|
+
#
|
198
|
+
# exists { button('sign in') } ? puts('true') : puts('false')
|
199
|
+
#
|
200
|
+
# @return [Boolean]
|
201
|
+
def exists &search_block
|
202
|
+
pre_check = 0
|
203
|
+
post_check = @default_wait
|
204
|
+
|
205
|
+
set_wait pre_check # set wait to zero
|
206
|
+
|
207
|
+
# the element exists unless an error is raised.
|
208
|
+
exists = true
|
209
|
+
|
210
|
+
begin
|
211
|
+
search_block.call # search for element
|
212
|
+
rescue
|
213
|
+
exists = false # error means it's not there
|
214
|
+
end
|
215
|
+
|
216
|
+
# restore wait
|
217
|
+
set_wait post_check
|
218
|
+
|
219
|
+
exists
|
220
|
+
end
|
221
|
+
|
222
|
+
# The same as @driver.execute_script
|
223
|
+
# @return [Object] the object returned by execute_script
|
224
|
+
def execute_script script, *args
|
225
|
+
@driver.execute_script script, *args
|
226
|
+
end
|
227
|
+
|
228
|
+
# Helper method for mobile gestures
|
229
|
+
#
|
230
|
+
# https://github.com/appium/appium/wiki/Automating-mobile-gestures
|
231
|
+
#
|
232
|
+
# @driver.execute_script 'mobile: swipe', endX: 100, endY: 100, duration: 0.01
|
233
|
+
#
|
234
|
+
# becomes
|
235
|
+
#
|
236
|
+
# mobile :swipe, endX: 100, endY: 100, duration: 0.01
|
237
|
+
def mobile method, *args
|
238
|
+
raise 'Method must not be nil' if method.nil?
|
239
|
+
raise 'Method must have .to_s' unless method.respond_to? :to_s
|
240
|
+
@driver.execute_script "mobile: #{method.to_s}", *args
|
241
|
+
end
|
242
|
+
|
243
|
+
# Calls @driver.find_elements
|
244
|
+
def find_elements *args
|
245
|
+
@driver.find_elements *args
|
246
|
+
end
|
247
|
+
|
248
|
+
# Calls @driver.find_elements
|
249
|
+
def find_element *args
|
250
|
+
@driver.find_element *args
|
251
|
+
end
|
252
|
+
|
253
|
+
# Quit the driver and Pry.
|
254
|
+
# quit and exit are reserved by Pry.
|
255
|
+
def x
|
256
|
+
driver_quit
|
257
|
+
exit # exit pry
|
258
|
+
end
|
259
|
+
end # end class Driver
|
260
|
+
end # end module Appium
|
261
|
+
|
262
|
+
# Paging in Pry is annoying :q required to exit.
|
263
|
+
# With pager disabled, the output is similar to IRB
|
264
|
+
# Only set if Pry is defined.
|
265
|
+
Pry.config.pager = false if defined?(Pry)
|