selenium-webdriver 3.141.0 → 3.142.3

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 (128) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +125 -0
  3. data/Gemfile +2 -0
  4. data/LICENSE +1 -1
  5. data/lib/selenium-webdriver.rb +2 -0
  6. data/lib/selenium/server.rb +9 -7
  7. data/lib/selenium/webdriver.rb +3 -1
  8. data/lib/selenium/webdriver/atoms.rb +20 -1
  9. data/lib/selenium/webdriver/atoms/getAttribute.js +6 -7
  10. data/lib/selenium/webdriver/atoms/isDisplayed.js +60 -59
  11. data/lib/selenium/webdriver/chrome.rb +10 -4
  12. data/lib/selenium/webdriver/chrome/bridge.rb +5 -3
  13. data/lib/selenium/webdriver/chrome/driver.rb +10 -13
  14. data/lib/selenium/webdriver/chrome/options.rb +4 -4
  15. data/lib/selenium/webdriver/chrome/profile.rb +4 -3
  16. data/lib/selenium/webdriver/chrome/service.rb +13 -13
  17. data/lib/selenium/webdriver/common.rb +4 -2
  18. data/lib/selenium/webdriver/common/action_builder.rb +2 -0
  19. data/lib/selenium/webdriver/common/alert.rb +2 -0
  20. data/lib/selenium/webdriver/common/bridge_helper.rb +8 -5
  21. data/lib/selenium/webdriver/common/driver.rb +22 -7
  22. data/lib/selenium/webdriver/common/driver_extensions/downloads_files.rb +2 -0
  23. data/lib/selenium/webdriver/common/driver_extensions/has_addons.rb +2 -0
  24. data/lib/selenium/webdriver/common/driver_extensions/has_debugger.rb +2 -0
  25. data/lib/selenium/webdriver/common/driver_extensions/has_location.rb +3 -3
  26. data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +2 -0
  27. data/lib/selenium/webdriver/common/driver_extensions/has_network_connection.rb +3 -1
  28. data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +2 -0
  29. data/lib/selenium/webdriver/common/driver_extensions/has_remote_status.rb +2 -0
  30. data/lib/selenium/webdriver/common/driver_extensions/has_session_id.rb +2 -0
  31. data/lib/selenium/webdriver/common/driver_extensions/has_touch_screen.rb +2 -0
  32. data/lib/selenium/webdriver/common/driver_extensions/has_web_storage.rb +2 -0
  33. data/lib/selenium/webdriver/common/driver_extensions/rotatable.rb +3 -1
  34. data/lib/selenium/webdriver/common/driver_extensions/takes_screenshot.rb +2 -0
  35. data/lib/selenium/webdriver/common/driver_extensions/uploads_files.rb +3 -3
  36. data/lib/selenium/webdriver/common/element.rb +3 -1
  37. data/lib/selenium/webdriver/common/error.rb +74 -18
  38. data/lib/selenium/webdriver/common/file_reaper.rb +3 -3
  39. data/lib/selenium/webdriver/common/html5/local_storage.rb +2 -0
  40. data/lib/selenium/webdriver/common/html5/session_storage.rb +2 -0
  41. data/lib/selenium/webdriver/common/html5/shared_web_storage.rb +4 -1
  42. data/lib/selenium/webdriver/common/interactions/input_device.rb +3 -0
  43. data/lib/selenium/webdriver/common/interactions/interaction.rb +3 -0
  44. data/lib/selenium/webdriver/common/interactions/interactions.rb +3 -1
  45. data/lib/selenium/webdriver/common/interactions/key_actions.rb +2 -0
  46. data/lib/selenium/webdriver/common/interactions/key_input.rb +4 -0
  47. data/lib/selenium/webdriver/common/interactions/none_input.rb +3 -0
  48. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +2 -0
  49. data/lib/selenium/webdriver/common/interactions/pointer_input.rb +7 -0
  50. data/lib/selenium/webdriver/common/keyboard.rb +4 -1
  51. data/lib/selenium/webdriver/common/keys.rb +3 -0
  52. data/lib/selenium/webdriver/common/log_entry.rb +4 -2
  53. data/lib/selenium/webdriver/common/logger.rb +15 -40
  54. data/lib/selenium/webdriver/common/logs.rb +2 -0
  55. data/lib/selenium/webdriver/common/{options.rb → manager.rb} +27 -1
  56. data/lib/selenium/webdriver/common/mouse.rb +3 -0
  57. data/lib/selenium/webdriver/common/navigation.rb +2 -0
  58. data/lib/selenium/webdriver/common/platform.rb +26 -30
  59. data/lib/selenium/webdriver/common/port_prober.rb +6 -19
  60. data/lib/selenium/webdriver/common/profile_helper.rb +2 -0
  61. data/lib/selenium/webdriver/common/proxy.rb +13 -5
  62. data/lib/selenium/webdriver/common/search_context.rb +6 -8
  63. data/lib/selenium/webdriver/common/service.rb +87 -29
  64. data/lib/selenium/webdriver/common/socket_lock.rb +10 -3
  65. data/lib/selenium/webdriver/common/socket_poller.rb +26 -18
  66. data/lib/selenium/webdriver/common/target_locator.rb +6 -4
  67. data/lib/selenium/webdriver/common/timeouts.rb +2 -0
  68. data/lib/selenium/webdriver/common/touch_action_builder.rb +5 -6
  69. data/lib/selenium/webdriver/common/touch_screen.rb +4 -1
  70. data/lib/selenium/webdriver/common/w3c_action_builder.rb +3 -0
  71. data/lib/selenium/webdriver/common/{w3c_options.rb → w3c_manager.rb} +3 -1
  72. data/lib/selenium/webdriver/common/wait.rb +13 -5
  73. data/lib/selenium/webdriver/common/window.rb +2 -0
  74. data/lib/selenium/webdriver/common/zipper.rb +3 -3
  75. data/lib/selenium/webdriver/edge.rb +11 -5
  76. data/lib/selenium/webdriver/edge/bridge.rb +2 -0
  77. data/lib/selenium/webdriver/edge/driver.rb +5 -12
  78. data/lib/selenium/webdriver/edge/options.rb +3 -0
  79. data/lib/selenium/webdriver/edge/service.rb +8 -12
  80. data/lib/selenium/webdriver/firefox.rb +10 -4
  81. data/lib/selenium/webdriver/firefox/binary.rb +7 -6
  82. data/lib/selenium/webdriver/firefox/bridge.rb +47 -0
  83. data/lib/selenium/webdriver/firefox/driver.rb +2 -0
  84. data/lib/selenium/webdriver/firefox/extension.rb +4 -4
  85. data/lib/selenium/webdriver/firefox/launcher.rb +3 -0
  86. data/lib/selenium/webdriver/firefox/legacy/driver.rb +7 -3
  87. data/lib/selenium/webdriver/firefox/marionette/bridge.rb +4 -2
  88. data/lib/selenium/webdriver/firefox/marionette/driver.rb +5 -11
  89. data/lib/selenium/webdriver/firefox/options.rb +20 -7
  90. data/lib/selenium/webdriver/firefox/profile.rb +7 -8
  91. data/lib/selenium/webdriver/firefox/profiles_ini.rb +3 -0
  92. data/lib/selenium/webdriver/firefox/service.rb +8 -19
  93. data/lib/selenium/webdriver/firefox/util.rb +2 -0
  94. data/lib/selenium/webdriver/ie.rb +10 -4
  95. data/lib/selenium/webdriver/ie/driver.rb +4 -10
  96. data/lib/selenium/webdriver/ie/options.rb +4 -2
  97. data/lib/selenium/webdriver/ie/service.rb +8 -12
  98. data/lib/selenium/webdriver/remote.rb +2 -0
  99. data/lib/selenium/webdriver/remote/bridge.rb +6 -4
  100. data/lib/selenium/webdriver/remote/capabilities.rb +23 -10
  101. data/lib/selenium/webdriver/remote/commands.rb +156 -0
  102. data/lib/selenium/webdriver/remote/driver.rb +2 -0
  103. data/lib/selenium/webdriver/remote/http/common.rb +11 -4
  104. data/lib/selenium/webdriver/remote/http/curb.rb +4 -2
  105. data/lib/selenium/webdriver/remote/http/default.rb +31 -25
  106. data/lib/selenium/webdriver/remote/http/persistent.rb +3 -1
  107. data/lib/selenium/webdriver/remote/oss/bridge.rb +5 -2
  108. data/lib/selenium/webdriver/remote/oss/commands.rb +106 -104
  109. data/lib/selenium/webdriver/remote/response.rb +11 -3
  110. data/lib/selenium/webdriver/remote/server_error.rb +2 -0
  111. data/lib/selenium/webdriver/remote/w3c/bridge.rb +28 -13
  112. data/lib/selenium/webdriver/remote/w3c/capabilities.rb +37 -21
  113. data/lib/selenium/webdriver/remote/w3c/commands.rb +61 -58
  114. data/lib/selenium/webdriver/safari.rb +11 -4
  115. data/lib/selenium/webdriver/safari/bridge.rb +5 -3
  116. data/lib/selenium/webdriver/safari/driver.rb +8 -10
  117. data/lib/selenium/webdriver/safari/options.rb +2 -0
  118. data/lib/selenium/webdriver/safari/service.rb +6 -25
  119. data/lib/selenium/webdriver/support.rb +2 -0
  120. data/lib/selenium/webdriver/support/abstract_event_listener.rb +2 -0
  121. data/lib/selenium/webdriver/support/block_event_listener.rb +3 -1
  122. data/lib/selenium/webdriver/support/color.rb +11 -9
  123. data/lib/selenium/webdriver/support/escaper.rb +2 -0
  124. data/lib/selenium/webdriver/support/event_firing_bridge.rb +3 -1
  125. data/lib/selenium/webdriver/support/select.rb +19 -18
  126. data/lib/selenium/webdriver/version.rb +3 -1
  127. data/selenium-webdriver.gemspec +14 -7
  128. metadata +89 -23
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Licensed to the Software Freedom Conservancy (SFC) under one
2
4
  # or more contributor license agreements. See the NOTICE file
3
5
  # distributed with this work for additional information
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Licensed to the Software Freedom Conservancy (SFC) under one
2
4
  # or more contributor license agreements. See the NOTICE file
3
5
  # distributed with this work for additional information
@@ -17,7 +19,7 @@
17
19
 
18
20
  module Selenium
19
21
  module WebDriver
20
- class Options
22
+ class Manager
21
23
  #
22
24
  # @api private
23
25
  #
@@ -103,6 +105,30 @@ module Selenium
103
105
  @logs ||= Logs.new(@bridge)
104
106
  end
105
107
 
108
+ #
109
+ # Create a new top-level browsing context
110
+ # https://w3c.github.io/webdriver/#new-window
111
+ # @param type [Symbol] Supports two values: :tab and :window.
112
+ # Use :tab if you'd like the new window to share an OS-level window
113
+ # with the current browsing context.
114
+ # Use :window otherwise
115
+ # @return [String] The value of the window handle
116
+ #
117
+ def new_window(type = :tab)
118
+ case type
119
+ when :tab, :window
120
+ result = @bridge.new_window(type)
121
+ unless result.key?('handle')
122
+ raise UnknownError, "the driver did not return a handle. " \
123
+ "The returned result: #{result.inspect}"
124
+ end
125
+ result['handle']
126
+ else
127
+ raise ArgumentError, "invalid argument for type. Got: '#{type.inspect}'. " \
128
+ "Try :tab or :window"
129
+ end
130
+ end
131
+
106
132
  #
107
133
  # @api beta This API may be changed or removed in a future release.
108
134
  #
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Licensed to the Software Freedom Conservancy (SFC) under one
2
4
  # or more contributor license agreements. See the NOTICE file
3
5
  # distributed with this work for additional information
@@ -79,6 +81,7 @@ module Selenium
79
81
 
80
82
  def assert_element(element)
81
83
  return if element.is_a? Element
84
+
82
85
  raise TypeError, "expected #{Element}, got #{element.inspect}:#{element.class}"
83
86
  end
84
87
  end # Mouse
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Licensed to the Software Freedom Conservancy (SFC) under one
2
4
  # or more contributor license agreements. See the NOTICE file
3
5
  # distributed with this work for additional information
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Licensed to the Software Freedom Conservancy (SFC) under one
2
4
  # or more contributor license agreements. See the NOTICE file
3
5
  # distributed with this work for additional information
@@ -25,12 +27,11 @@ module Selenium
25
27
  module_function
26
28
 
27
29
  def home
28
- # jruby has an issue with ENV['HOME'] on Windows
29
- @home ||= jruby? ? ENV_JAVA['user.home'] : ENV['HOME']
30
+ @home ||= Dir.home
30
31
  end
31
32
 
32
33
  def engine
33
- @engine ||= defined?(RUBY_ENGINE) ? RUBY_ENGINE.to_sym : :ruby
34
+ @engine ||= RUBY_ENGINE.to_sym
34
35
  end
35
36
 
36
37
  def os
@@ -50,8 +51,13 @@ module Selenium
50
51
  end
51
52
 
52
53
  def ci
53
- return :travis if ENV['TRAVIS']
54
- :jenkins if ENV['JENKINS']
54
+ if ENV['TRAVIS']
55
+ :travis
56
+ elsif ENV['JENKINS']
57
+ :jenkins
58
+ elsif ENV['APPVEYOR']
59
+ :appveyor
60
+ end
55
61
  end
56
62
 
57
63
  def bitsize
@@ -70,10 +76,6 @@ module Selenium
70
76
  engine == :jruby
71
77
  end
72
78
 
73
- def ironruby?
74
- engine == :ironruby
75
- end
76
-
77
79
  def ruby_version
78
80
  RUBY_VERSION
79
81
  end
@@ -92,6 +94,7 @@ module Selenium
92
94
 
93
95
  def wsl?
94
96
  return false unless linux?
97
+
95
98
  File.read('/proc/version').include?('Microsoft')
96
99
  end
97
100
 
@@ -101,30 +104,35 @@ module Selenium
101
104
  end
102
105
 
103
106
  def null_device
104
- @null_device ||= if defined?(File::NULL)
105
- File::NULL
106
- else
107
- Platform.windows? ? 'NUL' : '/dev/null'
108
- end
107
+ File::NULL
109
108
  end
110
109
 
111
110
  def wrap_in_quotes_if_necessary(str)
112
111
  windows? && !cygwin? ? %("#{str}") : str
113
112
  end
114
113
 
115
- def cygwin_path(path, opts = {})
114
+ def cygwin_path(path, **opts)
116
115
  flags = []
117
116
  opts.each { |k, v| flags << "--#{k}" if v }
118
117
 
119
118
  `cygpath #{flags.join ' '} "#{path}"`.strip
120
119
  end
121
120
 
121
+ def unix_path(path)
122
+ path.tr(File::ALT_SEPARATOR, File::SEPARATOR)
123
+ end
124
+
125
+ def windows_path(path)
126
+ path.tr(File::SEPARATOR, File::ALT_SEPARATOR)
127
+ end
128
+
122
129
  def make_writable(file)
123
130
  File.chmod 0o766, file
124
131
  end
125
132
 
126
133
  def assert_file(path)
127
134
  return if File.file? path
135
+
128
136
  raise Error::WebDriverError, "not a file: #{path.inspect}"
129
137
  end
130
138
 
@@ -132,6 +140,7 @@ module Selenium
132
140
  assert_file(path)
133
141
 
134
142
  return if File.executable? path
143
+
135
144
  raise Error::WebDriverError, "not executable: #{path.inspect}"
136
145
  end
137
146
 
@@ -152,7 +161,7 @@ module Selenium
152
161
  binary_names.each do |binary_name|
153
162
  paths.each do |path|
154
163
  full_path = File.join(path, binary_name)
155
- full_path.tr!('\\', '/') if windows?
164
+ full_path = unix_path(full_path) if windows?
156
165
  exe = Dir.glob(full_path).find { |f| File.executable?(f) }
157
166
  return exe if exe
158
167
  end
@@ -182,6 +191,7 @@ module Selenium
182
191
  info = Socket.getaddrinfo 'localhost', 80, Socket::AF_INET, Socket::SOCK_STREAM
183
192
 
184
193
  return info[0][3] unless info.empty?
194
+
185
195
  raise Error::WebDriverError, "unable to translate 'localhost' for TCP + IPv4"
186
196
  end
187
197
 
@@ -210,17 +220,3 @@ module Selenium
210
220
  end # Platform
211
221
  end # WebDriver
212
222
  end # Selenium
213
-
214
- if $PROGRAM_NAME == __FILE__
215
- p engine: Selenium::WebDriver::Platform.engine,
216
- os: Selenium::WebDriver::Platform.os,
217
- ruby_version: Selenium::WebDriver::Platform.ruby_version,
218
- jruby?: Selenium::WebDriver::Platform.jruby?,
219
- windows?: Selenium::WebDriver::Platform.windows?,
220
- home: Selenium::WebDriver::Platform.home,
221
- bitsize: Selenium::WebDriver::Platform.bitsize,
222
- localhost: Selenium::WebDriver::Platform.localhost,
223
- ip: Selenium::WebDriver::Platform.ip,
224
- interfaces: Selenium::WebDriver::Platform.interfaces,
225
- null_device: Selenium::WebDriver::Platform.null_device
226
- end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Licensed to the Software Freedom Conservancy (SFC) under one
2
4
  # or more contributor license agreements. See the NOTICE file
3
5
  # distributed with this work for additional information
@@ -23,25 +25,10 @@ module Selenium
23
25
  port
24
26
  end
25
27
 
26
- def self.random
27
- # TODO: Avoid this
28
- #
29
- # (a) should pick a port that's guaranteed to be free on all interfaces
30
- # (b) should pick a random port outside the ephemeral port range
31
- #
32
- WebDriver.logger.deprecate 'PortProber.random', 'PortProber.above(port)'
33
-
34
- server = TCPServer.new(Platform.localhost, 0)
35
- port = server.addr[1]
36
- server.close
37
-
38
- port
39
- end
40
-
41
- IGNORED_ERRORS = [Errno::EADDRNOTAVAIL, Errno::EAFNOSUPPORT]
42
- IGNORED_ERRORS << Errno::EBADF if Platform.cygwin?
43
- IGNORED_ERRORS << Errno::EACCES if Platform.windows?
44
- IGNORED_ERRORS.freeze
28
+ IGNORED_ERRORS = [Errno::EADDRNOTAVAIL, Errno::EAFNOSUPPORT].tap { |arr|
29
+ arr << Errno::EBADF if Platform.cygwin?
30
+ arr << Errno::EACCES if Platform.windows?
31
+ }.freeze
45
32
 
46
33
  def self.free?(port)
47
34
  Platform.interfaces.each do |host|
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Licensed to the Software Freedom Conservancy (SFC) under one
2
4
  # or more contributor license agreements. See the NOTICE file
3
5
  # distributed with this work for additional information
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Licensed to the Software Freedom Conservancy (SFC) under one
2
4
  # or more contributor license agreements. See the NOTICE file
3
5
  # distributed with this work for additional information
@@ -35,7 +37,8 @@ module Selenium
35
37
  auto_detect: 'autodetect',
36
38
  socks: 'socksProxy',
37
39
  socks_username: 'socksUsername',
38
- socks_password: 'socksPassword'}.freeze
40
+ socks_password: 'socksPassword',
41
+ socks_version: 'socksVersion'}.freeze
39
42
 
40
43
  ALLOWED.each_key { |t| attr_reader t }
41
44
 
@@ -64,6 +67,7 @@ module Selenium
64
67
  end
65
68
 
66
69
  return if not_allowed.empty?
70
+
67
71
  raise ArgumentError, "unknown option#{'s' if not_allowed.size != 1}: #{not_allowed.inspect}"
68
72
  end
69
73
 
@@ -117,10 +121,13 @@ module Selenium
117
121
  @socks_password = value
118
122
  end
119
123
 
124
+ def socks_version=(value)
125
+ self.type = :manual
126
+ @socks_version = value
127
+ end
128
+
120
129
  def type=(type)
121
- unless TYPES.key? type
122
- raise ArgumentError, "invalid proxy type: #{type.inspect}, expected one of #{TYPES.keys.inspect}"
123
- end
130
+ raise ArgumentError, "invalid proxy type: #{type.inspect}, expected one of #{TYPES.keys.inspect}" unless TYPES.key? type
124
131
 
125
132
  if defined?(@type) && type != @type
126
133
  raise ArgumentError, "incompatible proxy type #{type.inspect} (already set to #{@type.inspect})"
@@ -140,7 +147,8 @@ module Selenium
140
147
  'autodetect' => auto_detect,
141
148
  'socksProxy' => socks,
142
149
  'socksUsername' => socks_username,
143
- 'socksPassword' => socks_password
150
+ 'socksPassword' => socks_password,
151
+ 'socksVersion' => socks_version
144
152
  }.delete_if { |_k, v| v.nil? }
145
153
 
146
154
  json_result if json_result.length > 1
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Licensed to the Software Freedom Conservancy (SFC) under one
2
4
  # or more contributor license agreements. See the NOTICE file
3
5
  # distributed with this work for additional information
@@ -58,7 +60,7 @@ module Selenium
58
60
  raise ArgumentError, "cannot find element by #{how.inspect}" unless by
59
61
 
60
62
  bridge.find_element_by by, what.to_s, ref
61
- rescue Selenium::WebDriver::Error::TimeOutError
63
+ rescue Selenium::WebDriver::Error::TimeoutError
62
64
  # Implicit Wait times out in Edge
63
65
  raise Selenium::WebDriver::Error::NoSuchElementError
64
66
  end
@@ -76,7 +78,7 @@ module Selenium
76
78
  raise ArgumentError, "cannot find elements by #{how.inspect}" unless by
77
79
 
78
80
  bridge.find_elements_by by, what.to_s, ref
79
- rescue Selenium::WebDriver::Error::TimeOutError
81
+ rescue Selenium::WebDriver::Error::TimeoutError
80
82
  # Implicit Wait times out in Edge
81
83
  []
82
84
  end
@@ -90,15 +92,11 @@ module Selenium
90
92
  when 1
91
93
  arg = args.first
92
94
 
93
- unless arg.respond_to?(:shift)
94
- raise ArgumentError, "expected #{arg.inspect}:#{arg.class} to respond to #shift"
95
- end
95
+ raise ArgumentError, "expected #{arg.inspect}:#{arg.class} to respond to #shift" unless arg.respond_to?(:shift)
96
96
 
97
97
  # this will be a single-entry hash, so use #shift over #first or #[]
98
98
  arr = arg.dup.shift
99
- unless arr.size == 2
100
- raise ArgumentError, "expected #{arr.inspect} to have 2 elements"
101
- end
99
+ raise ArgumentError, "expected #{arr.inspect} to have 2 elements" unless arr.size == 2
102
100
 
103
101
  arr
104
102
  else
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Licensed to the Software Freedom Conservancy (SFC) under one
2
4
  # or more contributor license agreements. See the NOTICE file
3
5
  # distributed with this work for additional information
@@ -21,50 +23,87 @@ module Selenium
21
23
  # Base class implementing default behavior of service object,
22
24
  # responsible for starting and stopping driver implementations.
23
25
  #
24
- # Subclasses must implement the following private methods:
25
- # * #start_process
26
- # * #stop_server
27
- # * #cannot_connect_error_text
28
- #
29
- # @api private
30
- #
31
26
 
32
27
  class Service
33
- START_TIMEOUT = 20
28
+ START_TIMEOUT = 20
34
29
  SOCKET_LOCK_TIMEOUT = 45
35
- STOP_TIMEOUT = 20
30
+ STOP_TIMEOUT = 20
36
31
 
32
+ @default_port = nil
33
+ @driver_path = nil
37
34
  @executable = nil
38
35
  @missing_text = nil
39
36
 
40
37
  class << self
41
- attr_reader :executable, :missing_text
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
42
78
  end
43
79
 
44
80
  attr_accessor :host
81
+ attr_reader :executable_path
45
82
 
46
- def initialize(executable_path, port, driver_opts)
47
- @executable_path = binary_path(executable_path)
48
- @host = Platform.localhost
49
- @port = Integer(port)
50
- @extra_args = extract_service_args(driver_opts)
83
+ #
84
+ # End users should use a class method for the desired driver, rather than using this directly.
85
+ #
86
+ # @api private
87
+ #
51
88
 
52
- raise Error::WebDriverError, "invalid port: #{@port}" if @port < 1
53
- end
89
+ def initialize(path: nil, port: nil, args: nil)
90
+ path ||= self.class.driver_path
91
+ port ||= self.class.default_port
92
+ args ||= []
54
93
 
55
- def binary_path(path)
56
- path = Platform.find_binary(self.class.executable) if path.nil?
57
- raise Error::WebDriverError, self.class.missing_text unless path
58
- Platform.assert_executable path
59
- path
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
60
101
  end
61
102
 
62
103
  def start
63
- if process_running?
64
- raise "already started: #{uri.inspect} #{@executable_path.inspect}"
65
- end
104
+ raise "already started: #{uri.inspect} #{@executable_path.inspect}" if process_running?
66
105
 
67
- Platform.exit_hook { stop } # make sure we don't leave the server running
106
+ Platform.exit_hook(&method(:stop)) # make sure we don't leave the server running
68
107
 
69
108
  socket_lock.locked do
70
109
  find_free_port
@@ -74,9 +113,12 @@ module Selenium
74
113
  end
75
114
 
76
115
  def stop
116
+ return unless self.class.shutdown_supported
117
+
77
118
  stop_server
78
119
  @process.poll_for_exit STOP_TIMEOUT
79
120
  rescue ChildProcess::TimeoutError
121
+ nil # noop
80
122
  ensure
81
123
  stop_process
82
124
  end
@@ -87,6 +129,16 @@ module Selenium
87
129
 
88
130
  private
89
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
+
90
142
  def build_process(*command)
91
143
  WebDriver.logger.debug("Executing Process #{command}")
92
144
  @process = ChildProcess.build(*command)
@@ -114,22 +166,27 @@ module Selenium
114
166
  end
115
167
 
116
168
  def start_process
117
- raise NotImplementedError, 'subclass responsibility'
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
118
173
  end
119
174
 
120
175
  def stop_process
121
176
  return if process_exited?
177
+
122
178
  @process.stop STOP_TIMEOUT
123
179
  @process.io.stdout.close if Platform.jruby? && !WebDriver.logger.debug?
124
180
  end
125
181
 
126
182
  def stop_server
127
183
  return if process_exited?
184
+
128
185
  connect_to_server { |http| http.get('/shutdown') }
129
186
  end
130
187
 
131
188
  def process_running?
132
- defined?(@process) && @process && @process.alive?
189
+ defined?(@process) && @process&.alive?
133
190
  end
134
191
 
135
192
  def process_exited?
@@ -139,11 +196,12 @@ module Selenium
139
196
  def connect_until_stable
140
197
  socket_poller = SocketPoller.new @host, @port, START_TIMEOUT
141
198
  return if socket_poller.connected?
199
+
142
200
  raise Error::WebDriverError, cannot_connect_error_text
143
201
  end
144
202
 
145
203
  def cannot_connect_error_text
146
- raise NotImplementedError, 'subclass responsibility'
204
+ "unable to connect to #{self.class.executable} #{@host}:#{@port}"
147
205
  end
148
206
 
149
207
  def socket_lock