selenium-webdriver 4.19.0 → 4.20.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 +9 -0
- 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/webdriver/atoms/findElements.js +2 -2
- data/lib/selenium/webdriver/common/driver_finder.rb +53 -14
- data/lib/selenium/webdriver/common/local_driver.rb +8 -1
- data/lib/selenium/webdriver/common/platform.rb +3 -1
- data/lib/selenium/webdriver/common/selenium_manager.rb +36 -73
- data/lib/selenium/webdriver/common/service.rb +1 -1
- data/lib/selenium/webdriver/devtools/network_interceptor.rb +1 -1
- data/lib/selenium/webdriver/version.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 23bbac4b8a7b9441e660be148e5d20a1be253fd73126c186020ba35516f42d0a
|
|
4
|
+
data.tar.gz: d4e5933a2fa3d43a252fe044f7880ef02be3802d30a201c88a8bc1d030bb607f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9d036f7c45d23ab7a8bed8197e8c8bfa2ebcc3f6b375dbba2de0e980042f3f05245330d0a527e0d30ccb43277d35fa9a12f1fdc64344ef6e1569ef3c19402ae7
|
|
7
|
+
data.tar.gz: 807e4a2d6f4db71d7a7ba1332c882c712b1b1ffdc3e12b33922a26dae7ddcc849b9e1742eb261acd9717d5eba23173ccadfc184d0322efb08bbb3850f659e16a
|
data/CHANGES
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
4.20.0 (2024-04-24)
|
|
2
|
+
=========================
|
|
3
|
+
|
|
4
|
+
* Add CDP for Chrome 124 and remove 121
|
|
5
|
+
* Making Selenium Manager a thin wrapper (#13386)
|
|
6
|
+
* This change has been made to make it easier to maintain and improve, the interface has
|
|
7
|
+
changed and if users were invoking it, they might experience issues. Selenium Manager is
|
|
8
|
+
still in beta and these type of changes are expected.
|
|
9
|
+
|
|
1
10
|
4.19.0 (2024-03-27)
|
|
2
11
|
=========================
|
|
3
12
|
|
data/bin/linux/selenium-manager
CHANGED
|
Binary file
|
data/bin/macos/selenium-manager
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -105,8 +105,8 @@ function gd(a){var b=a.shape.toLowerCase();a=a.coords.split(",");if("rect"==b&&4
|
|
|
105
105
|
function hd(a){return a.replace(/^[^\S\xa0]+|[^\S\xa0]+$/g,"")}function id(a){var b=[];Yc?jd(a,b):kd(a,b);a=qa(b,hd);return hd(a.join("\n")).replace(/\xa0/g," ")}
|
|
106
106
|
function ld(a,b,c){if(S(a,"BR"))b.push("");else{var d=S(a,"TD"),e=U(a,"display"),f=!d&&!(0<=oa(md,e)),g=void 0!==a.previousElementSibling?a.previousElementSibling:gb(a.previousSibling);g=g?U(g,"display"):"";var h=U(a,"float")||U(a,"cssFloat")||U(a,"styleFloat");!f||"run-in"==g&&"none"==h||/^[\s\xa0]*$/.test(b[b.length-1]||"")||b.push("");var n=ed(a),u=null,p=null;n&&(u=U(a,"white-space"),p=U(a,"text-transform"));l(a.childNodes,function(G){c(G,b,n,u,p)});a=b[b.length-1]||"";!d&&"table-cell"!=e||!a||
|
|
107
107
|
za(a)||(b[b.length-1]+=" ");f&&"run-in"!=e&&!/^[\s\xa0]*$/.test(a)&&b.push("")}}function kd(a,b){ld(a,b,function(c,d,e,f,g){3==c.nodeType&&e?nd(c,d,f,g):S(c)&&kd(c,d)})}var md="inline inline-block inline-table none table-cell table-column table-column-group".split(" ");
|
|
108
|
-
function nd(a,b,c,d){a=a.nodeValue.replace(/[\u200b\u200e\u200f]/g,"");a=a.replace(/(\r\n|\r|\n)/g,"\n");if("normal"==c||"nowrap"==c)a=a.replace(/\n/g," ");a="pre"==c||"pre-wrap"==c?a.replace(/[ \f\t\v\u2028\u2029]/g,"\u00a0"):a.replace(/[ \f\t\v\u2028\u2029]+/g," ");"capitalize"==d?a=a.replace(t?/(^|\s|\b)(\S)/g:/(
|
|
109
|
-
|
|
108
|
+
function nd(a,b,c,d){a=a.nodeValue.replace(/[\u200b\u200e\u200f]/g,"");a=a.replace(/(\r\n|\r|\n)/g,"\n");if("normal"==c||"nowrap"==c)a=a.replace(/\n/g," ");a="pre"==c||"pre-wrap"==c?a.replace(/[ \f\t\v\u2028\u2029]/g,"\u00a0"):a.replace(/[ \f\t\v\u2028\u2029]+/g," ");"capitalize"==d?a=a.replace(t?/(^|\s|\b)(\S)/g:/(^|\s|\b)(\S)/gu,function(e,f,g){return f+g.toUpperCase()}):"uppercase"==d?a=a.toUpperCase():"lowercase"==d&&(a=a.toLowerCase());c=b.pop()||"";za(c)&&0==a.lastIndexOf(" ",0)&&(a=a.substr(1));
|
|
109
|
+
b.push(c+a)}function dd(a){if(Mc){if("relative"==U(a,"position"))return 1;a=U(a,"filter");return(a=a.match(/^alpha\(opacity=(\d*)\)/)||a.match(/^progid:DXImageTransform.Microsoft.Alpha\(Opacity=(\d*)\)/))?Number(a[1])/100:1}return od(a)}function od(a){var b=1,c=U(a,"opacity");c&&(b=Number(c));(a=Zc(a))&&(b*=od(a));return b}
|
|
110
110
|
function pd(a,b,c,d,e){if(3==a.nodeType&&c)nd(a,b,d,e);else if(S(a))if(S(a,"CONTENT")||S(a,"SLOT")){for(var f=a;f.parentNode;)f=f.parentNode;f instanceof ShadowRoot?(f=S(a,"CONTENT")?a.getDistributedNodes():a.assignedNodes(),l(0<f.length?f:a.childNodes,function(g){pd(g,b,c,d,e)})):jd(a,b)}else if(S(a,"SHADOW")){for(f=a;f.parentNode;)f=f.parentNode;if(f instanceof ShadowRoot&&(a=f))for(a=a.olderShadowRoot;a;)l(a.childNodes,function(g){pd(g,b,c,d,e)}),a=a.olderShadowRoot}else jd(a,b)}
|
|
111
111
|
function jd(a,b){a.shadowRoot&&l(a.shadowRoot.childNodes,function(c){pd(c,b,!0,null,null)});ld(a,b,function(c,d,e,f,g){var h=null;1==c.nodeType?h=c:3==c.nodeType&&(h=c);null!=h&&(null!=h.assignedSlot||h.getDestinationInsertionPoints&&0<h.getDestinationInsertionPoints().length)||pd(c,d,e,f,g)})};var qd={C:function(a,b){return!(!a.querySelectorAll||!a.querySelector)&&!/^\d.*/.test(b)},o:function(a,b){var c=eb(b),d="string"===typeof a?c.a.getElementById(a):a;return d?Uc(d,"id")==a&&b!=d&&hb(b,d)?d:ua(mb(c,"*"),function(e){return Uc(e,"id")==a&&b!=e&&hb(b,e)}):null},j:function(a,b){if(!a)return[];if(qd.C(b,a))try{return b.querySelectorAll("#"+qd.T(a))}catch(c){return[]}b=mb(eb(b),"*",null,b);return pa(b,function(c){return Uc(c,"id")==a})},T:function(a){return a.replace(/([\s'"\\#.:;,!?+<>=~*^$|%&@`{}\-\/\[\]\(\)])/g,
|
|
112
112
|
"\\$1")}};var X={},rd={};X.N=function(a,b,c){try{var d=Nc.j("a",b)}catch(e){d=mb(eb(b),"A",null,b)}return ua(d,function(e){e=id(e);e=e.replace(/^[\s]+|[\s]+$/g,"");return c&&-1!=e.indexOf(a)||e==a})};X.K=function(a,b,c){try{var d=Nc.j("a",b)}catch(e){d=mb(eb(b),"A",null,b)}return pa(d,function(e){e=id(e);e=e.replace(/^[\s]+|[\s]+$/g,"");return c&&-1!=e.indexOf(a)||e==a})};X.o=function(a,b){return X.N(a,b,!1)};X.j=function(a,b){return X.K(a,b,!1)};rd.o=function(a,b){return X.N(a,b,!0)};
|
|
@@ -20,25 +20,64 @@
|
|
|
20
20
|
module Selenium
|
|
21
21
|
module WebDriver
|
|
22
22
|
class DriverFinder
|
|
23
|
-
def
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
def initialize(options, service)
|
|
24
|
+
@options = options
|
|
25
|
+
@service = service
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def browser_path
|
|
29
|
+
paths[:browser_path]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def driver_path
|
|
33
|
+
paths[:driver_path]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def browser_path?
|
|
37
|
+
!browser_path.nil? && !browser_path.empty?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
26
41
|
|
|
27
|
-
|
|
28
|
-
|
|
42
|
+
def paths
|
|
43
|
+
@paths ||= begin
|
|
44
|
+
path = @service.class.driver_path
|
|
45
|
+
path = path.call if path.is_a?(Proc)
|
|
46
|
+
exe = @service.class::EXECUTABLE
|
|
47
|
+
if path
|
|
48
|
+
WebDriver.logger.debug("Skipping Selenium Manager; path to #{exe} specified in service class: #{path}")
|
|
49
|
+
Platform.assert_executable(path)
|
|
50
|
+
{driver_path: path}
|
|
51
|
+
else
|
|
52
|
+
output = SeleniumManager.binary_paths(*to_args)
|
|
53
|
+
formatted = {driver_path: Platform.cygwin_path(output['driver_path'], only_cygwin: true),
|
|
54
|
+
browser_path: Platform.cygwin_path(output['browser_path'], only_cygwin: true)}
|
|
55
|
+
Platform.assert_executable(formatted[:driver_path])
|
|
56
|
+
Platform.assert_executable(formatted[:browser_path])
|
|
57
|
+
formatted
|
|
58
|
+
end
|
|
29
59
|
rescue StandardError => e
|
|
30
|
-
|
|
60
|
+
WebDriver.logger.error("Exception occurred: #{e.message}")
|
|
61
|
+
WebDriver.logger.error("Backtrace:\n\t#{e.backtrace&.join("\n\t")}")
|
|
62
|
+
raise Error::NoSuchDriverError, "Unable to obtain #{exe}"
|
|
31
63
|
end
|
|
64
|
+
end
|
|
32
65
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
raise Error::NoSuchDriverError, "#{klass::EXECUTABLE} located, but: #{e.message}"
|
|
66
|
+
def to_args
|
|
67
|
+
args = ['--browser', @options.browser_name]
|
|
68
|
+
if @options.browser_version
|
|
69
|
+
args << '--browser-version'
|
|
70
|
+
args << @options.browser_version
|
|
39
71
|
end
|
|
40
|
-
|
|
41
|
-
|
|
72
|
+
if @options.respond_to?(:binary) && !@options.binary.nil?
|
|
73
|
+
args << '--browser-path'
|
|
74
|
+
args << @options.binary.gsub('\\', '\\\\\\')
|
|
75
|
+
end
|
|
76
|
+
if @options.proxy
|
|
77
|
+
args << '--proxy'
|
|
78
|
+
args << (@options.proxy.ssl || @options.proxy.http)
|
|
79
|
+
end
|
|
80
|
+
args
|
|
42
81
|
end
|
|
43
82
|
end
|
|
44
83
|
end
|
|
@@ -38,7 +38,14 @@ module Selenium
|
|
|
38
38
|
raise ArgumentError, ":options must be an instance of #{default_options.class}"
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
service.executable_path ||=
|
|
41
|
+
service.executable_path ||= begin
|
|
42
|
+
finder = WebDriver::DriverFinder.new(options, service)
|
|
43
|
+
if options.respond_to?(:binary) && finder.browser_path?
|
|
44
|
+
options.binary = finder.browser_path
|
|
45
|
+
options.browser_version = nil
|
|
46
|
+
end
|
|
47
|
+
finder.driver_path
|
|
48
|
+
end
|
|
42
49
|
options.as_json
|
|
43
50
|
end
|
|
44
51
|
end
|
|
@@ -111,7 +111,9 @@ module Selenium
|
|
|
111
111
|
windows? && !cygwin? ? %("#{str}") : str
|
|
112
112
|
end
|
|
113
113
|
|
|
114
|
-
def cygwin_path(path, **opts)
|
|
114
|
+
def cygwin_path(path, only_cygwin: false, **opts)
|
|
115
|
+
return path if only_cygwin && !cygwin?
|
|
116
|
+
|
|
115
117
|
flags = []
|
|
116
118
|
opts.each { |k, v| flags << "--#{k}" if v }
|
|
117
119
|
|
|
@@ -34,88 +34,33 @@ module Selenium
|
|
|
34
34
|
@bin_path ||= '../../../../../bin'
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
# @param [
|
|
38
|
-
# @return [
|
|
39
|
-
def
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
driver_path = Platform.cygwin? ? Platform.cygwin_path(output['driver_path']) : output['driver_path']
|
|
46
|
-
Platform.assert_executable driver_path
|
|
47
|
-
|
|
48
|
-
if options.respond_to?(:binary) && browser_path && !browser_path.empty?
|
|
49
|
-
options.binary = browser_path
|
|
50
|
-
options.browser_version = nil
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
driver_path
|
|
37
|
+
# @param [Array] arguments what gets sent to to Selenium Manager binary.
|
|
38
|
+
# @return [Hash] paths to the requested assets.
|
|
39
|
+
def binary_paths(*arguments)
|
|
40
|
+
arguments += %w[--language-binding ruby]
|
|
41
|
+
arguments += %w[--output json]
|
|
42
|
+
arguments << '--debug' if WebDriver.logger.debug?
|
|
43
|
+
|
|
44
|
+
run(binary, *arguments)
|
|
54
45
|
end
|
|
55
46
|
|
|
56
47
|
private
|
|
57
48
|
|
|
58
|
-
def generate_command(binary, options)
|
|
59
|
-
command = [binary, '--browser', options.browser_name]
|
|
60
|
-
if options.browser_version
|
|
61
|
-
command << '--browser-version'
|
|
62
|
-
command << options.browser_version
|
|
63
|
-
end
|
|
64
|
-
if options.respond_to?(:binary) && !options.binary.nil?
|
|
65
|
-
command << '--browser-path'
|
|
66
|
-
command << options.binary.squeeze('\\').gsub('\\', '\\\\\\')
|
|
67
|
-
end
|
|
68
|
-
if options.proxy
|
|
69
|
-
command << '--proxy'
|
|
70
|
-
command << (options.proxy.ssl || options.proxy.http)
|
|
71
|
-
end
|
|
72
|
-
command
|
|
73
|
-
end
|
|
74
|
-
|
|
75
49
|
# @return [String] the path to the correct selenium manager
|
|
76
50
|
def binary
|
|
77
51
|
@binary ||= begin
|
|
78
|
-
location = ENV.fetch('SE_MANAGER_PATH',
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
elsif Platform.mac?
|
|
83
|
-
"#{directory}/macos/selenium-manager"
|
|
84
|
-
elsif Platform.linux?
|
|
85
|
-
"#{directory}/linux/selenium-manager"
|
|
86
|
-
elsif Platform.unix?
|
|
87
|
-
WebDriver.logger.warn('Selenium Manager binary may not be compatible with Unix; verify settings',
|
|
88
|
-
id: %i[selenium_manager unix_binary])
|
|
89
|
-
"#{directory}/linux/selenium-manager"
|
|
90
|
-
end
|
|
91
|
-
rescue Error::WebDriverError => e
|
|
92
|
-
raise Error::WebDriverError, "Unable to obtain Selenium Manager binary for #{e.message}"
|
|
93
|
-
end)
|
|
52
|
+
if (location = ENV.fetch('SE_MANAGER_PATH', nil))
|
|
53
|
+
WebDriver.logger.debug("Selenium Manager set by ENV['SE_MANAGER_PATH']: #{location}")
|
|
54
|
+
end
|
|
55
|
+
location ||= platform_location
|
|
94
56
|
|
|
95
|
-
validate_location(location)
|
|
96
|
-
location
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def validate_location(location)
|
|
101
|
-
begin
|
|
102
|
-
Platform.assert_file(location)
|
|
103
57
|
Platform.assert_executable(location)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
"Unable to locate or obtain Selenium Manager binary; #{location} is not a valid file object"
|
|
107
|
-
rescue Error::WebDriverError => e
|
|
108
|
-
raise Error::WebDriverError, "Selenium Manager binary located, but #{e.message}"
|
|
58
|
+
WebDriver.logger.debug("Selenium Manager binary found at #{location}", id: :selenium_manager)
|
|
59
|
+
location
|
|
109
60
|
end
|
|
110
|
-
|
|
111
|
-
WebDriver.logger.debug("Selenium Manager binary found at #{location}", id: :selenium_manager)
|
|
112
61
|
end
|
|
113
62
|
|
|
114
63
|
def run(*command)
|
|
115
|
-
command += %w[--language-binding ruby]
|
|
116
|
-
command += %w[--output json]
|
|
117
|
-
command << '--debug' if WebDriver.logger.debug?
|
|
118
|
-
|
|
119
64
|
WebDriver.logger.debug("Executing Process #{command}", id: :selenium_manager)
|
|
120
65
|
|
|
121
66
|
begin
|
|
@@ -124,16 +69,34 @@ module Selenium
|
|
|
124
69
|
raise Error::WebDriverError, "Unsuccessful command executed: #{command}; #{e.message}"
|
|
125
70
|
end
|
|
126
71
|
|
|
127
|
-
json_output = stdout.empty? ? {} : JSON.parse(stdout)
|
|
128
|
-
|
|
72
|
+
json_output = stdout.empty? ? {'logs' => [], 'result' => {}} : JSON.parse(stdout)
|
|
73
|
+
json_output['logs'].each do |log|
|
|
129
74
|
level = log['level'].casecmp('info').zero? ? 'debug' : log['level'].downcase
|
|
130
75
|
WebDriver.logger.send(level, log['message'], id: :selenium_manager)
|
|
131
76
|
end
|
|
132
77
|
|
|
133
78
|
result = json_output['result']
|
|
134
|
-
return result unless status.exitstatus.positive?
|
|
79
|
+
return result unless status.exitstatus.positive? || result.nil?
|
|
135
80
|
|
|
136
|
-
raise Error::WebDriverError,
|
|
81
|
+
raise Error::WebDriverError,
|
|
82
|
+
"Unsuccessful command executed: #{command} - Code #{status.exitstatus}\n#{result}\n#{stderr}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def platform_location
|
|
86
|
+
directory = File.expand_path(bin_path, __FILE__)
|
|
87
|
+
if Platform.windows?
|
|
88
|
+
"#{directory}/windows/selenium-manager.exe"
|
|
89
|
+
elsif Platform.mac?
|
|
90
|
+
"#{directory}/macos/selenium-manager"
|
|
91
|
+
elsif Platform.linux?
|
|
92
|
+
"#{directory}/linux/selenium-manager"
|
|
93
|
+
elsif Platform.unix?
|
|
94
|
+
WebDriver.logger.warn('Selenium Manager binary may not be compatible with Unix',
|
|
95
|
+
id: %i[selenium_manager unix_binary])
|
|
96
|
+
"#{directory}/linux/selenium-manager"
|
|
97
|
+
else
|
|
98
|
+
raise Error::WebDriverError, "unsupported platform: #{Platform.os}"
|
|
99
|
+
end
|
|
137
100
|
end
|
|
138
101
|
end
|
|
139
102
|
end # SeleniumManager
|
|
@@ -89,7 +89,7 @@ module Selenium
|
|
|
89
89
|
def launch
|
|
90
90
|
@executable_path ||= begin
|
|
91
91
|
default_options = WebDriver.const_get("#{self.class.name&.split('::')&.[](2)}::Options").new
|
|
92
|
-
DriverFinder.
|
|
92
|
+
DriverFinder.new(default_options, self).driver_path
|
|
93
93
|
end
|
|
94
94
|
ServiceManager.new(self).tap(&:start)
|
|
95
95
|
end
|
|
@@ -98,7 +98,7 @@ module Selenium
|
|
|
98
98
|
original = DevTools::Request.from(id, params)
|
|
99
99
|
mutable = DevTools::Request.from(id, params)
|
|
100
100
|
|
|
101
|
-
block.call(mutable) do |&continue|
|
|
101
|
+
block.call(mutable) do |&continue|
|
|
102
102
|
pending_response_requests[id] = continue
|
|
103
103
|
|
|
104
104
|
if original == mutable
|
metadata
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: selenium-webdriver
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.20.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex Rodionov
|
|
8
8
|
- Titus Fortner
|
|
9
9
|
- Thomas Walpole
|
|
10
|
-
autorequire:
|
|
10
|
+
autorequire:
|
|
11
11
|
bindir: bin
|
|
12
12
|
cert_chain: []
|
|
13
|
-
date: 2024-
|
|
13
|
+
date: 2024-04-24 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: base64
|
|
@@ -429,7 +429,7 @@ metadata:
|
|
|
429
429
|
github_repo: ssh://github.com/SeleniumHQ/selenium
|
|
430
430
|
source_code_uri: https://github.com/SeleniumHQ/selenium/tree/trunk/rb
|
|
431
431
|
rubygems_mfa_required: 'true'
|
|
432
|
-
post_install_message:
|
|
432
|
+
post_install_message:
|
|
433
433
|
rdoc_options: []
|
|
434
434
|
require_paths:
|
|
435
435
|
- lib
|
|
@@ -445,7 +445,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
445
445
|
version: 1.3.1
|
|
446
446
|
requirements: []
|
|
447
447
|
rubygems_version: 3.2.33
|
|
448
|
-
signing_key:
|
|
448
|
+
signing_key:
|
|
449
449
|
specification_version: 4
|
|
450
450
|
summary: Selenium is a browser automation tool for automated testing of webapps and
|
|
451
451
|
more
|