selenium-webdriver 4.0.0.beta2 → 4.0.0.rc2

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