selenium-webdriver 4.1.0 → 4.15.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 (161) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +304 -1
  3. data/Gemfile +2 -0
  4. data/LICENSE +1 -1
  5. data/NOTICE +1 -1
  6. data/README.md +2 -2
  7. data/bin/linux/selenium-manager +0 -0
  8. data/bin/macos/selenium-manager +0 -0
  9. data/bin/windows/selenium-manager.exe +0 -0
  10. data/lib/selenium/server.rb +36 -39
  11. data/lib/selenium/webdriver/atoms/findElements.js +3 -4
  12. data/lib/selenium/webdriver/atoms/getAttribute.js +0 -0
  13. data/lib/selenium/webdriver/atoms/isDisplayed.js +0 -0
  14. data/lib/selenium/webdriver/atoms/mutationListener.js +0 -0
  15. data/lib/selenium/webdriver/atoms.rb +5 -3
  16. data/lib/selenium/webdriver/bidi/browsing_context.rb +88 -0
  17. data/lib/selenium/webdriver/bidi/browsing_context_info.rb +35 -0
  18. data/lib/selenium/webdriver/bidi/log/base_log_entry.rb +35 -0
  19. data/lib/selenium/webdriver/bidi/log/console_log_entry.rb +35 -0
  20. data/lib/selenium/webdriver/{common/driver_extensions/has_network_connection.rb → bidi/log/filter_by.rb} +14 -11
  21. data/lib/selenium/webdriver/bidi/log/generic_log_entry.rb +33 -0
  22. data/lib/selenium/webdriver/bidi/log/javascript_log_entry.rb +33 -0
  23. data/lib/selenium/webdriver/bidi/log_inspector.rb +143 -0
  24. data/lib/selenium/webdriver/bidi/navigate_result.rb +33 -0
  25. data/lib/selenium/webdriver/bidi/session.rb +51 -0
  26. data/lib/selenium/webdriver/bidi.rb +56 -0
  27. data/lib/selenium/webdriver/chrome/driver.rb +9 -29
  28. data/lib/selenium/webdriver/chrome/features.rb +9 -67
  29. data/lib/selenium/webdriver/chrome/options.rb +3 -223
  30. data/lib/selenium/webdriver/chrome/profile.rb +3 -83
  31. data/lib/selenium/webdriver/chrome/service.rb +4 -19
  32. data/lib/selenium/webdriver/chrome.rb +0 -16
  33. data/lib/selenium/webdriver/chromium/driver.rb +60 -0
  34. data/lib/selenium/webdriver/chromium/features.rb +99 -0
  35. data/lib/selenium/webdriver/chromium/options.rb +243 -0
  36. data/lib/selenium/webdriver/chromium/profile.rb +113 -0
  37. data/lib/selenium/webdriver/chromium.rb +29 -0
  38. data/lib/selenium/webdriver/common/action_builder.rb +62 -22
  39. data/lib/selenium/webdriver/common/child_process.rb +124 -0
  40. data/lib/selenium/webdriver/common/driver.rb +31 -81
  41. data/lib/selenium/webdriver/common/driver_extensions/downloads_files.rb +0 -2
  42. data/lib/selenium/webdriver/common/driver_extensions/full_page_screenshot.rb +0 -1
  43. data/lib/selenium/webdriver/common/driver_extensions/has_addons.rb +0 -2
  44. data/lib/selenium/webdriver/common/driver_extensions/has_apple_permissions.rb +0 -2
  45. data/lib/selenium/webdriver/common/driver_extensions/has_authentication.rb +0 -2
  46. data/lib/selenium/webdriver/common/driver_extensions/{has_location.rb → has_bidi.rb} +9 -10
  47. data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +10 -1
  48. data/lib/selenium/webdriver/common/driver_extensions/has_cdp.rb +0 -2
  49. data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +1 -4
  50. data/lib/selenium/webdriver/common/driver_extensions/has_debugger.rb +0 -2
  51. data/lib/selenium/webdriver/common/driver_extensions/has_devtools.rb +0 -2
  52. data/lib/selenium/webdriver/common/driver_extensions/has_file_downloads.rb +65 -0
  53. data/lib/selenium/webdriver/common/driver_extensions/has_launching.rb +0 -2
  54. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +1 -2
  55. data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +0 -2
  56. data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +2 -69
  57. data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +0 -2
  58. data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +1 -3
  59. data/lib/selenium/webdriver/common/driver_finder.rb +45 -0
  60. data/lib/selenium/webdriver/common/element.rb +8 -8
  61. data/lib/selenium/webdriver/common/error.rb +28 -5
  62. data/lib/selenium/webdriver/common/html5/shared_web_storage.rb +2 -2
  63. data/lib/selenium/webdriver/common/interactions/input_device.rb +10 -4
  64. data/lib/selenium/webdriver/common/interactions/interaction.rb +12 -25
  65. data/lib/selenium/webdriver/common/interactions/interactions.rb +24 -4
  66. data/lib/selenium/webdriver/common/interactions/key_actions.rb +5 -1
  67. data/lib/selenium/webdriver/common/interactions/key_input.rb +11 -27
  68. data/lib/selenium/webdriver/common/interactions/none_input.rb +10 -8
  69. data/lib/selenium/webdriver/common/interactions/pause.rb +49 -0
  70. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +59 -70
  71. data/lib/selenium/webdriver/common/interactions/pointer_cancel.rb +45 -0
  72. data/lib/selenium/webdriver/common/interactions/pointer_event_properties.rb +63 -0
  73. data/lib/selenium/webdriver/common/interactions/pointer_input.rb +15 -84
  74. data/lib/selenium/webdriver/common/interactions/pointer_move.rb +60 -0
  75. data/lib/selenium/webdriver/common/interactions/pointer_press.rb +85 -0
  76. data/lib/selenium/webdriver/common/interactions/scroll.rb +59 -0
  77. data/lib/selenium/webdriver/common/interactions/scroll_origin.rb +48 -0
  78. data/lib/selenium/webdriver/common/interactions/typing_interaction.rb +54 -0
  79. data/lib/selenium/webdriver/common/interactions/wheel_actions.rb +113 -0
  80. data/lib/selenium/webdriver/common/interactions/wheel_input.rb +42 -0
  81. data/lib/selenium/webdriver/common/keys.rb +1 -0
  82. data/lib/selenium/webdriver/common/local_driver.rb +46 -0
  83. data/lib/selenium/webdriver/common/logger.rb +90 -25
  84. data/lib/selenium/webdriver/common/manager.rb +0 -27
  85. data/lib/selenium/webdriver/common/options.rb +17 -17
  86. data/lib/selenium/webdriver/common/platform.rb +5 -51
  87. data/lib/selenium/webdriver/common/port_prober.rb +1 -1
  88. data/lib/selenium/webdriver/common/profile_helper.rb +1 -1
  89. data/lib/selenium/webdriver/common/proxy.rb +2 -2
  90. data/lib/selenium/webdriver/common/search_context.rb +0 -6
  91. data/lib/selenium/webdriver/common/selenium_manager.rb +134 -0
  92. data/lib/selenium/webdriver/common/service.rb +21 -30
  93. data/lib/selenium/webdriver/common/service_manager.rb +8 -15
  94. data/lib/selenium/webdriver/common/shadow_root.rb +2 -3
  95. data/lib/selenium/webdriver/common/socket_lock.rb +3 -3
  96. data/lib/selenium/webdriver/common/socket_poller.rb +2 -2
  97. data/lib/selenium/webdriver/common/takes_screenshot.rb +2 -3
  98. data/lib/selenium/webdriver/common/target_locator.rb +2 -3
  99. data/lib/selenium/webdriver/common/timeouts.rb +2 -2
  100. data/lib/selenium/webdriver/common/virtual_authenticator/credential.rb +85 -0
  101. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator.rb +72 -0
  102. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator_options.rb +62 -0
  103. data/lib/selenium/webdriver/common/websocket_connection.rb +164 -0
  104. data/lib/selenium/webdriver/common/window.rb +6 -6
  105. data/lib/selenium/webdriver/common/zipper.rb +1 -1
  106. data/lib/selenium/webdriver/common.rb +22 -5
  107. data/lib/selenium/webdriver/devtools/console_event.rb +0 -2
  108. data/lib/selenium/webdriver/devtools/exception_event.rb +0 -2
  109. data/lib/selenium/webdriver/devtools/mutation_event.rb +0 -2
  110. data/lib/selenium/webdriver/devtools/network_interceptor.rb +173 -0
  111. data/lib/selenium/webdriver/devtools/pinned_script.rb +0 -2
  112. data/lib/selenium/webdriver/devtools/request.rb +1 -3
  113. data/lib/selenium/webdriver/devtools/response.rb +1 -3
  114. data/lib/selenium/webdriver/devtools.rb +17 -114
  115. data/lib/selenium/webdriver/edge/driver.rb +9 -3
  116. data/lib/selenium/webdriver/edge/features.rb +8 -4
  117. data/lib/selenium/webdriver/edge/options.rb +17 -5
  118. data/lib/selenium/webdriver/edge/profile.rb +2 -2
  119. data/lib/selenium/webdriver/edge/service.rb +8 -7
  120. data/lib/selenium/webdriver/edge.rb +0 -2
  121. data/lib/selenium/webdriver/firefox/driver.rb +9 -2
  122. data/lib/selenium/webdriver/firefox/features.rb +11 -7
  123. data/lib/selenium/webdriver/firefox/options.rb +7 -16
  124. data/lib/selenium/webdriver/firefox/profile.rb +8 -12
  125. data/lib/selenium/webdriver/firefox/service.rb +0 -18
  126. data/lib/selenium/webdriver/firefox/util.rb +46 -0
  127. data/lib/selenium/webdriver/firefox.rb +1 -14
  128. data/lib/selenium/webdriver/ie/driver.rb +7 -1
  129. data/lib/selenium/webdriver/{common/driver_extensions/has_remote_status.rb → ie/features.rb} +10 -7
  130. data/lib/selenium/webdriver/ie/options.rb +4 -3
  131. data/lib/selenium/webdriver/ie/service.rb +0 -22
  132. data/lib/selenium/webdriver/ie.rb +4 -17
  133. data/lib/selenium/webdriver/remote/{commands.rb → bridge/commands.rb} +10 -9
  134. data/lib/selenium/webdriver/remote/bridge.rb +63 -52
  135. data/lib/selenium/webdriver/remote/capabilities.rb +3 -53
  136. data/lib/selenium/webdriver/remote/driver.rb +35 -14
  137. data/lib/selenium/webdriver/remote/features.rb +75 -0
  138. data/lib/selenium/webdriver/remote/http/common.rb +3 -3
  139. data/lib/selenium/webdriver/remote/http/curb.rb +1 -3
  140. data/lib/selenium/webdriver/remote/http/default.rb +8 -14
  141. data/lib/selenium/webdriver/remote/response.rb +2 -3
  142. data/lib/selenium/webdriver/remote/server_error.rb +1 -1
  143. data/lib/selenium/webdriver/remote.rb +1 -1
  144. data/lib/selenium/webdriver/safari/driver.rb +7 -1
  145. data/lib/selenium/webdriver/safari/features.rb +5 -3
  146. data/lib/selenium/webdriver/safari/options.rb +5 -1
  147. data/lib/selenium/webdriver/safari/service.rb +10 -4
  148. data/lib/selenium/webdriver/safari.rb +1 -15
  149. data/lib/selenium/webdriver/support/color.rb +22 -22
  150. data/lib/selenium/webdriver/support/guards/guard.rb +6 -5
  151. data/lib/selenium/webdriver/support/guards/guard_condition.rb +1 -3
  152. data/lib/selenium/webdriver/support/guards.rb +2 -2
  153. data/lib/selenium/webdriver/support/relative_locator.rb +0 -1
  154. data/lib/selenium/webdriver/support/select.rb +3 -1
  155. data/lib/selenium/webdriver/version.rb +1 -1
  156. data/lib/selenium/webdriver.rb +6 -4
  157. data/selenium-webdriver.gemspec +14 -12
  158. metadata +76 -65
  159. data/lib/selenium/webdriver/remote/http/persistent.rb +0 -65
  160. data/lib/selenium/webdriver/support/cdp/domain.rb.erb +0 -63
  161. data/lib/selenium/webdriver/support/cdp_client_generator.rb +0 -108
@@ -21,6 +21,7 @@ module Selenium
21
21
  module WebDriver
22
22
  module Remote
23
23
  class Bridge
24
+ autoload :COMMANDS, 'selenium/webdriver/remote/bridge/commands'
24
25
  include Atoms
25
26
 
26
27
  PORT = 4444
@@ -30,8 +31,8 @@ module Selenium
30
31
 
31
32
  #
32
33
  # Initializes the bridge with the given server URL
33
- # @param [String, URI] :url url for the remote server
34
- # @param [Object] :http_client an HTTP client instance that implements the same protocol as Http::Default
34
+ # @param [String, URI] url url for the remote server
35
+ # @param [Object] http_client an HTTP client instance that implements the same protocol as Http::Default
35
36
  # @api private
36
37
  #
37
38
 
@@ -118,7 +119,7 @@ module Selenium
118
119
  end
119
120
 
120
121
  def alert=(keys)
121
- execute :send_alert_text, {}, {value: keys.split(//), text: keys}
122
+ execute :send_alert_text, {}, {value: keys.chars, text: keys}
122
123
  end
123
124
 
124
125
  def alert_text
@@ -146,9 +147,7 @@ module Selenium
146
147
  end
147
148
 
148
149
  def page_source
149
- execute_script('var source = document.documentElement.outerHTML;' \
150
- 'if (!source) { source = new XMLSerializer().serializeToString(document); }' \
151
- 'return source;')
150
+ execute :get_page_source
152
151
  end
153
152
 
154
153
  #
@@ -188,6 +187,7 @@ module Selenium
188
187
  execute :delete_session
189
188
  http.close
190
189
  rescue *QUIT_ERRORS
190
+ nil
191
191
  end
192
192
 
193
193
  def close
@@ -369,21 +369,10 @@ module Selenium
369
369
  # actions
370
370
  #
371
371
 
372
- def action(async = false)
373
- ActionBuilder.new self,
374
- Interactions.pointer(:mouse, name: 'mouse'),
375
- Interactions.key('keyboard'),
376
- async
377
- end
378
- alias_method :actions, :action
379
-
380
- def mouse
381
- raise Error::UnsupportedOperationError, '#mouse is no longer supported, use #action instead'
382
- end
383
-
384
- def keyboard
385
- raise Error::UnsupportedOperationError, '#keyboard is no longer supported, use #action instead'
372
+ def action(async: false, devices: [], duration: 250)
373
+ ActionBuilder.new self, async: async, devices: devices, duration: duration
386
374
  end
375
+ alias actions action
387
376
 
388
377
  def send_actions(data)
389
378
  execute :actions, {}, {actions: data}
@@ -402,27 +391,9 @@ module Selenium
402
391
  end
403
392
 
404
393
  def send_keys_to_element(element, keys)
405
- # TODO: rework file detectors before Selenium 4.0
406
- if @file_detector
407
- local_files = keys.first&.split("\n")&.map { |key| @file_detector.call(Array(key)) }&.compact
408
- if local_files.any?
409
- keys = local_files.map { |local_file| upload(local_file) }
410
- keys = Array(keys.join("\n"))
411
- end
412
- end
413
-
414
- # Keep .split(//) for backward compatibility for now
415
- text = keys.join('')
416
- execute :element_send_keys, {id: element}, {value: text.split(//), text: text}
417
- end
418
-
419
- def upload(local_file)
420
- unless File.file?(local_file)
421
- WebDriver.logger.debug("File detector only works with files. #{local_file.inspect} isn`t a file!")
422
- raise Error::WebDriverError, "You are trying to work with something that isn't a file."
423
- end
424
-
425
- execute :upload_file, {}, {file: Zipper.zip_file(local_file)}
394
+ keys = upload_if_necessary(keys) if @file_detector
395
+ text = keys.join
396
+ execute :element_send_keys, {id: element}, {value: text.chars, text: text}
426
397
  end
427
398
 
428
399
  def clear_element(element)
@@ -430,10 +401,19 @@ module Selenium
430
401
  end
431
402
 
432
403
  def submit_element(element)
433
- form = find_element_by('xpath', "./ancestor-or-self::form", [:element, element])
434
- execute_script("var e = arguments[0].ownerDocument.createEvent('Event');" \
435
- "e.initEvent('submit', true, true);" \
436
- 'if (arguments[0].dispatchEvent(e)) { arguments[0].submit() }', form.as_json)
404
+ script = "/* submitForm */ var form = arguments[0];\n" \
405
+ "while (form.nodeName != \"FORM\" && form.parentNode) {\n " \
406
+ "form = form.parentNode;\n" \
407
+ "}\n" \
408
+ "if (!form) { throw Error('Unable to find containing form element'); }\n" \
409
+ "if (!form.ownerDocument) { throw Error('Unable to find owning document'); }\n" \
410
+ "var e = form.ownerDocument.createEvent('Event');\n" \
411
+ "e.initEvent('submit', true, true);\n" \
412
+ "if (form.dispatchEvent(e)) { HTMLFormElement.prototype.submit.call(form) }\n"
413
+
414
+ execute_script(script, Element::ELEMENT_KEY => element)
415
+ rescue Error::JavascriptError
416
+ raise Error::UnsupportedOperationError, 'To submit an element, it must be nested inside a form element'
437
417
  end
438
418
 
439
419
  #
@@ -445,7 +425,7 @@ module Selenium
445
425
  end
446
426
 
447
427
  def element_attribute(element, name)
448
- WebDriver.logger.info "Using script for :getAttribute of #{name}"
428
+ WebDriver.logger.debug "Using script for :getAttribute of #{name}", id: :script
449
429
  execute_atom :getAttribute, element, name
450
430
  end
451
431
 
@@ -505,7 +485,7 @@ module Selenium
505
485
  end
506
486
 
507
487
  def element_displayed?(element)
508
- WebDriver.logger.info 'Using script for :isDisplayed'
488
+ WebDriver.logger.debug 'Using script for :isDisplayed', id: :script
509
489
  execute_atom :isDisplayed, element
510
490
  end
511
491
 
@@ -521,7 +501,7 @@ module Selenium
521
501
  Element.new self, element_id_from(execute(:get_active_element))
522
502
  end
523
503
 
524
- alias_method :switch_to_active_element, :active_element
504
+ alias switch_to_active_element active_element
525
505
 
526
506
  def find_element_by(how, what, parent_ref = [])
527
507
  how, what = convert_locator(how, what)
@@ -564,6 +544,39 @@ module Selenium
564
544
  ShadowRoot.new self, shadow_root_id_from(id)
565
545
  end
566
546
 
547
+ #
548
+ # virtual-authenticator
549
+ #
550
+
551
+ def add_virtual_authenticator(options)
552
+ authenticator_id = execute :add_virtual_authenticator, {}, options.as_json
553
+ VirtualAuthenticator.new(self, authenticator_id, options)
554
+ end
555
+
556
+ def remove_virtual_authenticator(id)
557
+ execute :remove_virtual_authenticator, {authenticatorId: id}
558
+ end
559
+
560
+ def add_credential(credential, id)
561
+ execute :add_credential, {authenticatorId: id}, credential
562
+ end
563
+
564
+ def credentials(authenticator_id)
565
+ execute :get_credentials, {authenticatorId: authenticator_id}
566
+ end
567
+
568
+ def remove_credential(credential_id, authenticator_id)
569
+ execute :remove_credential, {credentialId: credential_id, authenticatorId: authenticator_id}
570
+ end
571
+
572
+ def remove_all_credentials(authenticator_id)
573
+ execute :remove_all_credentials, {authenticatorId: authenticator_id}
574
+ end
575
+
576
+ def user_verified(verified, authenticator_id)
577
+ execute :set_user_verified, {authenticatorId: authenticator_id}, {isUserVerified: verified}
578
+ end
579
+
567
580
  private
568
581
 
569
582
  #
@@ -584,7 +597,7 @@ module Selenium
584
597
  raise ArgumentError, "#{opts.inspect} invalid for #{command.inspect}"
585
598
  end
586
599
 
587
- WebDriver.logger.info("-> #{verb.to_s.upcase} #{path}")
600
+ WebDriver.logger.debug("-> #{verb.to_s.upcase} #{path}", id: :command)
588
601
  http.call(verb, path, command_hash)['value']
589
602
  end
590
603
 
@@ -639,8 +652,6 @@ module Selenium
639
652
  when 'name'
640
653
  how = 'css selector'
641
654
  what = "*[name='#{escape_css(what.to_s)}']"
642
- when 'tag name'
643
- how = 'css selector'
644
655
  end
645
656
 
646
657
  if what.is_a?(Hash)
@@ -653,7 +664,7 @@ module Selenium
653
664
  [how, what]
654
665
  end
655
666
 
656
- ESCAPE_CSS_REGEXP = /(['"\\#.:;,!?+<>=~*^$|%&@`{}\-\[\]()])/.freeze
667
+ ESCAPE_CSS_REGEXP = /(['"\\#.:;,!?+<>=~*^$|%&@`{}\-\[\]()])/
657
668
  UNICODE_CODE_POINT = 30
658
669
 
659
670
  # Escapes invalid characters in CSS selector.
@@ -20,14 +20,12 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  module Remote
23
-
24
23
  #
25
24
  # Specification of the desired and/or actual capabilities of the browser that the
26
25
  # server is being asked to create.
27
26
  #
28
27
 
29
28
  class Capabilities
30
-
31
29
  KNOWN = [
32
30
  :browser_name,
33
31
  :browser_version,
@@ -55,60 +53,11 @@ module Selenium
55
53
  end
56
54
  end
57
55
 
58
- #
59
- # Backward compatibility
60
- #
61
-
62
- alias_method :version, :browser_version
63
- alias_method :version=, :browser_version=
64
- alias_method :platform, :platform_name
65
- alias_method :platform=, :platform_name=
66
-
67
56
  #
68
57
  # Convenience methods for the common choices.
69
58
  #
70
59
 
71
60
  class << self
72
- def chrome(opts = {})
73
- new({
74
- browser_name: 'chrome'
75
- }.merge(opts))
76
- end
77
-
78
- def edge(opts = {})
79
- new({
80
- browser_name: 'MicrosoftEdge'
81
- }.merge(opts))
82
- end
83
- alias_method :microsoftedge, :edge
84
-
85
- def firefox(opts = {})
86
- new({
87
- browser_name: 'firefox'
88
- }.merge(opts))
89
- end
90
- alias_method :ff, :firefox
91
-
92
- def safari(opts = {})
93
- new({
94
- browser_name: Selenium::WebDriver::Safari.technology_preview? ? "Safari Technology Preview" : 'safari'
95
- }.merge(opts))
96
- end
97
-
98
- def htmlunit(opts = {})
99
- new({
100
- browser_name: 'htmlunit'
101
- }.merge(opts))
102
- end
103
-
104
- def internet_explorer(opts = {})
105
- new({
106
- browser_name: 'internet explorer',
107
- platform_name: :windows
108
- }.merge(opts))
109
- end
110
- alias_method :ie, :internet_explorer
111
-
112
61
  def always_match(capabilities)
113
62
  new(always_match: capabilities)
114
63
  end
@@ -134,7 +83,8 @@ module Selenium
134
83
 
135
84
  # Remote Server Specific
136
85
  if data.key?('webdriver.remote.sessionid')
137
- caps[:remote_session_id] = data.delete('webdriver.remote.sessionid')
86
+ caps[:remote_session_id] =
87
+ data.delete('webdriver.remote.sessionid')
138
88
  end
139
89
 
140
90
  KNOWN.each do |cap|
@@ -269,7 +219,7 @@ module Selenium
269
219
  as_json == other.as_json
270
220
  end
271
221
 
272
- alias_method :eql?, :==
222
+ alias eql? ==
273
223
 
274
224
  protected
275
225
 
@@ -20,7 +20,6 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  module Remote
23
-
24
23
  #
25
24
  # Driver implementation for remote server.
26
25
  # @api private
@@ -29,20 +28,18 @@ module Selenium
29
28
  class Driver < WebDriver::Driver
30
29
  include DriverExtensions::UploadsFiles
31
30
  include DriverExtensions::HasSessionId
32
- include DriverExtensions::HasRemoteStatus
31
+ include DriverExtensions::HasFileDownloads
33
32
 
34
- def initialize(bridge: nil, listener: nil, **opts)
35
- desired_capabilities = opts[:desired_capabilities]
36
- if desired_capabilities.is_a?(Symbol)
37
- unless Remote::Capabilities.respond_to?(desired_capabilities)
38
- raise Error::WebDriverError, "invalid desired capability: #{desired_capabilities.inspect}"
39
- end
33
+ def initialize(capabilities: nil, options: nil, service: nil, url: nil, **opts)
34
+ raise ArgumentError, "Can not set :service object on #{self.class}" if service
40
35
 
41
- opts[:desired_capabilities] = Remote::Capabilities.__send__(desired_capabilities)
42
- end
43
- opts[:url] ||= "http://#{Platform.localhost}:4444/wd/hub"
44
- super
36
+ url ||= "http://#{Platform.localhost}:4444/wd/hub"
37
+ caps = process_options(options, capabilities)
38
+ super(caps: caps, url: url, **opts)
45
39
  @bridge.file_detector = ->((filename, *)) { File.exist?(filename) && filename.to_s }
40
+ command_list = @bridge.command_list
41
+ @bridge.extend(WebDriver::Remote::Features)
42
+ @bridge.add_commands(command_list)
46
43
  end
47
44
 
48
45
  private
@@ -52,8 +49,32 @@ module Selenium
52
49
  end
53
50
 
54
51
  def devtools_version
55
- capabilities['se:cdpVersion']&.split('.')&.first ||
56
- raise(Error::WebDriverError, "DevTools is not supported by the Remote Server")
52
+ cdp_version = capabilities['se:cdpVersion']&.split('.')&.first
53
+ raise Error::WebDriverError, 'DevTools is not supported by the Remote Server' unless cdp_version
54
+
55
+ Integer(cdp_version)
56
+ end
57
+
58
+ def process_options(options, capabilities)
59
+ if options && capabilities
60
+ msg = "Don't use both :options and :capabilities when initializing #{self.class}, prefer :options"
61
+ raise ArgumentError, msg
62
+ elsif options.nil? && capabilities.nil?
63
+ raise ArgumentError, "#{self.class} needs :options to be set"
64
+ end
65
+ options ? options.as_json : generate_capabilities(capabilities)
66
+ end
67
+
68
+ def generate_capabilities(capabilities)
69
+ Array(capabilities).map { |cap|
70
+ if cap.is_a? Symbol
71
+ cap = WebDriver::Options.send(cap)
72
+ elsif !cap.respond_to? :as_json
73
+ msg = ":capabilities parameter only accepts objects responding to #as_json which #{cap.class} does not"
74
+ raise ArgumentError, msg
75
+ end
76
+ cap.as_json
77
+ }.inject(:merge)
57
78
  end
58
79
  end # Driver
59
80
  end # Remote
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Licensed to the Software Freedom Conservancy (SFC) under one
4
+ # or more contributor license agreements. See the NOTICE file
5
+ # distributed with this work for additional information
6
+ # regarding copyright ownership. The SFC licenses this file
7
+ # to you under the Apache License, Version 2.0 (the
8
+ # "License"); you may not use this file except in compliance
9
+ # with the License. You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing,
14
+ # software distributed under the License is distributed on an
15
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ # KIND, either express or implied. See the License for the
17
+ # specific language governing permissions and limitations
18
+ # under the License.
19
+
20
+ module Selenium
21
+ module WebDriver
22
+ module Remote
23
+ module Features
24
+ REMOTE_COMMANDS = {
25
+ upload_file: [:post, 'session/:session_id/se/file'],
26
+ get_downloadable_files: [:get, 'session/:session_id/se/files'],
27
+ download_file: [:post, 'session/:session_id/se/files'],
28
+ delete_downloadable_files: [:delete, 'session/:session_id/se/files']
29
+ }.freeze
30
+
31
+ def add_commands(commands)
32
+ @command_list = command_list.merge(commands)
33
+ end
34
+
35
+ def command_list
36
+ @command_list ||= REMOTE_COMMANDS
37
+ end
38
+
39
+ def commands(command)
40
+ command_list[command]
41
+ end
42
+
43
+ def upload(local_file)
44
+ unless File.file?(local_file)
45
+ WebDriver.logger.error("File detector only works with files. #{local_file.inspect} isn`t a file!",
46
+ id: :file_detector)
47
+ raise Error::WebDriverError, "You are trying to upload something that isn't a file."
48
+ end
49
+
50
+ execute :upload_file, {}, {file: Zipper.zip_file(local_file)}
51
+ end
52
+
53
+ def upload_if_necessary(keys)
54
+ local_files = keys.first&.split("\n")&.map { |key| @file_detector.call(Array(key)) }&.compact
55
+ return keys unless local_files&.any?
56
+
57
+ keys = local_files.map { |local_file| upload(local_file) }
58
+ Array(keys.join("\n"))
59
+ end
60
+
61
+ def downloadable_files
62
+ execute :get_downloadable_files
63
+ end
64
+
65
+ def download_file(name)
66
+ execute :download_file, {}, {name: name}
67
+ end
68
+
69
+ def delete_downloadable_files
70
+ execute :delete_downloadable_files
71
+ end
72
+ end
73
+ end # Remote
74
+ end # WebDriver
75
+ end # Selenium
@@ -49,8 +49,8 @@ module Selenium
49
49
  payload = JSON.generate(command_hash)
50
50
  headers['Content-Length'] = payload.bytesize.to_s if %i[post put].include?(verb)
51
51
 
52
- WebDriver.logger.info(" >>> #{url} | #{payload}")
53
- WebDriver.logger.debug(" > #{headers.inspect}")
52
+ WebDriver.logger.debug(" >>> #{url} | #{payload}", id: :command)
53
+ WebDriver.logger.debug(" > #{headers.inspect}", id: :header)
54
54
  elsif verb == :post
55
55
  payload = '{}'
56
56
  headers['Content-Length'] = '2'
@@ -75,7 +75,7 @@ module Selenium
75
75
  code = code.to_i
76
76
  body = body.to_s.strip
77
77
  content_type = content_type.to_s
78
- WebDriver.logger.info("<- #{body}")
78
+ WebDriver.logger.debug("<- #{body}", id: :command)
79
79
 
80
80
  if content_type.include? CONTENT_TYPE
81
81
  raise Error::WebDriverError, "empty body: #{content_type.inspect} (#{code})\n#{body}" if body.empty?
@@ -22,7 +22,6 @@ require 'curb'
22
22
  module Selenium
23
23
  module WebDriver
24
24
  module Remote
25
-
26
25
  module Http
27
26
  #
28
27
  # An alternative to the default Net::HTTP client.
@@ -38,7 +37,6 @@ module Selenium
38
37
  #
39
38
 
40
39
  class Curb < Common
41
-
42
40
  def quit_errors
43
41
  [Curl::Err::RecvError] + super
44
42
  end
@@ -85,7 +83,7 @@ module Selenium
85
83
  c.max_redirects = MAX_REDIRECTS
86
84
  c.follow_location = true
87
85
  c.timeout = @timeout if @timeout
88
- c.verbose = WebDriver.logger.info?
86
+ c.verbose = WebDriver.logger.debug?
89
87
 
90
88
  c
91
89
  end
@@ -16,8 +16,6 @@
16
16
  # KIND, either express or implied. See the License for the
17
17
  # specific language governing permissions and limitations
18
18
  # under the License.
19
-
20
- require 'net/https'
21
19
  require 'ipaddr'
22
20
 
23
21
  module Selenium
@@ -75,9 +73,10 @@ module Selenium
75
73
  begin
76
74
  request = new_request_for(verb, url, headers, payload)
77
75
  response = response_for(request)
78
- rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EADDRINUSE
79
- # a retry is sometimes needed on Windows XP where we may quickly
80
- # run out of ephemeral ports
76
+ rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EADDRINUSE, Errno::EADDRNOTAVAIL
77
+ # a retry is sometimes needed:
78
+ # on Windows XP where we may quickly run out of ephemeral ports
79
+ # when the port becomes temporarily unavailable
81
80
  #
82
81
  # A more robust solution is bumping the MaxUserPort setting
83
82
  # as described here:
@@ -85,13 +84,6 @@ module Selenium
85
84
  # http://msdn.microsoft.com/en-us/library/aa560610%28v=bts.20%29.aspx
86
85
  raise if retries >= MAX_RETRIES
87
86
 
88
- retries += 1
89
- sleep 2
90
- retry
91
- rescue Errno::EADDRNOTAVAIL => e
92
- # a retry is sometimes needed when the port becomes temporarily unavailable
93
- raise if retries >= MAX_RETRIES
94
-
95
87
  retries += 1
96
88
  sleep 2
97
89
  retry
@@ -102,10 +94,12 @@ module Selenium
102
94
  end
103
95
 
104
96
  if response.is_a? Net::HTTPRedirection
97
+ WebDriver.logger.debug("Redirect to #{response['Location']}; times: #{redirects}")
105
98
  raise Error::WebDriverError, 'too many redirects' if redirects >= MAX_REDIRECTS
106
99
 
107
100
  request(:get, URI.parse(response['Location']), DEFAULT_HEADERS.dup, nil, redirects + 1)
108
101
  else
102
+ WebDriver.logger.debug(" <<< #{response.instance_variable_get(:@header).inspect}", id: :header)
109
103
  create_response response.code, response.body, response.content_type
110
104
  end
111
105
  end
@@ -142,8 +136,8 @@ module Selenium
142
136
 
143
137
  def proxy
144
138
  @proxy ||= begin
145
- proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
146
- no_proxy = ENV['no_proxy'] || ENV['NO_PROXY']
139
+ proxy = ENV.fetch('http_proxy', nil) || ENV.fetch('HTTP_PROXY', nil)
140
+ no_proxy = ENV.fetch('no_proxy', nil) || ENV.fetch('NO_PROXY', nil)
147
141
 
148
142
  if proxy
149
143
  proxy = "http://#{proxy}" unless proxy.start_with?('http://')
@@ -20,7 +20,6 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  module Remote
23
-
24
23
  #
25
24
  # @api private
26
25
  #
@@ -74,7 +73,7 @@ module Selenium
74
73
  end
75
74
 
76
75
  def backtrace_from_remote(server_trace)
77
- server_trace.map { |frame|
76
+ server_trace.filter_map do |frame|
78
77
  next unless frame.is_a?(Hash)
79
78
 
80
79
  file = frame['fileName']
@@ -87,7 +86,7 @@ module Selenium
87
86
  meth = 'unknown' if meth.nil? || meth.empty?
88
87
 
89
88
  "[remote server] #{file}:#{line}:in `#{meth}'"
90
- }.compact
89
+ end
91
90
  end
92
91
 
93
92
  def process_error
@@ -25,7 +25,7 @@ module Selenium
25
25
  if response.is_a? String
26
26
  super(response)
27
27
  else
28
- super("status code #{response.code}")
28
+ super("status code #{response.code}; payload #{response.payload}")
29
29
  end
30
30
  end
31
31
  end # ServerError
@@ -23,11 +23,11 @@ require 'selenium/webdriver/remote/server_error'
23
23
  module Selenium
24
24
  module WebDriver
25
25
  module Remote
26
+ autoload :Features, 'selenium/webdriver/remote/features'
26
27
  autoload :Bridge, 'selenium/webdriver/remote/bridge'
27
28
  autoload :Driver, 'selenium/webdriver/remote/driver'
28
29
  autoload :Response, 'selenium/webdriver/remote/response'
29
30
  autoload :Capabilities, 'selenium/webdriver/remote/capabilities'
30
- autoload :COMMANDS, 'selenium/webdriver/remote/commands'
31
31
 
32
32
  module Http
33
33
  autoload :Common, 'selenium/webdriver/remote/http/common'
@@ -20,7 +20,6 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  module Safari
23
-
24
23
  #
25
24
  # Driver implementation for Safari.
26
25
  # @api private
@@ -31,6 +30,13 @@ module Selenium
31
30
  DriverExtensions::HasApplePermissions,
32
31
  DriverExtensions::HasWebStorage].freeze
33
32
 
33
+ include LocalDriver
34
+
35
+ def initialize(options: nil, service: nil, url: nil, **opts)
36
+ caps, url = initialize_local_driver(options, service, url)
37
+ super(caps: caps, url: url, **opts)
38
+ end
39
+
34
40
  def browser
35
41
  :safari
36
42
  end
@@ -21,7 +21,6 @@ module Selenium
21
21
  module WebDriver
22
22
  module Safari
23
23
  module Features
24
-
25
24
  # https://developer.apple.com/library/content/documentation/NetworkingInternetWeb/Conceptual/WebDriverEndpointDoc/Commands/Commands.html
26
25
  SAFARI_COMMANDS = {
27
26
  get_permissions: [:get, 'session/:session_id/apple/permissions'],
@@ -29,8 +28,12 @@ module Selenium
29
28
  attach_debugger: [:post, 'session/:session_id/apple/attach_debugger']
30
29
  }.freeze
31
30
 
31
+ def command_list
32
+ SAFARI_COMMANDS.merge(self.class::COMMANDS)
33
+ end
34
+
32
35
  def commands(command)
33
- SAFARI_COMMANDS[command] || self.class::COMMANDS[command]
36
+ command_list[command]
34
37
  end
35
38
 
36
39
  def permissions
@@ -44,7 +47,6 @@ module Selenium
44
47
  def attach_debugger
45
48
  execute :attach_debugger, {}, {}
46
49
  end
47
-
48
50
  end # Bridge
49
51
  end # Safari
50
52
  end # WebDriver