selenium-webdriver 3.142.7 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGES +324 -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 +21 -29
  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 +156 -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 +10 -2
  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.rb +23 -17
  63. data/lib/selenium/webdriver/devtools/console_event.rb +38 -0
  64. data/lib/selenium/webdriver/devtools/exception_event.rb +36 -0
  65. data/lib/selenium/webdriver/devtools/mutation_event.rb +37 -0
  66. data/lib/selenium/webdriver/devtools/pinned_script.rb +59 -0
  67. data/lib/selenium/webdriver/devtools/request.rb +67 -0
  68. data/lib/selenium/webdriver/devtools/response.rb +66 -0
  69. data/lib/selenium/webdriver/devtools.rb +182 -0
  70. data/lib/selenium/webdriver/edge/driver.rb +7 -29
  71. data/lib/selenium/webdriver/edge/features.rb +44 -0
  72. data/lib/selenium/webdriver/edge/options.rb +11 -48
  73. data/lib/selenium/webdriver/{common/w3c_manager.rb → edge/profile.rb} +7 -19
  74. data/lib/selenium/webdriver/edge/service.rb +10 -26
  75. data/lib/selenium/webdriver/edge.rb +11 -14
  76. data/lib/selenium/webdriver/firefox/driver.rb +31 -19
  77. data/lib/selenium/webdriver/firefox/extension.rb +8 -0
  78. data/lib/selenium/webdriver/firefox/features.rb +66 -0
  79. data/lib/selenium/webdriver/firefox/options.rb +70 -49
  80. data/lib/selenium/webdriver/firefox/profile.rb +21 -71
  81. data/lib/selenium/webdriver/firefox/service.rb +5 -9
  82. data/lib/selenium/webdriver/firefox.rb +22 -20
  83. data/lib/selenium/webdriver/ie/driver.rb +1 -47
  84. data/lib/selenium/webdriver/ie/options.rb +13 -44
  85. data/lib/selenium/webdriver/ie/service.rb +13 -15
  86. data/lib/selenium/webdriver/ie.rb +8 -7
  87. data/lib/selenium/webdriver/remote/bridge.rb +558 -86
  88. data/lib/selenium/webdriver/remote/capabilities.rb +159 -123
  89. data/lib/selenium/webdriver/remote/commands.rb +163 -0
  90. data/lib/selenium/webdriver/remote/driver.rb +22 -12
  91. data/lib/selenium/webdriver/remote/http/common.rb +0 -5
  92. data/lib/selenium/webdriver/remote/http/default.rb +17 -20
  93. data/lib/selenium/webdriver/remote/http/persistent.rb +11 -6
  94. data/lib/selenium/webdriver/remote/response.rb +16 -47
  95. data/lib/selenium/webdriver/remote.rb +15 -13
  96. data/lib/selenium/webdriver/safari/driver.rb +3 -31
  97. data/lib/selenium/webdriver/safari/{bridge.rb → features.rb} +3 -3
  98. data/lib/selenium/webdriver/safari/options.rb +10 -29
  99. data/lib/selenium/webdriver/safari/service.rb +4 -8
  100. data/lib/selenium/webdriver/safari.rb +17 -9
  101. data/lib/selenium/webdriver/support/block_event_listener.rb +1 -1
  102. data/lib/selenium/webdriver/support/cdp/domain.rb.erb +63 -0
  103. data/lib/selenium/webdriver/support/cdp_client_generator.rb +108 -0
  104. data/lib/selenium/webdriver/support/color.rb +2 -2
  105. data/lib/selenium/webdriver/support/event_firing_bridge.rb +4 -4
  106. data/lib/selenium/webdriver/support/guards/guard.rb +89 -0
  107. data/lib/selenium/webdriver/{firefox/marionette/bridge.rb → support/guards/guard_condition.rb} +22 -19
  108. data/lib/selenium/webdriver/support/guards.rb +95 -0
  109. data/lib/selenium/webdriver/support/relative_locator.rb +51 -0
  110. data/lib/selenium/webdriver/support/select.rb +3 -3
  111. data/lib/selenium/webdriver/support.rb +1 -0
  112. data/lib/selenium/webdriver/version.rb +1 -1
  113. data/lib/selenium/webdriver.rb +12 -12
  114. data/selenium-webdriver.gemspec +28 -12
  115. metadata +128 -69
  116. data/lib/selenium/webdriver/common/bridge_helper.rb +0 -82
  117. data/lib/selenium/webdriver/common/driver_extensions/takes_screenshot.rb +0 -64
  118. data/lib/selenium/webdriver/common/keyboard.rb +0 -70
  119. data/lib/selenium/webdriver/common/mouse.rb +0 -89
  120. data/lib/selenium/webdriver/common/touch_action_builder.rb +0 -78
  121. data/lib/selenium/webdriver/common/touch_screen.rb +0 -123
  122. data/lib/selenium/webdriver/common/w3c_action_builder.rb +0 -212
  123. data/lib/selenium/webdriver/edge/bridge.rb +0 -76
  124. data/lib/selenium/webdriver/firefox/binary.rb +0 -187
  125. data/lib/selenium/webdriver/firefox/extension/prefs.json +0 -69
  126. data/lib/selenium/webdriver/firefox/extension/webdriver.xpi +0 -0
  127. data/lib/selenium/webdriver/firefox/launcher.rb +0 -111
  128. data/lib/selenium/webdriver/firefox/legacy/driver.rb +0 -83
  129. data/lib/selenium/webdriver/firefox/marionette/driver.rb +0 -90
  130. data/lib/selenium/webdriver/firefox/native/linux/amd64/x_ignore_nofocus.so +0 -0
  131. data/lib/selenium/webdriver/firefox/native/linux/x86/x_ignore_nofocus.so +0 -0
  132. data/lib/selenium/webdriver/remote/oss/bridge.rb +0 -594
  133. data/lib/selenium/webdriver/remote/oss/commands.rb +0 -223
  134. data/lib/selenium/webdriver/remote/w3c/bridge.rb +0 -605
  135. data/lib/selenium/webdriver/remote/w3c/capabilities.rb +0 -310
  136. 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,168 @@
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
39
98
 
40
- def convert_json_key(key)
41
- key = camel_case(key) if key.is_a?(Symbol)
42
- return key if key.is_a?(String)
99
+ def ==(other)
100
+ return false unless other.is_a? self.class
43
101
 
44
- raise TypeError, "expected String or Symbol, got #{key.inspect}:#{key.class}"
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
121
+
122
+ process_browser_options(browser_options)
123
+ generate_as_json(w3c_options.merge(browser_options))
124
+ end
125
+
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
160
+
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?
46
164
 
47
- def camel_case(str)
48
- str.to_s.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }
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
183
+
184
+ # Options
52
185
  end # WebDriver
53
186
  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,7 +71,7 @@ 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
76
  # can't use Tempfile here since it doesn't support File::BINARY mode on 1.8
69
77
  # can't use Dir.mktmpdir(&blk) because of http://jira.codehaus.org/browse/JRUBY-4082
@@ -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