appium_lib_core 4.1.0 → 7.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +250 -271
- data/README.md +65 -15
- data/Rakefile +4 -0
- data/appium_lib_core.gemspec +5 -8
- data/bin/console +0 -4
- data/lib/appium_lib_core/android/device/auth_finger_print.rb +2 -1
- data/lib/appium_lib_core/android/device.rb +45 -4
- data/lib/appium_lib_core/common/base/bridge.rb +320 -90
- data/lib/appium_lib_core/common/base/capabilities.rb +8 -9
- data/lib/appium_lib_core/common/base/device_ime.rb +49 -0
- data/lib/appium_lib_core/common/base/driver.rb +232 -191
- data/lib/appium_lib_core/common/base/driver_settings.rb +51 -0
- data/lib/appium_lib_core/common/base/has_location.rb +80 -0
- data/lib/appium_lib_core/common/base/has_network_connection.rb +56 -0
- data/lib/appium_lib_core/common/base/http_default.rb +15 -38
- data/lib/appium_lib_core/{ios/uiautomation/bridge.rb → common/base/remote_status.rb} +9 -8
- data/lib/appium_lib_core/common/base/rotable.rb +62 -0
- data/lib/appium_lib_core/common/base/screenshot.rb +8 -8
- data/lib/appium_lib_core/common/base/search_context.rb +20 -6
- data/lib/appium_lib_core/common/base.rb +1 -3
- data/lib/appium_lib_core/common/command.rb +259 -4
- data/lib/appium_lib_core/common/device/app_management.rb +8 -14
- data/lib/appium_lib_core/common/device/image_comparison.rb +12 -4
- data/lib/appium_lib_core/common/device/keyevent.rb +4 -4
- data/lib/appium_lib_core/common/{command/mjsonwp.rb → device/orientation.rb} +14 -11
- data/lib/appium_lib_core/common/device/touch_actions.rb +2 -0
- data/lib/appium_lib_core/common/device/value.rb +6 -8
- data/lib/appium_lib_core/common/error.rb +4 -5
- data/lib/appium_lib_core/common/log.rb +4 -1
- data/lib/appium_lib_core/common/touch_action/multi_touch.rb +19 -0
- data/lib/appium_lib_core/common/touch_action/touch_actions.rb +16 -2
- data/lib/appium_lib_core/common/wait.rb +38 -6
- data/lib/appium_lib_core/device.rb +1 -5
- data/lib/appium_lib_core/driver.rb +194 -108
- data/lib/appium_lib_core/{patch.rb → element.rb} +66 -9
- data/lib/appium_lib_core/{common/base/command.rb → mac2/bridge.rb} +9 -8
- data/lib/appium_lib_core/mac2/device/screen.rb +48 -0
- data/lib/appium_lib_core/mac2/device.rb +92 -0
- data/lib/appium_lib_core/{ios.rb → mac2.rb} +2 -5
- data/lib/appium_lib_core/support/event_firing_bridge.rb +57 -0
- data/lib/appium_lib_core/version.rb +2 -2
- data/lib/appium_lib_core/windows/device/app_management.rb +38 -0
- data/lib/appium_lib_core/windows/device.rb +2 -0
- data/lib/appium_lib_core.rb +21 -10
- metadata +31 -86
- data/.github/ISSUE_TEMPLATE/issue-report.md +0 -29
- data/.github/contributing.md +0 -26
- data/.github/issue_template.md +0 -20
- data/.github/workflows/unittest.yml +0 -68
- data/.gitignore +0 -18
- data/.rubocop.yml +0 -58
- data/azure-pipelines.yml +0 -15
- data/ci-jobs/functional/android_setup.yml +0 -3
- data/ci-jobs/functional/ios_setup.yml +0 -7
- data/ci-jobs/functional/publish_test_result.yml +0 -18
- data/ci-jobs/functional/run_appium.yml +0 -25
- data/ci-jobs/functional/start-emulator.sh +0 -26
- data/ci-jobs/functional_test.yml +0 -298
- data/docs/mobile_command.md +0 -34
- data/lib/appium_lib_core/common/base/bridge/mjsonwp.rb +0 -81
- data/lib/appium_lib_core/common/base/bridge/w3c.rb +0 -252
- data/lib/appium_lib_core/common/command/common.rb +0 -110
- data/lib/appium_lib_core/common/command/w3c.rb +0 -56
- data/lib/appium_lib_core/ios/uiautomation/device.rb +0 -44
- data/lib/appium_lib_core/ios/uiautomation/patch.rb +0 -34
- data/release_notes.md +0 -816
- data/script/commands.rb +0 -200
@@ -38,6 +38,8 @@ module Appium
|
|
38
38
|
#
|
39
39
|
# result = Appium::Core::Wait.until { @driver.find_element(:id, 'something') }
|
40
40
|
#
|
41
|
+
# result = Appium::Core::Wait.until(timeout: 30, message: 'timeout') { @driver.find_element(:id, 'something') }
|
42
|
+
#
|
41
43
|
# result = Appium::Core::Wait.until(object: 'some object') { |object|
|
42
44
|
# @driver.find_element(:id, object)
|
43
45
|
# }
|
@@ -82,6 +84,8 @@ module Appium
|
|
82
84
|
#
|
83
85
|
# Appium::Core::Wait.until_true { @driver.find_element(:id, 'something') }
|
84
86
|
#
|
87
|
+
# Appium::Core::Wait.until_true(timeout: 30) { @driver.find_element(:id, 'something') }
|
88
|
+
#
|
85
89
|
# Appium::Core::Wait.until_true(object: 'some object') { |object|
|
86
90
|
# @driver.find_element(:id, object)
|
87
91
|
# }
|
@@ -133,17 +137,31 @@ module Appium
|
|
133
137
|
# @param [String] message Exception message if timed out.
|
134
138
|
# @param [Array, Exception] ignored Exceptions to ignore while polling (default: Exception)
|
135
139
|
#
|
136
|
-
# @example
|
140
|
+
# @example With core instance
|
137
141
|
#
|
138
142
|
# @core.wait_true { @driver.find_element :accessibility_id, 'something' }
|
143
|
+
# @core.wait_true(timeout: 30, interval: 2) { @driver.find_element :accessibility_id, 'something' }
|
144
|
+
#
|
145
|
+
# @core.wait_until_true { @driver.find_element :accessibility_id, 'something' }
|
146
|
+
# @core.wait_until_true(timeout: 30, interval: 2) { @driver.find_element :accessibility_id, 'something' }
|
147
|
+
#
|
148
|
+
# @example With driver instance
|
149
|
+
#
|
150
|
+
# @driver.wait_true { |d| d.find_element :accessibility_id, 'something' }
|
151
|
+
# @driver.wait_true(timeout: 30, interval: 2) { |d| driver.find_element :accessibility_id, 'something' }
|
139
152
|
#
|
140
|
-
|
153
|
+
# @driver.wait_until_true { |d| d.find_element :accessibility_id, 'something' }
|
154
|
+
# @driver.wait_until_true(timeout: 30, interval: 2) { |d| driver.find_element :accessibility_id, 'something' }
|
155
|
+
#
|
156
|
+
def wait_until_true(timeout: nil, interval: nil, message: nil, ignored: nil, &block)
|
141
157
|
Wait.until_true(timeout: timeout || @wait_timeout,
|
142
158
|
interval: interval || @wait_interval,
|
143
159
|
message: message,
|
144
160
|
ignored: ignored,
|
145
|
-
object: self
|
161
|
+
object: self,
|
162
|
+
&block)
|
146
163
|
end
|
164
|
+
alias wait_true wait_until_true
|
147
165
|
|
148
166
|
# Check every interval seconds to see if yield doesn't raise an exception.
|
149
167
|
# Give up after timeout seconds.
|
@@ -155,17 +173,31 @@ module Appium
|
|
155
173
|
# @param [String] message Exception message if timed out.
|
156
174
|
# @param [Array, Exception] ignored Exceptions to ignore while polling (default: Exception)
|
157
175
|
#
|
158
|
-
# @example
|
176
|
+
# @example With core instance
|
159
177
|
#
|
160
178
|
# @core.wait { @driver.find_element :accessibility_id, 'something' }
|
179
|
+
# @core.wait(timeout: 30, interval: 2) { @driver.find_element :accessibility_id, 'something' }
|
180
|
+
#
|
181
|
+
# @core.wait_until { @driver.find_element :accessibility_id, 'something' }
|
182
|
+
# @core.wait_until(timeout: 30, interval: 2) { @driver.find_element :accessibility_id, 'something' }
|
183
|
+
#
|
184
|
+
# @example With driver instance
|
185
|
+
#
|
186
|
+
# @driver.wait { @driver.find_element :accessibility_id, 'something' }
|
187
|
+
# @driver.wait(timeout: 30, interval: 2) { @driver.find_element :accessibility_id, 'something' }
|
188
|
+
#
|
189
|
+
# @driver.wait_until { |d| d.find_element :accessibility_id, 'something' }
|
190
|
+
# @driver.wait_until(timeout: 30, interval: 2) { |d| d.find_element :accessibility_id, 'something' }
|
161
191
|
#
|
162
|
-
def
|
192
|
+
def wait_until(timeout: nil, interval: nil, message: nil, ignored: nil, &block)
|
163
193
|
Wait.until(timeout: timeout || @wait_timeout,
|
164
194
|
interval: interval || @wait_interval,
|
165
195
|
message: message,
|
166
196
|
ignored: ignored,
|
167
|
-
object: self
|
197
|
+
object: self,
|
198
|
+
&block)
|
168
199
|
end
|
200
|
+
alias wait wait_until
|
169
201
|
end
|
170
202
|
end # module Core
|
171
203
|
end # module Appium
|
@@ -79,11 +79,7 @@ module Appium
|
|
79
79
|
end
|
80
80
|
|
81
81
|
def create_bridge_command(method, &block)
|
82
|
-
::Appium::Core::Base::Bridge
|
83
|
-
undef_method method if method_defined? method
|
84
|
-
block_given? ? class_eval(&block) : define_method(method) { execute method }
|
85
|
-
end
|
86
|
-
::Appium::Core::Base::Bridge::W3C.class_eval do
|
82
|
+
::Appium::Core::Base::Bridge.class_eval do
|
87
83
|
undef_method method if method_defined? method
|
88
84
|
block_given? ? class_eval(&block) : define_method(method) { execute method }
|
89
85
|
end
|
@@ -15,6 +15,9 @@
|
|
15
15
|
require 'uri'
|
16
16
|
|
17
17
|
module Appium
|
18
|
+
# The struct for 'location'
|
19
|
+
Location = Struct.new(:latitude, :longitude, :altitude)
|
20
|
+
|
18
21
|
module Core
|
19
22
|
module Android
|
20
23
|
autoload :Uiautomator1, 'appium_lib_core/android'
|
@@ -23,10 +26,11 @@ module Appium
|
|
23
26
|
end
|
24
27
|
|
25
28
|
module Ios
|
26
|
-
autoload :Uiautomation, 'appium_lib_core/ios'
|
27
29
|
autoload :Xcuitest, 'appium_lib_core/ios_xcuitest'
|
28
30
|
end
|
29
31
|
|
32
|
+
autoload :Mac2, 'appium_lib_core/mac2'
|
33
|
+
|
30
34
|
autoload :Windows, 'appium_lib_core/windows'
|
31
35
|
|
32
36
|
# This options affects only client side as <code>:appium_lib</code> key.<br>
|
@@ -38,18 +42,18 @@ module Appium
|
|
38
42
|
|
39
43
|
def initialize(appium_lib_opts)
|
40
44
|
@custom_url = appium_lib_opts.fetch :server_url, nil
|
41
|
-
@default_wait = appium_lib_opts.fetch :wait,
|
45
|
+
@default_wait = appium_lib_opts.fetch :wait, nil
|
42
46
|
@enable_idempotency_header = appium_lib_opts.fetch :enable_idempotency_header, true
|
43
47
|
|
44
48
|
# bump current session id into a particular file
|
45
49
|
@export_session = appium_lib_opts.fetch :export_session, false
|
46
50
|
@export_session_path = appium_lib_opts.fetch :export_session_path, default_tmp_appium_lib_session
|
47
51
|
|
48
|
-
@direct_connect = appium_lib_opts.fetch :direct_connect,
|
52
|
+
@direct_connect = appium_lib_opts.fetch :direct_connect, true
|
49
53
|
|
50
54
|
@port = appium_lib_opts.fetch :port, Driver::DEFAULT_APPIUM_PORT
|
51
55
|
|
52
|
-
# timeout and interval used in ::Appium::
|
56
|
+
# timeout and interval used in ::Appium::Commn.wait/wait_true
|
53
57
|
@wait_timeout = appium_lib_opts.fetch :wait_timeout, ::Appium::Core::Wait::DEFAULT_TIMEOUT
|
54
58
|
@wait_interval = appium_lib_opts.fetch :wait_interval, ::Appium::Core::Wait::DEFAULT_INTERVAL
|
55
59
|
|
@@ -74,6 +78,13 @@ module Appium
|
|
74
78
|
path: 'directConnectPath'
|
75
79
|
}.freeze
|
76
80
|
|
81
|
+
W3C_KEYS = {
|
82
|
+
protocol: 'appium:directConnectProtocol',
|
83
|
+
host: 'appium:directConnectHost',
|
84
|
+
port: 'appium:directConnectPort',
|
85
|
+
path: 'appium:directConnectPath'
|
86
|
+
}.freeze
|
87
|
+
|
77
88
|
# @return [string] Returns a protocol such as http/https
|
78
89
|
attr_reader :protocol
|
79
90
|
|
@@ -87,10 +98,10 @@ module Appium
|
|
87
98
|
attr_reader :path
|
88
99
|
|
89
100
|
def initialize(capabilities)
|
90
|
-
@protocol = capabilities[KEYS[:protocol]]
|
91
|
-
@host = capabilities[KEYS[:host]]
|
92
|
-
@port = capabilities[KEYS[:port]]
|
93
|
-
@path = capabilities[KEYS[:path]]
|
101
|
+
@protocol = capabilities[W3C_KEYS[:protocol]] || capabilities[KEYS[:protocol]]
|
102
|
+
@host = capabilities[W3C_KEYS[:host]] || capabilities[KEYS[:host]]
|
103
|
+
@port = capabilities[W3C_KEYS[:port]] || capabilities[KEYS[:port]]
|
104
|
+
@path = capabilities[W3C_KEYS[:path]] || capabilities[KEYS[:path]]
|
94
105
|
end
|
95
106
|
end
|
96
107
|
|
@@ -133,11 +144,9 @@ module Appium
|
|
133
144
|
attr_reader :export_session_path
|
134
145
|
|
135
146
|
# Default wait time for elements to appear in Appium server side.
|
136
|
-
# Defaults to {::Appium::Core::Driver::DEFAULT_IMPLICIT_WAIT}.<br>
|
137
147
|
# Provide <code>{ appium_lib: { wait: 30 } }</code> to {::Appium::Core.for}
|
138
148
|
# @return [Integer]
|
139
149
|
attr_reader :default_wait
|
140
|
-
DEFAULT_IMPLICIT_WAIT = 0
|
141
150
|
|
142
151
|
# Appium's server port. 4723 is by default. Defaults to {::Appium::Core::Driver::DEFAULT_APPIUM_PORT}.<br>
|
143
152
|
# Provide <code>{ appium_lib: { port: 8080 } }</code> to {::Appium::Core.for}.
|
@@ -175,7 +184,8 @@ module Appium
|
|
175
184
|
# - <code>directConnectPort</code>
|
176
185
|
# - <code>directConnectPath</code>
|
177
186
|
#
|
178
|
-
#
|
187
|
+
# ignore them if this parameter is <code>false</code>. Defaults to true.
|
188
|
+
# These keys can have <code>appium:</code> prefix.
|
179
189
|
#
|
180
190
|
# @return [Bool]
|
181
191
|
attr_reader :direct_connect
|
@@ -185,8 +195,6 @@ module Appium
|
|
185
195
|
# @option opts [Hash] :caps Appium capabilities.
|
186
196
|
# @option opts [Hash] :capabilities The same as :caps.
|
187
197
|
# This param is for compatibility with Selenium WebDriver format
|
188
|
-
# @option opts [Hash] :desired_capabilities The same as :caps.
|
189
|
-
# This param is for compatibility with Selenium WebDriver format
|
190
198
|
# @option opts [Appium::Core::Options] :appium_lib Capabilities affect only ruby client
|
191
199
|
# @option opts [String] :url The same as :custom_url in :appium_lib.
|
192
200
|
# This param is for compatibility with Selenium WebDriver format
|
@@ -197,10 +205,8 @@ module Appium
|
|
197
205
|
#
|
198
206
|
# # format 1
|
199
207
|
# @core = Appium::Core.for caps: {...}, appium_lib: {...}
|
200
|
-
# # format 2. 'capabilities:'
|
208
|
+
# # format 2. 'capabilities:' is also available instead of 'caps:'.
|
201
209
|
# @core = Appium::Core.for url: "http://127.0.0.1:8080/wd/hub", capabilities: {...}, appium_lib: {...}
|
202
|
-
# # format 3. 'appium_lib: {...}' can be blank
|
203
|
-
# @core = Appium::Core.for url: "http://127.0.0.1:8080/wd/hub", desired_capabilities: {...}
|
204
210
|
#
|
205
211
|
#
|
206
212
|
# require 'rubygems'
|
@@ -228,7 +234,7 @@ module Appium
|
|
228
234
|
# @core.start_driver # Connect to 'http://127.0.0.1:8080/wd/hub' because of 'port: 8080'
|
229
235
|
#
|
230
236
|
# # Start iOS driver with .zip file over HTTP
|
231
|
-
# #
|
237
|
+
# # 'capabilities:' is also available instead of 'caps:'. Either is fine.
|
232
238
|
# opts = {
|
233
239
|
# capabilities: {
|
234
240
|
# platformName: :ios,
|
@@ -252,7 +258,7 @@ module Appium
|
|
252
258
|
# # Start iOS driver as another format. 'url' is available like below
|
253
259
|
# opts = {
|
254
260
|
# url: "http://custom-host:8080/wd/hub.com",
|
255
|
-
#
|
261
|
+
# capabilities: {
|
256
262
|
# platformName: :ios,
|
257
263
|
# platformVersion: '11.0',
|
258
264
|
# deviceName: 'iPhone Simulator',
|
@@ -270,8 +276,58 @@ module Appium
|
|
270
276
|
# @core = Appium::Core.for(opts) # create a core driver with 'opts' and extend methods into 'self'
|
271
277
|
# @core.start_driver # start driver with 'url'. Connect to 'http://custom-host:8080/wd/hub.com'
|
272
278
|
#
|
279
|
+
# # With a custom listener
|
280
|
+
# class CustomListener < ::Selenium::WebDriver::Support::AbstractEventListener
|
281
|
+
# // something
|
282
|
+
# end
|
283
|
+
# capabilities: {
|
284
|
+
# platformName: :ios,
|
285
|
+
# platformVersion: '11.0',
|
286
|
+
# deviceName: 'iPhone Simulator',
|
287
|
+
# automationName: 'XCUITest',
|
288
|
+
# app: '/path/to/MyiOS.app'
|
289
|
+
# },
|
290
|
+
# appium_lib: {
|
291
|
+
# listener: CustomListener.new,
|
292
|
+
# }
|
293
|
+
# @core = Appium::Core.for capabilities: capabilities, appium_lib: appium_lib
|
294
|
+
# @core.start_driver
|
295
|
+
#
|
273
296
|
def self.for(opts = {})
|
274
|
-
new(opts)
|
297
|
+
new.setup_for_new_session(opts)
|
298
|
+
end
|
299
|
+
|
300
|
+
# Attach to an existing session. The main usage of this method is to attach to
|
301
|
+
# an existing session for debugging. The generated driver instance has the capabilities which
|
302
|
+
# has the given automationName and platformName only since the W3C WebDriver spec does not provide
|
303
|
+
# an endpoint to get running session's capabilities.
|
304
|
+
#
|
305
|
+
#
|
306
|
+
# @param [String] The session id to attach to.
|
307
|
+
# @param [String] url The WebDriver URL to attach to with the session_id.
|
308
|
+
# @param [String] automation_name The platform name to keep in the dummy capabilities
|
309
|
+
# @param [String] platform_name The automation name to keep in the dummy capabilities
|
310
|
+
# @return [Selenium::WebDriver] A new driver instance with the given session id.
|
311
|
+
#
|
312
|
+
# @example
|
313
|
+
#
|
314
|
+
# new_driver = ::Appium::Core::Driver.attach_to(
|
315
|
+
# driver.session_id, # The 'driver' has an existing session id
|
316
|
+
# url: 'http://127.0.0.1:4723/wd/hub', automation_name: 'UiAutomator2', platform_name: 'Android'
|
317
|
+
# )
|
318
|
+
# new_driver.page_source # for example
|
319
|
+
#
|
320
|
+
def self.attach_to(
|
321
|
+
session_id, url: nil, automation_name: nil, platform_name: nil,
|
322
|
+
http_client_ops: { http_client: nil, open_timeout: 999_999, read_timeout: 999_999 }
|
323
|
+
)
|
324
|
+
new.attach_to(
|
325
|
+
session_id,
|
326
|
+
automation_name: automation_name,
|
327
|
+
platform_name: platform_name,
|
328
|
+
url: url,
|
329
|
+
http_client_ops: http_client_ops
|
330
|
+
)
|
275
331
|
end
|
276
332
|
|
277
333
|
private
|
@@ -282,17 +338,25 @@ module Appium
|
|
282
338
|
@delegate_target
|
283
339
|
end
|
284
340
|
|
285
|
-
public
|
286
|
-
|
287
341
|
# @private
|
288
|
-
def initialize
|
342
|
+
def initialize
|
289
343
|
@delegate_target = self # for testing purpose
|
290
344
|
@automation_name = nil # initialise before 'set_automation_name'
|
345
|
+
end
|
291
346
|
|
292
|
-
|
293
|
-
|
347
|
+
public
|
348
|
+
|
349
|
+
# @private
|
350
|
+
# Set up for a new session
|
351
|
+
def setup_for_new_session(opts = {})
|
352
|
+
@custom_url = opts.delete :url # to set the custom url as :url
|
353
|
+
|
354
|
+
# TODO: Remove when we implement Options
|
355
|
+
# The symbolize_keys is to keep compatiility for the legacy code, which allows capabilities to give 'string' as the key.
|
356
|
+
# The toplevel `caps`, `capabilities` and `appium_lib` are expected to be symbol.
|
357
|
+
# FIXME: First, please try to remove `nested: true` to `nested: false`.
|
358
|
+
opts = Appium.symbolize_keys(opts, nested: true)
|
294
359
|
|
295
|
-
@custom_url = opts.delete :url
|
296
360
|
@caps = get_caps(opts)
|
297
361
|
|
298
362
|
set_appium_lib_specific_values(get_appium_lib_opts(opts))
|
@@ -301,8 +365,7 @@ module Appium
|
|
301
365
|
set_automation_name
|
302
366
|
|
303
367
|
extend_for(device: @device, automation_name: @automation_name)
|
304
|
-
|
305
|
-
self # rubocop:disable Lint/Void
|
368
|
+
self
|
306
369
|
end
|
307
370
|
|
308
371
|
# Creates a new global driver and quits the old one if it exists.
|
@@ -313,7 +376,7 @@ module Appium
|
|
313
376
|
# @option http_client_ops [Hash] :http_client Custom HTTP Client
|
314
377
|
# @option http_client_ops [Hash] :open_timeout Custom open timeout for http client.
|
315
378
|
# @option http_client_ops [Hash] :read_timeout Custom read timeout for http client.
|
316
|
-
# @return [Selenium::WebDriver]
|
379
|
+
# @return [Selenium::WebDriver] A new driver instance
|
317
380
|
#
|
318
381
|
# @example
|
319
382
|
#
|
@@ -324,7 +387,7 @@ module Appium
|
|
324
387
|
#
|
325
388
|
# # Start iOS driver
|
326
389
|
# opts = {
|
327
|
-
#
|
390
|
+
# capabilities: {
|
328
391
|
# platformName: :ios,
|
329
392
|
# platformVersion: '11.0',
|
330
393
|
# deviceName: 'iPhone Simulator',
|
@@ -365,11 +428,12 @@ module Appium
|
|
365
428
|
end
|
366
429
|
|
367
430
|
begin
|
368
|
-
|
369
|
-
|
370
|
-
|
431
|
+
@driver = ::Appium::Core::Base::Driver.new(listener: @listener,
|
432
|
+
http_client: @http_client,
|
433
|
+
capabilities: @caps, # ::Appium::Core::Base::Capabilities
|
371
434
|
url: @custom_url,
|
372
|
-
|
435
|
+
wait_timeout: @wait_timeout,
|
436
|
+
wait_interval: @wait_interval)
|
373
437
|
|
374
438
|
if @direct_connect
|
375
439
|
d_c = DirectConnections.new(@driver.capabilities)
|
@@ -387,6 +451,8 @@ module Appium
|
|
387
451
|
@http_client.additional_headers.delete Appium::Core::Base::Http::RequestHeaders::KEYS[:idempotency]
|
388
452
|
end
|
389
453
|
|
454
|
+
# TODO: this method can be removed after releasing Appium 2.0, and after a while
|
455
|
+
# since Appium 2.0 reuqires 'automationName'. This method won't help anymore then.
|
390
456
|
# If "automationName" is set only server side, this method set "automationName" attribute into @automation_name.
|
391
457
|
# Since @automation_name is set only client side before start_driver is called.
|
392
458
|
set_automation_name_if_nil
|
@@ -396,20 +462,56 @@ module Appium
|
|
396
462
|
@driver
|
397
463
|
end
|
398
464
|
|
399
|
-
private
|
465
|
+
# @private
|
466
|
+
# Attach to an existing session
|
467
|
+
def attach_to(session_id, url: nil, automation_name: nil, platform_name: nil,
|
468
|
+
http_client_ops: { http_client: nil, open_timeout: 999_999, read_timeout: 999_999 })
|
400
469
|
|
401
|
-
|
402
|
-
|
470
|
+
raise ::Appium::Core::Error::ArgumentError, 'The :url must not be nil' if url.nil?
|
471
|
+
raise ::Appium::Core::Error::ArgumentError, 'The :automation_name must not be nil' if automation_name.nil?
|
472
|
+
raise ::Appium::Core::Error::ArgumentError, 'The :platform_name must not be nil' if platform_name.nil?
|
473
|
+
|
474
|
+
@custom_url = url
|
475
|
+
|
476
|
+
# use lowercase internally
|
477
|
+
@automation_name = convert_downcase(automation_name)
|
478
|
+
@device = convert_downcase(platform_name)
|
479
|
+
|
480
|
+
extend_for(device: @device, automation_name: @automation_name)
|
403
481
|
|
404
|
-
|
405
|
-
|
406
|
-
|
482
|
+
@http_client = get_http_client http_client: http_client_ops.delete(:http_client),
|
483
|
+
open_timeout: http_client_ops.delete(:open_timeout),
|
484
|
+
read_timeout: http_client_ops.delete(:read_timeout)
|
407
485
|
|
408
|
-
|
486
|
+
# Note that 'enable_idempotency_header' works only a new session reqeust. The attach_to method skips
|
487
|
+
# the new session request, this it does not needed.
|
488
|
+
|
489
|
+
begin
|
490
|
+
# included https://github.com/SeleniumHQ/selenium/blob/43f8b3f66e7e01124eff6a5805269ee441f65707/rb/lib/selenium/webdriver/remote/driver.rb#L29
|
491
|
+
@driver = ::Appium::Core::Base::Driver.new(http_client: @http_client,
|
492
|
+
url: @custom_url,
|
493
|
+
listener: @listener,
|
494
|
+
existing_session_id: session_id,
|
495
|
+
automation_name: automation_name,
|
496
|
+
platform_name: platform_name)
|
497
|
+
|
498
|
+
# export session
|
499
|
+
write_session_id(@driver.session_id, @export_session_path) if @export_session
|
500
|
+
rescue Errno::ECONNREFUSED
|
501
|
+
raise "ERROR: Unable to connect to Appium. Is the server running on #{@custom_url}?"
|
502
|
+
end
|
503
|
+
|
504
|
+
@driver
|
505
|
+
end
|
506
|
+
|
507
|
+
def get_http_client(http_client: nil, open_timeout: nil, read_timeout: nil)
|
508
|
+
http_client || Appium::Core::Base::Http::Default.new(open_timeout: open_timeout, read_timeout: read_timeout)
|
409
509
|
end
|
410
510
|
|
411
511
|
# Ignore setting default wait if the target driver has no implementation
|
412
512
|
def set_implicit_wait_by_default(wait)
|
513
|
+
return if @default_wait.nil?
|
514
|
+
|
413
515
|
@driver.manage.timeouts.implicit_wait = wait
|
414
516
|
rescue ::Selenium::WebDriver::Error::UnknownError => e
|
415
517
|
unless e.message.include?('The operation requested is not yet implemented')
|
@@ -420,9 +522,7 @@ module Appium
|
|
420
522
|
{}
|
421
523
|
end
|
422
524
|
|
423
|
-
|
424
|
-
|
425
|
-
# Quits the driver
|
525
|
+
# [Deprecated] Quits the driver. This method is the same as @driver.quit
|
426
526
|
# @return [void]
|
427
527
|
#
|
428
528
|
# @example
|
@@ -430,12 +530,14 @@ module Appium
|
|
430
530
|
# @core.quit_driver
|
431
531
|
#
|
432
532
|
def quit_driver
|
533
|
+
::Appium::Logger.warn('[DEPRECATION] quit_driver will be removed. Please use @driver.quit instead.')
|
433
534
|
@driver.quit
|
434
535
|
rescue # rubocop:disable Style/RescueStandardError
|
435
536
|
nil
|
436
537
|
end
|
437
538
|
|
438
|
-
# Returns the server's version info
|
539
|
+
# Returns the server's version info. This method calls +driver.remote_status+ internally
|
540
|
+
#
|
439
541
|
# @return [Hash]
|
440
542
|
#
|
441
543
|
# @example
|
@@ -449,18 +551,20 @@ module Appium
|
|
449
551
|
# }
|
450
552
|
# }
|
451
553
|
#
|
452
|
-
# Returns blank hash
|
554
|
+
# Returns blank hash in a case +driver.remote_status+ got an error
|
555
|
+
# such as Selenium Grid. It returns 500 error against 'remote_status'.
|
453
556
|
#
|
454
557
|
# @example
|
455
558
|
#
|
456
559
|
# @core.appium_server_version #=> {}
|
457
560
|
#
|
458
561
|
def appium_server_version
|
459
|
-
@driver.
|
460
|
-
rescue Selenium::WebDriver::Error::ServerError => e
|
461
|
-
raise ::Appium::Core::Error::ServerError unless e.message.include?('status code 500')
|
562
|
+
return {} if @driver.nil?
|
462
563
|
|
463
|
-
|
564
|
+
@driver.remote_status
|
565
|
+
rescue StandardError
|
566
|
+
# Ignore error case in a case the target appium server
|
567
|
+
# does not support `/status` API.
|
464
568
|
{}
|
465
569
|
end
|
466
570
|
|
@@ -472,35 +576,35 @@ module Appium
|
|
472
576
|
# @core.platform_version #=> [10,1,1]
|
473
577
|
#
|
474
578
|
def platform_version
|
579
|
+
::Appium::Logger.warn(
|
580
|
+
'[DEPRECATION] platform_version method will be. ' \
|
581
|
+
'Please check the platformVersion via @driver.capabilities["platformVersion"] instead.'
|
582
|
+
)
|
583
|
+
|
475
584
|
p_version = @driver.capabilities['platformVersion'] || @driver.session_capabilities['platformVersion']
|
476
585
|
p_version.split('.').map(&:to_i)
|
477
586
|
end
|
478
587
|
|
479
|
-
# Takes a png screenshot and saves to the target path.
|
480
|
-
#
|
481
|
-
# @param png_save_path [String] the full path to save the png
|
482
|
-
# @return [File]
|
483
|
-
#
|
484
|
-
# @example
|
485
|
-
#
|
486
|
-
# @core.screenshot '/tmp/hi.png' #=> nil
|
487
|
-
# # same as '@driver.save_screenshot png_save_path'
|
488
|
-
#
|
489
|
-
def screenshot(png_save_path)
|
490
|
-
::Appium::Logger.warn '[DEPRECATION] screenshot will be removed. Please use driver.save_screenshot instead.'
|
491
|
-
@driver.save_screenshot png_save_path
|
492
|
-
end
|
493
|
-
|
494
588
|
private
|
495
589
|
|
590
|
+
def convert_to_symbol(value)
|
591
|
+
if value.nil?
|
592
|
+
value
|
593
|
+
else
|
594
|
+
value.to_sym
|
595
|
+
end
|
596
|
+
end
|
597
|
+
|
496
598
|
# @private
|
497
599
|
def extend_for(device:, automation_name:) # rubocop:disable Metrics/CyclomaticComplexity
|
498
600
|
extend Appium::Core
|
499
601
|
extend Appium::Core::Device
|
500
602
|
|
501
|
-
|
603
|
+
sym_automation_name = convert_to_symbol(automation_name)
|
604
|
+
|
605
|
+
case convert_to_symbol(device)
|
502
606
|
when :android
|
503
|
-
case
|
607
|
+
case sym_automation_name
|
504
608
|
when :espresso
|
505
609
|
::Appium::Core::Android::Espresso::Bridge.for self
|
506
610
|
when :uiautomator2
|
@@ -511,28 +615,26 @@ module Appium
|
|
511
615
|
::Appium::Core::Android::Uiautomator1::Bridge.for self
|
512
616
|
end
|
513
617
|
when :ios, :tvos
|
514
|
-
case
|
618
|
+
case sym_automation_name
|
515
619
|
when :safari
|
516
620
|
::Appium::Logger.debug('SafariDriver for iOS')
|
517
|
-
|
621
|
+
else # XCUITest
|
518
622
|
::Appium::Core::Ios::Xcuitest::Bridge.for self
|
519
|
-
else # default and UIAutomation
|
520
|
-
::Appium::Core::Ios::Uiautomation::Bridge.for self
|
521
623
|
end
|
522
624
|
when :mac
|
523
|
-
case
|
625
|
+
case sym_automation_name
|
524
626
|
when :safari
|
525
627
|
::Appium::Logger.debug('SafariDriver for macOS')
|
526
628
|
when :gecko
|
527
629
|
::Appium::Logger.debug('Gecko Driver for macOS')
|
528
630
|
when :mac2
|
529
|
-
::Appium::
|
631
|
+
::Appium::Core::Mac2::Bridge.for self
|
530
632
|
else
|
531
633
|
# no Mac specific extentions
|
532
634
|
::Appium::Logger.debug('macOS Native')
|
533
635
|
end
|
534
636
|
when :windows
|
535
|
-
case
|
637
|
+
case sym_automation_name
|
536
638
|
when :gecko
|
537
639
|
::Appium::Logger.debug('Gecko Driver for Windows')
|
538
640
|
else
|
@@ -542,7 +644,7 @@ module Appium
|
|
542
644
|
# https://github.com/Samsung/appium-tizen-driver
|
543
645
|
::Appium::Logger.debug('tizen')
|
544
646
|
else
|
545
|
-
case
|
647
|
+
case sym_automation_name
|
546
648
|
when :youiengine
|
547
649
|
# https://github.com/YOU-i-Labs/appium-youiengine-driver
|
548
650
|
::Appium::Logger.debug('YouiEngine')
|
@@ -559,36 +661,9 @@ module Appium
|
|
559
661
|
self
|
560
662
|
end
|
561
663
|
|
562
|
-
# @private
|
563
|
-
def validate_keys(opts)
|
564
|
-
flatten_ops = flatten_hash_keys(opts)
|
565
|
-
|
566
|
-
# FIXME: Remove 'desired_capabilities' in the next major Selenium update
|
567
|
-
unless opts.member?(:caps) || opts.member?(:capabilities) || opts.member?(:desired_capabilities)
|
568
|
-
raise Error::NoCapabilityError
|
569
|
-
end
|
570
|
-
|
571
|
-
if !opts.member?(:appium_lib) && flatten_ops.member?(:appium_lib)
|
572
|
-
raise Error::CapabilityStructureError, 'Please check the value of appium_lib in the capability'
|
573
|
-
end
|
574
|
-
|
575
|
-
true
|
576
|
-
end
|
577
|
-
|
578
|
-
# @private
|
579
|
-
def flatten_hash_keys(hash, flatten_keys_result = [])
|
580
|
-
hash.each do |key, value|
|
581
|
-
flatten_keys_result << key
|
582
|
-
flatten_hash_keys(value, flatten_keys_result) if value.is_a?(Hash)
|
583
|
-
end
|
584
|
-
|
585
|
-
flatten_keys_result
|
586
|
-
end
|
587
|
-
|
588
664
|
# @private
|
589
665
|
def get_caps(opts)
|
590
|
-
|
591
|
-
Core::Base::Capabilities.create_capabilities(opts[:caps] || opts[:capabilities] || opts[:desired_capabilities] || {})
|
666
|
+
Core::Base::Capabilities.new(opts[:caps] || opts[:capabilities] || {})
|
592
667
|
end
|
593
668
|
|
594
669
|
# @private
|
@@ -601,6 +676,7 @@ module Appium
|
|
601
676
|
# The path can be local, HTTP/S, Windows Share and other path like 'sauce-storage:'.
|
602
677
|
# Use @caps[:app] without modifications if the path isn't HTTP/S or local path.
|
603
678
|
def set_app_path
|
679
|
+
# FIXME: maybe `:app` should check `app` as well.
|
604
680
|
return unless @caps && @caps[:app] && !@caps[:app].empty?
|
605
681
|
return if @caps[:app] =~ URI::DEFAULT_PARSER.make_regexp
|
606
682
|
|
@@ -639,18 +715,24 @@ module Appium
|
|
639
715
|
# @private
|
640
716
|
def set_appium_device
|
641
717
|
# https://code.google.com/p/selenium/source/browse/spec-draft.md?repo=mobile
|
642
|
-
|
718
|
+
# TODO: check if the Appium.symbolize_keys(opts, nested: false) enoug with this
|
719
|
+
@device = @caps[:platformName] || @caps['platformName']
|
643
720
|
return @device unless @device
|
644
721
|
|
645
|
-
@device =
|
722
|
+
@device = convert_downcase @device
|
646
723
|
end
|
647
724
|
|
648
725
|
# @private
|
649
726
|
def set_automation_name
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
727
|
+
# TODO: check if the Appium.symbolize_keys(opts, nested: false) enoug with this
|
728
|
+
candidate = @caps[:automationName] || @caps['automationName']
|
729
|
+
@automation_name = candidate if candidate
|
730
|
+
@automation_name = convert_downcase @automation_name if @automation_name
|
731
|
+
end
|
732
|
+
|
733
|
+
# @private
|
734
|
+
def convert_downcase(value)
|
735
|
+
value.is_a?(Symbol) ? value.downcase : value.downcase.strip.intern
|
654
736
|
end
|
655
737
|
|
656
738
|
# @private
|
@@ -664,6 +746,10 @@ module Appium
|
|
664
746
|
|
665
747
|
# @private
|
666
748
|
def write_session_id(session_id, export_path = '/tmp/appium_lib_session')
|
749
|
+
::Appium::Logger.warn(
|
750
|
+
'[DEPRECATION] export_session option will be removed. ' \
|
751
|
+
'Please save the session id by yourself with #session_id method like @driver.session_id.'
|
752
|
+
)
|
667
753
|
export_path = export_path.tr('/', '\\') if ::Appium::Core::Base.platform.windows?
|
668
754
|
File.write(export_path, session_id)
|
669
755
|
rescue IOError => e
|