selenium-webdriver 4.0.0.beta2 → 4.0.0.rc2

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +1947 -0
  3. data/Gemfile +6 -0
  4. data/LICENSE +202 -0
  5. data/NOTICE +2 -0
  6. data/README.md +34 -0
  7. data/lib/selenium/webdriver/atoms/findElements.js +0 -0
  8. data/lib/selenium/webdriver/atoms/getAttribute.js +25 -25
  9. data/lib/selenium/webdriver/atoms/isDisplayed.js +0 -0
  10. data/lib/selenium/webdriver/chrome/driver.rb +16 -4
  11. data/lib/selenium/webdriver/chrome/features.rb +44 -4
  12. data/lib/selenium/webdriver/chrome/options.rb +25 -2
  13. data/lib/selenium/webdriver/chrome/profile.rb +5 -2
  14. data/lib/selenium/webdriver/common/driver.rb +5 -1
  15. data/lib/selenium/webdriver/common/driver_extensions/full_page_screenshot.rb +43 -0
  16. data/lib/selenium/webdriver/common/driver_extensions/has_apple_permissions.rb +51 -0
  17. data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +77 -0
  18. data/lib/selenium/webdriver/common/driver_extensions/has_cdp.rb +38 -0
  19. data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +45 -0
  20. data/lib/selenium/webdriver/common/driver_extensions/has_devtools.rb +0 -17
  21. data/lib/selenium/{devtools.rb → webdriver/common/driver_extensions/has_launching.rb} +16 -8
  22. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +1 -6
  23. data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +8 -0
  24. data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +87 -18
  25. data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +11 -11
  26. data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +77 -0
  27. data/lib/selenium/webdriver/common/driver_extensions/prints_page.rb +28 -1
  28. data/lib/selenium/webdriver/common/element.rb +35 -6
  29. data/lib/selenium/webdriver/common/error.rb +12 -0
  30. data/lib/selenium/webdriver/common/log_entry.rb +2 -2
  31. data/lib/selenium/webdriver/common/manager.rb +3 -13
  32. data/lib/selenium/webdriver/common/options.rb +31 -12
  33. data/lib/selenium/webdriver/common/proxy.rb +2 -2
  34. data/lib/selenium/webdriver/common/shadow_root.rb +87 -0
  35. data/lib/selenium/webdriver/common/socket_poller.rb +19 -30
  36. data/lib/selenium/webdriver/common/takes_screenshot.rb +9 -6
  37. data/lib/selenium/webdriver/common/target_locator.rb +28 -0
  38. data/lib/selenium/webdriver/common/window.rb +0 -4
  39. data/lib/selenium/webdriver/common.rb +8 -0
  40. data/lib/selenium/webdriver/devtools/pinned_script.rb +59 -0
  41. data/lib/selenium/webdriver/devtools/request.rb +27 -17
  42. data/lib/selenium/webdriver/devtools/response.rb +66 -0
  43. data/lib/selenium/webdriver/devtools.rb +50 -12
  44. data/lib/selenium/webdriver/edge/features.rb +5 -0
  45. data/lib/selenium/webdriver/edge/options.rb +0 -0
  46. data/lib/selenium/webdriver/edge/profile.rb +0 -0
  47. data/lib/selenium/webdriver/firefox/driver.rb +15 -2
  48. data/lib/selenium/webdriver/firefox/features.rb +20 -1
  49. data/lib/selenium/webdriver/firefox/options.rb +24 -1
  50. data/lib/selenium/webdriver/firefox.rb +0 -1
  51. data/lib/selenium/webdriver/ie/options.rb +3 -1
  52. data/lib/selenium/webdriver/ie/service.rb +1 -1
  53. data/lib/selenium/webdriver/remote/bridge.rb +43 -13
  54. data/lib/selenium/webdriver/remote/capabilities.rb +97 -53
  55. data/lib/selenium/webdriver/remote/commands.rb +5 -0
  56. data/lib/selenium/webdriver/remote/driver.rb +7 -7
  57. data/lib/selenium/webdriver/remote.rb +1 -1
  58. data/lib/selenium/webdriver/safari/driver.rb +1 -1
  59. data/lib/selenium/webdriver/safari/options.rb +7 -0
  60. data/lib/selenium/webdriver/support/cdp/domain.rb.erb +63 -0
  61. data/lib/selenium/webdriver/support/cdp_client_generator.rb +108 -0
  62. data/lib/selenium/webdriver/support/event_firing_bridge.rb +2 -2
  63. data/lib/selenium/webdriver/version.rb +1 -1
  64. data/lib/selenium/webdriver.rb +1 -1
  65. data/selenium-webdriver.gemspec +63 -0
  66. metadata +60 -44
@@ -0,0 +1,66 @@
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 DevTools
23
+ class Response
24
+
25
+ attr_accessor :code, :body, :headers
26
+ attr_reader :id
27
+
28
+ #
29
+ # Creates response from DevTools message.
30
+ # @api private
31
+ #
32
+
33
+ def self.from(id, encoded_body, params)
34
+ new(
35
+ id: id,
36
+ code: params['responseStatusCode'],
37
+ body: (Base64.strict_decode64(encoded_body) if encoded_body),
38
+ headers: params['responseHeaders'].each_with_object({}) do |header, hash|
39
+ hash[header['name']] = header['value']
40
+ end
41
+ )
42
+ end
43
+
44
+ def initialize(id:, code:, body:, headers:)
45
+ @id = id
46
+ @code = code
47
+ @body = body
48
+ @headers = headers
49
+ end
50
+
51
+ def ==(other)
52
+ self.class == other.class &&
53
+ id == other.id &&
54
+ code == other.code &&
55
+ body == other.body &&
56
+ headers == other.headers
57
+ end
58
+
59
+ def inspect
60
+ %(#<#{self.class.name} @id="#{id}" @code="#{code}")
61
+ end
62
+
63
+ end # Response
64
+ end # DevTools
65
+ end # WebDriver
66
+ end # Selenium
@@ -20,21 +20,34 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  class DevTools
23
+ RESPONSE_WAIT_TIMEOUT = 30
24
+ RESPONSE_WAIT_INTERVAL = 0.1
25
+
23
26
  autoload :ConsoleEvent, 'selenium/webdriver/devtools/console_event'
24
27
  autoload :ExceptionEvent, 'selenium/webdriver/devtools/exception_event'
25
28
  autoload :MutationEvent, 'selenium/webdriver/devtools/mutation_event'
29
+ autoload :PinnedScript, 'selenium/webdriver/devtools/pinned_script'
26
30
  autoload :Request, 'selenium/webdriver/devtools/request'
31
+ autoload :Response, 'selenium/webdriver/devtools/response'
27
32
 
28
33
  def initialize(url:)
34
+ @callback_threads = ThreadGroup.new
35
+
29
36
  @messages = []
30
37
  @session_id = nil
31
38
  @url = url
32
39
 
33
40
  process_handshake
34
- attach_socket_listener
41
+ @socket_thread = attach_socket_listener
35
42
  start_session
36
43
  end
37
44
 
45
+ def close
46
+ @callback_threads.list.each(&:exit)
47
+ @socket_thread.exit
48
+ socket.close
49
+ end
50
+
38
51
  def callbacks
39
52
  @callbacks ||= Hash.new { |callbacks, event| callbacks[event] = [] }
40
53
  end
@@ -84,27 +97,24 @@ module Selenium
84
97
  end
85
98
 
86
99
  def attach_socket_listener
87
- socket_listener = Thread.new do
100
+ Thread.new do
101
+ Thread.current.abort_on_exception = true
102
+ Thread.current.report_on_exception = false
103
+
88
104
  until socket.eof?
89
105
  incoming_frame << socket.readpartial(1024)
90
106
 
91
107
  while (frame = incoming_frame.next)
92
- # Firefox will periodically fail on unparsable empty frame
93
- break if frame.to_s.empty?
94
-
95
- message = JSON.parse(frame.to_s)
96
- @messages << message
97
- WebDriver.logger.debug "DevTools <- #{message}"
108
+ message = process_frame(frame)
98
109
  next unless message['method']
99
110
 
111
+ params = message['params']
100
112
  callbacks[message['method']].each do |callback|
101
- params = message['params'] # take in current thread!
102
- Thread.new { callback.call(params) }
113
+ @callback_threads.add(callback_thread(params, &callback))
103
114
  end
104
115
  end
105
116
  end
106
117
  end
107
- socket_listener.abort_on_exception = true
108
118
  end
109
119
 
110
120
  def start_session
@@ -118,8 +128,36 @@ module Selenium
118
128
  @incoming_frame ||= WebSocket::Frame::Incoming::Client.new(version: ws.version)
119
129
  end
120
130
 
131
+ def process_frame(frame)
132
+ message = frame.to_s
133
+
134
+ # Firefox will periodically fail on unparsable empty frame
135
+ return {} if message.empty?
136
+
137
+ message = JSON.parse(message)
138
+ @messages << message
139
+ WebDriver.logger.debug "DevTools <- #{message}"
140
+
141
+ message
142
+ end
143
+
144
+ def callback_thread(params)
145
+ Thread.new do
146
+ Thread.current.abort_on_exception = true
147
+
148
+ # We might end up blocked forever when we have an error in event.
149
+ # For example, if network interception event raises error,
150
+ # the browser will keep waiting for the request to be proceeded
151
+ # before returning back to the original thread. In this case,
152
+ # we should at least print the error.
153
+ Thread.current.report_on_exception = true
154
+
155
+ yield params
156
+ end
157
+ end
158
+
121
159
  def wait
122
- @wait ||= Wait.new(timeout: 10, interval: 0.1)
160
+ @wait ||= Wait.new(timeout: RESPONSE_WAIT_TIMEOUT, interval: RESPONSE_WAIT_INTERVAL)
123
161
  end
124
162
 
125
163
  def socket
@@ -27,6 +27,11 @@ module Selenium
27
27
  include WebDriver::Chrome::Features
28
28
 
29
29
  EDGE_COMMANDS = {
30
+ get_cast_sinks: [:get, 'session/:session_id/ms/cast/get_sinks'],
31
+ set_cast_sink_to_use: [:post, 'session/:session_id/ms/cast/set_sink_to_use'],
32
+ start_cast_tab_mirroring: [:post, 'session/:session_id/ms/cast/start_tab_mirroring'],
33
+ get_cast_issue_message: [:get, 'session/:session_id/ms/cast/get_issue_message'],
34
+ stop_casting: [:post, 'session/:session_id/ms/cast/stop_casting'],
30
35
  send_command: [:post, 'session/:session_id/ms/cdp/execute']
31
36
  }.freeze
32
37
 
File without changes
File without changes
@@ -28,6 +28,8 @@ module Selenium
28
28
 
29
29
  class Driver < WebDriver::Driver
30
30
  EXTENSIONS = [DriverExtensions::HasAddons,
31
+ DriverExtensions::FullPageScreenshot,
32
+ DriverExtensions::HasContext,
31
33
  DriverExtensions::HasDevTools,
32
34
  DriverExtensions::HasLogEvents,
33
35
  DriverExtensions::HasNetworkInterception,
@@ -40,8 +42,19 @@ module Selenium
40
42
 
41
43
  private
42
44
 
43
- def devtools_address
44
- "http://#{capabilities['moz:debuggerAddress']}"
45
+ def devtools_url
46
+ if capabilities['moz:debuggerAddress'].nil?
47
+ raise(Error::WebDriverError, "DevTools is not supported by this version of Firefox; use v85 or higher")
48
+ end
49
+
50
+ uri = URI("http://#{capabilities['moz:debuggerAddress']}")
51
+ response = Net::HTTP.get(uri.hostname, '/json/version', uri.port)
52
+
53
+ JSON.parse(response)['webSocketDebuggerUrl']
54
+ end
55
+
56
+ def devtools_version
57
+ Firefox::DEVTOOLS_VERSION
45
58
  end
46
59
  end # Driver
47
60
  end # Firefox
@@ -23,8 +23,11 @@ module Selenium
23
23
  module Features
24
24
 
25
25
  FIREFOX_COMMANDS = {
26
+ get_context: [:get, 'session/:session_id/moz/context'],
27
+ set_context: [:post, 'session/:session_id/moz/context'],
26
28
  install_addon: [:post, 'session/:session_id/moz/addon/install'],
27
- uninstall_addon: [:post, 'session/:session_id/moz/addon/uninstall']
29
+ uninstall_addon: [:post, 'session/:session_id/moz/addon/uninstall'],
30
+ full_page_screenshot: [:get, 'session/:session_id/moz/screenshot/full']
28
31
  }.freeze
29
32
 
30
33
  def commands(command)
@@ -32,6 +35,11 @@ module Selenium
32
35
  end
33
36
 
34
37
  def install_addon(path, temporary)
38
+ if @file_detector
39
+ local_file = @file_detector.call(path)
40
+ path = upload(local_file) if local_file
41
+ end
42
+
35
43
  payload = {path: path}
36
44
  payload[:temporary] = temporary unless temporary.nil?
37
45
  execute :install_addon, {}, payload
@@ -41,6 +49,17 @@ module Selenium
41
49
  execute :uninstall_addon, {}, {id: id}
42
50
  end
43
51
 
52
+ def full_screenshot
53
+ execute :full_page_screenshot
54
+ end
55
+
56
+ def context=(context)
57
+ execute :set_context, {}, {context: context}
58
+ end
59
+
60
+ def context
61
+ execute :get_context
62
+ end
44
63
  end # Bridge
45
64
  end # Firefox
46
65
  end # WebDriver
@@ -29,7 +29,11 @@ module Selenium
29
29
  CAPABILITIES = {binary: 'binary',
30
30
  args: 'args',
31
31
  log: 'log',
32
- prefs: 'prefs'}.freeze
32
+ prefs: 'prefs',
33
+ android_package: 'androidPackage',
34
+ android_activity: 'androidActivity',
35
+ android_device_serial: 'androidDeviceSerial',
36
+ android_intent_arguments: 'androidIntentArguments'}.freeze
33
37
  BROWSER = 'firefox'
34
38
 
35
39
  # NOTE: special handling of 'profile' to validate when set instead of when used
@@ -131,6 +135,25 @@ module Selenium
131
135
  @options[:log] = {level: level}
132
136
  end
133
137
 
138
+ #
139
+ # Enables mobile browser use on Android.
140
+ #
141
+ # @see https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities/firefoxOptions#android
142
+ #
143
+ # @param [String] package The package name of the Chrome or WebView app.
144
+ # @param [String] serial_number The serial number of the device on which to launch the application.
145
+ # If not specified and multiple devices are attached, an error will be returned.
146
+ # @param [String] activity The fully qualified class name of the activity to be launched.
147
+ # @param [Array] intent_arguments Arguments to launch the intent with.
148
+ #
149
+
150
+ def enable_android(package: 'org.mozilla.firefox', serial_number: nil, activity: nil, intent_arguments: nil)
151
+ @options[:android_package] = package
152
+ @options[:android_activity] = activity unless activity.nil?
153
+ @options[:android_device_serial] = serial_number unless serial_number.nil?
154
+ @options[:android_intent_arguments] = intent_arguments unless intent_arguments.nil?
155
+ end
156
+
134
157
  private
135
158
 
136
159
  def process_browser_options(browser_options)
@@ -33,7 +33,6 @@ module Selenium
33
33
  autoload :Service, 'selenium/webdriver/firefox/service'
34
34
 
35
35
  DEFAULT_PORT = 7055
36
- DEFAULT_ENABLE_NATIVE_EVENTS = Platform.os == :windows
37
36
  DEFAULT_SECURE_SSL = false
38
37
  DEFAULT_ASSUME_UNTRUSTED_ISSUER = true
39
38
  DEFAULT_LOAD_NO_FOCUS_LIB = false
@@ -39,7 +39,9 @@ module Selenium
39
39
  persistent_hover: 'enablePersistentHover',
40
40
  require_window_focus: 'requireWindowFocus',
41
41
  use_per_process_proxy: 'ie.usePerProcessProxy',
42
- validate_cookie_document_type: 'ie.validateCookieDocumentType'
42
+ use_legacy_file_upload_dialog_handling: 'ie.useLegacyFileUploadDialogHandling',
43
+ attach_to_edge_chrome: 'ie.edgechromium',
44
+ edge_executable_path: 'ie.edgepath'
43
45
  }.freeze
44
46
  BROWSER = 'internet explorer'
45
47
 
@@ -25,7 +25,7 @@ module Selenium
25
25
  EXECUTABLE = 'IEDriverServer'
26
26
  MISSING_TEXT = <<~ERROR
27
27
  Unable to find IEDriverServer. Please download the server from
28
- http://selenium-release.storage.googleapis.com/index.html and place it somewhere on your PATH.
28
+ https://www.selenium.dev/downloads/ and place it somewhere on your PATH.
29
29
  More info at https://github.com/SeleniumHQ/selenium/wiki/InternetExplorerDriver.
30
30
  ERROR
31
31
  SHUTDOWN_SUPPORTED = true
@@ -25,7 +25,7 @@ module Selenium
25
25
 
26
26
  PORT = 4444
27
27
 
28
- attr_accessor :context, :http, :file_detector
28
+ attr_accessor :http, :file_detector
29
29
  attr_reader :capabilities
30
30
 
31
31
  #
@@ -49,7 +49,7 @@ module Selenium
49
49
  #
50
50
 
51
51
  def create_session(capabilities)
52
- response = execute(:new_session, {}, {capabilities: {firstMatch: [capabilities]}})
52
+ response = execute(:new_session, {}, prepare_capabilities_payload(capabilities))
53
53
 
54
54
  @session_id = response['sessionId']
55
55
  capabilities = response['capabilities']
@@ -269,7 +269,7 @@ module Selenium
269
269
  end
270
270
 
271
271
  def element_screenshot(element)
272
- execute :take_element_screenshot, id: element.ref
272
+ execute :take_element_screenshot, id: element
273
273
  end
274
274
 
275
275
  #
@@ -431,7 +431,7 @@ module Selenium
431
431
  end
432
432
 
433
433
  def submit_element(element)
434
- form = find_element_by('xpath', "./ancestor-or-self::form", element)
434
+ form = find_element_by('xpath', "./ancestor-or-self::form", [:element, element])
435
435
  execute_script("var e = arguments[0].ownerDocument.createEvent('Event');" \
436
436
  "e.initEvent('submit', true, true);" \
437
437
  'if (arguments[0].dispatchEvent(e)) { arguments[0].submit() }', form.as_json)
@@ -451,11 +451,19 @@ module Selenium
451
451
  end
452
452
 
453
453
  def element_dom_attribute(element, name)
454
- execute :get_element_attribute, id: element.ref, name: name
454
+ execute :get_element_attribute, id: element, name: name
455
455
  end
456
456
 
457
457
  def element_property(element, name)
458
- execute :get_element_property, id: element.ref, name: name
458
+ execute :get_element_property, id: element, name: name
459
+ end
460
+
461
+ def element_aria_role(element)
462
+ execute :get_element_aria_role, id: element
463
+ end
464
+
465
+ def element_aria_label(element)
466
+ execute :get_element_aria_label, id: element
459
467
  end
460
468
 
461
469
  def element_value(element)
@@ -516,13 +524,17 @@ module Selenium
516
524
 
517
525
  alias_method :switch_to_active_element, :active_element
518
526
 
519
- def find_element_by(how, what, parent = nil)
527
+ def find_element_by(how, what, parent_ref = [])
520
528
  how, what = convert_locator(how, what)
521
529
 
522
530
  return execute_atom(:findElements, Support::RelativeLocator.new(what).as_json).first if how == 'relative'
523
531
 
524
- id = if parent
525
- execute :find_child_element, {id: parent}, {using: how, value: what.to_s}
532
+ parent_type, parent_id = parent_ref
533
+ id = case parent_type
534
+ when :element
535
+ execute :find_child_element, {id: parent_id}, {using: how, value: what.to_s}
536
+ when :shadow_root
537
+ execute :find_shadow_child_element, {id: parent_id}, {using: how, value: what.to_s}
526
538
  else
527
539
  execute :find_element, {}, {using: how, value: what.to_s}
528
540
  end
@@ -530,13 +542,17 @@ module Selenium
530
542
  Element.new self, element_id_from(id)
531
543
  end
532
544
 
533
- def find_elements_by(how, what, parent = nil)
545
+ def find_elements_by(how, what, parent_ref = [])
534
546
  how, what = convert_locator(how, what)
535
547
 
536
548
  return execute_atom :findElements, Support::RelativeLocator.new(what).as_json if how == 'relative'
537
549
 
538
- ids = if parent
539
- execute :find_child_elements, {id: parent}, {using: how, value: what.to_s}
550
+ parent_type, parent_id = parent_ref
551
+ ids = case parent_type
552
+ when :element
553
+ execute :find_child_elements, {id: parent_id}, {using: how, value: what.to_s}
554
+ when :shadow_root
555
+ execute :find_shadow_child_elements, {id: parent_id}, {using: how, value: what.to_s}
540
556
  else
541
557
  execute :find_elements, {}, {using: how, value: what.to_s}
542
558
  end
@@ -544,6 +560,11 @@ module Selenium
544
560
  ids.map { |id| Element.new self, element_id_from(id) }
545
561
  end
546
562
 
563
+ def shadow_root(element)
564
+ id = execute :get_element_shadow_root, id: element
565
+ ShadowRoot.new self, shadow_root_id_from(id)
566
+ end
567
+
547
568
  private
548
569
 
549
570
  #
@@ -591,7 +612,16 @@ module Selenium
591
612
  end
592
613
 
593
614
  def element_id_from(id)
594
- id['ELEMENT'] || id['element-6066-11e4-a52e-4f735466cecf']
615
+ id['ELEMENT'] || id[Element::ELEMENT_KEY]
616
+ end
617
+
618
+ def shadow_root_id_from(id)
619
+ id[ShadowRoot::ROOT_KEY]
620
+ end
621
+
622
+ def prepare_capabilities_payload(capabilities)
623
+ capabilities = {alwaysMatch: capabilities} if !capabilities['alwaysMatch'] && !capabilities['firstMatch']
624
+ {capabilities: capabilities}
595
625
  end
596
626
 
597
627
  def convert_locator(how, what)