selenium-webdriver 4.1.0 → 4.23.0

Sign up to get free protection for your applications and to get access to all the features.
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