selenium-webdriver 4.1.0 → 4.9.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 (153) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +221 -1
  3. data/LICENSE +1 -1
  4. data/NOTICE +1 -1
  5. data/bin/linux/selenium-manager +0 -0
  6. data/bin/macos/selenium-manager +0 -0
  7. data/bin/windows/selenium-manager.exe +0 -0
  8. data/lib/selenium/server.rb +35 -26
  9. data/lib/selenium/webdriver/atoms/findElements.js +0 -0
  10. data/lib/selenium/webdriver/atoms/getAttribute.js +0 -0
  11. data/lib/selenium/webdriver/atoms/isDisplayed.js +0 -0
  12. data/lib/selenium/webdriver/atoms/mutationListener.js +0 -0
  13. data/lib/selenium/webdriver/atoms.rb +2 -3
  14. data/lib/selenium/webdriver/bidi/browsing_context.rb +88 -0
  15. data/lib/selenium/webdriver/bidi/browsing_context_info.rb +35 -0
  16. data/lib/selenium/webdriver/bidi/log/base_log_entry.rb +35 -0
  17. data/lib/selenium/webdriver/bidi/log/console_log_entry.rb +35 -0
  18. data/lib/selenium/webdriver/bidi/log/filter_by.rb +40 -0
  19. data/lib/selenium/webdriver/bidi/log/generic_log_entry.rb +33 -0
  20. data/lib/selenium/webdriver/bidi/log/javascript_log_entry.rb +33 -0
  21. data/lib/selenium/webdriver/bidi/log_inspector.rb +143 -0
  22. data/lib/selenium/webdriver/bidi/navigate_result.rb +33 -0
  23. data/lib/selenium/webdriver/bidi/session.rb +51 -0
  24. data/lib/selenium/webdriver/bidi.rb +56 -0
  25. data/lib/selenium/webdriver/chrome/driver.rb +9 -29
  26. data/lib/selenium/webdriver/chrome/features.rb +6 -68
  27. data/lib/selenium/webdriver/chrome/options.rb +3 -223
  28. data/lib/selenium/webdriver/chrome/profile.rb +3 -83
  29. data/lib/selenium/webdriver/chrome/service.rb +3 -23
  30. data/lib/selenium/webdriver/chrome.rb +0 -14
  31. data/lib/selenium/webdriver/chromium/driver.rb +61 -0
  32. data/lib/selenium/webdriver/chromium/features.rb +103 -0
  33. data/lib/selenium/webdriver/chromium/options.rb +261 -0
  34. data/lib/selenium/webdriver/chromium/profile.rb +113 -0
  35. data/lib/selenium/webdriver/chromium/service.rb +42 -0
  36. data/lib/selenium/webdriver/chromium.rb +32 -0
  37. data/lib/selenium/webdriver/common/action_builder.rb +72 -22
  38. data/lib/selenium/webdriver/common/child_process.rb +124 -0
  39. data/lib/selenium/webdriver/common/driver.rb +36 -74
  40. data/lib/selenium/webdriver/common/driver_extensions/downloads_files.rb +0 -2
  41. data/lib/selenium/webdriver/common/driver_extensions/full_page_screenshot.rb +0 -1
  42. data/lib/selenium/webdriver/common/driver_extensions/has_addons.rb +0 -2
  43. data/lib/selenium/webdriver/common/driver_extensions/has_apple_permissions.rb +0 -2
  44. data/lib/selenium/webdriver/common/driver_extensions/has_authentication.rb +0 -2
  45. data/lib/selenium/webdriver/common/driver_extensions/{has_remote_status.rb → has_bidi.rb} +10 -5
  46. data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +10 -1
  47. data/lib/selenium/webdriver/common/driver_extensions/has_cdp.rb +0 -2
  48. data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +1 -4
  49. data/lib/selenium/webdriver/common/driver_extensions/has_debugger.rb +0 -2
  50. data/lib/selenium/webdriver/common/driver_extensions/has_devtools.rb +0 -2
  51. data/lib/selenium/webdriver/common/driver_extensions/has_launching.rb +0 -2
  52. data/lib/selenium/webdriver/common/driver_extensions/has_location.rb +1 -2
  53. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +1 -2
  54. data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +0 -2
  55. data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +2 -69
  56. data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +0 -2
  57. data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +1 -3
  58. data/lib/selenium/webdriver/common/driver_finder.rb +43 -0
  59. data/lib/selenium/webdriver/common/element.rb +8 -8
  60. data/lib/selenium/webdriver/common/error.rb +1 -3
  61. data/lib/selenium/webdriver/common/html5/shared_web_storage.rb +2 -2
  62. data/lib/selenium/webdriver/common/interactions/input_device.rb +10 -4
  63. data/lib/selenium/webdriver/common/interactions/interaction.rb +12 -25
  64. data/lib/selenium/webdriver/common/interactions/interactions.rb +24 -4
  65. data/lib/selenium/webdriver/common/interactions/key_actions.rb +5 -1
  66. data/lib/selenium/webdriver/common/interactions/key_input.rb +11 -27
  67. data/lib/selenium/webdriver/common/interactions/none_input.rb +10 -8
  68. data/lib/selenium/webdriver/common/interactions/pause.rb +49 -0
  69. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +59 -70
  70. data/lib/selenium/webdriver/common/interactions/pointer_cancel.rb +45 -0
  71. data/lib/selenium/webdriver/common/interactions/pointer_event_properties.rb +63 -0
  72. data/lib/selenium/webdriver/common/interactions/pointer_input.rb +15 -84
  73. data/lib/selenium/webdriver/common/interactions/pointer_move.rb +60 -0
  74. data/lib/selenium/webdriver/common/interactions/pointer_press.rb +85 -0
  75. data/lib/selenium/webdriver/common/interactions/scroll.rb +59 -0
  76. data/lib/selenium/webdriver/common/interactions/scroll_origin.rb +48 -0
  77. data/lib/selenium/webdriver/common/interactions/typing_interaction.rb +54 -0
  78. data/lib/selenium/webdriver/common/interactions/wheel_actions.rb +113 -0
  79. data/lib/selenium/webdriver/common/interactions/wheel_input.rb +42 -0
  80. data/lib/selenium/webdriver/common/keys.rb +1 -0
  81. data/lib/selenium/webdriver/common/local_driver.rb +55 -0
  82. data/lib/selenium/webdriver/common/logger.rb +10 -2
  83. data/lib/selenium/webdriver/common/manager.rb +0 -27
  84. data/lib/selenium/webdriver/common/options.rb +32 -17
  85. data/lib/selenium/webdriver/common/platform.rb +8 -5
  86. data/lib/selenium/webdriver/common/profile_helper.rb +1 -1
  87. data/lib/selenium/webdriver/common/proxy.rb +1 -1
  88. data/lib/selenium/webdriver/common/search_context.rb +0 -6
  89. data/lib/selenium/webdriver/common/selenium_manager.rb +108 -0
  90. data/lib/selenium/webdriver/common/service.rb +11 -22
  91. data/lib/selenium/webdriver/common/service_manager.rb +5 -14
  92. data/lib/selenium/webdriver/common/shadow_root.rb +2 -3
  93. data/lib/selenium/webdriver/common/socket_lock.rb +2 -2
  94. data/lib/selenium/webdriver/common/socket_poller.rb +1 -1
  95. data/lib/selenium/webdriver/common/takes_screenshot.rb +2 -3
  96. data/lib/selenium/webdriver/common/target_locator.rb +2 -3
  97. data/lib/selenium/webdriver/common/timeouts.rb +2 -2
  98. data/lib/selenium/webdriver/common/virtual_authenticator/credential.rb +85 -0
  99. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator.rb +72 -0
  100. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator_options.rb +62 -0
  101. data/lib/selenium/webdriver/common/websocket_connection.rb +164 -0
  102. data/lib/selenium/webdriver/common/window.rb +6 -6
  103. data/lib/selenium/webdriver/common/zipper.rb +1 -1
  104. data/lib/selenium/webdriver/common.rb +21 -3
  105. data/lib/selenium/webdriver/devtools/console_event.rb +0 -2
  106. data/lib/selenium/webdriver/devtools/exception_event.rb +0 -2
  107. data/lib/selenium/webdriver/devtools/mutation_event.rb +0 -2
  108. data/lib/selenium/webdriver/devtools/network_interceptor.rb +173 -0
  109. data/lib/selenium/webdriver/devtools/pinned_script.rb +0 -2
  110. data/lib/selenium/webdriver/devtools/request.rb +1 -3
  111. data/lib/selenium/webdriver/devtools/response.rb +1 -3
  112. data/lib/selenium/webdriver/devtools.rb +17 -114
  113. data/lib/selenium/webdriver/edge/driver.rb +9 -3
  114. data/lib/selenium/webdriver/edge/features.rb +4 -4
  115. data/lib/selenium/webdriver/edge/options.rb +3 -5
  116. data/lib/selenium/webdriver/edge/profile.rb +2 -2
  117. data/lib/selenium/webdriver/edge/service.rb +2 -6
  118. data/lib/selenium/webdriver/firefox/driver.rb +9 -2
  119. data/lib/selenium/webdriver/firefox/features.rb +6 -6
  120. data/lib/selenium/webdriver/firefox/options.rb +9 -3
  121. data/lib/selenium/webdriver/firefox/profile.rb +7 -11
  122. data/lib/selenium/webdriver/firefox/service.rb +0 -6
  123. data/lib/selenium/webdriver/firefox/util.rb +46 -0
  124. data/lib/selenium/webdriver/firefox.rb +1 -14
  125. data/lib/selenium/webdriver/ie/driver.rb +7 -1
  126. data/lib/selenium/webdriver/ie/service.rb +1 -7
  127. data/lib/selenium/webdriver/ie.rb +0 -14
  128. data/lib/selenium/webdriver/remote/{commands.rb → bridge/commands.rb} +15 -8
  129. data/lib/selenium/webdriver/remote/bridge.rb +59 -30
  130. data/lib/selenium/webdriver/remote/capabilities.rb +34 -12
  131. data/lib/selenium/webdriver/remote/driver.rb +19 -14
  132. data/lib/selenium/webdriver/remote/http/curb.rb +0 -2
  133. data/lib/selenium/webdriver/remote/http/default.rb +7 -12
  134. data/lib/selenium/webdriver/remote/response.rb +2 -3
  135. data/lib/selenium/webdriver/remote.rb +0 -1
  136. data/lib/selenium/webdriver/safari/driver.rb +7 -1
  137. data/lib/selenium/webdriver/safari/features.rb +0 -2
  138. data/lib/selenium/webdriver/safari/options.rb +5 -1
  139. data/lib/selenium/webdriver/safari/service.rb +0 -4
  140. data/lib/selenium/webdriver/safari.rb +1 -15
  141. data/lib/selenium/webdriver/support/color.rb +20 -20
  142. data/lib/selenium/webdriver/support/guards/guard.rb +0 -2
  143. data/lib/selenium/webdriver/support/guards/guard_condition.rb +1 -3
  144. data/lib/selenium/webdriver/support/guards.rb +1 -1
  145. data/lib/selenium/webdriver/support/relative_locator.rb +0 -1
  146. data/lib/selenium/webdriver/support/select.rb +3 -1
  147. data/lib/selenium/webdriver/version.rb +1 -1
  148. data/lib/selenium/webdriver.rb +4 -4
  149. data/selenium-webdriver.gemspec +13 -11
  150. metadata +70 -59
  151. data/lib/selenium/webdriver/remote/http/persistent.rb +0 -65
  152. data/lib/selenium/webdriver/support/cdp/domain.rb.erb +0 -63
  153. data/lib/selenium/webdriver/support/cdp_client_generator.rb +0 -108
@@ -39,12 +39,13 @@ module Selenium
39
39
  def ie(**opts)
40
40
  IE::Service.new(**opts)
41
41
  end
42
- alias_method :internet_explorer, :ie
42
+ alias internet_explorer ie
43
43
 
44
44
  def edge(**opts)
45
45
  Edge::Service.new(**opts)
46
46
  end
47
- alias_method :microsoftedge, :edge
47
+ alias microsoftedge edge
48
+ alias msedge edge
48
49
 
49
50
  def safari(**opts)
50
51
  Safari::Service.new(**opts)
@@ -56,8 +57,8 @@ module Selenium
56
57
  end
57
58
  end
58
59
 
59
- attr_accessor :host
60
- attr_reader :executable_path, :port, :extra_args
60
+ attr_accessor :host, :executable_path, :port, :args
61
+ alias extra_args args
61
62
 
62
63
  #
63
64
  # End users should use a class method for the desired driver, rather than using this directly.
@@ -66,23 +67,20 @@ module Selenium
66
67
  #
67
68
 
68
69
  def initialize(path: nil, port: nil, args: nil)
69
- path ||= self.class.driver_path
70
70
  port ||= self.class::DEFAULT_PORT
71
71
  args ||= []
72
72
 
73
- @executable_path = binary_path(path)
73
+ @executable_path = path
74
74
  @host = Platform.localhost
75
75
  @port = Integer(port)
76
76
 
77
- @extra_args = args.is_a?(Hash) ? extract_service_args(args) : args
77
+ @args = args.is_a?(Hash) ? extract_service_args(args) : args
78
78
 
79
79
  raise Error::WebDriverError, "invalid port: #{@port}" if @port < 1
80
80
  end
81
81
 
82
82
  def launch
83
- sm = ServiceManager.new(self)
84
- sm.start
85
- sm
83
+ ServiceManager.new(self).tap(&:start)
86
84
  end
87
85
 
88
86
  def shutdown_supported
@@ -92,20 +90,11 @@ module Selenium
92
90
  protected
93
91
 
94
92
  def extract_service_args(driver_opts)
93
+ WebDriver.logger.deprecate('initializing Service class with :args using Hash',
94
+ ':args parameter with an Array of String values',
95
+ id: :driver_opts)
95
96
  driver_opts.key?(:args) ? driver_opts.delete(:args) : []
96
97
  end
97
-
98
- private
99
-
100
- def binary_path(path = nil)
101
- path = path.call if path.is_a?(Proc)
102
- path ||= Platform.find_binary(self.class::EXECUTABLE)
103
-
104
- raise Error::WebDriverError, self.class::MISSING_TEXT unless path
105
-
106
- Platform.assert_executable path
107
- path
108
- end
109
98
  end # Service
110
99
  end # WebDriver
111
100
  end # Selenium
@@ -40,7 +40,7 @@ module Selenium
40
40
  @executable_path = config.executable_path
41
41
  @host = Platform.localhost
42
42
  @port = config.port
43
- @extra_args = config.extra_args
43
+ @extra_args = config.args
44
44
  @shutdown_supported = config.shutdown_supported
45
45
 
46
46
  raise Error::WebDriverError, "invalid port: #{@port}" if @port < 1
@@ -49,7 +49,7 @@ module Selenium
49
49
  def start
50
50
  raise "already started: #{uri.inspect} #{@executable_path.inspect}" if process_running?
51
51
 
52
- Platform.exit_hook(&method(:stop)) # make sure we don't leave the server running
52
+ Platform.exit_hook { stop } # make sure we don't leave the server running
53
53
 
54
54
  socket_lock.locked do
55
55
  find_free_port
@@ -60,10 +60,11 @@ module Selenium
60
60
 
61
61
  def stop
62
62
  return unless @shutdown_supported
63
+ return if process_exited?
63
64
 
64
65
  stop_server
65
66
  @process.poll_for_exit STOP_TIMEOUT
66
- rescue ChildProcess::TimeoutError
67
+ rescue ChildProcess::TimeoutError, Errno::ECONNREFUSED
67
68
  nil # noop
68
69
  ensure
69
70
  stop_process
@@ -78,12 +79,7 @@ module Selenium
78
79
  def build_process(*command)
79
80
  WebDriver.logger.debug("Executing Process #{command}")
80
81
  @process = ChildProcess.build(*command)
81
- if WebDriver.logger.debug?
82
- @process.io.stdout = @process.io.stderr = WebDriver.logger.io
83
- elsif Platform.jruby?
84
- # Apparently we need to read the output of drivers on JRuby.
85
- @process.io.stdout = @process.io.stderr = File.new(Platform.null_device, 'w')
86
- end
82
+ @process.io = WebDriver.logger.io if WebDriver.logger.debug?
87
83
 
88
84
  @process
89
85
  end
@@ -103,8 +99,6 @@ module Selenium
103
99
 
104
100
  def start_process
105
101
  @process = build_process(@executable_path, "--port=#{@port}", *@extra_args)
106
- # NOTE: this is a bug only in Windows 7
107
- @process.leader = true unless Platform.windows?
108
102
  @process.start
109
103
  end
110
104
 
@@ -112,12 +106,9 @@ module Selenium
112
106
  return if process_exited?
113
107
 
114
108
  @process.stop STOP_TIMEOUT
115
- @process.io.stdout.close if Platform.jruby? && !WebDriver.logger.debug?
116
109
  end
117
110
 
118
111
  def stop_server
119
- return if process_exited?
120
-
121
112
  connect_to_server do |http|
122
113
  headers = WebDriver::Remote::Http::Common::DEFAULT_HEADERS.dup
123
114
  http.get('/shutdown', headers)
@@ -42,10 +42,10 @@ module Selenium
42
42
  def ==(other)
43
43
  other.is_a?(self.class) && ref == other.ref
44
44
  end
45
- alias_method :eql?, :==
45
+ alias eql? ==
46
46
 
47
47
  def hash
48
- @id.hash ^ @bridge.hash
48
+ [@id, @bridge].hash
49
49
  end
50
50
 
51
51
  #
@@ -81,7 +81,6 @@ module Selenium
81
81
  private
82
82
 
83
83
  attr_reader :bridge
84
-
85
84
  end # ShadowRoot
86
85
  end # WebDriver
87
86
  end # Selenium
@@ -26,6 +26,7 @@ module Selenium
26
26
  class SocketLock
27
27
  def initialize(port, timeout)
28
28
  @port = port
29
+ @server = nil
29
30
  @timeout = timeout
30
31
  end
31
32
 
@@ -66,8 +67,7 @@ module Selenium
66
67
 
67
68
  def can_lock?
68
69
  @server = TCPServer.new(Platform.localhost, @port)
69
- ChildProcess.close_on_exec @server
70
-
70
+ @server.close_on_exec = true
71
71
  true
72
72
  rescue SocketError, Errno::EADDRINUSE, Errno::EBADF => e
73
73
  WebDriver.logger.debug("#{self}: #{e.message}")
@@ -99,7 +99,7 @@ module Selenium
99
99
  end
100
100
 
101
101
  def socket_writable?(sock)
102
- IO.select(nil, [sock], nil, CONNECT_TIMEOUT)
102
+ sock.wait_writable(CONNECT_TIMEOUT)
103
103
  end
104
104
 
105
105
  def conn_completed?(sock)
@@ -32,8 +32,8 @@ module Selenium
32
32
  def save_screenshot(png_path, full_page: false)
33
33
  extension = File.extname(png_path).downcase
34
34
  if extension != '.png'
35
- WebDriver.logger.warn "name used for saved screenshot does not match file type. "\
36
- "It should end with .png extension",
35
+ WebDriver.logger.warn 'name used for saved screenshot does not match file type. ' \
36
+ 'It should end with .png extension',
37
37
  id: :screenshot
38
38
  end
39
39
  File.open(png_path, 'wb') { |f| f << screenshot_as(:png, full_page: full_page) }
@@ -60,7 +60,6 @@ module Selenium
60
60
  rescue NameError
61
61
  raise Error::UnsupportedOperationError, "Full Page Screenshots are not supported for #{inspect}"
62
62
  end
63
-
64
63
  end # TakesScreenshot
65
64
  end # WebDriver
66
65
  end # Selenium
@@ -51,9 +51,8 @@ module Selenium
51
51
  #
52
52
 
53
53
  def new_window(type = :window)
54
- unless %i[window tab].include?(type)
55
- raise ArgumentError, "Valid types are :tab and :window, received: #{type.inspect}"
56
- end
54
+ raise ArgumentError, "Valid types are :tab and :window, received: #{type.inspect}" unless %i[window
55
+ tab].include?(type)
57
56
 
58
57
  handle = @bridge.new_window(type)['handle']
59
58
 
@@ -48,7 +48,7 @@ module Selenium
48
48
  def script
49
49
  Float(@bridge.timeouts['script']) / 1000
50
50
  end
51
- alias_method :script_timeout, :script
51
+ alias script_timeout script
52
52
 
53
53
  #
54
54
  # Sets the amount of time to wait for an asynchronous script to finish
@@ -59,7 +59,7 @@ module Selenium
59
59
  def script=(seconds)
60
60
  @bridge.timeouts = {'script' => Integer(seconds * 1000)}
61
61
  end
62
- alias_method :script_timeout=, :script=
62
+ alias script_timeout= script=
63
63
 
64
64
  #
65
65
  # Gets the amount of time to wait for a page load to complete before throwing an error.
@@ -0,0 +1,85 @@
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
+ #
21
+ # A credential stored in a virtual authenticator.
22
+ # @see https://w3c.github.io/webauthn/#credential-parameters
23
+ #
24
+
25
+ module Selenium
26
+ module WebDriver
27
+ class Credential
28
+ class << self
29
+ def resident(**opts)
30
+ Credential.new(resident_credential: true, **opts)
31
+ end
32
+
33
+ def non_resident(**opts)
34
+ Credential.new(resident_credential: false, **opts)
35
+ end
36
+
37
+ def encode(byte_array)
38
+ Base64.urlsafe_encode64(byte_array&.pack('C*'))
39
+ end
40
+
41
+ def decode(base64)
42
+ Base64.urlsafe_decode64(base64).unpack('C*')
43
+ end
44
+
45
+ def from_json(opts)
46
+ user_handle = opts['userHandle'] ? decode(opts['userHandle']) : nil
47
+ new(id: decode(opts['credentialId']),
48
+ resident_credential: opts['isResidentCredential'],
49
+ rp_id: opts['rpId'],
50
+ private_key: opts['privateKey'],
51
+ sign_count: opts['signCount'],
52
+ user_handle: user_handle)
53
+ end
54
+ end
55
+
56
+ attr_reader :id, :resident_credential, :rp_id, :user_handle, :private_key, :sign_count
57
+ alias resident_credential? resident_credential
58
+
59
+ def initialize(id:, resident_credential:, rp_id:, private_key:, **opts)
60
+ @id = id
61
+ @resident_credential = resident_credential
62
+ @rp_id = rp_id
63
+ @user_handle = opts.delete(:user_handle) { nil }
64
+ @private_key = private_key
65
+ @sign_count = opts.delete(:sign_count) { 0 }
66
+
67
+ raise ArgumentError, "Invalid arguments: #{opts.keys}" unless opts.empty?
68
+ end
69
+
70
+ #
71
+ # @api private
72
+ #
73
+
74
+ def as_json(*)
75
+ credential_data = {'credentialId' => Credential.encode(id),
76
+ 'isResidentCredential' => resident_credential?,
77
+ 'rpId' => rp_id,
78
+ 'privateKey' => Credential.encode(private_key),
79
+ 'signCount' => sign_count}
80
+ credential_data['userHandle'] = Credential.encode(user_handle) if user_handle
81
+ credential_data
82
+ end
83
+ end # Credential
84
+ end # WebDriver
85
+ end # Selenium
@@ -0,0 +1,72 @@
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
+ class VirtualAuthenticator
23
+ attr_reader :options
24
+
25
+ #
26
+ # api private
27
+ # Use `Driver#add_virtual_authenticator`
28
+ #
29
+
30
+ def initialize(bridge, authenticator_id, options)
31
+ @id = authenticator_id
32
+ @bridge = bridge
33
+ @options = options
34
+ @valid = true
35
+ end
36
+
37
+ def add_credential(credential)
38
+ credential = credential.as_json
39
+ @bridge.add_credential credential, @id
40
+ end
41
+
42
+ def credentials
43
+ credential_data = @bridge.credentials @id
44
+ credential_data.map do |cred|
45
+ Credential.from_json(cred)
46
+ end
47
+ end
48
+
49
+ def remove_credential(credential_id)
50
+ credential_id = Credential.encode(credential_id) if credential_id.instance_of?(Array)
51
+ @bridge.remove_credential credential_id, @id
52
+ end
53
+
54
+ def remove_all_credentials
55
+ @bridge.remove_all_credentials @id
56
+ end
57
+
58
+ def user_verified=(verified)
59
+ @bridge.user_verified verified, @id
60
+ end
61
+
62
+ def remove!
63
+ @bridge.remove_virtual_authenticator(@id)
64
+ @valid = false
65
+ end
66
+
67
+ def valid?
68
+ @valid
69
+ end
70
+ end # VirtualAuthenticator
71
+ end # WebDriver
72
+ end # Selenium
@@ -0,0 +1,62 @@
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
+ #
21
+ # Options for the creation of virtual authenticators.
22
+ # @see http://w3c.github.io/webauthn/#sctn-automation
23
+ #
24
+
25
+ module Selenium
26
+ module WebDriver
27
+ class VirtualAuthenticatorOptions
28
+ PROTOCOL = {ctap2: 'ctap2', u2f: 'ctap1/u2f'}.freeze
29
+ TRANSPORT = {ble: 'ble', usb: 'usb', nfc: 'nfc', internal: 'internal'}.freeze
30
+
31
+ attr_accessor :protocol, :transport, :resident_key, :user_verification, :user_consenting, :user_verified
32
+ alias resident_key? resident_key
33
+ alias user_verification? user_verification
34
+ alias user_consenting? user_consenting
35
+ alias user_verified? user_verified
36
+
37
+ def initialize(**opts)
38
+ @protocol = opts.delete(:protocol) { :ctap2 }
39
+ @transport = opts.delete(:transport) { :usb }
40
+ @resident_key = opts.delete(:resident_key) { false }
41
+ @user_verification = opts.delete(:user_verification) { false }
42
+ @user_consenting = opts.delete(:user_consenting) { true }
43
+ @user_verified = opts.delete(:user_verified) { false }
44
+
45
+ raise ArgumentError, "Invalid arguments: #{opts.keys}" unless opts.empty?
46
+ end
47
+
48
+ #
49
+ # @api private
50
+ #
51
+
52
+ def as_json(*)
53
+ {'protocol' => PROTOCOL[protocol],
54
+ 'transport' => TRANSPORT[transport],
55
+ 'hasResidentKey' => resident_key?,
56
+ 'hasUserVerification' => user_verification?,
57
+ 'isUserConsenting' => user_consenting?,
58
+ 'isUserVerified' => user_verified?}
59
+ end
60
+ end # VirtualAuthenticatorOptions
61
+ end # WebDriver
62
+ end # Selenium
@@ -0,0 +1,164 @@
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
+ require 'websocket'
21
+
22
+ module Selenium
23
+ module WebDriver
24
+ class WebSocketConnection
25
+ CONNECTION_ERRORS = [
26
+ Errno::ECONNRESET, # connection is aborted (browser process was killed)
27
+ Errno::EPIPE # broken pipe (browser process was killed)
28
+ ].freeze
29
+
30
+ RESPONSE_WAIT_TIMEOUT = 30
31
+ RESPONSE_WAIT_INTERVAL = 0.1
32
+
33
+ MAX_LOG_MESSAGE_SIZE = 9999
34
+
35
+ def initialize(url:)
36
+ @callback_threads = ThreadGroup.new
37
+
38
+ @session_id = nil
39
+ @url = url
40
+
41
+ process_handshake
42
+ @socket_thread = attach_socket_listener
43
+ end
44
+
45
+ def close
46
+ @callback_threads.list.each(&:exit)
47
+ @socket_thread.exit
48
+ socket.close
49
+ end
50
+
51
+ def callbacks
52
+ @callbacks ||= Hash.new { |callbacks, event| callbacks[event] = [] }
53
+ end
54
+
55
+ def send_cmd(**payload)
56
+ id = next_id
57
+ data = payload.merge(id: id)
58
+ WebDriver.logger.debug "WebSocket -> #{data}"[...MAX_LOG_MESSAGE_SIZE]
59
+ data = JSON.generate(data)
60
+ out_frame = WebSocket::Frame::Outgoing::Client.new(version: ws.version, data: data, type: 'text')
61
+ socket.write(out_frame.to_s)
62
+
63
+ wait.until { messages.delete(id) }
64
+ end
65
+
66
+ private
67
+
68
+ # We should be thread-safe to use the hash without synchronization
69
+ # because its keys are WebSocket message identifiers and they should be
70
+ # unique within a devtools session.
71
+ def messages
72
+ @messages ||= {}
73
+ end
74
+
75
+ def process_handshake
76
+ socket.print(ws.to_s)
77
+ ws << socket.readpartial(1024)
78
+ end
79
+
80
+ def attach_socket_listener
81
+ Thread.new do
82
+ Thread.current.abort_on_exception = true
83
+ Thread.current.report_on_exception = false
84
+
85
+ until socket.eof?
86
+ incoming_frame << socket.readpartial(1024)
87
+
88
+ while (frame = incoming_frame.next)
89
+ message = process_frame(frame)
90
+ next unless message['method']
91
+
92
+ params = message['params']
93
+ callbacks[message['method']].each do |callback|
94
+ @callback_threads.add(callback_thread(params, &callback))
95
+ end
96
+ end
97
+ end
98
+ rescue *CONNECTION_ERRORS
99
+ Thread.stop
100
+ end
101
+ end
102
+
103
+ def incoming_frame
104
+ @incoming_frame ||= WebSocket::Frame::Incoming::Client.new(version: ws.version)
105
+ end
106
+
107
+ def process_frame(frame)
108
+ message = frame.to_s
109
+
110
+ # Firefox will periodically fail on unparsable empty frame
111
+ return {} if message.empty?
112
+
113
+ message = JSON.parse(message)
114
+ messages[message['id']] = message
115
+ WebDriver.logger.debug "WebSocket <- #{message}"[...MAX_LOG_MESSAGE_SIZE]
116
+
117
+ message
118
+ end
119
+
120
+ def callback_thread(params)
121
+ Thread.new do
122
+ Thread.current.abort_on_exception = true
123
+
124
+ # We might end up blocked forever when we have an error in event.
125
+ # For example, if network interception event raises error,
126
+ # the browser will keep waiting for the request to be proceeded
127
+ # before returning back to the original thread. In this case,
128
+ # we should at least print the error.
129
+ Thread.current.report_on_exception = true
130
+
131
+ yield params
132
+ rescue *CONNECTION_ERRORS
133
+ Thread.stop
134
+ end
135
+ end
136
+
137
+ def wait
138
+ @wait ||= Wait.new(timeout: RESPONSE_WAIT_TIMEOUT, interval: RESPONSE_WAIT_INTERVAL)
139
+ end
140
+
141
+ def socket
142
+ @socket ||= if URI(@url).scheme == 'wss'
143
+ socket = TCPSocket.new(ws.host, ws.port)
144
+ socket = OpenSSL::SSL::SSLSocket.new(socket, OpenSSL::SSL::SSLContext.new)
145
+ socket.sync_close = true
146
+ socket.connect
147
+
148
+ socket
149
+ else
150
+ TCPSocket.new(ws.host, ws.port)
151
+ end
152
+ end
153
+
154
+ def ws
155
+ @ws ||= WebSocket::Handshake::Client.new(url: @url)
156
+ end
157
+
158
+ def next_id
159
+ @id ||= 0
160
+ @id += 1
161
+ end
162
+ end # BiDi
163
+ end # WebDriver
164
+ end # Selenium
@@ -36,8 +36,8 @@ module Selenium
36
36
 
37
37
  def size=(dimension)
38
38
  unless dimension.respond_to?(:width) && dimension.respond_to?(:height)
39
- raise ArgumentError, "expected #{dimension.inspect}:#{dimension.class}" \
40
- ' to respond to #width and #height'
39
+ raise ArgumentError, "expected #{dimension.inspect}:#{dimension.class} " \
40
+ 'to respond to #width and #height'
41
41
  end
42
42
 
43
43
  @bridge.resize_window dimension.width, dimension.height
@@ -61,8 +61,8 @@ module Selenium
61
61
 
62
62
  def position=(point)
63
63
  unless point.respond_to?(:x) && point.respond_to?(:y)
64
- raise ArgumentError, "expected #{point.inspect}:#{point.class}" \
65
- ' to respond to #x and #y'
64
+ raise ArgumentError, "expected #{point.inspect}:#{point.class} " \
65
+ 'to respond to #x and #y'
66
66
  end
67
67
 
68
68
  @bridge.reposition_window point.x, point.y
@@ -86,8 +86,8 @@ module Selenium
86
86
 
87
87
  def rect=(rectangle)
88
88
  unless %w[x y width height].all? { |val| rectangle.respond_to? val }
89
- raise ArgumentError, "expected #{rectangle.inspect}:#{rectangle.class}" \
90
- ' to respond to #x, #y, #width, and #height'
89
+ raise ArgumentError, "expected #{rectangle.inspect}:#{rectangle.class} " \
90
+ 'to respond to #x, #y, #width, and #height'
91
91
  end
92
92
 
93
93
  @bridge.set_window_rect(x: rectangle.x,
@@ -41,7 +41,7 @@ module Selenium
41
41
  to = File.join(destination, entry.name)
42
42
  dirname = File.dirname(to)
43
43
 
44
- FileUtils.mkdir_p dirname unless File.exist? dirname
44
+ FileUtils.mkdir_p dirname
45
45
  zip.extract(entry, to)
46
46
  end
47
47
  end