appium_lib_core 0.1.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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rubocop.yml +20 -0
  4. data/.travis.yml +16 -0
  5. data/CHANGELOG.md +11 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +202 -0
  8. data/README.md +5 -0
  9. data/Rakefile +62 -0
  10. data/Thorfile +7 -0
  11. data/appium_lib_core.gemspec +35 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/lib/appium_lib_core.rb +41 -0
  15. data/lib/appium_lib_core/android.rb +5 -0
  16. data/lib/appium_lib_core/android/device.rb +174 -0
  17. data/lib/appium_lib_core/android/espresso/bridge.rb +16 -0
  18. data/lib/appium_lib_core/android/search_context.rb +18 -0
  19. data/lib/appium_lib_core/android/touch.rb +17 -0
  20. data/lib/appium_lib_core/android/uiautomator1/bridge.rb +16 -0
  21. data/lib/appium_lib_core/android/uiautomator2/bridge.rb +16 -0
  22. data/lib/appium_lib_core/android_espresso.rb +5 -0
  23. data/lib/appium_lib_core/android_uiautomator2.rb +5 -0
  24. data/lib/appium_lib_core/common.rb +6 -0
  25. data/lib/appium_lib_core/common/base.rb +8 -0
  26. data/lib/appium_lib_core/common/base/bridge.rb +47 -0
  27. data/lib/appium_lib_core/common/base/capabilities.rb +16 -0
  28. data/lib/appium_lib_core/common/base/command.rb +10 -0
  29. data/lib/appium_lib_core/common/base/driver.rb +51 -0
  30. data/lib/appium_lib_core/common/base/http_default.rb +13 -0
  31. data/lib/appium_lib_core/common/base/search_context.rb +89 -0
  32. data/lib/appium_lib_core/common/base/wait.rb +56 -0
  33. data/lib/appium_lib_core/common/command.rb +77 -0
  34. data/lib/appium_lib_core/common/device.rb +567 -0
  35. data/lib/appium_lib_core/common/error.rb +18 -0
  36. data/lib/appium_lib_core/common/log.rb +30 -0
  37. data/lib/appium_lib_core/common/logger.rb +30 -0
  38. data/lib/appium_lib_core/device/multi_touch.rb +48 -0
  39. data/lib/appium_lib_core/device/touch_actions.rb +191 -0
  40. data/lib/appium_lib_core/driver.rb +459 -0
  41. data/lib/appium_lib_core/ios.rb +7 -0
  42. data/lib/appium_lib_core/ios/device.rb +54 -0
  43. data/lib/appium_lib_core/ios/search_context.rb +27 -0
  44. data/lib/appium_lib_core/ios/touch.rb +17 -0
  45. data/lib/appium_lib_core/ios/uiautomation/bridge.rb +18 -0
  46. data/lib/appium_lib_core/ios/uiautomation/patch.rb +20 -0
  47. data/lib/appium_lib_core/ios/xcuitest/bridge.rb +18 -0
  48. data/lib/appium_lib_core/ios/xcuitest/device.rb +66 -0
  49. data/lib/appium_lib_core/ios/xcuitest/search_context.rb +40 -0
  50. data/lib/appium_lib_core/ios_xcuitest.rb +8 -0
  51. data/lib/appium_lib_core/patch.rb +78 -0
  52. data/lib/appium_lib_core/version.rb +6 -0
  53. data/release_notes.md +0 -0
  54. metadata +262 -0
@@ -0,0 +1,18 @@
1
+ module Appium
2
+ module Core
3
+ module Error
4
+ class CoreError < StandardError; end
5
+
6
+ # Capability related errors
7
+ class NoCapabilityError < CoreError; end
8
+ class CapabilityStructureError < CoreError; end
9
+
10
+ # Appium related errors
11
+ class NotSupportedAppiumServer < CoreError; end
12
+ class NoSuchElementError < CoreError; end
13
+
14
+ # Server side error
15
+ class ServerError; end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,30 @@
1
+ module Appium
2
+ module Core
3
+ class Logs
4
+ def initialize(bridge)
5
+ @bridge = bridge
6
+ end
7
+
8
+ # @param [String|Hash] type You can get particular type's logs.
9
+ # @return [[Selenium::WebDriver::LogEntry]] A list of logs data.
10
+ #
11
+ # @example
12
+ # Appium::Core::Logs.new(driver).get("syslog") #=> [[Selenium::WebDriver::LogEntry]]
13
+ # Appium::Core::Logs.new(driver).get(:syslog) #=> [[Selenium::WebDriver::LogEntry]]
14
+ #
15
+ def get(type)
16
+ @bridge.get type
17
+ end
18
+
19
+ # Get a list of available log types
20
+ #
21
+ # @return [[Hash]] A list of available log types.
22
+ # @example
23
+ # Appium::Core::Logs.new(driver).available_types #=> [:syslog, :crashlog, :performance]
24
+ #
25
+ def available_types
26
+ @bridge.available_types
27
+ end
28
+ end
29
+ end # module Core
30
+ end # module Appium
@@ -0,0 +1,30 @@
1
+ require 'forwardable'
2
+ require 'logger'
3
+
4
+ module Appium
5
+ module Logger
6
+ #
7
+ # @example Use logger manually
8
+ # Appium::Logger.debug('This is info message')
9
+ # Appium::Logger.warn('This is warning message')
10
+ #
11
+ class << self
12
+ extend Forwardable
13
+ def_delegators :logger, :fatal, :error, :warn, :info, :debug, :level, :level=, :formatter, :formatter=
14
+
15
+ attr_writer :logger
16
+
17
+ private
18
+
19
+ def logger
20
+ @logger ||= begin
21
+ logger = ::Logger.new($stdout)
22
+ logger.progname = 'ruby_lib'
23
+ logger.level = ::Logger::WARN
24
+ logger.formatter = proc { |_severity, _datetime, _progname, msg| "#{msg}\n" }
25
+ logger
26
+ end
27
+ end
28
+ end # class << self
29
+ end # module Logger
30
+ end # module Appium
@@ -0,0 +1,48 @@
1
+ module Appium
2
+ module Core
3
+ # MultiTouch actions allow for multiple touches to happen at the same time,
4
+ # for instance, to simulate multiple finger swipes.
5
+ #
6
+ # Create a series of touch actions by themselves (without a `prepare()`), then
7
+ # add to a new MultiTouch action. When ready, call `prepare()` and all
8
+ # actions will be executed simultaneously.
9
+ #
10
+ # @example
11
+ #
12
+ # action_1 = TouchAction.new.press(x: 45, y: 100).wait(5).release
13
+ # action_2 = TouchAction.new.tap(element: el, x: 50, y:5, count: 3)
14
+ #
15
+ # multi_touch_action = MultiTouch.new
16
+ # multi_touch_action.add action_1
17
+ # multi_touch_action.add action_2
18
+ # multi_touch_action.perform
19
+ #
20
+ # # with an arbitrary driver
21
+ # driver = Appium::Driver.new(opts, false).start_driver
22
+ # multi_touch_action = MultiTouch.new(driver)
23
+ # multi_touch_action.add action_1
24
+ # multi_touch_action.add action_2
25
+ # multi_touch_action.perform
26
+ #
27
+ class MultiTouch
28
+ attr_reader :driver
29
+
30
+ def initialize(driver)
31
+ @actions = []
32
+ @driver = driver
33
+ end
34
+
35
+ # Add a touch_action to be performed
36
+ # @param chain (TouchAction) The action to add to the chain
37
+ def add(chain)
38
+ @actions << chain.actions
39
+ end
40
+
41
+ # Ask Appium to perform the actions
42
+ def perform
43
+ @driver.multi_touch @actions
44
+ @actions.clear
45
+ end
46
+ end # class MultiTouch
47
+ end # module Core
48
+ end # module Appium
@@ -0,0 +1,191 @@
1
+ module Appium
2
+ # Perform a series of gestures, one after another. Gestures are chained
3
+ # together and only performed when `perform()` is called. Default is conducted by global driver.
4
+ #
5
+ # Each method returns the object itself, so calls can be chained.
6
+ #
7
+ # ```ruby
8
+ # action = TouchAction(@driver).new.press(x: 45, y: 100).wait(5).release
9
+ # action.perform
10
+ # action = TouchAction.new.swipe(....)
11
+ # action.perform
12
+ # ```
13
+ module Core
14
+ class TouchAction
15
+ ACTIONS = [:move_to, :long_press, :double_tap, :two_finger_tap, :press, :release, :tap, :wait, :perform].freeze
16
+ COMPLEX_ACTIONS = [:swipe].freeze
17
+
18
+ attr_reader :actions, :driver
19
+
20
+ def initialize(driver)
21
+ @actions = []
22
+ @driver = driver
23
+ end
24
+
25
+ # Move to the given co-ordinates.
26
+ #
27
+ # `move_to`'s `x` and `y` have two case. One is working as coordinate, the other is working as offset.
28
+ #
29
+ # @option opts [integer] :x x co-ordinate to move to if element isn't set. Works as an offset if x is set with Element.
30
+ # @option opts [integer] :y y co-ordinate to move to if element isn't set. Works as an offset if y is set with Element.
31
+ # @option opts [WebDriver::Element] Element to scope this move within.
32
+ def move_to(opts)
33
+ opts = args_with_ele_ref(opts)
34
+ chain_method(:moveTo, opts)
35
+ end
36
+
37
+ # Press down for a specific duration.
38
+ # Alternatively, you can use `press(...).wait(...).release()` instead of `long_press` if duration doesn't work well.
39
+ # https://github.com/appium/ruby_lib/issues/231#issuecomment-269895512
40
+ # e.g. Appium::TouchAction.new.press(x: 280, y: 530).wait(2000).release.perform
41
+ #
42
+ # @option element [WebDriver::Element] the element to press.
43
+ # @option x [integer] x co-ordinate to press on.
44
+ # @option y [integer] y co-ordinate to press on.
45
+ # @option duration [integer] Number of milliseconds to press.
46
+ def long_press(opts)
47
+ args = opts.select { |k, _v| [:element, :x, :y, :duration].include? k }
48
+ args = args_with_ele_ref(args)
49
+ chain_method(:longPress, args) # longPress is what the appium server expects
50
+ end
51
+
52
+ # Press a finger onto the screen. Finger will stay down until you call
53
+ # `release`.
54
+ #
55
+ # @option opts [WebDriver::Element] :element (Optional) Element to press within.
56
+ # @option opts [integer] :x x co-ordinate to press on
57
+ # @option opts [integer] :y y co-ordinate to press on
58
+ def press(opts)
59
+ args = opts.select { |k, _v| [:element, :x, :y].include? k }
60
+ args = args_with_ele_ref(args)
61
+ chain_method(:press, args)
62
+ end
63
+
64
+ # Remove a finger from the screen.
65
+ #
66
+ # @option opts [WebDriver::Element] :element (Optional) Element to release from.
67
+ # @option opts [integer] :x x co-ordinate to release from
68
+ # @option opts [integer] :y y co-ordinate to release from
69
+ def release(opts = nil)
70
+ args = args_with_ele_ref(opts) if opts
71
+ chain_method(:release, args)
72
+ end
73
+
74
+ # Touch a point on the screen.
75
+ # Alternatively, you can use `press(...).release.perform` instead of `tap(...).perform`.
76
+ #
77
+ # @option opts [WebDriver::Element] :element (Optional) Element to restrict scope too.
78
+ # @option opts [integer] :x x co-ordinate to tap
79
+ # @option opts [integer] :y y co-ordinate to tap
80
+ # @option opts [integer] :fingers how many fingers to tap with (Default 1)
81
+ def tap(opts)
82
+ opts[:count] = opts.delete(:fingers) if opts[:fingers]
83
+ opts[:count] ||= 1
84
+ args = args_with_ele_ref opts
85
+ chain_method(:tap, args)
86
+ end
87
+
88
+ # Double tap an element on the screen
89
+ #
90
+ # @option opts [WebDriver::Element] :element (Optional) Element to restrict scope too.
91
+ # @option opts [integer] :x x co-ordinate to tap
92
+ # @option opts [integer] :y y co-ordinate to tap
93
+
94
+ def double_tap(opts)
95
+ args = opts.select { |k, _v| [:element, :x, :y].include? k }
96
+ args = args_with_ele_ref(args)
97
+ chain_method(:doubleTap, args) # doubleTap is what the appium server expects
98
+ end
99
+
100
+ # Two finger tap an element on the screen
101
+ #
102
+ # @option opts [WebDriver::Element] :element (Optional) Element to restrict scope too.
103
+ # @option opts [integer] :x x co-ordinate to tap
104
+ # @option opts [integer] :y y co-ordinate to tap
105
+ def two_finger_tap(opts)
106
+ args = opts.select { |k, _v| [:element, :x, :y].include? k }
107
+ args = args_with_ele_ref(args)
108
+ chain_method(:twoFingerTap, args) # twoFingerTap is what the appium server expects
109
+ end
110
+
111
+ # Pause for a number of milliseconds before the next action
112
+ # @param milliseconds [integer] Number of milliseconds to pause for
113
+ def wait(milliseconds)
114
+ args = { ms: milliseconds }
115
+ chain_method(:wait, args)
116
+ end
117
+
118
+ # Convenience method to peform a swipe.
119
+ #
120
+ # Note that iOS 7 simulators have broken swipe.
121
+ #
122
+ # For iOS: Use `offset_x` and `offset_y` to define the end point.
123
+ #
124
+ # For Android: Use `end_x` and `end_y` to define the end point.
125
+ #
126
+ # If you'd like more details, please read tests and its log samples in
127
+ # `ios_tests/lib/ios/specs/device/touch_actions.rb` and `ios_tests/lib/ios/specs/device/touch_actions.rb`
128
+ #
129
+ # @option opts [int] :start_x Where to start swiping, on the x axis. Default 0.
130
+ # @option opts [int] :start_y Where to start swiping, on the y axis. Default 0.
131
+ # @option opts [int] :offset_x Offset, on the x axis. Default 0.
132
+ # @option opts [int] :offset_y Offset, on the y axis. Default 0.
133
+ # @option opts [int] :duration How long the actual swipe takes to complete in milliseconds. Default 200.
134
+ def swipe(opts, ele = nil)
135
+ start_x = opts.fetch :start_x, 0
136
+ start_y = opts.fetch :start_y, 0
137
+ offset_x = opts.fetch :offset_x, 0
138
+ offset_y = opts.fetch :offset_y, 0
139
+ duration = opts.fetch :duration, 200
140
+
141
+ coordinates = swipe_coordinates(start_x: start_x, start_y: start_y, offset_x: offset_x, offset_y: offset_y)
142
+
143
+ if ele # pinch/zoom for XCUITest
144
+ press x: start_x, y: start_y, element: ele
145
+ move_to x: coordinates[:offset_x], y: coordinates[:offset_y], element: ele
146
+ else
147
+ press x: start_x, y: start_y
148
+ wait(duration) if duration
149
+ move_to x: coordinates[:offset_x], y: coordinates[:offset_y]
150
+ end
151
+ release
152
+
153
+ self
154
+ end
155
+
156
+ # Ask the driver to perform all actions in this action chain.
157
+ def perform
158
+ @driver.touch_actions @actions
159
+ @actions.clear
160
+ self
161
+ end
162
+
163
+ # Does nothing, currently.
164
+ def cancel
165
+ @actions << { action: cancel }
166
+ @driver.touch_actions @actions
167
+ self
168
+ end
169
+
170
+ # Visible for testing
171
+ # @private
172
+ def swipe_coordinates(start_x: 0, start_y: 0, offset_x: 0, offset_y: 0)
173
+ Appium::Logger.info "start_x: #{start_x}, start_y: #{start_y}, offset_x: #{offset_x}, offset_y: #{offset_y}"
174
+ { offset_x: offset_x, offset_y: offset_y }
175
+ end
176
+
177
+ private
178
+
179
+ def chain_method(method, args = nil)
180
+ action = args ? { action: method, options: args } : { action: method }
181
+ @actions << action
182
+ self
183
+ end
184
+
185
+ def args_with_ele_ref(args)
186
+ args[:element] = args[:element].ref if args.key? :element
187
+ args
188
+ end
189
+ end # class TouchAction
190
+ end # module Core
191
+ end # module Appium
@@ -0,0 +1,459 @@
1
+ module Appium
2
+ module Core
3
+ class Driver
4
+ # Selenium webdriver capabilities
5
+ # @return [Core::Base::Capabilities]
6
+ attr_reader :caps
7
+
8
+ # Return http client called in start_driver()
9
+ # @return [Appium::Core::Base::Http::Default] the http client
10
+ attr_reader :http_client
11
+
12
+ # Device type to request from the appium server
13
+ # @return [Symbol] :android and :ios, for example
14
+ attr_reader :device
15
+
16
+ # Automation name sent to appium server or received from server
17
+ # If automation_name is nil, it is not set both client side and server side.
18
+ # @return [Hash]
19
+ attr_reader :automation_name
20
+
21
+ # Custom URL for the selenium server. If set this attribute, ruby_lib try to handshake to the custom url.
22
+ # @return [String]
23
+ attr_reader :custom_url
24
+
25
+ # Export session id to textfile in /tmp for 3rd party tools
26
+ # @return [Boolean]
27
+ attr_reader :export_session
28
+ # @return [String] By default, session id is exported in '/tmp/appium_lib_session'
29
+ attr_reader :export_session_path
30
+
31
+ # Default wait time for elements to appear
32
+ # Returns the default client side wait.
33
+ # This value is independent of what the server is using
34
+ # Provide Appium::Drive like { appium_lib: { wait: 20 } }
35
+ # @return [Integer]
36
+ attr_reader :default_wait
37
+
38
+ # Appium's server port
39
+ # Provide Appium::Drive like { appium_lib: { port: 8080 } }
40
+ # @return [Integer]
41
+ attr_reader :port
42
+
43
+ # Return a time wait timeout
44
+ # Wait time for ::Appium::Core::Base::Wait, wait and wait_true
45
+ # Provide Appium::Drive like { appium_lib: { wait_timeout: 20 } }
46
+ # @return [Integer]
47
+ attr_reader :wait_timeout
48
+
49
+ # Return a time wait timeout
50
+ # Wait interval time for ::Appium::Core::Base::Wait, wait and wait_true
51
+ # Provide Appium::Drive like { appium_lib: { wait_interval: 20 } }
52
+ # @return [Integer]
53
+ attr_reader :wait_interval
54
+
55
+ # instance of AbstractEventListener for logging support
56
+ attr_reader :listener
57
+
58
+ # @return [Appium::Core::Base::Driver]
59
+ attr_reader :driver
60
+
61
+ # Creates a new global driver and extend particular methods to `target`
62
+ # @param [Class] target Extend particular methods to this target.
63
+ # @param [Hash] opts A options include capabilities for the Appium Server and for the client.
64
+ # @return [Driver]
65
+ #
66
+ # @example
67
+ #
68
+ # require 'rubygems'
69
+ # require 'appium_lib'
70
+ #
71
+ # # Start iOS driver
72
+ # opts = {
73
+ # caps: {
74
+ # platformName: :ios,
75
+ # app: '/path/to/MyiOS.app'
76
+ # },
77
+ # appium_lib: {
78
+ # server_url: "http://custom-host:8080/wd/hub.com",
79
+ # export_session: false,
80
+ # port: 8080,
81
+ # wait: 0,
82
+ # wait_timeout: 20,
83
+ # wait_interval: 0.3,
84
+ # listener: nil,
85
+ # }
86
+ # }
87
+ # @core_driver = Appium::Core.for(self, opts) # create a core driver with `opts` and extend methods into `self`
88
+ # @core_driver.start_driver(server_url: server_url, http_client_ops: http_client_ops) # start driver
89
+ #
90
+ def self.for(target, opts = {})
91
+ new(target, opts)
92
+ end
93
+
94
+ # @private
95
+ def initialize(target, opts = {})
96
+ opts = Appium.symbolize_keys opts
97
+ validate_keys(opts)
98
+
99
+ @caps = get_caps(opts)
100
+
101
+ set_appium_lib_specific_values(get_appium_lib_opts(opts))
102
+ set_app_path
103
+ set_appium_device
104
+ set_automation_name
105
+
106
+ extend_for(device: @device, automation_name: @automation_name, target: target)
107
+
108
+ self
109
+ end
110
+
111
+ # Creates a new global driver and quits the old one if it exists.
112
+ # You can customise http_client as the following
113
+ #
114
+ # @param [String] server_url Custom server url to send to requests. Default is "http://127.0.0.1:4723/wd/hub".
115
+ # @option http_client_ops [Hash] :http_client Custom HTTP Client
116
+ # @option http_client_ops [Hash] :open_timeout Custom open timeout for http client.
117
+ # @option http_client_ops [Hash] :read_timeout Custom read timeout for http client.
118
+ # @return [Selenium::WebDriver] the new global driver
119
+ #
120
+ # @example
121
+ #
122
+ # require 'rubygems'
123
+ # require 'appium_lib'
124
+ #
125
+ # # platformName takes a string or a symbol.
126
+ #
127
+ # # Start iOS driver
128
+ # opts = {
129
+ # caps: {
130
+ # platformName: :ios,
131
+ # app: '/path/to/MyiOS.app'
132
+ # },
133
+ # appium_lib: {
134
+ # server_url: "http://custom-host:8080/wd/hub.com",
135
+ # export_session: false,
136
+ # port: 8080,
137
+ # wait: 0,
138
+ # wait_timeout: 20,
139
+ # wait_interval: 0.3,
140
+ # listener: nil,
141
+ # }
142
+ # }
143
+ # @core = Appium::Driver.new(opts)
144
+ # @driver = @core.start_driver
145
+ #
146
+ def start_driver(server_url: nil,
147
+ http_client_ops: { http_client: nil, open_timeout: 999_999, read_timeout: 999_999 })
148
+ server_url = server_url ? server_url : "http://127.0.0.1:#{@port}/wd/hub"
149
+
150
+ # open_timeout and read_timeout are explicit wait.
151
+ open_timeout = http_client_ops.delete(:open_timeout)
152
+ read_timeout = http_client_ops.delete(:read_timeout)
153
+
154
+ http_client = http_client_ops.delete(:http_client)
155
+ @http_client ||= http_client ? http_client : Appium::Core::Base::Http::Default.new
156
+
157
+ @http_client.open_timeout = open_timeout if open_timeout
158
+ @http_client.read_timeout = read_timeout if read_timeout
159
+
160
+ begin
161
+ # included https://github.com/SeleniumHQ/selenium/blob/43f8b3f66e7e01124eff6a5805269ee441f65707/rb/lib/selenium/webdriver/remote/driver.rb#L29
162
+ @driver = ::Appium::Core::Base::Driver.new(http_client: @http_client,
163
+ desired_capabilities: @caps,
164
+ url: server_url,
165
+ listener: @listener)
166
+
167
+ # export session
168
+ write_session_id(@driver.session_id, @export_session_path) if @export_session
169
+ rescue Errno::ECONNREFUSED
170
+ raise "ERROR: Unable to connect to Appium. Is the server running on #{server_url}?"
171
+ end
172
+
173
+ # If "automationName" is set only server side, this method set "automationName" attribute into @automation_name.
174
+ # Since @automation_name is set only client side before start_driver is called.
175
+ set_automation_name_if_nil
176
+
177
+ set_implicit_wait_by_default(@default_wait)
178
+
179
+ @driver
180
+ end
181
+
182
+ private
183
+
184
+ # Ignore setting default wait if the target driver has no implementation
185
+ def set_implicit_wait_by_default(wait)
186
+ @driver.manage.timeouts.implicit_wait = wait
187
+ rescue Selenium::WebDriver::Error::UnknownError => e
188
+ unless e.message.include?('The operation requested is not yet implemented')
189
+ raise e.message, ::Appium::Core::Error::ServerError
190
+ end
191
+
192
+ Appium::Logger.debug(e.message)
193
+ {}
194
+ end
195
+
196
+ public
197
+
198
+ # Quits the driver
199
+ # @return [void]
200
+ #
201
+ # @example
202
+ #
203
+ # @core.quit_driver
204
+ #
205
+ def quit_driver
206
+ @driver.quit
207
+ rescue
208
+ nil
209
+ end
210
+
211
+ # Returns the server's version info
212
+ # @return [Hash]
213
+ #
214
+ # @example
215
+ #
216
+ # @core.appium_server_version
217
+ # {
218
+ # "build" => {
219
+ # "version" => "0.18.1",
220
+ # "revision" => "d242ebcfd92046a974347ccc3a28f0e898595198"
221
+ # }
222
+ # }
223
+ #
224
+ # Returns blank hash for Selenium Grid since `remote_status` gets 500 error
225
+ #
226
+ # @example
227
+ #
228
+ # @core.appium_server_version #=> {}
229
+ #
230
+ def appium_server_version
231
+ @driver.remote_status
232
+ rescue Selenium::WebDriver::Error::ServerError => e
233
+ raise ::Appium::Core::Error::ServerError unless e.message.include?('status code 500')
234
+ # driver.remote_status returns 500 error for using selenium grid
235
+ {}
236
+ end
237
+
238
+ # Return the platform version as an array of integers
239
+ # @return [Array<Integer>]
240
+ #
241
+ # @example
242
+ #
243
+ # @core.platform_version #=> [10.1.1]
244
+ #
245
+ def platform_version
246
+ p_version = @driver.capabilities['platformVersion']
247
+ p_version.split('.').map(&:to_i)
248
+ end
249
+
250
+ # Takes a png screenshot and saves to the target path.
251
+ #
252
+ # @param png_save_path [String] the full path to save the png
253
+ # @return [File]
254
+ #
255
+ # @example
256
+ #
257
+ # @core.screenshot '/tmp/hi.png' #=> nil
258
+ # # same as `@driver.save_screenshot png_save_path`
259
+ #
260
+ def screenshot(png_save_path)
261
+ warn '[DEPRECATION] screenshot will be removed. Please use driver.save_screenshot instead.'
262
+ @driver.save_screenshot png_save_path
263
+ end
264
+
265
+ # Check every interval seconds to see if yield returns a truthy value.
266
+ # Note this isn't a strict boolean true, any truthy value is accepted.
267
+ # false and nil are considered failures.
268
+ # Give up after timeout seconds.
269
+ #
270
+ # Wait code from the selenium Ruby gem
271
+ # https://github.com/SeleniumHQ/selenium/blob/cf501dda3f0ed12233de51ce8170c0e8090f0c20/rb/lib/selenium/webdriver/common/wait.rb
272
+ #
273
+ # If only a number is provided then it's treated as the timeout value.
274
+ #
275
+ # @param [Hash] opts Options
276
+ # @option opts [Numeric] :timeout Seconds to wait before timing out. Set default by `appium_wait_timeout` (30).
277
+ # @option opts [Numeric] :interval Seconds to sleep between polls. Set default by `appium_wait_interval` (0.5).
278
+ # @option opts [String] :message Exception message if timed out.
279
+ # @option opts [Array, Exception] :ignore Exceptions to ignore while polling (default: Exception)
280
+ #
281
+ # @example
282
+ #
283
+ # @core.wait_true { @driver.find_element :accessibility_id, 'something' }
284
+ #
285
+ def wait_true(opts = {})
286
+ opts = process_wait_opts(opts).merge(return_if_true: true)
287
+
288
+ opts[:timeout] ||= @wait_timeout
289
+ opts[:interval] ||= @wait_interval
290
+
291
+ wait = ::Appium::Core::Base::Wait.new opts
292
+ wait.until { yield }
293
+ end
294
+
295
+ # Check every interval seconds to see if yield doesn't raise an exception.
296
+ # Give up after timeout seconds.
297
+ #
298
+ # Wait code from the selenium Ruby gem
299
+ # https://github.com/SeleniumHQ/selenium/blob/cf501dda3f0ed12233de51ce8170c0e8090f0c20/rb/lib/selenium/webdriver/common/wait.rb
300
+ #
301
+ # If only a number is provided then it's treated as the timeout value.
302
+ #
303
+ # @param [Hash] opts Options
304
+ # @option opts [Numeric] :timeout Seconds to wait before timing out. Set default by `appium_wait_timeout` (30).
305
+ # @option opts [Numeric] :interval Seconds to sleep between polls. Set default by `appium_wait_interval` (0.5).
306
+ # @option opts [String] :message Exception message if timed out.
307
+ # @option opts [Array, Exception] :ignore Exceptions to ignore while polling (default: Exception)
308
+ #
309
+ # @example
310
+ #
311
+ # @core.wait { @driver.find_element :accessibility_id, 'something' }
312
+ #
313
+ def wait(opts = {})
314
+ opts = process_wait_opts(opts).merge(return_if_true: false)
315
+
316
+ opts[:timeout] ||= @wait_timeout
317
+ opts[:interval] ||= @wait_interval
318
+
319
+ wait = ::Appium::Core::Base::Wait.new opts
320
+ wait.until { yield }
321
+ end
322
+
323
+ private
324
+
325
+ # @private
326
+ def process_wait_opts(opts)
327
+ opts = { timeout: opts } if opts.is_a?(Numeric)
328
+ raise 'opts must be a hash' unless opts.is_a? Hash
329
+ opts
330
+ end
331
+
332
+ # @private
333
+ def extend_for(device:, automation_name:, target:)
334
+ target.extend Appium::Core
335
+ target.extend Appium::Core::Device
336
+
337
+ case device
338
+ when :android
339
+ case automation_name
340
+ when :espresso
341
+ ::Appium::Core::Android::Espresso::Bridge.for(self)
342
+ when :uiautomator2
343
+ ::Appium::Core::Android::Uiautomator2::Bridge.for(self)
344
+ else # default and UiAutomator
345
+ ::Appium::Core::Android::Uiautomator1::Bridge.for(self)
346
+ end
347
+ when :ios
348
+ case automation_name
349
+ when :xcuitest
350
+ ::Appium::Core::Ios::Xcuitest::Bridge.for(self)
351
+ else # default and UIAutomation
352
+ ::Appium::Core::Ios::Uiautomation::Bridge.for(self)
353
+ end
354
+ when :mac
355
+ # no Mac specific extentions
356
+ Appium::Logger.debug('mac')
357
+ when :windows
358
+ # no windows specific extentions
359
+ Appium::Logger.debug('windows')
360
+ else
361
+ Appium::Logger.warn('no device matched')
362
+ end
363
+
364
+ target
365
+ end
366
+
367
+ # @private
368
+ def validate_keys(opts)
369
+ flatten_ops = flatten_hash_keys(opts)
370
+
371
+ raise Error::NoCapabilityError unless opts.member?(:caps)
372
+ if !opts.member?(:appium_lib) && flatten_ops.member?(:appium_lib)
373
+ raise Error::CapabilityStructureError, 'Please check the value of appium_lib in the capability'
374
+ end
375
+
376
+ true
377
+ end
378
+
379
+ # @private
380
+ def flatten_hash_keys(hash, flatten_keys_result = [])
381
+ hash.each do |key, value|
382
+ flatten_keys_result << key
383
+ flatten_hash_keys(value, flatten_keys_result) if value.is_a?(Hash)
384
+ end
385
+
386
+ flatten_keys_result
387
+ end
388
+
389
+ # @private
390
+ def get_caps(opts)
391
+ Core::Base::Capabilities.create_capabilities(opts[:caps] || {})
392
+ end
393
+
394
+ # @private
395
+ def get_appium_lib_opts(opts)
396
+ opts[:appium_lib] || {}
397
+ end
398
+
399
+ # @private
400
+ # Path to the .apk, .app or .app.zip.
401
+ # The path can be local or remote for Sauce.
402
+ def set_app_path
403
+ return unless @caps && @caps[:app] && !@caps[:app].empty?
404
+ @caps[:app] = File.expand_path(@caps[:app])
405
+ end
406
+
407
+ # @private
408
+ def set_appium_lib_specific_values(appium_lib_opts)
409
+ @custom_url = appium_lib_opts.fetch :server_url, false
410
+ @default_wait = appium_lib_opts.fetch :wait, 0
411
+
412
+ # bump current session id into a particular file
413
+ @export_session = appium_lib_opts.fetch :export_session, false
414
+ @export_session_path = appium_lib_opts.fetch :export_session_path, '/tmp/appium_lib_session'
415
+
416
+ @port = appium_lib_opts.fetch :port, 4723
417
+
418
+ # timeout and interval used in ::Appium::Comm.wait/wait_true
419
+ @wait_timeout = appium_lib_opts.fetch :wait_timeout, 30
420
+ @wait_interval = appium_lib_opts.fetch :wait_interval, 0.5
421
+
422
+ # to pass it in Selenium.new.
423
+ # `listener = opts.delete(:listener)` is called in Selenium::Driver.new
424
+ @listener = appium_lib_opts.fetch :listener, nil
425
+ end
426
+
427
+ # @private
428
+ def set_appium_device
429
+ # https://code.google.com/p/selenium/source/browse/spec-draft.md?repo=mobile
430
+ @device = @caps[:platformName]
431
+ return @device unless @device
432
+
433
+ @device = @device.is_a?(Symbol) ? @device : @device.downcase.strip.intern
434
+ end
435
+
436
+ # @private
437
+ def set_automation_name
438
+ @automation_name = @caps[:automationName] if @caps[:automationName]
439
+ @automation_name = if @automation_name
440
+ @automation_name.is_a?(Symbol) ? @automation_name : @automation_name.downcase.strip.intern
441
+ end
442
+ end
443
+
444
+ # @private
445
+ def set_automation_name_if_nil
446
+ return unless @automation_name.nil?
447
+ @automation_name = @driver.capabilities['automationName']
448
+ end
449
+
450
+ # @private
451
+ def write_session_id(session_id, export_path = '/tmp/appium_lib_session')
452
+ File.open(export_path, 'w') { |f| f.puts session_id }
453
+ rescue IOError => e
454
+ ::Appium::Logger.warn e
455
+ nil
456
+ end
457
+ end # class Driver
458
+ end # module Core
459
+ end # module Appium