selenium-webdriver 4.1.0 → 4.23.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 (172) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +405 -1
  3. data/Gemfile +3 -0
  4. data/LICENSE +1 -1
  5. data/NOTICE +1 -1
  6. data/README.md +3 -3
  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 +38 -40
  11. data/lib/selenium/webdriver/atoms/findElements.js +27 -27
  12. data/lib/selenium/webdriver/atoms/getAttribute.js +6 -100
  13. data/lib/selenium/webdriver/atoms/isDisplayed.js +24 -96
  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_location.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_handler.rb +63 -0
  24. data/lib/selenium/webdriver/bidi/log_inspector.rb +147 -0
  25. data/lib/selenium/webdriver/bidi/navigate_result.rb +33 -0
  26. data/lib/selenium/webdriver/bidi/session.rb +51 -0
  27. data/lib/selenium/webdriver/bidi/struct.rb +44 -0
  28. data/lib/selenium/webdriver/bidi.rb +66 -0
  29. data/lib/selenium/webdriver/chrome/driver.rb +9 -29
  30. data/lib/selenium/webdriver/chrome/features.rb +9 -67
  31. data/lib/selenium/webdriver/chrome/options.rb +3 -223
  32. data/lib/selenium/webdriver/chrome/profile.rb +3 -83
  33. data/lib/selenium/webdriver/chrome/service.rb +4 -19
  34. data/lib/selenium/webdriver/chrome.rb +0 -16
  35. data/lib/selenium/webdriver/chromium/driver.rb +61 -0
  36. data/lib/selenium/webdriver/chromium/features.rb +99 -0
  37. data/lib/selenium/webdriver/chromium/options.rb +243 -0
  38. data/lib/selenium/webdriver/chromium/profile.rb +113 -0
  39. data/lib/selenium/webdriver/chromium.rb +29 -0
  40. data/lib/selenium/webdriver/common/action_builder.rb +60 -22
  41. data/lib/selenium/webdriver/common/child_process.rb +130 -0
  42. data/lib/selenium/webdriver/common/driver.rb +46 -90
  43. data/lib/selenium/webdriver/common/driver_extensions/downloads_files.rb +0 -2
  44. data/lib/selenium/webdriver/common/driver_extensions/full_page_screenshot.rb +0 -1
  45. data/lib/selenium/webdriver/common/driver_extensions/has_addons.rb +0 -2
  46. data/lib/selenium/webdriver/common/driver_extensions/has_apple_permissions.rb +0 -2
  47. data/lib/selenium/webdriver/common/driver_extensions/has_authentication.rb +0 -2
  48. data/lib/selenium/webdriver/common/driver_extensions/{has_remote_status.rb → has_bidi.rb} +10 -5
  49. data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +10 -1
  50. data/lib/selenium/webdriver/common/driver_extensions/has_cdp.rb +0 -2
  51. data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +1 -4
  52. data/lib/selenium/webdriver/common/driver_extensions/has_debugger.rb +0 -2
  53. data/lib/selenium/webdriver/common/driver_extensions/has_devtools.rb +0 -2
  54. data/lib/selenium/webdriver/common/driver_extensions/has_fedcm_dialog.rb +55 -0
  55. data/lib/selenium/webdriver/common/driver_extensions/has_file_downloads.rb +65 -0
  56. data/lib/selenium/webdriver/common/driver_extensions/has_launching.rb +0 -2
  57. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +2 -3
  58. data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +0 -2
  59. data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +2 -69
  60. data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +0 -2
  61. data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +1 -3
  62. data/lib/selenium/webdriver/common/driver_finder.rb +97 -0
  63. data/lib/selenium/webdriver/common/element.rb +8 -8
  64. data/lib/selenium/webdriver/common/error.rb +27 -4
  65. data/lib/selenium/webdriver/common/fedcm/account.rb +50 -0
  66. data/lib/selenium/webdriver/common/fedcm/dialog.rb +74 -0
  67. data/lib/selenium/webdriver/common/fedcm.rb +27 -0
  68. data/lib/selenium/webdriver/common/html5/shared_web_storage.rb +2 -2
  69. data/lib/selenium/webdriver/common/interactions/input_device.rb +10 -4
  70. data/lib/selenium/webdriver/common/interactions/interaction.rb +12 -25
  71. data/lib/selenium/webdriver/common/interactions/interactions.rb +24 -4
  72. data/lib/selenium/webdriver/common/interactions/key_actions.rb +5 -1
  73. data/lib/selenium/webdriver/common/interactions/key_input.rb +11 -27
  74. data/lib/selenium/webdriver/common/interactions/none_input.rb +10 -8
  75. data/lib/selenium/webdriver/common/interactions/pause.rb +49 -0
  76. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +59 -71
  77. data/lib/selenium/webdriver/common/{driver_extensions/has_network_connection.rb → interactions/pointer_cancel.rb} +19 -11
  78. data/lib/selenium/webdriver/common/interactions/pointer_event_properties.rb +63 -0
  79. data/lib/selenium/webdriver/common/interactions/pointer_input.rb +15 -84
  80. data/lib/selenium/webdriver/common/interactions/pointer_move.rb +60 -0
  81. data/lib/selenium/webdriver/common/interactions/pointer_press.rb +85 -0
  82. data/lib/selenium/webdriver/common/interactions/scroll.rb +59 -0
  83. data/lib/selenium/webdriver/common/interactions/scroll_origin.rb +48 -0
  84. data/lib/selenium/webdriver/common/interactions/typing_interaction.rb +54 -0
  85. data/lib/selenium/webdriver/common/interactions/wheel_actions.rb +114 -0
  86. data/lib/selenium/webdriver/common/interactions/wheel_input.rb +42 -0
  87. data/lib/selenium/webdriver/common/keys.rb +1 -0
  88. data/lib/selenium/webdriver/common/local_driver.rb +53 -0
  89. data/lib/selenium/webdriver/common/logger.rb +93 -28
  90. data/lib/selenium/webdriver/common/manager.rb +1 -28
  91. data/lib/selenium/webdriver/common/options.rb +19 -19
  92. data/lib/selenium/webdriver/common/platform.rb +12 -52
  93. data/lib/selenium/webdriver/common/port_prober.rb +1 -1
  94. data/lib/selenium/webdriver/common/profile_helper.rb +1 -1
  95. data/lib/selenium/webdriver/common/proxy.rb +4 -4
  96. data/lib/selenium/webdriver/common/script.rb +45 -0
  97. data/lib/selenium/webdriver/common/search_context.rb +10 -8
  98. data/lib/selenium/webdriver/common/selenium_manager.rb +104 -0
  99. data/lib/selenium/webdriver/common/service.rb +21 -30
  100. data/lib/selenium/webdriver/common/service_manager.rb +8 -15
  101. data/lib/selenium/webdriver/common/shadow_root.rb +2 -3
  102. data/lib/selenium/webdriver/common/socket_lock.rb +3 -3
  103. data/lib/selenium/webdriver/common/socket_poller.rb +3 -3
  104. data/lib/selenium/webdriver/common/takes_screenshot.rb +6 -5
  105. data/lib/selenium/webdriver/common/target_locator.rb +2 -3
  106. data/lib/selenium/webdriver/common/timeouts.rb +2 -2
  107. data/lib/selenium/webdriver/common/virtual_authenticator/credential.rb +85 -0
  108. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator.rb +72 -0
  109. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator_options.rb +62 -0
  110. data/lib/selenium/webdriver/common/websocket_connection.rb +176 -0
  111. data/lib/selenium/webdriver/common/window.rb +6 -6
  112. data/lib/selenium/webdriver/common/zipper.rb +1 -1
  113. data/lib/selenium/webdriver/common.rb +26 -5
  114. data/lib/selenium/webdriver/devtools/console_event.rb +0 -2
  115. data/lib/selenium/webdriver/devtools/exception_event.rb +0 -2
  116. data/lib/selenium/webdriver/devtools/mutation_event.rb +0 -2
  117. data/lib/selenium/webdriver/devtools/network_interceptor.rb +173 -0
  118. data/lib/selenium/webdriver/devtools/pinned_script.rb +0 -2
  119. data/lib/selenium/webdriver/devtools/request.rb +1 -3
  120. data/lib/selenium/webdriver/devtools/response.rb +1 -3
  121. data/lib/selenium/webdriver/devtools.rb +17 -114
  122. data/lib/selenium/webdriver/edge/driver.rb +9 -3
  123. data/lib/selenium/webdriver/edge/features.rb +8 -4
  124. data/lib/selenium/webdriver/edge/options.rb +17 -5
  125. data/lib/selenium/webdriver/edge/profile.rb +2 -2
  126. data/lib/selenium/webdriver/edge/service.rb +8 -7
  127. data/lib/selenium/webdriver/edge.rb +0 -2
  128. data/lib/selenium/webdriver/firefox/driver.rb +9 -2
  129. data/lib/selenium/webdriver/firefox/features.rb +11 -7
  130. data/lib/selenium/webdriver/firefox/options.rb +10 -16
  131. data/lib/selenium/webdriver/firefox/profile.rb +21 -17
  132. data/lib/selenium/webdriver/firefox/profiles_ini.rb +1 -1
  133. data/lib/selenium/webdriver/firefox/service.rb +0 -18
  134. data/lib/selenium/webdriver/firefox/util.rb +46 -0
  135. data/lib/selenium/webdriver/firefox.rb +1 -14
  136. data/lib/selenium/webdriver/ie/driver.rb +7 -1
  137. data/lib/selenium/webdriver/ie/features.rb +34 -0
  138. data/lib/selenium/webdriver/ie/options.rb +6 -4
  139. data/lib/selenium/webdriver/ie/service.rb +0 -22
  140. data/lib/selenium/webdriver/ie.rb +4 -17
  141. data/lib/selenium/webdriver/remote/bidi_bridge.rb +44 -0
  142. data/lib/selenium/webdriver/remote/{commands.rb → bridge/commands.rb} +22 -9
  143. data/lib/selenium/webdriver/remote/bridge/locator_converter.rb +76 -0
  144. data/lib/selenium/webdriver/remote/bridge.rb +147 -99
  145. data/lib/selenium/webdriver/remote/capabilities.rb +5 -55
  146. data/lib/selenium/webdriver/remote/driver.rb +35 -14
  147. data/lib/selenium/webdriver/remote/features.rb +75 -0
  148. data/lib/selenium/webdriver/remote/http/common.rb +24 -6
  149. data/lib/selenium/webdriver/remote/http/curb.rb +1 -3
  150. data/lib/selenium/webdriver/remote/http/default.rb +8 -14
  151. data/lib/selenium/webdriver/remote/response.rb +8 -34
  152. data/lib/selenium/webdriver/remote/server_error.rb +2 -2
  153. data/lib/selenium/webdriver/remote.rb +2 -1
  154. data/lib/selenium/webdriver/safari/driver.rb +7 -1
  155. data/lib/selenium/webdriver/safari/features.rb +5 -3
  156. data/lib/selenium/webdriver/safari/options.rb +5 -1
  157. data/lib/selenium/webdriver/safari/service.rb +10 -4
  158. data/lib/selenium/webdriver/safari.rb +1 -15
  159. data/lib/selenium/webdriver/support/color.rb +22 -22
  160. data/lib/selenium/webdriver/support/event_firing_bridge.rb +4 -4
  161. data/lib/selenium/webdriver/support/guards/guard.rb +14 -14
  162. data/lib/selenium/webdriver/support/guards/guard_condition.rb +1 -3
  163. data/lib/selenium/webdriver/support/guards.rb +2 -2
  164. data/lib/selenium/webdriver/support/relative_locator.rb +0 -1
  165. data/lib/selenium/webdriver/support/select.rb +3 -1
  166. data/lib/selenium/webdriver/version.rb +1 -1
  167. data/lib/selenium/webdriver.rb +7 -5
  168. data/selenium-webdriver.gemspec +20 -16
  169. metadata +135 -47
  170. data/lib/selenium/webdriver/remote/http/persistent.rb +0 -65
  171. data/lib/selenium/webdriver/support/cdp/domain.rb.erb +0 -63
  172. data/lib/selenium/webdriver/support/cdp_client_generator.rb +0 -108
@@ -21,6 +21,9 @@ module Selenium
21
21
  module WebDriver
22
22
  module Remote
23
23
  class Bridge
24
+ autoload :COMMANDS, 'selenium/webdriver/remote/bridge/commands'
25
+ autoload :LocatorConverter, 'selenium/webdriver/remote/bridge/locator_converter'
26
+
24
27
  include Atoms
25
28
 
26
29
  PORT = 4444
@@ -28,10 +31,29 @@ module Selenium
28
31
  attr_accessor :http, :file_detector
29
32
  attr_reader :capabilities
30
33
 
34
+ class << self
35
+ attr_reader :extra_commands
36
+ attr_writer :element_class, :locator_converter
37
+
38
+ def add_command(name, verb, url, &block)
39
+ @extra_commands ||= {}
40
+ @extra_commands[name] = [verb, url]
41
+ define_method(name, &block)
42
+ end
43
+
44
+ def locator_converter
45
+ @locator_converter ||= LocatorConverter.new
46
+ end
47
+
48
+ def element_class
49
+ @element_class ||= Element
50
+ end
51
+ end
52
+
31
53
  #
32
54
  # 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
55
+ # @param [String, URI] url url for the remote server
56
+ # @param [Object] http_client an HTTP client instance that implements the same protocol as Http::Default
35
57
  # @api private
36
58
  #
37
59
 
@@ -42,6 +64,8 @@ module Selenium
42
64
  @http = http_client || Http::Default.new
43
65
  @http.server_url = uri
44
66
  @file_detector = nil
67
+
68
+ @locator_converter = self.class.locator_converter
45
69
  end
46
70
 
47
71
  #
@@ -59,14 +83,16 @@ module Selenium
59
83
  @capabilities = Capabilities.json_create(capabilities)
60
84
 
61
85
  case @capabilities[:browser_name]
62
- when 'chrome'
86
+ when 'chrome', 'chrome-headless-shell'
63
87
  extend(WebDriver::Chrome::Features)
64
88
  when 'firefox'
65
89
  extend(WebDriver::Firefox::Features)
66
- when 'msedge'
90
+ when 'msedge', 'MicrosoftEdge'
67
91
  extend(WebDriver::Edge::Features)
68
92
  when 'Safari', 'Safari Technology Preview'
69
93
  extend(WebDriver::Safari::Features)
94
+ when 'internet explorer'
95
+ extend(WebDriver::IE::Features)
70
96
  end
71
97
  end
72
98
 
@@ -81,7 +107,7 @@ module Selenium
81
107
  def browser
82
108
  @browser ||= begin
83
109
  name = @capabilities.browser_name
84
- name ? name.tr(' ', '_').downcase.to_sym : 'unknown'
110
+ name ? name.tr(' -', '_').downcase.to_sym : 'unknown'
85
111
  end
86
112
  end
87
113
 
@@ -118,7 +144,7 @@ module Selenium
118
144
  end
119
145
 
120
146
  def alert=(keys)
121
- execute :send_alert_text, {}, {value: keys.split(//), text: keys}
147
+ execute :send_alert_text, {}, {value: keys.chars, text: keys}
122
148
  end
123
149
 
124
150
  def alert_text
@@ -146,9 +172,7 @@ module Selenium
146
172
  end
147
173
 
148
174
  def page_source
149
- execute_script('var source = document.documentElement.outerHTML;' \
150
- 'if (!source) { source = new XMLSerializer().serializeToString(document); }' \
151
- 'return source;')
175
+ execute :get_page_source
152
176
  end
153
177
 
154
178
  #
@@ -188,6 +212,7 @@ module Selenium
188
212
  execute :delete_session
189
213
  http.close
190
214
  rescue *QUIT_ERRORS
215
+ nil
191
216
  end
192
217
 
193
218
  def close
@@ -369,21 +394,10 @@ module Selenium
369
394
  # actions
370
395
  #
371
396
 
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'
397
+ def action(async: false, devices: [], duration: 250)
398
+ ActionBuilder.new self, async: async, devices: devices, duration: duration
386
399
  end
400
+ alias actions action
387
401
 
388
402
  def send_actions(data)
389
403
  execute :actions, {}, {actions: data}
@@ -402,27 +416,9 @@ module Selenium
402
416
  end
403
417
 
404
418
  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)}
419
+ keys = upload_if_necessary(keys) if @file_detector
420
+ text = keys.join
421
+ execute :element_send_keys, {id: element}, {value: text.chars, text: text}
426
422
  end
427
423
 
428
424
  def clear_element(element)
@@ -430,10 +426,19 @@ module Selenium
430
426
  end
431
427
 
432
428
  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)
429
+ script = "/* submitForm */ var form = arguments[0];\n" \
430
+ "while (form.nodeName != \"FORM\" && form.parentNode) {\n " \
431
+ "form = form.parentNode;\n" \
432
+ "}\n" \
433
+ "if (!form) { throw Error('Unable to find containing form element'); }\n" \
434
+ "if (!form.ownerDocument) { throw Error('Unable to find owning document'); }\n" \
435
+ "var e = form.ownerDocument.createEvent('Event');\n" \
436
+ "e.initEvent('submit', true, true);\n" \
437
+ "if (form.dispatchEvent(e)) { HTMLFormElement.prototype.submit.call(form) }\n"
438
+
439
+ execute_script(script, Bridge.element_class::ELEMENT_KEY => element)
440
+ rescue Error::JavascriptError
441
+ raise Error::UnsupportedOperationError, 'To submit an element, it must be nested inside a form element'
437
442
  end
438
443
 
439
444
  #
@@ -445,7 +450,7 @@ module Selenium
445
450
  end
446
451
 
447
452
  def element_attribute(element, name)
448
- WebDriver.logger.info "Using script for :getAttribute of #{name}"
453
+ WebDriver.logger.debug "Using script for :getAttribute of #{name}", id: :script
449
454
  execute_atom :getAttribute, element, name
450
455
  end
451
456
 
@@ -505,7 +510,7 @@ module Selenium
505
510
  end
506
511
 
507
512
  def element_displayed?(element)
508
- WebDriver.logger.info 'Using script for :isDisplayed'
513
+ WebDriver.logger.debug 'Using script for :isDisplayed', id: :script
509
514
  execute_atom :isDisplayed, element
510
515
  end
511
516
 
@@ -518,13 +523,13 @@ module Selenium
518
523
  #
519
524
 
520
525
  def active_element
521
- Element.new self, element_id_from(execute(:get_active_element))
526
+ Bridge.element_class.new self, element_id_from(execute(:get_active_element))
522
527
  end
523
528
 
524
- alias_method :switch_to_active_element, :active_element
529
+ alias switch_to_active_element active_element
525
530
 
526
531
  def find_element_by(how, what, parent_ref = [])
527
- how, what = convert_locator(how, what)
532
+ how, what = @locator_converter.convert(how, what)
528
533
 
529
534
  return execute_atom(:findElements, Support::RelativeLocator.new(what).as_json).first if how == 'relative'
530
535
 
@@ -538,11 +543,11 @@ module Selenium
538
543
  execute :find_element, {}, {using: how, value: what.to_s}
539
544
  end
540
545
 
541
- Element.new self, element_id_from(id)
546
+ Bridge.element_class.new self, element_id_from(id)
542
547
  end
543
548
 
544
549
  def find_elements_by(how, what, parent_ref = [])
545
- how, what = convert_locator(how, what)
550
+ how, what = @locator_converter.convert(how, what)
546
551
 
547
552
  return execute_atom :findElements, Support::RelativeLocator.new(what).as_json if how == 'relative'
548
553
 
@@ -556,7 +561,7 @@ module Selenium
556
561
  execute :find_elements, {}, {using: how, value: what.to_s}
557
562
  end
558
563
 
559
- ids.map { |id| Element.new self, element_id_from(id) }
564
+ ids.map { |id| Bridge.element_class.new self, element_id_from(id) }
560
565
  end
561
566
 
562
567
  def shadow_root(element)
@@ -564,6 +569,88 @@ module Selenium
564
569
  ShadowRoot.new self, shadow_root_id_from(id)
565
570
  end
566
571
 
572
+ #
573
+ # virtual-authenticator
574
+ #
575
+
576
+ def add_virtual_authenticator(options)
577
+ authenticator_id = execute :add_virtual_authenticator, {}, options.as_json
578
+ VirtualAuthenticator.new(self, authenticator_id, options)
579
+ end
580
+
581
+ def remove_virtual_authenticator(id)
582
+ execute :remove_virtual_authenticator, {authenticatorId: id}
583
+ end
584
+
585
+ def add_credential(credential, id)
586
+ execute :add_credential, {authenticatorId: id}, credential
587
+ end
588
+
589
+ def credentials(authenticator_id)
590
+ execute :get_credentials, {authenticatorId: authenticator_id}
591
+ end
592
+
593
+ def remove_credential(credential_id, authenticator_id)
594
+ execute :remove_credential, {credentialId: credential_id, authenticatorId: authenticator_id}
595
+ end
596
+
597
+ def remove_all_credentials(authenticator_id)
598
+ execute :remove_all_credentials, {authenticatorId: authenticator_id}
599
+ end
600
+
601
+ def user_verified(verified, authenticator_id)
602
+ execute :set_user_verified, {authenticatorId: authenticator_id}, {isUserVerified: verified}
603
+ end
604
+
605
+ #
606
+ # federated-credential management
607
+ #
608
+
609
+ def cancel_fedcm_dialog
610
+ execute :cancel_fedcm_dialog
611
+ end
612
+
613
+ def select_fedcm_account(index)
614
+ execute :select_fedcm_account, {}, {accountIndex: index}
615
+ end
616
+
617
+ def fedcm_dialog_type
618
+ execute :get_fedcm_dialog_type
619
+ end
620
+
621
+ def fedcm_title
622
+ execute(:get_fedcm_title).fetch('title')
623
+ end
624
+
625
+ def fedcm_subtitle
626
+ execute(:get_fedcm_title).fetch('subtitle', nil)
627
+ end
628
+
629
+ def fedcm_account_list
630
+ execute :get_fedcm_account_list
631
+ end
632
+
633
+ def fedcm_delay(enabled)
634
+ execute :set_fedcm_delay, {}, {enabled: enabled}
635
+ end
636
+
637
+ def reset_fedcm_cooldown
638
+ execute :reset_fedcm_cooldown
639
+ end
640
+
641
+ def click_fedcm_dialog_button
642
+ execute :click_fedcm_dialog_button, {}, {dialogButton: 'ConfirmIdpLoginContinue'}
643
+ end
644
+
645
+ def bidi
646
+ msg = 'BiDi must be enabled by setting #web_socket_url to true in options class'
647
+ raise(WebDriver::Error::WebDriverError, msg)
648
+ end
649
+
650
+ def command_list
651
+ COMMANDS
652
+ end
653
+
567
654
  private
568
655
 
569
656
  #
@@ -584,7 +671,7 @@ module Selenium
584
671
  raise ArgumentError, "#{opts.inspect} invalid for #{command.inspect}"
585
672
  end
586
673
 
587
- WebDriver.logger.info("-> #{verb.to_s.upcase} #{path}")
674
+ WebDriver.logger.debug("-> #{verb.to_s.upcase} #{path}", id: :command)
588
675
  http.call(verb, path, command_hash)['value']
589
676
  end
590
677
 
@@ -593,7 +680,7 @@ module Selenium
593
680
  end
594
681
 
595
682
  def commands(command)
596
- COMMANDS[command]
683
+ command_list[command] || Bridge.extra_commands[command]
597
684
  end
598
685
 
599
686
  def unwrap_script_result(arg)
@@ -602,7 +689,7 @@ module Selenium
602
689
  arg.map { |e| unwrap_script_result(e) }
603
690
  when Hash
604
691
  element_id = element_id_from(arg)
605
- return Element.new(self, element_id) if element_id
692
+ return Bridge.element_class.new(self, element_id) if element_id
606
693
 
607
694
  shadow_root_id = shadow_root_id_from(arg)
608
695
  return ShadowRoot.new self, shadow_root_id if shadow_root_id
@@ -614,7 +701,7 @@ module Selenium
614
701
  end
615
702
 
616
703
  def element_id_from(id)
617
- id['ELEMENT'] || id[Element::ELEMENT_KEY]
704
+ id['ELEMENT'] || id[Bridge.element_class::ELEMENT_KEY]
618
705
  end
619
706
 
620
707
  def shadow_root_id_from(id)
@@ -625,45 +712,6 @@ module Selenium
625
712
  capabilities = {alwaysMatch: capabilities} if !capabilities['alwaysMatch'] && !capabilities['firstMatch']
626
713
  {capabilities: capabilities}
627
714
  end
628
-
629
- def convert_locator(how, what)
630
- how = SearchContext::FINDERS[how.to_sym] || how
631
-
632
- case how
633
- when 'class name'
634
- how = 'css selector'
635
- what = ".#{escape_css(what.to_s)}"
636
- when 'id'
637
- how = 'css selector'
638
- what = "##{escape_css(what.to_s)}"
639
- when 'name'
640
- how = 'css selector'
641
- what = "*[name='#{escape_css(what.to_s)}']"
642
- when 'tag name'
643
- how = 'css selector'
644
- end
645
-
646
- if what.is_a?(Hash)
647
- what = what.each_with_object({}) do |(h, w), hash|
648
- h, w = convert_locator(h.to_s, w)
649
- hash[h] = w
650
- end
651
- end
652
-
653
- [how, what]
654
- end
655
-
656
- ESCAPE_CSS_REGEXP = /(['"\\#.:;,!?+<>=~*^$|%&@`{}\-\[\]()])/.freeze
657
- UNICODE_CODE_POINT = 30
658
-
659
- # Escapes invalid characters in CSS selector.
660
- # @see https://mathiasbynens.be/notes/css-escapes
661
- def escape_css(string)
662
- string = string.gsub(ESCAPE_CSS_REGEXP) { |match| "\\#{match}" }
663
- string = "\\#{UNICODE_CODE_POINT + Integer(string[0])} #{string[1..]}" if string[0]&.match?(/[[:digit:]]/)
664
-
665
- string
666
- end
667
715
  end # Bridge
668
716
  end # Remote
669
717
  end # WebDriver
@@ -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,
@@ -50,65 +48,16 @@ module Selenium
50
48
  @capabilities[key]
51
49
  end
52
50
 
53
- define_method "#{key}=" do |value|
51
+ define_method :"#{key}=" do |value|
54
52
  @capabilities[key] = value
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|
@@ -149,7 +99,7 @@ module Selenium
149
99
  end
150
100
 
151
101
  def camel_case(str_or_sym)
152
- str_or_sym.to_s.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }
102
+ str_or_sym.to_s.gsub(/_([a-z])/) { Regexp.last_match(1)&.upcase }
153
103
  end
154
104
 
155
105
  private
@@ -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")&.filter_map { |key| @file_detector.call(Array(key)) }
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