selenium-webdriver 4.0.0.beta3 → 4.0.0.rc3

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +1953 -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 +6 -5
  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 +0 -0
  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/{devtools.rb → webdriver/common/driver_extensions/has_launching.rb} +16 -8
  21. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +1 -6
  22. data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +8 -0
  23. data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +87 -18
  24. data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +11 -11
  25. data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +77 -0
  26. data/lib/selenium/webdriver/common/driver_extensions/prints_page.rb +1 -1
  27. data/lib/selenium/webdriver/common/element.rb +17 -8
  28. data/lib/selenium/webdriver/common/error.rb +12 -0
  29. data/lib/selenium/webdriver/common/log_entry.rb +2 -2
  30. data/lib/selenium/webdriver/common/manager.rb +3 -13
  31. data/lib/selenium/webdriver/common/options.rb +20 -6
  32. data/lib/selenium/webdriver/common/proxy.rb +2 -2
  33. data/lib/selenium/webdriver/common/shadow_root.rb +87 -0
  34. data/lib/selenium/webdriver/common/takes_screenshot.rb +9 -6
  35. data/lib/selenium/webdriver/common/target_locator.rb +28 -0
  36. data/lib/selenium/webdriver/common/timeouts.rb +31 -4
  37. data/lib/selenium/webdriver/common/window.rb +0 -4
  38. data/lib/selenium/webdriver/common.rb +8 -0
  39. data/lib/selenium/webdriver/devtools/pinned_script.rb +59 -0
  40. data/lib/selenium/webdriver/devtools/request.rb +27 -17
  41. data/lib/selenium/webdriver/devtools/response.rb +66 -0
  42. data/lib/selenium/webdriver/devtools.rb +50 -12
  43. data/lib/selenium/webdriver/edge/features.rb +5 -0
  44. data/lib/selenium/webdriver/edge/options.rb +0 -0
  45. data/lib/selenium/webdriver/edge/profile.rb +0 -0
  46. data/lib/selenium/webdriver/firefox/driver.rb +6 -0
  47. data/lib/selenium/webdriver/firefox/features.rb +20 -1
  48. data/lib/selenium/webdriver/firefox/options.rb +24 -1
  49. data/lib/selenium/webdriver/firefox.rb +0 -1
  50. data/lib/selenium/webdriver/ie/options.rb +3 -1
  51. data/lib/selenium/webdriver/ie/service.rb +1 -1
  52. data/lib/selenium/webdriver/remote/bridge.rb +39 -23
  53. data/lib/selenium/webdriver/remote/capabilities.rb +4 -13
  54. data/lib/selenium/webdriver/remote/commands.rb +4 -0
  55. data/lib/selenium/webdriver/remote/driver.rb +3 -1
  56. data/lib/selenium/webdriver/remote.rb +1 -1
  57. data/lib/selenium/webdriver/safari/driver.rb +1 -1
  58. data/lib/selenium/webdriver/safari/options.rb +7 -0
  59. data/lib/selenium/webdriver/support/cdp/domain.rb.erb +63 -0
  60. data/lib/selenium/webdriver/support/cdp_client_generator.rb +108 -0
  61. data/lib/selenium/webdriver/support/event_firing_bridge.rb +2 -2
  62. data/lib/selenium/webdriver/version.rb +1 -1
  63. data/lib/selenium/webdriver.rb +1 -1
  64. data/selenium-webdriver.gemspec +63 -0
  65. metadata +60 -44
@@ -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,
@@ -41,6 +43,10 @@ module Selenium
41
43
  private
42
44
 
43
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
+
44
50
  uri = URI("http://#{capabilities['moz:debuggerAddress']}")
45
51
  response = Net::HTTP.get(uri.hostname, '/json/version', uri.port)
46
52
 
@@ -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
  #
@@ -93,17 +93,16 @@ module Selenium
93
93
  execute :get, {}, {url: url}
94
94
  end
95
95
 
96
- def implicit_wait_timeout=(milliseconds)
97
- timeout('implicit', milliseconds)
98
- end
96
+ #
97
+ # timeouts
98
+ #
99
99
 
100
- def script_timeout=(milliseconds)
101
- timeout('script', milliseconds)
100
+ def timeouts
101
+ execute :get_timeouts, {}
102
102
  end
103
103
 
104
- def timeout(type, milliseconds)
105
- type = 'pageLoad' if type == 'page load'
106
- execute :set_timeout, {}, {type => milliseconds}
104
+ def timeouts=(timeouts)
105
+ execute :set_timeout, {}, timeouts
107
106
  end
108
107
 
109
108
  #
@@ -269,7 +268,7 @@ module Selenium
269
268
  end
270
269
 
271
270
  def element_screenshot(element)
272
- execute :take_element_screenshot, id: element.ref
271
+ execute :take_element_screenshot, id: element
273
272
  end
274
273
 
275
274
  #
@@ -431,7 +430,7 @@ module Selenium
431
430
  end
432
431
 
433
432
  def submit_element(element)
434
- form = find_element_by('xpath', "./ancestor-or-self::form", element)
433
+ form = find_element_by('xpath', "./ancestor-or-self::form", [:element, element])
435
434
  execute_script("var e = arguments[0].ownerDocument.createEvent('Event');" \
436
435
  "e.initEvent('submit', true, true);" \
437
436
  'if (arguments[0].dispatchEvent(e)) { arguments[0].submit() }', form.as_json)
@@ -451,19 +450,19 @@ module Selenium
451
450
  end
452
451
 
453
452
  def element_dom_attribute(element, name)
454
- execute :get_element_attribute, id: element.ref, name: name
453
+ execute :get_element_attribute, id: element, name: name
455
454
  end
456
455
 
457
456
  def element_property(element, name)
458
- execute :get_element_property, id: element.ref, name: name
457
+ execute :get_element_property, id: element, name: name
459
458
  end
460
459
 
461
460
  def element_aria_role(element)
462
- execute :get_element_aria_role, id: element.ref
461
+ execute :get_element_aria_role, id: element
463
462
  end
464
463
 
465
464
  def element_aria_label(element)
466
- execute :get_element_aria_label, id: element.ref
465
+ execute :get_element_aria_label, id: element
467
466
  end
468
467
 
469
468
  def element_value(element)
@@ -524,13 +523,17 @@ module Selenium
524
523
 
525
524
  alias_method :switch_to_active_element, :active_element
526
525
 
527
- def find_element_by(how, what, parent = nil)
526
+ def find_element_by(how, what, parent_ref = [])
528
527
  how, what = convert_locator(how, what)
529
528
 
530
529
  return execute_atom(:findElements, Support::RelativeLocator.new(what).as_json).first if how == 'relative'
531
530
 
532
- id = if parent
533
- execute :find_child_element, {id: parent}, {using: how, value: what.to_s}
531
+ parent_type, parent_id = parent_ref
532
+ id = case parent_type
533
+ when :element
534
+ execute :find_child_element, {id: parent_id}, {using: how, value: what.to_s}
535
+ when :shadow_root
536
+ execute :find_shadow_child_element, {id: parent_id}, {using: how, value: what.to_s}
534
537
  else
535
538
  execute :find_element, {}, {using: how, value: what.to_s}
536
539
  end
@@ -538,13 +541,17 @@ module Selenium
538
541
  Element.new self, element_id_from(id)
539
542
  end
540
543
 
541
- def find_elements_by(how, what, parent = nil)
544
+ def find_elements_by(how, what, parent_ref = [])
542
545
  how, what = convert_locator(how, what)
543
546
 
544
547
  return execute_atom :findElements, Support::RelativeLocator.new(what).as_json if how == 'relative'
545
548
 
546
- ids = if parent
547
- execute :find_child_elements, {id: parent}, {using: how, value: what.to_s}
549
+ parent_type, parent_id = parent_ref
550
+ ids = case parent_type
551
+ when :element
552
+ execute :find_child_elements, {id: parent_id}, {using: how, value: what.to_s}
553
+ when :shadow_root
554
+ execute :find_shadow_child_elements, {id: parent_id}, {using: how, value: what.to_s}
548
555
  else
549
556
  execute :find_elements, {}, {using: how, value: what.to_s}
550
557
  end
@@ -552,6 +559,11 @@ module Selenium
552
559
  ids.map { |id| Element.new self, element_id_from(id) }
553
560
  end
554
561
 
562
+ def shadow_root(element)
563
+ id = execute :get_element_shadow_root, id: element
564
+ ShadowRoot.new self, shadow_root_id_from(id)
565
+ end
566
+
555
567
  private
556
568
 
557
569
  #
@@ -599,11 +611,15 @@ module Selenium
599
611
  end
600
612
 
601
613
  def element_id_from(id)
602
- id['ELEMENT'] || id['element-6066-11e4-a52e-4f735466cecf']
614
+ id['ELEMENT'] || id[Element::ELEMENT_KEY]
615
+ end
616
+
617
+ def shadow_root_id_from(id)
618
+ id[ShadowRoot::ROOT_KEY]
603
619
  end
604
620
 
605
621
  def prepare_capabilities_payload(capabilities)
606
- capabilities = {firstMatch: [capabilities]} if !capabilities['alwaysMatch'] && !capabilities['firstMatch']
622
+ capabilities = {alwaysMatch: capabilities} if !capabilities['alwaysMatch'] && !capabilities['firstMatch']
607
623
  {capabilities: capabilities}
608
624
  end
609
625
 
@@ -39,6 +39,7 @@ module Selenium
39
39
  :timeouts,
40
40
  :unhandled_prompt_behavior,
41
41
  :strict_file_interactability,
42
+ :web_socket_url,
42
43
 
43
44
  # remote-specific (webdriver.remote.sessionid)
44
45
  :remote_session_id
@@ -46,7 +47,7 @@ module Selenium
46
47
 
47
48
  (KNOWN - %i[proxy timeouts]).each do |key|
48
49
  define_method key do
49
- @capabilities.fetch(key)
50
+ @capabilities[key]
50
51
  end
51
52
 
52
53
  define_method "#{key}=" do |value|
@@ -202,7 +203,7 @@ module Selenium
202
203
  end
203
204
 
204
205
  def proxy
205
- @capabilities.fetch(:proxy)
206
+ @capabilities[:proxy]
206
207
  end
207
208
 
208
209
  def proxy=(proxy)
@@ -307,23 +308,13 @@ module Selenium
307
308
  when :platform
308
309
  value.to_s.upcase
309
310
  when :proxy
310
- convert_proxy(value)
311
+ value&.as_json
311
312
  when :unhandled_prompt_behavior
312
313
  value.is_a?(Symbol) ? value.to_s.tr('_', ' ') : value
313
314
  else
314
315
  value
315
316
  end
316
317
  end
317
-
318
- def convert_proxy(value)
319
- return unless value
320
-
321
- hash = value.as_json
322
- hash['proxyType'] &&= hash['proxyType'].downcase
323
- hash['noProxy'] = hash['noProxy'].split(', ') if hash['noProxy'].is_a?(String)
324
-
325
- hash
326
- end
327
318
  end # Capabilities
328
319
  end # Remote
329
320
  end # WebDriver
@@ -77,7 +77,10 @@ module Selenium
77
77
  find_elements: [:post, 'session/:session_id/elements'],
78
78
  find_child_element: [:post, 'session/:session_id/element/:id/element'],
79
79
  find_child_elements: [:post, 'session/:session_id/element/:id/elements'],
80
+ find_shadow_child_element: [:post, 'session/:session_id/shadow/:id/element'],
81
+ find_shadow_child_elements: [:post, 'session/:session_id/shadow/:id/elements'],
80
82
  get_active_element: [:get, 'session/:session_id/element/active'],
83
+ get_element_shadow_root: [:get, 'session/:session_id/element/:id/shadow'],
81
84
  is_element_selected: [:get, 'session/:session_id/element/:id/selected'],
82
85
  get_element_attribute: [:get, 'session/:session_id/element/:id/attribute/:name'],
83
86
  get_element_property: [:get, 'session/:session_id/element/:id/property/:name'],
@@ -111,6 +114,7 @@ module Selenium
111
114
  # timeouts
112
115
  #
113
116
 
117
+ get_timeouts: [:get, 'session/:session_id/timeouts'],
114
118
  set_timeout: [:post, 'session/:session_id/timeouts'],
115
119
 
116
120
  #
@@ -42,6 +42,7 @@ module Selenium
42
42
  end
43
43
  opts[:url] ||= "http://#{Platform.localhost}:4444/wd/hub"
44
44
  super
45
+ @bridge.file_detector = ->((filename, *)) { File.exist?(filename) && filename.to_s }
45
46
  end
46
47
 
47
48
  private
@@ -51,7 +52,8 @@ module Selenium
51
52
  end
52
53
 
53
54
  def devtools_version
54
- capabilities['se:cdpVersion'].split('.').first
55
+ capabilities['se:cdpVersion']&.split('.')&.first ||
56
+ raise(Error::WebDriverError, "DevTools is not supported by the Remote Server")
55
57
  end
56
58
  end # Driver
57
59
  end # Remote
@@ -18,6 +18,7 @@
18
18
  # under the License.
19
19
 
20
20
  require 'uri'
21
+ require 'selenium/webdriver/remote/server_error'
21
22
 
22
23
  module Selenium
23
24
  module WebDriver
@@ -25,7 +26,6 @@ module Selenium
25
26
  autoload :Bridge, 'selenium/webdriver/remote/bridge'
26
27
  autoload :Driver, 'selenium/webdriver/remote/driver'
27
28
  autoload :Response, 'selenium/webdriver/remote/response'
28
- autoload :ServerError, 'selenium/webdriver/remote/server_error'
29
29
  autoload :Capabilities, 'selenium/webdriver/remote/capabilities'
30
30
  autoload :COMMANDS, 'selenium/webdriver/remote/commands'
31
31
  module Http
@@ -28,7 +28,7 @@ module Selenium
28
28
 
29
29
  class Driver < WebDriver::Driver
30
30
  EXTENSIONS = [DriverExtensions::HasDebugger,
31
- DriverExtensions::HasPermissions,
31
+ DriverExtensions::HasApplePermissions,
32
32
  DriverExtensions::HasWebStorage].freeze
33
33
 
34
34
  def browser
@@ -28,6 +28,13 @@ module Selenium
28
28
  automatic_profiling: 'safari:automaticProfiling'}.freeze
29
29
  BROWSER = 'safari'
30
30
 
31
+ def add_option(name, value = nil)
32
+ key = name.is_a?(Hash) ? name.keys.first : name
33
+ raise ArgumentError, 'Safari does not support options that are not namespaced' unless key.to_s.include?(':')
34
+
35
+ super
36
+ end
37
+
31
38
  end # Options
32
39
  end # Safari
33
40
  end # WebDriver
@@ -0,0 +1,63 @@
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
+ # This file is automatically generated. Any changes will be lost!
21
+ module Selenium
22
+ module DevTools
23
+ module <%= version %>
24
+ class <%= domain[:domain] %>
25
+ <% if domain[:events] %>
26
+ EVENTS = {
27
+ <% domain[:events].each do |event| %>
28
+ <%= h.snake_case(event[:name]) %>: '<%= event[:name] %>',
29
+ <% end %>
30
+ }.freeze
31
+ <% end %>
32
+
33
+ def initialize(devtools)
34
+ @devtools = devtools
35
+ end
36
+
37
+ def on(event, &block)
38
+ event = EVENTS[event] if event.is_a?(Symbol)
39
+ @devtools.callbacks["<%= domain[:domain] %>.#{event}"] << block
40
+ end
41
+
42
+ <% domain[:commands].each do |command| %>
43
+ <% if command[:parameters] %>
44
+ def <%= h.snake_case(command[:name]) %>(<%= h.kwargs(command[:parameters]) %>)
45
+ <% else %>
46
+ def <%= h.snake_case(command[:name]) %>
47
+ <% end %>
48
+ <% if command[:parameters] %>
49
+ @devtools.send_cmd('<%= domain[:domain] %>.<%= command[:name] %>',
50
+ <% until command[:parameters].empty? %>
51
+ <% parameter = command[:parameters].shift %>
52
+ <%= parameter[:name] %>: <%= h.snake_case(parameter[:name]) %><%= command[:parameters].empty? ? ')' : ',' %>
53
+ <% end %>
54
+ <% else %>
55
+ @devtools.send_cmd('<%= domain[:domain] %>.<%= command[:name] %>')
56
+ <% end %>
57
+ end
58
+
59
+ <% end %>
60
+ end # <%= domain[:domain] %>
61
+ end # <%= version %>
62
+ end # DevTools
63
+ end # Selenium