selenium-webdriver 2.53.3 → 3.142.7
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 +7 -0
- data/CHANGES +665 -8
- data/Gemfile +2 -0
- data/LICENSE +1 -1
- data/README.md +2 -3
- data/lib/selenium/server.rb +76 -73
- data/lib/selenium/webdriver/atoms/getAttribute.js +7 -0
- data/lib/selenium/webdriver/atoms/isDisplayed.js +102 -0
- data/lib/selenium/webdriver/{phantomjs.rb → atoms.rb} +10 -14
- data/lib/selenium/webdriver/chrome/bridge.rb +30 -101
- data/lib/selenium/webdriver/chrome/driver.rb +127 -0
- data/lib/selenium/webdriver/chrome/options.rb +190 -0
- data/lib/selenium/webdriver/chrome/profile.rb +21 -20
- data/lib/selenium/webdriver/chrome/service.rb +26 -93
- data/lib/selenium/webdriver/chrome.rb +15 -6
- data/lib/selenium/webdriver/common/action_builder.rb +52 -58
- data/lib/selenium/webdriver/common/alert.rb +7 -15
- data/lib/selenium/webdriver/common/bridge_helper.rb +18 -22
- data/lib/selenium/webdriver/common/driver.rb +72 -72
- data/lib/selenium/webdriver/common/driver_extensions/downloads_files.rb +45 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_addons.rb +50 -0
- data/lib/selenium/webdriver/common/driver_extensions/{has_input_devices.rb → has_debugger.rb} +11 -27
- data/lib/selenium/webdriver/common/driver_extensions/has_location.rb +6 -10
- data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +51 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_network_connection.rb +7 -8
- data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +51 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_remote_status.rb +2 -4
- data/lib/selenium/webdriver/common/driver_extensions/has_session_id.rb +2 -4
- data/lib/selenium/webdriver/common/driver_extensions/has_touch_screen.rb +3 -5
- data/lib/selenium/webdriver/common/driver_extensions/has_web_storage.rb +2 -5
- data/lib/selenium/webdriver/common/driver_extensions/rotatable.rb +6 -9
- data/lib/selenium/webdriver/common/driver_extensions/takes_screenshot.rb +9 -7
- data/lib/selenium/webdriver/common/driver_extensions/uploads_files.rb +3 -8
- data/lib/selenium/webdriver/common/element.rb +59 -39
- data/lib/selenium/webdriver/common/error.rb +259 -104
- data/lib/selenium/webdriver/common/file_reaper.rb +6 -14
- data/lib/selenium/webdriver/common/html5/local_storage.rb +8 -10
- data/lib/selenium/webdriver/common/html5/session_storage.rb +8 -10
- data/lib/selenium/webdriver/common/html5/shared_web_storage.rb +8 -16
- data/lib/selenium/webdriver/common/interactions/input_device.rb +54 -0
- data/lib/selenium/webdriver/common/interactions/interaction.rb +53 -0
- data/lib/selenium/webdriver/{safari/browser.rb → common/interactions/interactions.rb} +17 -14
- data/lib/selenium/webdriver/common/interactions/key_actions.rb +145 -0
- data/lib/selenium/webdriver/common/interactions/key_input.rb +66 -0
- data/lib/selenium/webdriver/{android.rb → common/interactions/none_input.rb} +14 -6
- data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +363 -0
- data/lib/selenium/webdriver/common/interactions/pointer_input.rb +139 -0
- data/lib/selenium/webdriver/common/keyboard.rb +10 -14
- data/lib/selenium/webdriver/common/keys.rb +102 -82
- data/lib/selenium/webdriver/common/log_entry.rb +7 -8
- data/lib/selenium/webdriver/common/logger.rb +115 -0
- data/lib/selenium/webdriver/common/logs.rb +4 -6
- data/lib/selenium/webdriver/common/manager.rb +177 -0
- data/lib/selenium/webdriver/common/mouse.rb +12 -14
- data/lib/selenium/webdriver/common/navigation.rb +4 -6
- data/lib/selenium/webdriver/common/options.rb +26 -127
- data/lib/selenium/webdriver/common/platform.rb +75 -101
- data/lib/selenium/webdriver/common/port_prober.rb +7 -19
- data/lib/selenium/webdriver/common/profile_helper.rb +8 -11
- data/lib/selenium/webdriver/common/proxy.rb +68 -74
- data/lib/selenium/webdriver/common/search_context.rb +28 -37
- data/lib/selenium/webdriver/common/service.rb +219 -0
- data/lib/selenium/webdriver/common/socket_lock.rb +15 -16
- data/lib/selenium/webdriver/common/socket_poller.rb +30 -28
- data/lib/selenium/webdriver/common/target_locator.rb +16 -18
- data/lib/selenium/webdriver/common/timeouts.rb +6 -8
- data/lib/selenium/webdriver/common/touch_action_builder.rb +5 -10
- data/lib/selenium/webdriver/common/touch_screen.rb +22 -23
- data/lib/selenium/webdriver/common/w3c_action_builder.rb +212 -0
- data/lib/selenium/webdriver/common/w3c_manager.rb +45 -0
- data/lib/selenium/webdriver/common/wait.rb +17 -16
- data/lib/selenium/webdriver/common/window.rb +50 -17
- data/lib/selenium/webdriver/common/zipper.rb +9 -13
- data/lib/selenium/webdriver/common.rb +21 -7
- data/lib/selenium/webdriver/edge/bridge.rb +34 -63
- data/lib/selenium/webdriver/edge/driver.rb +66 -0
- data/lib/selenium/webdriver/edge/options.rb +80 -0
- data/lib/selenium/webdriver/edge/service.rb +23 -95
- data/lib/selenium/webdriver/edge.rb +13 -13
- data/lib/selenium/webdriver/firefox/binary.rb +45 -60
- data/lib/selenium/webdriver/firefox/driver.rb +50 -0
- data/lib/selenium/webdriver/firefox/extension/prefs.json +3 -12
- data/lib/selenium/webdriver/firefox/extension/webdriver.xpi +0 -0
- data/lib/selenium/webdriver/firefox/extension.rb +20 -10
- data/lib/selenium/webdriver/firefox/launcher.rb +16 -22
- data/lib/selenium/webdriver/firefox/legacy/driver.rb +83 -0
- data/lib/selenium/webdriver/firefox/marionette/bridge.rb +49 -0
- data/lib/selenium/webdriver/firefox/marionette/driver.rb +90 -0
- data/lib/selenium/webdriver/firefox/options.rb +162 -0
- data/lib/selenium/webdriver/firefox/profile.rb +47 -48
- data/lib/selenium/webdriver/firefox/profiles_ini.rb +11 -18
- data/lib/selenium/webdriver/firefox/service.rb +24 -95
- data/lib/selenium/webdriver/firefox/util.rb +2 -4
- data/lib/selenium/webdriver/firefox.rb +27 -12
- data/lib/selenium/webdriver/ie/driver.rb +85 -0
- data/lib/selenium/webdriver/ie/options.rb +138 -0
- data/lib/selenium/webdriver/ie/service.rb +54 -0
- data/lib/selenium/webdriver/ie.rb +12 -10
- data/lib/selenium/webdriver/remote/bridge.rb +100 -564
- data/lib/selenium/webdriver/remote/capabilities.rb +98 -100
- data/lib/selenium/webdriver/remote/driver.rb +51 -0
- data/lib/selenium/webdriver/remote/http/common.rb +34 -22
- data/lib/selenium/webdriver/remote/http/curb.rb +13 -14
- data/lib/selenium/webdriver/remote/http/default.rb +62 -43
- data/lib/selenium/webdriver/remote/http/persistent.rb +12 -9
- data/lib/selenium/webdriver/remote/oss/bridge.rb +594 -0
- data/lib/selenium/webdriver/remote/oss/commands.rb +223 -0
- data/lib/selenium/webdriver/remote/response.rb +48 -28
- data/lib/selenium/webdriver/remote/server_error.rb +3 -5
- data/lib/selenium/webdriver/remote/w3c/bridge.rb +605 -0
- data/lib/selenium/webdriver/remote/w3c/capabilities.rb +310 -0
- data/lib/selenium/webdriver/remote/w3c/commands.rb +157 -0
- data/lib/selenium/webdriver/remote.rb +10 -16
- data/lib/selenium/webdriver/safari/bridge.rb +17 -101
- data/lib/selenium/webdriver/{firefox/w3c_bridge.rb → safari/driver.rb} +27 -25
- data/lib/selenium/webdriver/safari/options.rb +29 -31
- data/lib/selenium/webdriver/safari/service.rb +38 -0
- data/lib/selenium/webdriver/safari.rb +27 -27
- data/lib/selenium/webdriver/support/abstract_event_listener.rb +19 -4
- data/lib/selenium/webdriver/support/block_event_listener.rb +3 -5
- data/lib/selenium/webdriver/support/color.rb +60 -43
- data/lib/selenium/webdriver/support/escaper.rb +43 -0
- data/lib/selenium/webdriver/support/event_firing_bridge.rb +39 -41
- data/lib/selenium/webdriver/support/select.rb +45 -97
- data/lib/selenium/webdriver/support.rb +3 -2
- data/lib/selenium/webdriver/{iphone.rb → version.rb} +3 -7
- data/lib/selenium/webdriver.rb +36 -23
- data/lib/selenium-webdriver.rb +2 -2
- data/selenium-webdriver.gemspec +42 -29
- metadata +331 -262
- data/lib/selenium/client/base.rb +0 -151
- data/lib/selenium/client/driver.rb +0 -29
- data/lib/selenium/client/errors.rb +0 -28
- data/lib/selenium/client/extensions.rb +0 -132
- data/lib/selenium/client/idiomatic.rb +0 -507
- data/lib/selenium/client/javascript_expression_builder.rb +0 -135
- data/lib/selenium/client/javascript_frameworks/jquery.rb +0 -32
- data/lib/selenium/client/javascript_frameworks/prototype.rb +0 -32
- data/lib/selenium/client/legacy_driver.rb +0 -1722
- data/lib/selenium/client/protocol.rb +0 -123
- data/lib/selenium/client/selenium_helper.rb +0 -49
- data/lib/selenium/client.rb +0 -57
- data/lib/selenium/rake/server_task.rb +0 -176
- data/lib/selenium/webdriver/android/bridge.rb +0 -68
- data/lib/selenium/webdriver/common/core_ext/base64.rb +0 -28
- data/lib/selenium/webdriver/common/core_ext/dir.rb +0 -61
- data/lib/selenium/webdriver/common/html5/location.rb +0 -19
- data/lib/selenium/webdriver/common/w3c_error.rb +0 -194
- data/lib/selenium/webdriver/edge/legacy_support.rb +0 -117
- data/lib/selenium/webdriver/firefox/bridge.rb +0 -89
- data/lib/selenium/webdriver/ie/bridge.rb +0 -88
- data/lib/selenium/webdriver/ie/server.rb +0 -133
- data/lib/selenium/webdriver/iphone/bridge.rb +0 -64
- data/lib/selenium/webdriver/phantomjs/bridge.rb +0 -78
- data/lib/selenium/webdriver/phantomjs/service.rb +0 -130
- data/lib/selenium/webdriver/remote/commands.rb +0 -211
- data/lib/selenium/webdriver/remote/w3c_bridge.rb +0 -668
- data/lib/selenium/webdriver/remote/w3c_capabilities.rb +0 -236
- data/lib/selenium/webdriver/remote/w3c_commands.rb +0 -132
- data/lib/selenium/webdriver/safari/resources/client.js +0 -7255
- data/lib/selenium/webdriver/safari/server.rb +0 -187
- data/lib/selenium-client.rb +0 -21
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
3
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
|
4
4
|
# or more contributor license agreements. See the NOTICE file
|
|
5
5
|
# distributed with this work for additional information
|
|
@@ -21,45 +21,58 @@ module Selenium
|
|
|
21
21
|
module WebDriver
|
|
22
22
|
class Proxy
|
|
23
23
|
TYPES = {
|
|
24
|
-
:
|
|
25
|
-
:
|
|
26
|
-
:
|
|
27
|
-
:
|
|
28
|
-
:
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
24
|
+
direct: 'DIRECT', # Direct connection, no proxy (default on Windows).
|
|
25
|
+
manual: 'MANUAL', # Manual proxy settings (e.g., for httpProxy).
|
|
26
|
+
pac: 'PAC', # Proxy autoconfiguration from URL.
|
|
27
|
+
auto_detect: 'AUTODETECT', # Proxy autodetection (presumably with WPAD).
|
|
28
|
+
system: 'SYSTEM' # Use system settings (default on Linux).
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
ALLOWED = {type: 'proxyType',
|
|
32
|
+
ftp: 'ftpProxy',
|
|
33
|
+
http: 'httpProxy',
|
|
34
|
+
no_proxy: 'noProxy',
|
|
35
|
+
pac: 'proxyAutoconfigUrl',
|
|
36
|
+
ssl: 'sslProxy',
|
|
37
|
+
auto_detect: 'autodetect',
|
|
38
|
+
socks: 'socksProxy',
|
|
39
|
+
socks_username: 'socksUsername',
|
|
40
|
+
socks_password: 'socksPassword',
|
|
41
|
+
socks_version: 'socksVersion'}.freeze
|
|
42
|
+
|
|
43
|
+
ALLOWED.each_key { |t| attr_reader t }
|
|
44
|
+
|
|
45
|
+
def self.json_create(data)
|
|
46
|
+
data['proxyType'] = data['proxyType'].downcase.to_sym
|
|
47
|
+
return if data['proxyType'] == :unspecified
|
|
48
|
+
|
|
49
|
+
proxy = new
|
|
50
|
+
|
|
51
|
+
ALLOWED.each do |k, v|
|
|
52
|
+
proxy.send("#{k}=", data[v]) if data.key?(v)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
proxy
|
|
56
|
+
end
|
|
41
57
|
|
|
42
58
|
def initialize(opts = {})
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
self.auto_detect = opts.delete(:auto_detect) if opts.has_key? :auto_detect
|
|
52
|
-
self.socks = opts.delete(:socks) if opts.has_key? :socks
|
|
53
|
-
self.socks_username = opts.delete(:socks_username) if opts.has_key? :socks_username
|
|
54
|
-
self.socks_password = opts.delete(:socks_password) if opts.has_key? :socks_password
|
|
55
|
-
|
|
56
|
-
unless opts.empty?
|
|
57
|
-
raise ArgumentError, "unknown option#{'s' if opts.size != 1}: #{opts.inspect}"
|
|
59
|
+
not_allowed = []
|
|
60
|
+
|
|
61
|
+
opts.each do |k, v|
|
|
62
|
+
if ALLOWED.key?(k)
|
|
63
|
+
send("#{k}=", v)
|
|
64
|
+
else
|
|
65
|
+
not_allowed << k
|
|
66
|
+
end
|
|
58
67
|
end
|
|
68
|
+
|
|
69
|
+
return if not_allowed.empty?
|
|
70
|
+
|
|
71
|
+
raise ArgumentError, "unknown option#{'s' if not_allowed.size != 1}: #{not_allowed.inspect}"
|
|
59
72
|
end
|
|
60
73
|
|
|
61
74
|
def ==(other)
|
|
62
|
-
other.
|
|
75
|
+
other.is_a?(self.class) && as_json == other.as_json
|
|
63
76
|
end
|
|
64
77
|
alias_method :eql?, :==
|
|
65
78
|
|
|
@@ -108,10 +121,13 @@ module Selenium
|
|
|
108
121
|
@socks_password = value
|
|
109
122
|
end
|
|
110
123
|
|
|
124
|
+
def socks_version=(value)
|
|
125
|
+
self.type = :manual
|
|
126
|
+
@socks_version = value
|
|
127
|
+
end
|
|
128
|
+
|
|
111
129
|
def type=(type)
|
|
112
|
-
unless TYPES.
|
|
113
|
-
raise ArgumentError, "invalid proxy type: #{type.inspect}, expected one of #{TYPES.keys.inspect}"
|
|
114
|
-
end
|
|
130
|
+
raise ArgumentError, "invalid proxy type: #{type.inspect}, expected one of #{TYPES.keys.inspect}" unless TYPES.key? type
|
|
115
131
|
|
|
116
132
|
if defined?(@type) && type != @type
|
|
117
133
|
raise ArgumentError, "incompatible proxy type #{type.inspect} (already set to #{@type.inspect})"
|
|
@@ -120,49 +136,27 @@ module Selenium
|
|
|
120
136
|
@type = type
|
|
121
137
|
end
|
|
122
138
|
|
|
123
|
-
def as_json(
|
|
139
|
+
def as_json(*)
|
|
124
140
|
json_result = {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
141
|
+
'proxyType' => TYPES[type],
|
|
142
|
+
'ftpProxy' => ftp,
|
|
143
|
+
'httpProxy' => http,
|
|
144
|
+
'noProxy' => no_proxy,
|
|
145
|
+
'proxyAutoconfigUrl' => pac,
|
|
146
|
+
'sslProxy' => ssl,
|
|
147
|
+
'autodetect' => auto_detect,
|
|
148
|
+
'socksProxy' => socks,
|
|
149
|
+
'socksUsername' => socks_username,
|
|
150
|
+
'socksPassword' => socks_password,
|
|
151
|
+
'socksVersion' => socks_version
|
|
152
|
+
}.delete_if { |_k, v| v.nil? }
|
|
137
153
|
|
|
138
154
|
json_result if json_result.length > 1
|
|
139
155
|
end
|
|
140
156
|
|
|
141
|
-
def to_json(*
|
|
157
|
+
def to_json(*)
|
|
142
158
|
JSON.generate as_json
|
|
143
159
|
end
|
|
144
|
-
|
|
145
|
-
class << self
|
|
146
|
-
def json_create(data)
|
|
147
|
-
return if data['proxyType'] == 'UNSPECIFIED'
|
|
148
|
-
|
|
149
|
-
proxy = new
|
|
150
|
-
|
|
151
|
-
proxy.type = data['proxyType'].downcase.to_sym if data.has_key? 'proxyType'
|
|
152
|
-
proxy.ftp = data['ftpProxy'] if data.has_key? 'ftpProxy'
|
|
153
|
-
proxy.http = data['httpProxy'] if data.has_key? 'httpProxy'
|
|
154
|
-
proxy.no_proxy = data['noProxy'] if data.has_key? 'noProxy'
|
|
155
|
-
proxy.pac = data['proxyAutoconfigUrl'] if data.has_key? 'proxyAutoconfigUrl'
|
|
156
|
-
proxy.ssl = data['sslProxy'] if data.has_key? 'sslProxy'
|
|
157
|
-
proxy.auto_detect = data['autodetect'] if data.has_key? 'autodetect'
|
|
158
|
-
proxy.socks = data['socksProxy'] if data.has_key? 'socksProxy'
|
|
159
|
-
proxy.socks_username = data['socksUsername'] if data.has_key? 'socksUsername'
|
|
160
|
-
proxy.socks_password = data['socksPassword'] if data.has_key? 'socksPassword'
|
|
161
|
-
|
|
162
|
-
proxy
|
|
163
|
-
end
|
|
164
|
-
end # class << self
|
|
165
|
-
|
|
166
160
|
end # Proxy
|
|
167
161
|
end # WebDriver
|
|
168
162
|
end # Selenium
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
3
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
|
4
4
|
# or more contributor license agreements. See the NOTICE file
|
|
5
5
|
# distributed with this work for additional information
|
|
@@ -20,46 +20,47 @@
|
|
|
20
20
|
module Selenium
|
|
21
21
|
module WebDriver
|
|
22
22
|
module SearchContext
|
|
23
|
-
|
|
24
23
|
# @api private
|
|
25
24
|
FINDERS = {
|
|
26
|
-
:
|
|
27
|
-
:
|
|
28
|
-
:
|
|
29
|
-
:
|
|
30
|
-
:
|
|
31
|
-
:
|
|
32
|
-
:
|
|
33
|
-
:
|
|
34
|
-
:
|
|
35
|
-
:
|
|
36
|
-
}
|
|
25
|
+
class: 'class name',
|
|
26
|
+
class_name: 'class name',
|
|
27
|
+
css: 'css selector',
|
|
28
|
+
id: 'id',
|
|
29
|
+
link: 'link text',
|
|
30
|
+
link_text: 'link text',
|
|
31
|
+
name: 'name',
|
|
32
|
+
partial_link_text: 'partial link text',
|
|
33
|
+
tag_name: 'tag name',
|
|
34
|
+
xpath: 'xpath'
|
|
35
|
+
}.freeze
|
|
37
36
|
|
|
38
37
|
#
|
|
39
|
-
# Find the first element matching the given arguments
|
|
38
|
+
# Find the first element matching the given arguments
|
|
40
39
|
#
|
|
41
40
|
# When using Element#find_element with :xpath, be aware that webdriver
|
|
42
41
|
# follows standard conventions: a search prefixed with "//" will search
|
|
43
42
|
# the entire document, not just the children of this current node. Use
|
|
44
43
|
# ".//" to limit your search to the children of the receiving Element.
|
|
45
44
|
#
|
|
46
|
-
# @
|
|
47
|
-
#
|
|
45
|
+
# @overload find_element(how, what)
|
|
46
|
+
# @param [Symbol, String] how The method to find the element by
|
|
47
|
+
# @param [String] what The locator to use
|
|
48
|
+
# @overload find_element(opts)
|
|
49
|
+
# @param [Hash] opts Find options
|
|
50
|
+
# @option opts [Symbol] :how Key named after the method to find the element by, containing the locator
|
|
48
51
|
# @return [Element]
|
|
49
52
|
#
|
|
50
53
|
# @raise [Error::NoSuchElementError] if the element doesn't exist
|
|
51
54
|
#
|
|
52
|
-
#
|
|
53
55
|
|
|
54
56
|
def find_element(*args)
|
|
55
57
|
how, what = extract_args(args)
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
end
|
|
59
|
+
by = FINDERS[how.to_sym]
|
|
60
|
+
raise ArgumentError, "cannot find element by #{how.inspect}" unless by
|
|
60
61
|
|
|
61
62
|
bridge.find_element_by by, what.to_s, ref
|
|
62
|
-
rescue Selenium::WebDriver::Error::
|
|
63
|
+
rescue Selenium::WebDriver::Error::TimeoutError
|
|
63
64
|
# Implicit Wait times out in Edge
|
|
64
65
|
raise Selenium::WebDriver::Error::NoSuchElementError
|
|
65
66
|
end
|
|
@@ -69,20 +70,15 @@ module Selenium
|
|
|
69
70
|
#
|
|
70
71
|
# @see SearchContext#find_element
|
|
71
72
|
#
|
|
72
|
-
# @param [:class, :class_name, :css, :id, :link_text, :link, :partial_link_text, :name, :tag_name, :xpath] how
|
|
73
|
-
# @param [String] what
|
|
74
|
-
# @return [Array<Element>]
|
|
75
|
-
#
|
|
76
73
|
|
|
77
74
|
def find_elements(*args)
|
|
78
75
|
how, what = extract_args(args)
|
|
79
76
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
end
|
|
77
|
+
by = FINDERS[how.to_sym]
|
|
78
|
+
raise ArgumentError, "cannot find elements by #{how.inspect}" unless by
|
|
83
79
|
|
|
84
80
|
bridge.find_elements_by by, what.to_s, ref
|
|
85
|
-
rescue Selenium::WebDriver::Error::
|
|
81
|
+
rescue Selenium::WebDriver::Error::TimeoutError
|
|
86
82
|
# Implicit Wait times out in Edge
|
|
87
83
|
[]
|
|
88
84
|
end
|
|
@@ -96,22 +92,17 @@ module Selenium
|
|
|
96
92
|
when 1
|
|
97
93
|
arg = args.first
|
|
98
94
|
|
|
99
|
-
unless arg.respond_to?(:shift)
|
|
100
|
-
raise ArgumentError, "expected #{arg.inspect}:#{arg.class} to respond to #shift"
|
|
101
|
-
end
|
|
95
|
+
raise ArgumentError, "expected #{arg.inspect}:#{arg.class} to respond to #shift" unless arg.respond_to?(:shift)
|
|
102
96
|
|
|
103
97
|
# this will be a single-entry hash, so use #shift over #first or #[]
|
|
104
98
|
arr = arg.dup.shift
|
|
105
|
-
unless arr.size == 2
|
|
106
|
-
raise ArgumentError, "expected #{arr.inspect} to have 2 elements"
|
|
107
|
-
end
|
|
99
|
+
raise ArgumentError, "expected #{arr.inspect} to have 2 elements" unless arr.size == 2
|
|
108
100
|
|
|
109
101
|
arr
|
|
110
102
|
else
|
|
111
103
|
raise ArgumentError, "wrong number of arguments (#{args.size} for 2)"
|
|
112
104
|
end
|
|
113
105
|
end
|
|
114
|
-
|
|
115
106
|
end # SearchContext
|
|
116
107
|
end # WebDriver
|
|
117
108
|
end # Selenium
|
|
@@ -0,0 +1,219 @@
|
|
|
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
|
+
#
|
|
23
|
+
# Base class implementing default behavior of service object,
|
|
24
|
+
# responsible for starting and stopping driver implementations.
|
|
25
|
+
#
|
|
26
|
+
|
|
27
|
+
class Service
|
|
28
|
+
START_TIMEOUT = 20
|
|
29
|
+
SOCKET_LOCK_TIMEOUT = 45
|
|
30
|
+
STOP_TIMEOUT = 20
|
|
31
|
+
|
|
32
|
+
@default_port = nil
|
|
33
|
+
@driver_path = nil
|
|
34
|
+
@executable = nil
|
|
35
|
+
@missing_text = nil
|
|
36
|
+
|
|
37
|
+
class << self
|
|
38
|
+
attr_reader :default_port, :driver_path, :executable, :missing_text, :shutdown_supported
|
|
39
|
+
|
|
40
|
+
def chrome(**opts)
|
|
41
|
+
Chrome::Service.new(**opts)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def firefox(**opts)
|
|
45
|
+
binary_path = Firefox::Binary.path
|
|
46
|
+
args = opts.delete(:args)
|
|
47
|
+
case args
|
|
48
|
+
when Hash
|
|
49
|
+
args[:binary] ||= binary_path
|
|
50
|
+
opts[:args] = args
|
|
51
|
+
when Array
|
|
52
|
+
opts[:args] = ["--binary=#{binary_path}"]
|
|
53
|
+
opts[:args] += args
|
|
54
|
+
else
|
|
55
|
+
opts[:args] = ["--binary=#{binary_path}"]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
Firefox::Service.new(**opts)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def ie(**opts)
|
|
62
|
+
IE::Service.new(**opts)
|
|
63
|
+
end
|
|
64
|
+
alias_method :internet_explorer, :ie
|
|
65
|
+
|
|
66
|
+
def edge(**opts)
|
|
67
|
+
Edge::Service.new(**opts)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def safari(**opts)
|
|
71
|
+
Safari::Service.new(**opts)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def driver_path=(path)
|
|
75
|
+
Platform.assert_executable path if path.is_a?(String)
|
|
76
|
+
@driver_path = path
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
attr_accessor :host
|
|
81
|
+
attr_reader :executable_path
|
|
82
|
+
|
|
83
|
+
#
|
|
84
|
+
# End users should use a class method for the desired driver, rather than using this directly.
|
|
85
|
+
#
|
|
86
|
+
# @api private
|
|
87
|
+
#
|
|
88
|
+
|
|
89
|
+
def initialize(path: nil, port: nil, args: nil)
|
|
90
|
+
path ||= self.class.driver_path
|
|
91
|
+
port ||= self.class.default_port
|
|
92
|
+
args ||= []
|
|
93
|
+
|
|
94
|
+
@executable_path = binary_path(path)
|
|
95
|
+
@host = Platform.localhost
|
|
96
|
+
@port = Integer(port)
|
|
97
|
+
|
|
98
|
+
@extra_args = args.is_a?(Hash) ? extract_service_args(args) : args
|
|
99
|
+
|
|
100
|
+
raise Error::WebDriverError, "invalid port: #{@port}" if @port < 1
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def start
|
|
104
|
+
raise "already started: #{uri.inspect} #{@executable_path.inspect}" if process_running?
|
|
105
|
+
|
|
106
|
+
Platform.exit_hook(&method(:stop)) # make sure we don't leave the server running
|
|
107
|
+
|
|
108
|
+
socket_lock.locked do
|
|
109
|
+
find_free_port
|
|
110
|
+
start_process
|
|
111
|
+
connect_until_stable
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def stop
|
|
116
|
+
return unless self.class.shutdown_supported
|
|
117
|
+
|
|
118
|
+
stop_server
|
|
119
|
+
@process.poll_for_exit STOP_TIMEOUT
|
|
120
|
+
rescue ChildProcess::TimeoutError
|
|
121
|
+
nil # noop
|
|
122
|
+
ensure
|
|
123
|
+
stop_process
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def uri
|
|
127
|
+
@uri ||= URI.parse("http://#{@host}:#{@port}")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
def binary_path(path = nil)
|
|
133
|
+
path = path.call if path.is_a?(Proc)
|
|
134
|
+
path ||= Platform.find_binary(self.class.executable)
|
|
135
|
+
|
|
136
|
+
raise Error::WebDriverError, self.class.missing_text unless path
|
|
137
|
+
|
|
138
|
+
Platform.assert_executable path
|
|
139
|
+
path
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def build_process(*command)
|
|
143
|
+
WebDriver.logger.debug("Executing Process #{command}")
|
|
144
|
+
@process = ChildProcess.build(*command)
|
|
145
|
+
if WebDriver.logger.debug?
|
|
146
|
+
@process.io.stdout = @process.io.stderr = WebDriver.logger.io
|
|
147
|
+
elsif Platform.jruby?
|
|
148
|
+
# Apparently we need to read the output of drivers on JRuby.
|
|
149
|
+
@process.io.stdout = @process.io.stderr = File.new(Platform.null_device, 'w')
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
@process
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def connect_to_server
|
|
156
|
+
Net::HTTP.start(@host, @port) do |http|
|
|
157
|
+
http.open_timeout = STOP_TIMEOUT / 2
|
|
158
|
+
http.read_timeout = STOP_TIMEOUT / 2
|
|
159
|
+
|
|
160
|
+
yield http
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def find_free_port
|
|
165
|
+
@port = PortProber.above(@port)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def start_process
|
|
169
|
+
@process = build_process(@executable_path, "--port=#{@port}", *@extra_args)
|
|
170
|
+
# Note: this is a bug only in Windows 7
|
|
171
|
+
@process.leader = true unless Platform.windows?
|
|
172
|
+
@process.start
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def stop_process
|
|
176
|
+
return if process_exited?
|
|
177
|
+
|
|
178
|
+
@process.stop STOP_TIMEOUT
|
|
179
|
+
@process.io.stdout.close if Platform.jruby? && !WebDriver.logger.debug?
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def stop_server
|
|
183
|
+
return if process_exited?
|
|
184
|
+
|
|
185
|
+
connect_to_server { |http| http.get('/shutdown') }
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def process_running?
|
|
189
|
+
defined?(@process) && @process&.alive?
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def process_exited?
|
|
193
|
+
@process.nil? || @process.exited?
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def connect_until_stable
|
|
197
|
+
socket_poller = SocketPoller.new @host, @port, START_TIMEOUT
|
|
198
|
+
return if socket_poller.connected?
|
|
199
|
+
|
|
200
|
+
raise Error::WebDriverError, cannot_connect_error_text
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def cannot_connect_error_text
|
|
204
|
+
"unable to connect to #{self.class.executable} #{@host}:#{@port}"
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def socket_lock
|
|
208
|
+
@socket_lock ||= SocketLock.new(@port - 1, SOCKET_LOCK_TIMEOUT)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
protected
|
|
212
|
+
|
|
213
|
+
def extract_service_args(driver_opts)
|
|
214
|
+
driver_opts.key?(:args) ? driver_opts.delete(:args) : []
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
end # Service
|
|
218
|
+
end # WebDriver
|
|
219
|
+
end # Selenium
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
3
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
|
4
4
|
# or more contributor license agreements. See the NOTICE file
|
|
5
5
|
# distributed with this work for additional information
|
|
@@ -19,13 +19,11 @@
|
|
|
19
19
|
|
|
20
20
|
module Selenium
|
|
21
21
|
module WebDriver
|
|
22
|
-
|
|
23
22
|
#
|
|
24
23
|
# @api private
|
|
25
24
|
#
|
|
26
25
|
|
|
27
26
|
class SocketLock
|
|
28
|
-
|
|
29
27
|
def initialize(port, timeout)
|
|
30
28
|
@port = port
|
|
31
29
|
@timeout = timeout
|
|
@@ -36,7 +34,7 @@ module Selenium
|
|
|
36
34
|
# execution block if the lock could be successfully obtained.
|
|
37
35
|
#
|
|
38
36
|
|
|
39
|
-
def locked
|
|
37
|
+
def locked
|
|
40
38
|
lock
|
|
41
39
|
|
|
42
40
|
begin
|
|
@@ -49,19 +47,21 @@ module Selenium
|
|
|
49
47
|
private
|
|
50
48
|
|
|
51
49
|
def lock
|
|
52
|
-
max_time =
|
|
50
|
+
max_time = current_time + @timeout
|
|
53
51
|
|
|
54
|
-
until can_lock? ||
|
|
55
|
-
sleep 0.1
|
|
56
|
-
end
|
|
52
|
+
sleep 0.1 until can_lock? || current_time >= max_time
|
|
57
53
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
return if did_lock?
|
|
55
|
+
|
|
56
|
+
raise Error::WebDriverError, "unable to bind to locking port #{@port} within #{@timeout} seconds"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def current_time
|
|
60
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
def release
|
|
64
|
-
@server
|
|
64
|
+
@server&.close
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
def can_lock?
|
|
@@ -70,14 +70,13 @@ module Selenium
|
|
|
70
70
|
|
|
71
71
|
true
|
|
72
72
|
rescue SocketError, Errno::EADDRINUSE, Errno::EBADF => ex
|
|
73
|
-
|
|
73
|
+
WebDriver.logger.debug("#{self}: #{ex.message}")
|
|
74
74
|
false
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
def did_lock?
|
|
78
|
-
|
|
78
|
+
!@server.nil?
|
|
79
79
|
end
|
|
80
|
-
|
|
81
80
|
end # SocketLock
|
|
82
81
|
end # WebDriver
|
|
83
82
|
end # Selenium
|