@@ -275,7 +275,40 @@ class Driver
275
275
# @core.start_driver # start driver with 'url'. Connect to 'http://custom-host:8080/wd/hub.com'
276
276
#
277
277
def self . for ( opts = { } )
278
- new ( opts )
278
+ new . setup_for_new_session ( opts )
279
+ end
280
+
281
+ # Attach to an existing session. The main usage of this method is to attach to
282
+ # an existing session for debugging. The generated driver instance has the capabilities which
283
+ # has the given automationName and platformName only since the W3C WebDriver spec does not provide
284
+ # an endpoint to get running session's capabilities.
285
+ #
286
+ #
287
+ # @param [String] The session id to attach to.
288
+ # @param [String] url The WebDriver URL to attach to with the session_id.
289
+ # @param [String] automation_name The platform name to keep in the dummy capabilities
290
+ # @param [String] platform_name The automation name to keep in the dummy capabilities
291
+ # @return [Selenium::WebDriver] A new driver instance with the given session id.
292
+ #
293
+ # @example
294
+ #
295
+ # new_driver = ::Appium::Core::Driver.attach_to(
296
+ # driver.session_id, # The 'driver' has an existing session id
297
+ # url: 'http://127.0.0.1:4723/wd/hub', automation_name: 'UiAutomator2', platform_name: 'Android'
298
+ # )
299
+ # new_driver.page_source # for example
300
+ #
301
+ def self . attach_to (
302
+ session_id , url : nil , automation_name : nil , platform_name : nil ,
303
+ http_client_ops : { http_client : nil , open_timeout : 999_999 , read_timeout : 999_999 }
304
+ )
305
+ new . attach_to (
306
+ session_id ,
307
+ automation_name : automation_name ,
308
+ platform_name : platform_name ,
309
+ url : url ,
310
+ http_client_ops : http_client_ops
311
+ )
279
312
end
280
313
281
314
private
@@ -286,20 +319,25 @@ def delegated_target_for_test
286
319
@delegate_target
287
320
end
288
321
289
- public
290
-
291
322
# @private
292
- def initialize ( opts = { } )
323
+ def initialize
293
324
@delegate_target = self # for testing purpose
294
325
@automation_name = nil # initialise before 'set_automation_name'
326
+ end
327
+
328
+ public
329
+
330
+ # @private
331
+ # Set up for a neww session
332
+ def setup_for_new_session ( opts = { } )
333
+ @custom_url = opts . delete :url # to set the custom url as :url
295
334
296
335
# TODO: Remove when we implement Options
297
336
# The symbolize_keys is to keep compatiility for the legacy code, which allows capabilities to give 'string' as the key.
298
337
# The toplevel `caps`, `capabilities` and `appium_lib` are expected to be symbol.
299
338
# FIXME: First, please try to remove `nested: true` to `nested: false`.
300
339
opts = Appium . symbolize_keys ( opts , nested : true )
301
340
302
- @custom_url = opts . delete :url
303
341
@caps = get_caps ( opts )
304
342
305
343
set_appium_lib_specific_values ( get_appium_lib_opts ( opts ) )
@@ -308,8 +346,7 @@ def initialize(opts = {})
308
346
set_automation_name
309
347
310
348
extend_for ( device : @device , automation_name : @automation_name )
311
-
312
- self # rubocop:disable Lint/Void
349
+ self
313
350
end
314
351
315
352
# Creates a new global driver and quits the old one if it exists.
@@ -320,7 +357,7 @@ def initialize(opts = {})
320
357
# @option http_client_ops [Hash] :http_client Custom HTTP Client
321
358
# @option http_client_ops [Hash] :open_timeout Custom open timeout for http client.
322
359
# @option http_client_ops [Hash] :read_timeout Custom read timeout for http client.
323
- # @return [Selenium::WebDriver] the new global driver
360
+ # @return [Selenium::WebDriver] A new driver instance
324
361
#
325
362
# @example
326
363
#
@@ -406,7 +443,47 @@ def start_driver(server_url: nil,
406
443
@driver
407
444
end
408
445
409
- private
446
+ # @privvate
447
+ # Attach to an existing session
448
+ def attach_to ( session_id , url : nil , automation_name : nil , platform_name : nil ,
449
+ http_client_ops : { http_client : nil , open_timeout : 999_999 , read_timeout : 999_999 } )
450
+
451
+ raise ::Appium ::Core ::Error ::ArgumentError , 'The :url must not be nil' if url . nil?
452
+ raise ::Appium ::Core ::Error ::ArgumentError , 'The :automation_name must not be nil' if automation_name . nil?
453
+ raise ::Appium ::Core ::Error ::ArgumentError , 'The :platform_name must not be nil' if platform_name . nil?
454
+
455
+ @custom_url = url
456
+
457
+ # use lowercase internally
458
+ @automation_name = convert_downcase ( automation_name )
459
+ @device = convert_downcase ( platform_name )
460
+
461
+ extend_for ( device : @device , automation_name : @automation_name )
462
+
463
+ @http_client = get_http_client http_client : http_client_ops . delete ( :http_client ) ,
464
+ open_timeout : http_client_ops . delete ( :open_timeout ) ,
465
+ read_timeout : http_client_ops . delete ( :read_timeout )
466
+
467
+ # Note that 'enable_idempotency_header' works only a new session reqeust. The attach_to method skips
468
+ # the new session request, this it does not needed.
469
+
470
+ begin
471
+ # included https://github.com/SeleniumHQ/selenium/blob/43f8b3f66e7e01124eff6a5805269ee441f65707/rb/lib/selenium/webdriver/remote/driver.rb#L29
472
+ @driver = ::Appium ::Core ::Base ::Driver . new ( http_client : @http_client ,
473
+ url : @custom_url ,
474
+ listener : @listener ,
475
+ existing_session_id : session_id ,
476
+ automation_name : automation_name ,
477
+ platform_name : platform_name )
478
+
479
+ # export session
480
+ write_session_id ( @driver . session_id , @export_session_path ) if @export_session
481
+ rescue Errno ::ECONNREFUSED
482
+ raise "ERROR: Unable to connect to Appium. Is the server running on #{ @custom_url } ?"
483
+ end
484
+
485
+ @driver
486
+ end
410
487
411
488
def get_http_client ( http_client : nil , open_timeout : nil , read_timeout : nil )
412
489
client = http_client || Appium ::Core ::Base ::Http ::Default . new
@@ -432,8 +509,6 @@ def set_implicit_wait_by_default(wait)
432
509
{ }
433
510
end
434
511
435
- public
436
-
437
512
# Quits the driver
438
513
# @return [void]
439
514
#
@@ -627,17 +702,20 @@ def set_appium_device
627
702
@device = @caps [ :platformName ] || @caps [ 'platformName' ]
628
703
return @device unless @device
629
704
630
- @device = @device . is_a? ( Symbol ) ? @device . downcase : @device . downcase . strip . intern
705
+ @device = convert_downcase @device
631
706
end
632
707
633
708
# @private
634
709
def set_automation_name
635
710
# TODO: check if the Appium.symbolize_keys(opts, nested: false) enoug with this
636
711
candidate = @caps [ :automationName ] || @caps [ 'automationName' ]
637
712
@automation_name = candidate if candidate
638
- @automation_name = if @automation_name
639
- @automation_name . is_a? ( Symbol ) ? @automation_name . downcase : @automation_name . downcase . strip . intern
640
- end
713
+ @automation_name = convert_downcase @automation_name if @automation_name
714
+ end
715
+
716
+ # @private
717
+ def convert_downcase ( value )
718
+ value . is_a? ( Symbol ) ? value . downcase : value . downcase . strip . intern
641
719
end
642
720
643
721
# @private
@@ -651,6 +729,10 @@ def set_automation_name_if_nil
651
729
652
730
# @private
653
731
def write_session_id ( session_id , export_path = '/tmp/appium_lib_session' )
732
+ ::Appium ::Logger . warn (
733
+ '[DEPRECATION] export_session option will be removed. ' \
734
+ 'Please save the session id by yourself with #session_id method like @driver.session_id.'
735
+ )
654
736
export_path = export_path . tr ( '/' , '\\' ) if ::Appium ::Core ::Base . platform . windows?
655
737
File . write ( export_path , session_id )
656
738
rescue IOError => e
0 commit comments