selenium-webdriver 4.12.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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +135 -1
  3. data/Gemfile +1 -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 +2 -1
  11. data/lib/selenium/webdriver/atoms/findElements.js +28 -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.rb +6 -3
  15. data/lib/selenium/webdriver/bidi/log/javascript_log_entry.rb +1 -1
  16. data/lib/selenium/webdriver/bidi/log_handler.rb +63 -0
  17. data/lib/selenium/webdriver/bidi/log_inspector.rb +5 -1
  18. data/lib/selenium/webdriver/bidi/session.rb +7 -7
  19. data/lib/selenium/webdriver/bidi/struct.rb +44 -0
  20. data/lib/selenium/webdriver/bidi.rb +10 -0
  21. data/lib/selenium/webdriver/chrome/features.rb +5 -1
  22. data/lib/selenium/webdriver/chrome/service.rb +7 -0
  23. data/lib/selenium/webdriver/chromium/driver.rb +1 -1
  24. data/lib/selenium/webdriver/chromium/features.rb +0 -4
  25. data/lib/selenium/webdriver/common/action_builder.rb +0 -4
  26. data/lib/selenium/webdriver/common/child_process.rb +8 -2
  27. data/lib/selenium/webdriver/common/driver.rb +23 -17
  28. data/lib/selenium/webdriver/common/driver_extensions/has_bidi.rb +1 -1
  29. data/lib/selenium/webdriver/common/driver_extensions/has_fedcm_dialog.rb +55 -0
  30. data/lib/selenium/webdriver/common/driver_extensions/has_file_downloads.rb +65 -0
  31. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +1 -1
  32. data/lib/selenium/webdriver/common/driver_finder.rb +66 -14
  33. data/lib/selenium/webdriver/common/error.rb +22 -22
  34. data/lib/selenium/webdriver/common/fedcm/account.rb +50 -0
  35. data/lib/selenium/webdriver/common/fedcm/dialog.rb +74 -0
  36. data/lib/selenium/webdriver/common/fedcm.rb +27 -0
  37. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +0 -1
  38. data/lib/selenium/webdriver/common/interactions/pointer_cancel.rb +1 -1
  39. data/lib/selenium/webdriver/common/interactions/wheel_actions.rb +2 -1
  40. data/lib/selenium/webdriver/common/interactions/wheel_input.rb +1 -1
  41. data/lib/selenium/webdriver/common/local_driver.rb +8 -1
  42. data/lib/selenium/webdriver/common/logger.rb +7 -7
  43. data/lib/selenium/webdriver/common/manager.rb +1 -1
  44. data/lib/selenium/webdriver/common/options.rb +6 -2
  45. data/lib/selenium/webdriver/common/platform.rb +7 -1
  46. data/lib/selenium/webdriver/common/proxy.rb +2 -2
  47. data/lib/selenium/webdriver/common/script.rb +45 -0
  48. data/lib/selenium/webdriver/common/search_context.rb +10 -2
  49. data/lib/selenium/webdriver/common/selenium_manager.rb +34 -59
  50. data/lib/selenium/webdriver/common/service.rb +4 -0
  51. data/lib/selenium/webdriver/common/service_manager.rb +1 -1
  52. data/lib/selenium/webdriver/common/socket_poller.rb +1 -1
  53. data/lib/selenium/webdriver/common/takes_screenshot.rb +4 -2
  54. data/lib/selenium/webdriver/common/websocket_connection.rb +12 -0
  55. data/lib/selenium/webdriver/common.rb +5 -2
  56. data/lib/selenium/webdriver/devtools/network_interceptor.rb +1 -1
  57. data/lib/selenium/webdriver/edge/features.rb +5 -1
  58. data/lib/selenium/webdriver/edge/service.rb +7 -0
  59. data/lib/selenium/webdriver/firefox/features.rb +5 -1
  60. data/lib/selenium/webdriver/firefox/options.rb +3 -0
  61. data/lib/selenium/webdriver/firefox/profile.rb +14 -6
  62. data/lib/selenium/webdriver/firefox/profiles_ini.rb +1 -1
  63. data/lib/selenium/webdriver/{common/driver_extensions/has_location.rb → ie/features.rb} +8 -10
  64. data/lib/selenium/webdriver/ie/options.rb +3 -2
  65. data/lib/selenium/webdriver/ie.rb +4 -3
  66. data/lib/selenium/webdriver/{common/driver_extensions/has_network_connection.rb → remote/bidi_bridge.rb} +18 -11
  67. data/lib/selenium/webdriver/remote/bridge/commands.rb +13 -7
  68. data/lib/selenium/webdriver/remote/bridge/locator_converter.rb +76 -0
  69. data/lib/selenium/webdriver/remote/bridge.rb +87 -69
  70. data/lib/selenium/webdriver/remote/capabilities.rb +2 -2
  71. data/lib/selenium/webdriver/remote/driver.rb +4 -0
  72. data/lib/selenium/webdriver/remote/features.rb +75 -0
  73. data/lib/selenium/webdriver/remote/http/common.rb +21 -3
  74. data/lib/selenium/webdriver/remote/response.rb +8 -33
  75. data/lib/selenium/webdriver/remote/server_error.rb +1 -1
  76. data/lib/selenium/webdriver/remote.rb +2 -0
  77. data/lib/selenium/webdriver/safari/features.rb +5 -1
  78. data/lib/selenium/webdriver/support/event_firing_bridge.rb +4 -4
  79. data/lib/selenium/webdriver/support/guards/guard.rb +14 -12
  80. data/lib/selenium/webdriver/support/guards.rb +1 -1
  81. data/lib/selenium/webdriver/version.rb +1 -1
  82. data/lib/selenium/webdriver.rb +1 -1
  83. data/selenium-webdriver.gemspec +9 -7
  84. metadata +84 -6
@@ -17,21 +17,28 @@
17
17
  # specific language governing permissions and limitations
18
18
  # under the License.
19
19
 
20
- # TODO: Deprecated; Delete after 4.0 release
21
20
  module Selenium
22
21
  module WebDriver
23
- module DriverExtensions
24
- module HasNetworkConnection
25
- def network_connection_type
26
- raise Error::UnsupportedOperationError,
27
- 'The W3C standard does not currently support getting network connection'
22
+ module Remote
23
+ class BiDiBridge < Bridge
24
+ attr_reader :bidi
25
+
26
+ def create_session(capabilities)
27
+ super
28
+ socket_url = @capabilities[:web_socket_url]
29
+ @bidi = Selenium::WebDriver::BiDi.new(url: socket_url)
30
+ end
31
+
32
+ def quit
33
+ super
34
+ ensure
35
+ bidi.close
28
36
  end
29
37
 
30
- def network_connection_type=(*)
31
- raise Error::UnsupportedOperationError,
32
- 'The W3C standard does not currently support setting network connection'
38
+ def close
39
+ execute(:close_window).tap { |handles| bidi.close if handles.empty? }
33
40
  end
34
- end # HasNetworkConnection
35
- end # DriverExtensions
41
+ end # BiDiBridge
42
+ end # Remote
36
43
  end # WebDriver
37
44
  end # Selenium
@@ -144,12 +144,6 @@ module Selenium
144
144
  take_screenshot: [:get, 'session/:session_id/screenshot'],
145
145
  take_element_screenshot: [:get, 'session/:session_id/element/:id/screenshot'],
146
146
 
147
- #
148
- # server extensions
149
- #
150
-
151
- upload_file: [:post, 'session/:session_id/se/file'],
152
-
153
147
  #
154
148
  # virtual-authenticator
155
149
  #
@@ -161,8 +155,20 @@ module Selenium
161
155
  remove_credential: [:delete,
162
156
  'session/:session_id/webauthn/authenticator/:authenticatorId/credentials/:credentialId'],
163
157
  remove_all_credentials: [:delete, 'session/:session_id/webauthn/authenticator/:authenticatorId/credentials'],
164
- set_user_verified: [:post, 'session/:session_id/webauthn/authenticator/:authenticatorId/uv']
158
+ set_user_verified: [:post, 'session/:session_id/webauthn/authenticator/:authenticatorId/uv'],
159
+
160
+ #
161
+ # federated-credential management
162
+ #
165
163
 
164
+ get_fedcm_title: [:get, 'session/:session_id/fedcm/gettitle'],
165
+ get_fedcm_dialog_type: [:get, 'session/:session_id/fedcm/getdialogtype'],
166
+ get_fedcm_account_list: [:get, 'session/:session_id/fedcm/accountlist'],
167
+ click_fedcm_dialog_button: [:post, 'session/:session_id/fedcm/clickdialogbutton'],
168
+ cancel_fedcm_dialog: [:post, 'session/:session_id/fedcm/canceldialog'],
169
+ select_fedcm_account: [:post, 'session/:session_id/fedcm/selectaccount'],
170
+ set_fedcm_delay: [:post, 'session/:session_id/fedcm/setdelayenabled'],
171
+ reset_fedcm_cooldown: [:post, 'session/:session_id/fedcm/resetcooldown']
166
172
  }.freeze
167
173
  end # Bridge
168
174
  end # Remote
@@ -0,0 +1,76 @@
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
+ class Bridge
24
+ class LocatorConverter
25
+ ESCAPE_CSS_REGEXP = /(['"\\#.:;,!?+<>=~*^$|%&@`{}\-\[\]()])/
26
+ UNICODE_CODE_POINT = 30
27
+
28
+ #
29
+ # Converts a locator to a specification compatible one.
30
+ # @param [String, Symbol] how
31
+ # @param [String] what
32
+ #
33
+
34
+ def convert(how, what)
35
+ how = SearchContext.finders[how.to_sym] || how
36
+
37
+ case how
38
+ when 'class name'
39
+ how = 'css selector'
40
+ what = ".#{escape_css(what.to_s)}"
41
+ when 'id'
42
+ how = 'css selector'
43
+ what = "##{escape_css(what.to_s)}"
44
+ when 'name'
45
+ how = 'css selector'
46
+ what = "*[name='#{escape_css(what.to_s)}']"
47
+ end
48
+
49
+ if what.is_a?(Hash)
50
+ what = what.each_with_object({}) do |(h, w), hash|
51
+ h, w = convert(h.to_s, w)
52
+ hash[h] = w
53
+ end
54
+ end
55
+
56
+ [how, what]
57
+ end
58
+
59
+ private
60
+
61
+ #
62
+ # Escapes invalid characters in CSS selector.
63
+ # @see https://mathiasbynens.be/notes/css-escapes
64
+ #
65
+
66
+ def escape_css(string)
67
+ string = string.gsub(ESCAPE_CSS_REGEXP) { |match| "\\#{match}" }
68
+ string = "\\#{UNICODE_CODE_POINT + Integer(string[0])} #{string[1..]}" if string[0]&.match?(/[[:digit:]]/)
69
+
70
+ string
71
+ end
72
+ end # LocatorConverter
73
+ end # Bridge
74
+ end # Remote
75
+ end # WebDriver
76
+ end # Selenium
@@ -22,6 +22,8 @@ module Selenium
22
22
  module Remote
23
23
  class Bridge
24
24
  autoload :COMMANDS, 'selenium/webdriver/remote/bridge/commands'
25
+ autoload :LocatorConverter, 'selenium/webdriver/remote/bridge/locator_converter'
26
+
25
27
  include Atoms
26
28
 
27
29
  PORT = 4444
@@ -29,6 +31,25 @@ module Selenium
29
31
  attr_accessor :http, :file_detector
30
32
  attr_reader :capabilities
31
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
+
32
53
  #
33
54
  # Initializes the bridge with the given server URL
34
55
  # @param [String, URI] url url for the remote server
@@ -43,6 +64,8 @@ module Selenium
43
64
  @http = http_client || Http::Default.new
44
65
  @http.server_url = uri
45
66
  @file_detector = nil
67
+
68
+ @locator_converter = self.class.locator_converter
46
69
  end
47
70
 
48
71
  #
@@ -60,14 +83,16 @@ module Selenium
60
83
  @capabilities = Capabilities.json_create(capabilities)
61
84
 
62
85
  case @capabilities[:browser_name]
63
- when 'chrome'
86
+ when 'chrome', 'chrome-headless-shell'
64
87
  extend(WebDriver::Chrome::Features)
65
88
  when 'firefox'
66
89
  extend(WebDriver::Firefox::Features)
67
- when 'msedge'
90
+ when 'msedge', 'MicrosoftEdge'
68
91
  extend(WebDriver::Edge::Features)
69
92
  when 'Safari', 'Safari Technology Preview'
70
93
  extend(WebDriver::Safari::Features)
94
+ when 'internet explorer'
95
+ extend(WebDriver::IE::Features)
71
96
  end
72
97
  end
73
98
 
@@ -82,7 +107,7 @@ module Selenium
82
107
  def browser
83
108
  @browser ||= begin
84
109
  name = @capabilities.browser_name
85
- name ? name.tr(' ', '_').downcase.to_sym : 'unknown'
110
+ name ? name.tr(' -', '_').downcase.to_sym : 'unknown'
86
111
  end
87
112
  end
88
113
 
@@ -391,30 +416,11 @@ module Selenium
391
416
  end
392
417
 
393
418
  def send_keys_to_element(element, keys)
394
- # TODO: rework file detectors before Selenium 4.0
395
- if @file_detector
396
- local_files = keys.first&.split("\n")&.map { |key| @file_detector.call(Array(key)) }&.compact
397
- if local_files&.any?
398
- keys = local_files.map { |local_file| upload(local_file) }
399
- keys = Array(keys.join("\n"))
400
- end
401
- end
402
-
403
- # Keep .split(//) for backward compatibility for now
419
+ keys = upload_if_necessary(keys) if @file_detector
404
420
  text = keys.join
405
421
  execute :element_send_keys, {id: element}, {value: text.chars, text: text}
406
422
  end
407
423
 
408
- def upload(local_file)
409
- unless File.file?(local_file)
410
- WebDriver.logger.debug("File detector only works with files. #{local_file.inspect} isn`t a file!",
411
- id: :file_detector)
412
- raise Error::WebDriverError, "You are trying to work with something that isn't a file."
413
- end
414
-
415
- execute :upload_file, {}, {file: Zipper.zip_file(local_file)}
416
- end
417
-
418
424
  def clear_element(element)
419
425
  execute :element_clear, id: element
420
426
  end
@@ -430,7 +436,7 @@ module Selenium
430
436
  "e.initEvent('submit', true, true);\n" \
431
437
  "if (form.dispatchEvent(e)) { HTMLFormElement.prototype.submit.call(form) }\n"
432
438
 
433
- execute_script(script, Element::ELEMENT_KEY => element)
439
+ execute_script(script, Bridge.element_class::ELEMENT_KEY => element)
434
440
  rescue Error::JavascriptError
435
441
  raise Error::UnsupportedOperationError, 'To submit an element, it must be nested inside a form element'
436
442
  end
@@ -517,13 +523,13 @@ module Selenium
517
523
  #
518
524
 
519
525
  def active_element
520
- Element.new self, element_id_from(execute(:get_active_element))
526
+ Bridge.element_class.new self, element_id_from(execute(:get_active_element))
521
527
  end
522
528
 
523
529
  alias switch_to_active_element active_element
524
530
 
525
531
  def find_element_by(how, what, parent_ref = [])
526
- how, what = convert_locator(how, what)
532
+ how, what = @locator_converter.convert(how, what)
527
533
 
528
534
  return execute_atom(:findElements, Support::RelativeLocator.new(what).as_json).first if how == 'relative'
529
535
 
@@ -537,11 +543,11 @@ module Selenium
537
543
  execute :find_element, {}, {using: how, value: what.to_s}
538
544
  end
539
545
 
540
- Element.new self, element_id_from(id)
546
+ Bridge.element_class.new self, element_id_from(id)
541
547
  end
542
548
 
543
549
  def find_elements_by(how, what, parent_ref = [])
544
- how, what = convert_locator(how, what)
550
+ how, what = @locator_converter.convert(how, what)
545
551
 
546
552
  return execute_atom :findElements, Support::RelativeLocator.new(what).as_json if how == 'relative'
547
553
 
@@ -555,7 +561,7 @@ module Selenium
555
561
  execute :find_elements, {}, {using: how, value: what.to_s}
556
562
  end
557
563
 
558
- ids.map { |id| Element.new self, element_id_from(id) }
564
+ ids.map { |id| Bridge.element_class.new self, element_id_from(id) }
559
565
  end
560
566
 
561
567
  def shadow_root(element)
@@ -596,6 +602,55 @@ module Selenium
596
602
  execute :set_user_verified, {authenticatorId: authenticator_id}, {isUserVerified: verified}
597
603
  end
598
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
+
599
654
  private
600
655
 
601
656
  #
@@ -625,7 +680,7 @@ module Selenium
625
680
  end
626
681
 
627
682
  def commands(command)
628
- COMMANDS[command]
683
+ command_list[command] || Bridge.extra_commands[command]
629
684
  end
630
685
 
631
686
  def unwrap_script_result(arg)
@@ -634,7 +689,7 @@ module Selenium
634
689
  arg.map { |e| unwrap_script_result(e) }
635
690
  when Hash
636
691
  element_id = element_id_from(arg)
637
- return Element.new(self, element_id) if element_id
692
+ return Bridge.element_class.new(self, element_id) if element_id
638
693
 
639
694
  shadow_root_id = shadow_root_id_from(arg)
640
695
  return ShadowRoot.new self, shadow_root_id if shadow_root_id
@@ -646,7 +701,7 @@ module Selenium
646
701
  end
647
702
 
648
703
  def element_id_from(id)
649
- id['ELEMENT'] || id[Element::ELEMENT_KEY]
704
+ id['ELEMENT'] || id[Bridge.element_class::ELEMENT_KEY]
650
705
  end
651
706
 
652
707
  def shadow_root_id_from(id)
@@ -657,43 +712,6 @@ module Selenium
657
712
  capabilities = {alwaysMatch: capabilities} if !capabilities['alwaysMatch'] && !capabilities['firstMatch']
658
713
  {capabilities: capabilities}
659
714
  end
660
-
661
- def convert_locator(how, what)
662
- how = SearchContext::FINDERS[how.to_sym] || how
663
-
664
- case how
665
- when 'class name'
666
- how = 'css selector'
667
- what = ".#{escape_css(what.to_s)}"
668
- when 'id'
669
- how = 'css selector'
670
- what = "##{escape_css(what.to_s)}"
671
- when 'name'
672
- how = 'css selector'
673
- what = "*[name='#{escape_css(what.to_s)}']"
674
- end
675
-
676
- if what.is_a?(Hash)
677
- what = what.each_with_object({}) do |(h, w), hash|
678
- h, w = convert_locator(h.to_s, w)
679
- hash[h] = w
680
- end
681
- end
682
-
683
- [how, what]
684
- end
685
-
686
- ESCAPE_CSS_REGEXP = /(['"\\#.:;,!?+<>=~*^$|%&@`{}\-\[\]()])/
687
- UNICODE_CODE_POINT = 30
688
-
689
- # Escapes invalid characters in CSS selector.
690
- # @see https://mathiasbynens.be/notes/css-escapes
691
- def escape_css(string)
692
- string = string.gsub(ESCAPE_CSS_REGEXP) { |match| "\\#{match}" }
693
- string = "\\#{UNICODE_CODE_POINT + Integer(string[0])} #{string[1..]}" if string[0]&.match?(/[[:digit:]]/)
694
-
695
- string
696
- end
697
715
  end # Bridge
698
716
  end # Remote
699
717
  end # WebDriver
@@ -48,7 +48,7 @@ module Selenium
48
48
  @capabilities[key]
49
49
  end
50
50
 
51
- define_method "#{key}=" do |value|
51
+ define_method :"#{key}=" do |value|
52
52
  @capabilities[key] = value
53
53
  end
54
54
  end
@@ -99,7 +99,7 @@ module Selenium
99
99
  end
100
100
 
101
101
  def camel_case(str_or_sym)
102
- 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 }
103
103
  end
104
104
 
105
105
  private
@@ -28,6 +28,7 @@ module Selenium
28
28
  class Driver < WebDriver::Driver
29
29
  include DriverExtensions::UploadsFiles
30
30
  include DriverExtensions::HasSessionId
31
+ include DriverExtensions::HasFileDownloads
31
32
 
32
33
  def initialize(capabilities: nil, options: nil, service: nil, url: nil, **opts)
33
34
  raise ArgumentError, "Can not set :service object on #{self.class}" if service
@@ -36,6 +37,9 @@ module Selenium
36
37
  caps = process_options(options, capabilities)
37
38
  super(caps: caps, url: url, **opts)
38
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)
39
43
  end
40
44
 
41
45
  private
@@ -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
@@ -26,10 +26,18 @@ module Selenium
26
26
  CONTENT_TYPE = 'application/json'
27
27
  DEFAULT_HEADERS = {
28
28
  'Accept' => CONTENT_TYPE,
29
- 'Content-Type' => "#{CONTENT_TYPE}; charset=UTF-8",
30
- 'User-Agent' => "selenium/#{WebDriver::VERSION} (ruby #{Platform.os})"
29
+ 'Content-Type' => "#{CONTENT_TYPE}; charset=UTF-8"
31
30
  }.freeze
32
31
 
32
+ class << self
33
+ attr_accessor :extra_headers
34
+ attr_writer :user_agent
35
+
36
+ def user_agent
37
+ @user_agent ||= "selenium/#{WebDriver::VERSION} (ruby #{Platform.os})"
38
+ end
39
+ end
40
+
33
41
  attr_writer :server_url
34
42
 
35
43
  def quit_errors
@@ -42,7 +50,7 @@ module Selenium
42
50
 
43
51
  def call(verb, url, command_hash)
44
52
  url = server_url.merge(url) unless url.is_a?(URI)
45
- headers = DEFAULT_HEADERS.dup
53
+ headers = common_headers.dup
46
54
  headers['Cache-Control'] = 'no-cache' if verb == :get
47
55
 
48
56
  if command_hash
@@ -61,6 +69,16 @@ module Selenium
61
69
 
62
70
  private
63
71
 
72
+ def common_headers
73
+ @common_headers ||= begin
74
+ headers = DEFAULT_HEADERS.dup
75
+ headers['User-Agent'] = Common.user_agent
76
+ headers = headers.merge(Common.extra_headers || {})
77
+
78
+ headers
79
+ end
80
+ end
81
+
64
82
  def server_url
65
83
  return @server_url if @server_url
66
84
 
@@ -28,7 +28,7 @@ module Selenium
28
28
  attr_reader :code, :payload
29
29
 
30
30
  def initialize(code, payload = nil)
31
- @code = code
31
+ @code = code
32
32
  @payload = payload || {}
33
33
 
34
34
  assert_ok
@@ -37,11 +37,8 @@ module Selenium
37
37
  def error
38
38
  error, message, backtrace = process_error
39
39
  klass = Error.for_error(error) || return
40
-
41
40
  ex = klass.new(message)
42
- ex.set_backtrace(caller)
43
- add_backtrace ex, backtrace
44
-
41
+ add_cause(ex, error, backtrace)
45
42
  ex
46
43
  end
47
44
 
@@ -59,34 +56,12 @@ module Selenium
59
56
  raise Error::ServerError, self
60
57
  end
61
58
 
62
- def add_backtrace(ex, server_trace)
63
- return unless server_trace
64
-
65
- backtrace = case server_trace
66
- when Array
67
- backtrace_from_remote(server_trace)
68
- when String
69
- server_trace.split("\n")
70
- end
71
-
72
- ex.set_backtrace(backtrace + ex.backtrace)
73
- end
74
-
75
- def backtrace_from_remote(server_trace)
76
- server_trace.filter_map do |frame|
77
- next unless frame.is_a?(Hash)
78
-
79
- file = frame['fileName']
80
- line = frame['lineNumber']
81
- meth = frame['methodName']
82
-
83
- class_name = frame['className']
84
- file = "#{class_name}(#{file})" if class_name
85
-
86
- meth = 'unknown' if meth.nil? || meth.empty?
87
-
88
- "[remote server] #{file}:#{line}:in `#{meth}'"
89
- end
59
+ def add_cause(ex, error, backtrace)
60
+ cause = Error::WebDriverError.new
61
+ cause.set_backtrace(backtrace)
62
+ raise ex, cause: cause
63
+ rescue Error.for_error(error)
64
+ ex
90
65
  end
91
66
 
92
67
  def process_error
@@ -23,7 +23,7 @@ module Selenium
23
23
  class ServerError < StandardError
24
24
  def initialize(response)
25
25
  if response.is_a? String
26
- super(response)
26
+ super
27
27
  else
28
28
  super("status code #{response.code}; payload #{response.payload}")
29
29
  end
@@ -23,7 +23,9 @@ 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'
28
+ autoload :BiDiBridge, 'selenium/webdriver/remote/bidi_bridge'
27
29
  autoload :Driver, 'selenium/webdriver/remote/driver'
28
30
  autoload :Response, 'selenium/webdriver/remote/response'
29
31
  autoload :Capabilities, 'selenium/webdriver/remote/capabilities'
@@ -28,8 +28,12 @@ module Selenium
28
28
  attach_debugger: [:post, 'session/:session_id/apple/attach_debugger']
29
29
  }.freeze
30
30
 
31
+ def command_list
32
+ SAFARI_COMMANDS.merge(self.class::COMMANDS)
33
+ end
34
+
31
35
  def commands(command)
32
- SAFARI_COMMANDS[command] || self.class::COMMANDS[command]
36
+ command_list[command]
33
37
  end
34
38
 
35
39
  def permissions