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