selenium-webdriver 3.142.7 → 4.0.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 (137) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGES +350 -5
  3. data/Gemfile +3 -1
  4. data/LICENSE +1 -1
  5. data/NOTICE +2 -0
  6. data/README.md +4 -5
  7. data/lib/selenium/server.rb +69 -63
  8. data/lib/selenium/webdriver/atoms/findElements.js +122 -0
  9. data/lib/selenium/webdriver/atoms/getAttribute.js +100 -7
  10. data/lib/selenium/webdriver/atoms/isDisplayed.js +76 -78
  11. data/lib/selenium/webdriver/atoms/mutationListener.js +55 -0
  12. data/lib/selenium/webdriver/chrome/driver.rb +26 -83
  13. data/lib/selenium/webdriver/chrome/{bridge.rb → features.rb} +50 -12
  14. data/lib/selenium/webdriver/chrome/options.rb +129 -58
  15. data/lib/selenium/webdriver/chrome/profile.rb +6 -3
  16. data/lib/selenium/webdriver/chrome/service.rb +8 -15
  17. data/lib/selenium/webdriver/chrome.rb +10 -9
  18. data/lib/selenium/webdriver/common/action_builder.rb +97 -249
  19. data/lib/selenium/webdriver/common/driver.rb +112 -23
  20. data/lib/selenium/webdriver/common/driver_extensions/full_page_screenshot.rb +43 -0
  21. data/lib/selenium/webdriver/common/driver_extensions/has_apple_permissions.rb +51 -0
  22. data/lib/selenium/webdriver/common/driver_extensions/has_authentication.rb +89 -0
  23. data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +77 -0
  24. data/lib/selenium/webdriver/common/driver_extensions/{has_touch_screen.rb → has_cdp.rb} +10 -8
  25. data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +45 -0
  26. data/lib/selenium/webdriver/{firefox/util.rb → common/driver_extensions/has_devtools.rb} +16 -19
  27. data/lib/selenium/webdriver/common/driver_extensions/has_launching.rb +38 -0
  28. data/lib/selenium/webdriver/common/driver_extensions/has_location.rb +5 -8
  29. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +144 -0
  30. data/lib/selenium/webdriver/common/driver_extensions/has_logs.rb +30 -0
  31. data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +17 -0
  32. data/lib/selenium/webdriver/common/driver_extensions/has_network_connection.rb +6 -27
  33. data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +136 -0
  34. data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +11 -11
  35. data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +77 -0
  36. data/lib/selenium/webdriver/common/driver_extensions/has_remote_status.rb +1 -0
  37. data/lib/selenium/webdriver/common/driver_extensions/{rotatable.rb → prints_page.rb} +18 -20
  38. data/lib/selenium/webdriver/common/element.rb +82 -22
  39. data/lib/selenium/webdriver/common/error.rb +32 -196
  40. data/lib/selenium/webdriver/common/interactions/interaction.rb +4 -1
  41. data/lib/selenium/webdriver/common/interactions/key_actions.rb +5 -5
  42. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +13 -13
  43. data/lib/selenium/webdriver/common/log_entry.rb +2 -2
  44. data/lib/selenium/webdriver/common/logger.rb +50 -15
  45. data/lib/selenium/webdriver/common/manager.rb +15 -15
  46. data/lib/selenium/webdriver/common/options.rb +154 -23
  47. data/lib/selenium/webdriver/common/platform.rb +6 -1
  48. data/lib/selenium/webdriver/common/port_prober.rb +4 -6
  49. data/lib/selenium/webdriver/common/profile_helper.rb +11 -9
  50. data/lib/selenium/webdriver/common/proxy.rb +6 -3
  51. data/lib/selenium/webdriver/common/search_context.rb +7 -3
  52. data/lib/selenium/webdriver/common/service.rb +17 -125
  53. data/lib/selenium/webdriver/common/service_manager.rb +151 -0
  54. data/lib/selenium/webdriver/common/shadow_root.rb +87 -0
  55. data/lib/selenium/webdriver/common/socket_lock.rb +2 -2
  56. data/lib/selenium/webdriver/common/socket_poller.rb +2 -2
  57. data/lib/selenium/webdriver/common/takes_screenshot.rb +66 -0
  58. data/lib/selenium/webdriver/common/target_locator.rb +32 -4
  59. data/lib/selenium/webdriver/common/timeouts.rb +31 -4
  60. data/lib/selenium/webdriver/common/wait.rb +1 -1
  61. data/lib/selenium/webdriver/common/window.rb +0 -4
  62. data/lib/selenium/webdriver/common/zipper.rb +1 -9
  63. data/lib/selenium/webdriver/common.rb +23 -17
  64. data/lib/selenium/webdriver/devtools/console_event.rb +38 -0
  65. data/lib/selenium/webdriver/devtools/exception_event.rb +36 -0
  66. data/lib/selenium/webdriver/devtools/mutation_event.rb +37 -0
  67. data/lib/selenium/webdriver/devtools/pinned_script.rb +59 -0
  68. data/lib/selenium/webdriver/devtools/request.rb +67 -0
  69. data/lib/selenium/webdriver/devtools/response.rb +66 -0
  70. data/lib/selenium/webdriver/devtools.rb +182 -0
  71. data/lib/selenium/webdriver/edge/driver.rb +7 -29
  72. data/lib/selenium/webdriver/edge/features.rb +44 -0
  73. data/lib/selenium/webdriver/edge/options.rb +11 -48
  74. data/lib/selenium/webdriver/{common/w3c_manager.rb → edge/profile.rb} +7 -19
  75. data/lib/selenium/webdriver/edge/service.rb +10 -26
  76. data/lib/selenium/webdriver/edge.rb +11 -14
  77. data/lib/selenium/webdriver/firefox/driver.rb +31 -19
  78. data/lib/selenium/webdriver/firefox/extension.rb +8 -0
  79. data/lib/selenium/webdriver/firefox/features.rb +66 -0
  80. data/lib/selenium/webdriver/firefox/options.rb +70 -49
  81. data/lib/selenium/webdriver/firefox/profile.rb +21 -71
  82. data/lib/selenium/webdriver/firefox/service.rb +5 -9
  83. data/lib/selenium/webdriver/firefox.rb +22 -20
  84. data/lib/selenium/webdriver/ie/driver.rb +1 -47
  85. data/lib/selenium/webdriver/ie/options.rb +13 -44
  86. data/lib/selenium/webdriver/ie/service.rb +13 -15
  87. data/lib/selenium/webdriver/ie.rb +8 -7
  88. data/lib/selenium/webdriver/remote/bridge.rb +558 -86
  89. data/lib/selenium/webdriver/remote/capabilities.rb +159 -123
  90. data/lib/selenium/webdriver/remote/commands.rb +163 -0
  91. data/lib/selenium/webdriver/remote/driver.rb +22 -12
  92. data/lib/selenium/webdriver/remote/http/common.rb +0 -5
  93. data/lib/selenium/webdriver/remote/http/default.rb +17 -20
  94. data/lib/selenium/webdriver/remote/http/persistent.rb +11 -6
  95. data/lib/selenium/webdriver/remote/response.rb +16 -47
  96. data/lib/selenium/webdriver/remote.rb +15 -13
  97. data/lib/selenium/webdriver/safari/driver.rb +3 -31
  98. data/lib/selenium/webdriver/safari/{bridge.rb → features.rb} +3 -3
  99. data/lib/selenium/webdriver/safari/options.rb +10 -29
  100. data/lib/selenium/webdriver/safari/service.rb +4 -8
  101. data/lib/selenium/webdriver/safari.rb +17 -9
  102. data/lib/selenium/webdriver/support/block_event_listener.rb +1 -1
  103. data/lib/selenium/webdriver/support/cdp/domain.rb.erb +63 -0
  104. data/lib/selenium/webdriver/support/cdp_client_generator.rb +108 -0
  105. data/lib/selenium/webdriver/support/color.rb +2 -2
  106. data/lib/selenium/webdriver/support/event_firing_bridge.rb +4 -4
  107. data/lib/selenium/webdriver/support/guards/guard.rb +89 -0
  108. data/lib/selenium/webdriver/{firefox/marionette/bridge.rb → support/guards/guard_condition.rb} +22 -19
  109. data/lib/selenium/webdriver/support/guards.rb +95 -0
  110. data/lib/selenium/webdriver/support/relative_locator.rb +51 -0
  111. data/lib/selenium/webdriver/support/select.rb +3 -3
  112. data/lib/selenium/webdriver/support.rb +1 -0
  113. data/lib/selenium/webdriver/version.rb +1 -1
  114. data/lib/selenium/webdriver.rb +12 -12
  115. data/selenium-webdriver.gemspec +28 -12
  116. metadata +128 -69
  117. data/lib/selenium/webdriver/common/bridge_helper.rb +0 -82
  118. data/lib/selenium/webdriver/common/driver_extensions/takes_screenshot.rb +0 -64
  119. data/lib/selenium/webdriver/common/keyboard.rb +0 -70
  120. data/lib/selenium/webdriver/common/mouse.rb +0 -89
  121. data/lib/selenium/webdriver/common/touch_action_builder.rb +0 -78
  122. data/lib/selenium/webdriver/common/touch_screen.rb +0 -123
  123. data/lib/selenium/webdriver/common/w3c_action_builder.rb +0 -212
  124. data/lib/selenium/webdriver/edge/bridge.rb +0 -76
  125. data/lib/selenium/webdriver/firefox/binary.rb +0 -187
  126. data/lib/selenium/webdriver/firefox/extension/prefs.json +0 -69
  127. data/lib/selenium/webdriver/firefox/extension/webdriver.xpi +0 -0
  128. data/lib/selenium/webdriver/firefox/launcher.rb +0 -111
  129. data/lib/selenium/webdriver/firefox/legacy/driver.rb +0 -83
  130. data/lib/selenium/webdriver/firefox/marionette/driver.rb +0 -90
  131. data/lib/selenium/webdriver/firefox/native/linux/amd64/x_ignore_nofocus.so +0 -0
  132. data/lib/selenium/webdriver/firefox/native/linux/x86/x_ignore_nofocus.so +0 -0
  133. data/lib/selenium/webdriver/remote/oss/bridge.rb +0 -594
  134. data/lib/selenium/webdriver/remote/oss/commands.rb +0 -223
  135. data/lib/selenium/webdriver/remote/w3c/bridge.rb +0 -605
  136. data/lib/selenium/webdriver/remote/w3c/capabilities.rb +0 -310
  137. data/lib/selenium/webdriver/remote/w3c/commands.rb +0 -157
@@ -36,6 +36,7 @@ module Selenium
36
36
  # @option opts [String] :value A value
37
37
  # @option opts [String] :path ('/') A path
38
38
  # @option opts [String] :secure (false) A boolean
39
+ # @option opts [String] :same_site (Strict or Lax) currently supported only in chrome 80+ versions
39
40
  # @option opts [Time,DateTime,Numeric,nil] :expires (nil) Expiry date, either as a Time, DateTime, or seconds since epoch.
40
41
  #
41
42
  # @raise [ArgumentError] if :name or :value is not specified
@@ -45,9 +46,15 @@ module Selenium
45
46
  raise ArgumentError, 'name is required' unless opts[:name]
46
47
  raise ArgumentError, 'value is required' unless opts[:value]
47
48
 
48
- opts[:path] ||= '/'
49
+ # NOTE: This is required because of https://bugs.chromium.org/p/chromedriver/issues/detail?id=3732
49
50
  opts[:secure] ||= false
50
51
 
52
+ same_site = opts.delete(:same_site)
53
+ opts[:sameSite] = same_site if same_site
54
+
55
+ http_only = opts.delete(:http_only)
56
+ opts[:httpOnly] = http_only if http_only
57
+
51
58
  obj = opts.delete(:expires)
52
59
  opts[:expiry] = seconds_from(obj).to_i if obj
53
60
 
@@ -62,7 +69,7 @@ module Selenium
62
69
  #
63
70
 
64
71
  def cookie_named(name)
65
- all_cookies.find { |c| c[:name] == name }
72
+ convert_cookie(@bridge.cookie(name))
66
73
  end
67
74
 
68
75
  #
@@ -97,24 +104,19 @@ module Selenium
97
104
  @timeouts ||= Timeouts.new(@bridge)
98
105
  end
99
106
 
100
- #
101
- # @api beta This API may be changed or removed in a future release.
102
- #
103
-
104
107
  def logs
108
+ WebDriver.logger.deprecate('Manager#logs', 'Chrome::Driver#logs')
105
109
  @logs ||= Logs.new(@bridge)
106
110
  end
107
111
 
108
112
  #
109
- # Create a new top-level browsing context
110
- # https://w3c.github.io/webdriver/#new-window
111
113
  # @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
114
  # @return [String] The value of the window handle
116
115
  #
117
116
  def new_window(type = :tab)
117
+ WebDriver.logger.deprecate('Manager#new_window', 'TargetLocator#new_window', id: :new_window) do
118
+ 'e.g., `driver.switch_to.new_window(:tab)`'
119
+ end
118
120
  case type
119
121
  when :tab, :window
120
122
  result = @bridge.new_window(type)
@@ -129,10 +131,6 @@ module Selenium
129
131
  end
130
132
  end
131
133
 
132
- #
133
- # @api beta This API may be changed or removed in a future release.
134
- #
135
-
136
134
  def window
137
135
  @window ||= Window.new(@bridge)
138
136
  end
@@ -169,6 +167,8 @@ module Selenium
169
167
  path: cookie['path'],
170
168
  domain: cookie['domain'] && strip_port(cookie['domain']),
171
169
  expires: cookie['expiry'] && datetime_at(cookie['expiry']),
170
+ same_site: cookie['sameSite'],
171
+ http_only: cookie['httpOnly'],
172
172
  secure: cookie['secure']
173
173
  }
174
174
  end
@@ -19,35 +19,166 @@
19
19
 
20
20
  module Selenium
21
21
  module WebDriver
22
- module Common
23
- class Options
24
- private
25
-
26
- def generate_as_json(value)
27
- if value.respond_to?(:as_json)
28
- value.as_json
29
- elsif value.is_a?(Hash)
30
- value.each_with_object({}) { |(key, val), hash| hash[convert_json_key(key)] = generate_as_json(val) }
31
- elsif value.is_a?(Array)
32
- value.map(&method(:generate_as_json))
33
- elsif value.is_a?(Symbol)
34
- value.to_s
35
- else
36
- value
22
+ class Options
23
+ W3C_OPTIONS = %i[browser_name browser_version platform_name accept_insecure_certs page_load_strategy proxy
24
+ set_window_rect timeouts unhandled_prompt_behavior strict_file_interactability
25
+ web_socket_url].freeze
26
+
27
+ class << self
28
+ attr_reader :driver_path
29
+
30
+ def chrome(**opts)
31
+ Chrome::Options.new(**opts)
32
+ end
33
+
34
+ def firefox(**opts)
35
+ Firefox::Options.new(**opts)
36
+ end
37
+
38
+ def ie(**opts)
39
+ IE::Options.new(**opts)
40
+ end
41
+ alias_method :internet_explorer, :ie
42
+
43
+ def edge(**opts)
44
+ Edge::Options.new(**opts)
45
+ end
46
+ alias_method :microsoftedge, :edge
47
+
48
+ def safari(**opts)
49
+ Safari::Options.new(**opts)
50
+ end
51
+
52
+ def set_capabilities
53
+ (W3C_OPTIONS + self::CAPABILITIES.keys).each do |key|
54
+ next if method_defined? key
55
+
56
+ define_method key do
57
+ @options[key]
58
+ end
59
+
60
+ define_method "#{key}=" do |value|
61
+ @options[key] = value
62
+ end
37
63
  end
38
64
  end
65
+ end
66
+
67
+ attr_accessor :options
68
+
69
+ def initialize(options: nil, **opts)
70
+ self.class.set_capabilities
71
+
72
+ @options = if options
73
+ WebDriver.logger.deprecate(":options as keyword for initializing #{self.class}",
74
+ "custom values directly in #new constructor",
75
+ id: :options_options)
76
+ opts.merge(options)
77
+ else
78
+ opts
79
+ end
80
+ @options[:browser_name] = self.class::BROWSER
81
+ end
82
+
83
+ #
84
+ # Add a new option not yet handled by bindings.
85
+ #
86
+ # @example Leave Chrome open when chromedriver is killed
87
+ # options = Selenium::WebDriver::Chrome::Options.new
88
+ # options.add_option(:detach, true)
89
+ #
90
+ # @param [String, Symbol] name Name of the option
91
+ # @param [Boolean, String, Integer] value Value of the option
92
+ #
93
+
94
+ def add_option(name, value = nil)
95
+ @options[name.keys.first] = name.values.first if value.nil? && name.is_a?(Hash)
96
+ @options[name] = value
97
+ end
98
+
99
+ def ==(other)
100
+ return false unless other.is_a? self.class
101
+
102
+ as_json == other.as_json
103
+ end
104
+
105
+ alias_method :eql?, :==
106
+
107
+ #
108
+ # @api private
109
+ #
110
+
111
+ def as_json(*)
112
+ options = @options.dup
113
+
114
+ w3c_options = process_w3c_options(options)
115
+
116
+ self.class::CAPABILITIES.each do |capability_alias, capability_name|
117
+ capability_value = options.delete(capability_alias)
118
+ options[capability_name] = capability_value if !capability_value.nil? && !options.key?(capability_name)
119
+ end
120
+ browser_options = defined?(self.class::KEY) ? {self.class::KEY => options} : options
39
121
 
40
- def convert_json_key(key)
41
- key = camel_case(key) if key.is_a?(Symbol)
42
- return key if key.is_a?(String)
122
+ process_browser_options(browser_options)
123
+ generate_as_json(w3c_options.merge(browser_options))
124
+ end
43
125
 
44
- raise TypeError, "expected String or Symbol, got #{key.inspect}:#{key.class}"
126
+ private
127
+
128
+ def w3c?(key)
129
+ W3C_OPTIONS.include?(key) || key.to_s.include?(':')
130
+ end
131
+
132
+ def process_w3c_options(options)
133
+ w3c_options = options.select { |key, _val| w3c?(key) }
134
+ w3c_options[:unhandled_prompt_behavior] &&= w3c_options[:unhandled_prompt_behavior]&.to_s&.tr('_', ' ')
135
+ options.delete_if { |key, _val| w3c?(key) }
136
+ w3c_options
137
+ end
138
+
139
+ def process_browser_options(_browser_options)
140
+ nil
141
+ end
142
+
143
+ def camelize?(_key)
144
+ true
145
+ end
146
+
147
+ def generate_as_json(value, camelize_keys: true)
148
+ if value.is_a?(Hash)
149
+ process_json_hash(value, camelize_keys)
150
+ elsif value.respond_to?(:as_json)
151
+ value.as_json
152
+ elsif value.is_a?(Array)
153
+ value.map { |val| generate_as_json(val, camelize_keys: camelize_keys) }
154
+ elsif value.is_a?(Symbol)
155
+ value.to_s
156
+ else
157
+ value
45
158
  end
159
+ end
46
160
 
47
- def camel_case(str)
48
- str.to_s.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }
161
+ def process_json_hash(value, camelize_keys)
162
+ value.each_with_object({}) do |(key, val), hash|
163
+ next if val.respond_to?(:empty?) && val.empty?
164
+
165
+ camelize = camelize_keys ? camelize?(key) : false
166
+ key = convert_json_key(key, camelize: camelize)
167
+ hash[key] = generate_as_json(val, camelize_keys: camelize)
49
168
  end
50
- end # Options
51
- end # Common
169
+ end
170
+
171
+ def convert_json_key(key, camelize: true)
172
+ key = key.to_s if key.is_a?(Symbol)
173
+ key = camel_case(key) if camelize
174
+ return key if key.is_a?(String)
175
+
176
+ raise TypeError, "expected String or Symbol, got #{key.inspect}:#{key.class}"
177
+ end
178
+
179
+ def camel_case(str)
180
+ str.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }
181
+ end
182
+ end # Options
52
183
  end # WebDriver
53
184
  end # Selenium
@@ -57,6 +57,8 @@ module Selenium
57
57
  :jenkins
58
58
  elsif ENV['APPVEYOR']
59
59
  :appveyor
60
+ elsif ENV['GITHUB_ACTIONS']
61
+ :github
60
62
  end
61
63
  end
62
64
 
@@ -95,7 +97,10 @@ module Selenium
95
97
  def wsl?
96
98
  return false unless linux?
97
99
 
98
- File.read('/proc/version').include?('Microsoft')
100
+ File.read('/proc/version').downcase.include?('microsoft')
101
+ rescue Errno::EACCES
102
+ # the file cannot be accessed on Linux on DeX
103
+ false
99
104
  end
100
105
 
101
106
  def cygwin?
@@ -32,12 +32,10 @@ module Selenium
32
32
 
33
33
  def self.free?(port)
34
34
  Platform.interfaces.each do |host|
35
- begin
36
- TCPServer.new(host, port).close
37
- rescue *IGNORED_ERRORS => ex
38
- WebDriver.logger.debug("port prober could not bind to #{host}:#{port} (#{ex.message})")
39
- # ignored - some machines appear unable to bind to some of their interfaces
40
- end
35
+ TCPServer.new(host, port).close
36
+ rescue *IGNORED_ERRORS => e
37
+ WebDriver.logger.debug("port prober could not bind to #{host}:#{port} (#{e.message})")
38
+ # ignored - some machines appear unable to bind to some of their interfaces
41
39
  end
42
40
 
43
41
  true
@@ -31,8 +31,16 @@ module Selenium
31
31
  base.extend ClassMethods
32
32
  end
33
33
 
34
+ def self.decoded(json)
35
+ JSON.parse(json).fetch('zip')
36
+ end
37
+
38
+ def encoded
39
+ Zipper.zip(layout_on_disk)
40
+ end
41
+
34
42
  def as_json(*)
35
- {"zip" => Zipper.zip(layout_on_disk)}
43
+ {"zip" => encoded}
36
44
  end
37
45
 
38
46
  def to_json(*)
@@ -63,18 +71,12 @@ module Selenium
63
71
 
64
72
  module ClassMethods
65
73
  def from_json(json)
66
- data = JSON.parse(json).fetch('zip')
74
+ data = decoded(json)
67
75
 
68
- # can't use Tempfile here since it doesn't support File::BINARY mode on 1.8
69
- # can't use Dir.mktmpdir(&blk) because of http://jira.codehaus.org/browse/JRUBY-4082
70
- tmp_dir = Dir.mktmpdir
71
- begin
72
- zip_path = File.join(tmp_dir, "webdriver-profile-duplicate-#{json.hash}.zip")
76
+ Tempfile.create do |zip_path|
73
77
  File.open(zip_path, 'wb') { |zip_file| zip_file << Base64.decode64(data) }
74
78
 
75
79
  new Zipper.unzip(zip_path)
76
- ensure
77
- FileUtils.rm_rf tmp_dir
78
80
  end
79
81
  end
80
82
  end # ClassMethods
@@ -127,7 +127,10 @@ module Selenium
127
127
  end
128
128
 
129
129
  def type=(type)
130
- raise ArgumentError, "invalid proxy type: #{type.inspect}, expected one of #{TYPES.keys.inspect}" unless TYPES.key? type
130
+ unless TYPES.key? type
131
+ raise ArgumentError,
132
+ "invalid proxy type: #{type.inspect}, expected one of #{TYPES.keys.inspect}"
133
+ end
131
134
 
132
135
  if defined?(@type) && type != @type
133
136
  raise ArgumentError, "incompatible proxy type #{type.inspect} (already set to #{@type.inspect})"
@@ -138,10 +141,10 @@ module Selenium
138
141
 
139
142
  def as_json(*)
140
143
  json_result = {
141
- 'proxyType' => TYPES[type],
144
+ 'proxyType' => TYPES[type].downcase,
142
145
  'ftpProxy' => ftp,
143
146
  'httpProxy' => http,
144
- 'noProxy' => no_proxy,
147
+ 'noProxy' => no_proxy.is_a?(String) ? no_proxy.split(', ') : no_proxy,
145
148
  'proxyAutoconfigUrl' => pac,
146
149
  'sslProxy' => ssl,
147
150
  'autodetect' => auto_detect,
@@ -30,6 +30,7 @@ module Selenium
30
30
  link_text: 'link text',
31
31
  name: 'name',
32
32
  partial_link_text: 'partial link text',
33
+ relative: 'relative',
33
34
  tag_name: 'tag name',
34
35
  xpath: 'xpath'
35
36
  }.freeze
@@ -59,7 +60,7 @@ module Selenium
59
60
  by = FINDERS[how.to_sym]
60
61
  raise ArgumentError, "cannot find element by #{how.inspect}" unless by
61
62
 
62
- bridge.find_element_by by, what.to_s, ref
63
+ bridge.find_element_by by, what, ref
63
64
  rescue Selenium::WebDriver::Error::TimeoutError
64
65
  # Implicit Wait times out in Edge
65
66
  raise Selenium::WebDriver::Error::NoSuchElementError
@@ -77,7 +78,7 @@ module Selenium
77
78
  by = FINDERS[how.to_sym]
78
79
  raise ArgumentError, "cannot find elements by #{how.inspect}" unless by
79
80
 
80
- bridge.find_elements_by by, what.to_s, ref
81
+ bridge.find_elements_by by, what, ref
81
82
  rescue Selenium::WebDriver::Error::TimeoutError
82
83
  # Implicit Wait times out in Edge
83
84
  []
@@ -92,7 +93,10 @@ module Selenium
92
93
  when 1
93
94
  arg = args.first
94
95
 
95
- raise ArgumentError, "expected #{arg.inspect}:#{arg.class} to respond to #shift" unless arg.respond_to?(:shift)
96
+ unless arg.respond_to?(:shift)
97
+ raise ArgumentError,
98
+ "expected #{arg.inspect}:#{arg.class} to respond to #shift"
99
+ end
96
100
 
97
101
  # this will be a single-entry hash, so use #shift over #first or #[]
98
102
  arr = arg.dup.shift
@@ -21,40 +21,18 @@ module Selenium
21
21
  module WebDriver
22
22
  #
23
23
  # Base class implementing default behavior of service object,
24
- # responsible for starting and stopping driver implementations.
24
+ # responsible for storing a service manager configuration.
25
25
  #
26
26
 
27
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
28
  class << self
38
- attr_reader :default_port, :driver_path, :executable, :missing_text, :shutdown_supported
29
+ attr_reader :driver_path
39
30
 
40
31
  def chrome(**opts)
41
32
  Chrome::Service.new(**opts)
42
33
  end
43
34
 
44
35
  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
36
  Firefox::Service.new(**opts)
59
37
  end
60
38
 
@@ -66,6 +44,7 @@ module Selenium
66
44
  def edge(**opts)
67
45
  Edge::Service.new(**opts)
68
46
  end
47
+ alias_method :microsoftedge, :edge
69
48
 
70
49
  def safari(**opts)
71
50
  Safari::Service.new(**opts)
@@ -78,7 +57,7 @@ module Selenium
78
57
  end
79
58
 
80
59
  attr_accessor :host
81
- attr_reader :executable_path
60
+ attr_reader :executable_path, :port, :extra_args
82
61
 
83
62
  #
84
63
  # End users should use a class method for the desired driver, rather than using this directly.
@@ -88,7 +67,7 @@ module Selenium
88
67
 
89
68
  def initialize(path: nil, port: nil, args: nil)
90
69
  path ||= self.class.driver_path
91
- port ||= self.class.default_port
70
+ port ||= self.class::DEFAULT_PORT
92
71
  args ||= []
93
72
 
94
73
  @executable_path = binary_path(path)
@@ -100,120 +79,33 @@ module Selenium
100
79
  raise Error::WebDriverError, "invalid port: #{@port}" if @port < 1
101
80
  end
102
81
 
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
82
+ def launch
83
+ sm = ServiceManager.new(self)
84
+ sm.start
85
+ sm
113
86
  end
114
87
 
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
88
+ def shutdown_supported
89
+ self.class::SHUTDOWN_SUPPORTED
124
90
  end
125
91
 
126
- def uri
127
- @uri ||= URI.parse("http://#{@host}:#{@port}")
92
+ protected
93
+
94
+ def extract_service_args(driver_opts)
95
+ driver_opts.key?(:args) ? driver_opts.delete(:args) : []
128
96
  end
129
97
 
130
98
  private
131
99
 
132
100
  def binary_path(path = nil)
133
101
  path = path.call if path.is_a?(Proc)
134
- path ||= Platform.find_binary(self.class.executable)
102
+ path ||= Platform.find_binary(self.class::EXECUTABLE)
135
103
 
136
- raise Error::WebDriverError, self.class.missing_text unless path
104
+ raise Error::WebDriverError, self.class::MISSING_TEXT unless path
137
105
 
138
106
  Platform.assert_executable path
139
107
  path
140
108
  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
109
  end # Service
218
110
  end # WebDriver
219
111
  end # Selenium