selenium-webdriver 3.0.0.beta4.0 → 4.0.0.alpha5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +679 -0
  3. data/Gemfile +2 -0
  4. data/LICENSE +1 -1
  5. data/README.md +1 -1
  6. data/lib/selenium-webdriver.rb +2 -2
  7. data/lib/selenium/server.rb +23 -16
  8. data/lib/selenium/webdriver.rb +43 -25
  9. data/lib/selenium/webdriver/atoms.rb +20 -1
  10. data/lib/selenium/webdriver/atoms/findElements.js +122 -0
  11. data/lib/selenium/webdriver/atoms/getAttribute.js +84 -11
  12. data/lib/selenium/webdriver/atoms/isDisplayed.js +100 -0
  13. data/lib/selenium/webdriver/chrome.rb +15 -20
  14. data/lib/selenium/webdriver/chrome/bridge.rb +29 -66
  15. data/lib/selenium/webdriver/{edge/service.rb → chrome/driver.rb} +19 -22
  16. data/lib/selenium/webdriver/chrome/options.rb +221 -0
  17. data/lib/selenium/webdriver/chrome/profile.rb +6 -7
  18. data/lib/selenium/webdriver/chrome/service.rb +20 -20
  19. data/lib/selenium/webdriver/common.rb +19 -11
  20. data/lib/selenium/webdriver/common/action_builder.rb +98 -246
  21. data/lib/selenium/webdriver/common/alert.rb +2 -7
  22. data/lib/selenium/webdriver/common/driver.rb +89 -51
  23. data/lib/selenium/webdriver/common/driver_extensions/downloads_files.rb +45 -0
  24. data/lib/selenium/webdriver/common/driver_extensions/has_addons.rb +50 -0
  25. data/lib/selenium/webdriver/common/driver_extensions/{has_input_devices.rb → has_debugger.rb} +12 -25
  26. data/lib/selenium/webdriver/common/driver_extensions/has_devtools.rb +38 -0
  27. data/lib/selenium/webdriver/common/driver_extensions/has_location.rb +3 -5
  28. data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +51 -0
  29. data/lib/selenium/webdriver/common/driver_extensions/has_network_connection.rb +3 -3
  30. data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +51 -0
  31. data/lib/selenium/webdriver/common/driver_extensions/has_remote_status.rb +2 -2
  32. data/lib/selenium/webdriver/common/driver_extensions/has_session_id.rb +2 -2
  33. data/lib/selenium/webdriver/common/driver_extensions/has_web_storage.rb +2 -2
  34. data/lib/selenium/webdriver/common/driver_extensions/rotatable.rb +4 -4
  35. data/lib/selenium/webdriver/common/driver_extensions/takes_screenshot.rb +9 -3
  36. data/lib/selenium/webdriver/common/driver_extensions/uploads_files.rb +3 -5
  37. data/lib/selenium/webdriver/common/element.rb +32 -10
  38. data/lib/selenium/webdriver/common/error.rb +103 -121
  39. data/lib/selenium/webdriver/common/file_reaper.rb +3 -5
  40. data/lib/selenium/webdriver/common/html5/local_storage.rb +2 -2
  41. data/lib/selenium/webdriver/common/html5/session_storage.rb +2 -2
  42. data/lib/selenium/webdriver/common/html5/shared_web_storage.rb +3 -2
  43. data/lib/selenium/webdriver/common/interactions/input_device.rb +54 -0
  44. data/lib/selenium/webdriver/common/interactions/interaction.rb +53 -0
  45. data/lib/selenium/webdriver/{phantomjs.rb → common/interactions/interactions.rb} +17 -20
  46. data/lib/selenium/webdriver/common/interactions/key_actions.rb +145 -0
  47. data/lib/selenium/webdriver/common/interactions/key_input.rb +66 -0
  48. data/lib/selenium/webdriver/common/interactions/none_input.rb +36 -0
  49. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +363 -0
  50. data/lib/selenium/webdriver/common/interactions/pointer_input.rb +139 -0
  51. data/lib/selenium/webdriver/common/keys.rb +37 -15
  52. data/lib/selenium/webdriver/common/log_entry.rb +4 -4
  53. data/lib/selenium/webdriver/common/logger.rb +147 -0
  54. data/lib/selenium/webdriver/common/logs.rb +2 -2
  55. data/lib/selenium/webdriver/common/manager.rb +177 -0
  56. data/lib/selenium/webdriver/common/navigation.rb +2 -2
  57. data/lib/selenium/webdriver/common/options.rb +47 -105
  58. data/lib/selenium/webdriver/common/platform.rb +44 -38
  59. data/lib/selenium/webdriver/common/port_prober.rb +8 -19
  60. data/lib/selenium/webdriver/common/profile_helper.rb +13 -5
  61. data/lib/selenium/webdriver/common/proxy.rb +14 -8
  62. data/lib/selenium/webdriver/common/search_context.rb +16 -20
  63. data/lib/selenium/webdriver/common/service.rb +115 -30
  64. data/lib/selenium/webdriver/common/socket_lock.rb +12 -7
  65. data/lib/selenium/webdriver/common/socket_poller.rb +29 -22
  66. data/lib/selenium/webdriver/common/target_locator.rb +6 -6
  67. data/lib/selenium/webdriver/common/timeouts.rb +2 -2
  68. data/lib/selenium/webdriver/common/wait.rb +14 -8
  69. data/lib/selenium/webdriver/common/window.rb +38 -2
  70. data/lib/selenium/webdriver/common/zipper.rb +3 -5
  71. data/lib/selenium/webdriver/edge.rb +33 -20
  72. data/lib/selenium/webdriver/edge_chrome/bridge.rb +30 -0
  73. data/lib/selenium/webdriver/edge_chrome/driver.rb +38 -0
  74. data/lib/selenium/webdriver/{common/driver_extensions/has_touch_screen.rb → edge_chrome/options.rb} +10 -12
  75. data/lib/selenium/webdriver/edge_chrome/profile.rb +33 -0
  76. data/lib/selenium/webdriver/edge_chrome/service.rb +36 -0
  77. data/lib/selenium/webdriver/edge_html/driver.rb +39 -0
  78. data/lib/selenium/webdriver/edge_html/options.rb +91 -0
  79. data/lib/selenium/webdriver/edge_html/service.rb +47 -0
  80. data/lib/selenium/webdriver/firefox.rb +24 -29
  81. data/lib/selenium/webdriver/firefox/bridge.rb +15 -39
  82. data/lib/selenium/webdriver/firefox/{util.rb → driver.rb} +13 -19
  83. data/lib/selenium/webdriver/firefox/extension.rb +28 -8
  84. data/lib/selenium/webdriver/firefox/options.rb +162 -0
  85. data/lib/selenium/webdriver/firefox/profile.rb +23 -82
  86. data/lib/selenium/webdriver/firefox/profiles_ini.rb +5 -5
  87. data/lib/selenium/webdriver/firefox/service.rb +18 -37
  88. data/lib/selenium/webdriver/ie.rb +13 -19
  89. data/lib/selenium/webdriver/ie/driver.rb +40 -0
  90. data/lib/selenium/webdriver/ie/options.rb +118 -0
  91. data/lib/selenium/webdriver/ie/service.rb +20 -20
  92. data/lib/selenium/webdriver/remote.rb +15 -17
  93. data/lib/selenium/webdriver/remote/bridge.rb +281 -300
  94. data/lib/selenium/webdriver/remote/capabilities.rb +102 -83
  95. data/lib/selenium/webdriver/remote/commands.rb +132 -192
  96. data/lib/selenium/webdriver/remote/driver.rb +52 -0
  97. data/lib/selenium/webdriver/remote/http/common.rb +23 -13
  98. data/lib/selenium/webdriver/remote/http/curb.rb +10 -7
  99. data/lib/selenium/webdriver/remote/http/default.rb +52 -32
  100. data/lib/selenium/webdriver/remote/http/persistent.rb +8 -4
  101. data/lib/selenium/webdriver/remote/response.rb +32 -35
  102. data/lib/selenium/webdriver/remote/server_error.rb +2 -2
  103. data/lib/selenium/webdriver/safari.rb +24 -22
  104. data/lib/selenium/webdriver/safari/bridge.rb +21 -21
  105. data/lib/selenium/webdriver/safari/driver.rb +41 -0
  106. data/lib/selenium/webdriver/safari/options.rb +66 -0
  107. data/lib/selenium/webdriver/safari/service.rb +8 -24
  108. data/lib/selenium/webdriver/support.rb +3 -2
  109. data/lib/selenium/webdriver/support/abstract_event_listener.rb +2 -2
  110. data/lib/selenium/webdriver/support/block_event_listener.rb +3 -3
  111. data/lib/selenium/webdriver/support/color.rb +13 -13
  112. data/lib/selenium/webdriver/support/escaper.rb +2 -2
  113. data/lib/selenium/webdriver/support/event_firing_bridge.rb +4 -4
  114. data/lib/selenium/webdriver/support/relative_locator.rb +51 -0
  115. data/lib/selenium/webdriver/support/select.rb +20 -21
  116. data/lib/selenium/webdriver/version.rb +24 -0
  117. data/selenium-webdriver.gemspec +31 -17
  118. metadata +331 -248
  119. data/lib/selenium/webdriver/common/bridge_helper.rb +0 -82
  120. data/lib/selenium/webdriver/common/keyboard.rb +0 -69
  121. data/lib/selenium/webdriver/common/mouse.rb +0 -88
  122. data/lib/selenium/webdriver/common/touch_action_builder.rb +0 -81
  123. data/lib/selenium/webdriver/common/touch_screen.rb +0 -121
  124. data/lib/selenium/webdriver/common/w3c_error.rb +0 -191
  125. data/lib/selenium/webdriver/edge/bridge.rb +0 -76
  126. data/lib/selenium/webdriver/edge/legacy_support.rb +0 -117
  127. data/lib/selenium/webdriver/firefox/binary.rb +0 -186
  128. data/lib/selenium/webdriver/firefox/extension/prefs.json +0 -69
  129. data/lib/selenium/webdriver/firefox/extension/webdriver.xpi +0 -0
  130. data/lib/selenium/webdriver/firefox/launcher.rb +0 -114
  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/firefox/w3c_bridge.rb +0 -79
  134. data/lib/selenium/webdriver/ie/bridge.rb +0 -76
  135. data/lib/selenium/webdriver/phantomjs/bridge.rb +0 -65
  136. data/lib/selenium/webdriver/phantomjs/service.rb +0 -66
  137. data/lib/selenium/webdriver/remote/w3c_bridge.rb +0 -682
  138. data/lib/selenium/webdriver/remote/w3c_capabilities.rb +0 -228
  139. data/lib/selenium/webdriver/remote/w3c_commands.rb +0 -135
@@ -1,5 +1,5 @@
1
- # encoding: utf-8
2
- #
1
+ # frozen_string_literal: true
2
+
3
3
  # Licensed to the Software Freedom Conservancy (SFC) under one
4
4
  # or more contributor license agreements. See the NOTICE file
5
5
  # distributed with this work for additional information
@@ -27,12 +27,11 @@ module Selenium
27
27
  module_function
28
28
 
29
29
  def home
30
- # jruby has an issue with ENV['HOME'] on Windows
31
- @home ||= jruby? ? ENV_JAVA['user.home'] : ENV['HOME']
30
+ @home ||= Dir.home
32
31
  end
33
32
 
34
33
  def engine
35
- @engine ||= defined?(RUBY_ENGINE) ? RUBY_ENGINE.to_sym : :ruby
34
+ @engine ||= RUBY_ENGINE.to_sym
36
35
  end
37
36
 
38
37
  def os
@@ -52,8 +51,13 @@ module Selenium
52
51
  end
53
52
 
54
53
  def ci
55
- return :travis if ENV['TRAVIS']
56
- :jenkins if ENV['JENKINS']
54
+ if ENV['TRAVIS']
55
+ :travis
56
+ elsif ENV['JENKINS']
57
+ :jenkins
58
+ elsif ENV['APPVEYOR']
59
+ :appveyor
60
+ end
57
61
  end
58
62
 
59
63
  def bitsize
@@ -72,10 +76,6 @@ module Selenium
72
76
  engine == :jruby
73
77
  end
74
78
 
75
- def ironruby?
76
- engine == :ironruby
77
- end
78
-
79
79
  def ruby_version
80
80
  RUBY_VERSION
81
81
  end
@@ -92,36 +92,50 @@ module Selenium
92
92
  os == :linux
93
93
  end
94
94
 
95
+ def wsl?
96
+ return false unless linux?
97
+
98
+ File.read('/proc/version').include?('Microsoft')
99
+ rescue Errno::EACCES
100
+ # the file cannot be accessed on Linux on DeX
101
+ false
102
+ end
103
+
95
104
  def cygwin?
96
105
  RUBY_PLATFORM =~ /cygwin/
97
106
  !Regexp.last_match.nil?
98
107
  end
99
108
 
100
109
  def null_device
101
- @null_device ||= if defined?(File::NULL)
102
- File::NULL
103
- else
104
- Platform.windows? ? 'NUL' : '/dev/null'
105
- end
110
+ File::NULL
106
111
  end
107
112
 
108
113
  def wrap_in_quotes_if_necessary(str)
109
114
  windows? && !cygwin? ? %("#{str}") : str
110
115
  end
111
116
 
112
- def cygwin_path(path, opts = {})
117
+ def cygwin_path(path, **opts)
113
118
  flags = []
114
119
  opts.each { |k, v| flags << "--#{k}" if v }
115
120
 
116
121
  `cygpath #{flags.join ' '} "#{path}"`.strip
117
122
  end
118
123
 
124
+ def unix_path(path)
125
+ path.tr(File::ALT_SEPARATOR, File::SEPARATOR)
126
+ end
127
+
128
+ def windows_path(path)
129
+ path.tr(File::SEPARATOR, File::ALT_SEPARATOR)
130
+ end
131
+
119
132
  def make_writable(file)
120
- File.chmod 0766, file
133
+ File.chmod 0o766, file
121
134
  end
122
135
 
123
136
  def assert_file(path)
124
137
  return if File.file? path
138
+
125
139
  raise Error::WebDriverError, "not a file: #{path.inspect}"
126
140
  end
127
141
 
@@ -129,6 +143,7 @@ module Selenium
129
143
  assert_file(path)
130
144
 
131
145
  return if File.executable? path
146
+
132
147
  raise Error::WebDriverError, "not executable: #{path.inspect}"
133
148
  end
134
149
 
@@ -140,15 +155,18 @@ module Selenium
140
155
 
141
156
  def find_binary(*binary_names)
142
157
  paths = ENV['PATH'].split(File::PATH_SEPARATOR)
143
- binary_names.map! { |n| "#{n}.exe" } if windows?
158
+
159
+ if windows?
160
+ binary_names.map! { |n| "#{n}.exe" }
161
+ binary_names.dup.each { |n| binary_names << n.gsub('exe', 'bat') }
162
+ end
144
163
 
145
164
  binary_names.each do |binary_name|
146
165
  paths.each do |path|
147
166
  full_path = File.join(path, binary_name)
148
- full_path.tr!('\\', '/') if windows?
149
- exe = Dir.glob(full_path).first
150
- next unless exe
151
- return exe if File.executable?(exe)
167
+ full_path = unix_path(full_path) if windows?
168
+ exe = Dir.glob(full_path).find { |f| File.executable?(f) }
169
+ return exe if exe
152
170
  end
153
171
  end
154
172
 
@@ -158,7 +176,8 @@ module Selenium
158
176
  def find_in_program_files(*binary_names)
159
177
  paths = [
160
178
  ENV['PROGRAMFILES'] || '\\Program Files',
161
- ENV['ProgramFiles(x86)'] || '\\Program Files (x86)'
179
+ ENV['ProgramFiles(x86)'] || '\\Program Files (x86)',
180
+ ENV['ProgramW6432'] || '\\Program Files'
162
181
  ]
163
182
 
164
183
  paths.each do |root|
@@ -175,6 +194,7 @@ module Selenium
175
194
  info = Socket.getaddrinfo 'localhost', 80, Socket::AF_INET, Socket::SOCK_STREAM
176
195
 
177
196
  return info[0][3] unless info.empty?
197
+
178
198
  raise Error::WebDriverError, "unable to translate 'localhost' for TCP + IPv4"
179
199
  end
180
200
 
@@ -203,17 +223,3 @@ module Selenium
203
223
  end # Platform
204
224
  end # WebDriver
205
225
  end # Selenium
206
-
207
- if __FILE__ == $PROGRAM_NAME
208
- p engine: Selenium::WebDriver::Platform.engine,
209
- os: Selenium::WebDriver::Platform.os,
210
- ruby_version: Selenium::WebDriver::Platform.ruby_version,
211
- jruby?: Selenium::WebDriver::Platform.jruby?,
212
- windows?: Selenium::WebDriver::Platform.windows?,
213
- home: Selenium::WebDriver::Platform.home,
214
- bitsize: Selenium::WebDriver::Platform.bitsize,
215
- localhost: Selenium::WebDriver::Platform.localhost,
216
- ip: Selenium::WebDriver::Platform.ip,
217
- interfaces: Selenium::WebDriver::Platform.interfaces,
218
- null_device: Selenium::WebDriver::Platform.null_device
219
- end
@@ -1,5 +1,5 @@
1
- # encoding: utf-8
2
- #
1
+ # frozen_string_literal: true
2
+
3
3
  # Licensed to the Software Freedom Conservancy (SFC) under one
4
4
  # or more contributor license agreements. See the NOTICE file
5
5
  # distributed with this work for additional information
@@ -25,28 +25,17 @@ module Selenium
25
25
  port
26
26
  end
27
27
 
28
- def self.random
29
- # TODO: Avoid this
30
- #
31
- # (a) should pick a port that's guaranteed to be free on all interfaces
32
- # (b) should pick a random port outside the ephemeral port range
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].freeze
42
- IGNORED_ERRORS << Errno::EBADF if Platform.cygwin?
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
43
32
 
44
33
  def self.free?(port)
45
34
  Platform.interfaces.each do |host|
46
35
  begin
47
36
  TCPServer.new(host, port).close
48
- rescue *IGNORED_ERRORS => ex
49
- $stderr.puts "port prober could not bind to #{host}:#{port} (#{ex.message})" if $DEBUG
37
+ rescue *IGNORED_ERRORS => e
38
+ WebDriver.logger.debug("port prober could not bind to #{host}:#{port} (#{e.message})")
50
39
  # ignored - some machines appear unable to bind to some of their interfaces
51
40
  end
52
41
  end
@@ -1,5 +1,5 @@
1
- # encoding: utf-8
2
- #
1
+ # frozen_string_literal: true
2
+
3
3
  # Licensed to the Software Freedom Conservancy (SFC) under one
4
4
  # or more contributor license agreements. See the NOTICE file
5
5
  # distributed with this work for additional information
@@ -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(*)
@@ -46,7 +54,7 @@ module Selenium
46
54
 
47
55
  # TODO: must be a better way..
48
56
  FileUtils.rm_rf tmp_directory
49
- FileUtils.mkdir_p File.dirname(tmp_directory), mode: 0700
57
+ FileUtils.mkdir_p File.dirname(tmp_directory), mode: 0o700
50
58
  FileUtils.cp_r directory, tmp_directory
51
59
 
52
60
  tmp_directory
@@ -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
@@ -1,5 +1,5 @@
1
- # encoding: utf-8
2
- #
1
+ # frozen_string_literal: true
2
+
3
3
  # Licensed to the Software Freedom Conservancy (SFC) under one
4
4
  # or more contributor license agreements. See the NOTICE file
5
5
  # distributed with this work for additional information
@@ -37,9 +37,10 @@ module Selenium
37
37
  auto_detect: 'autodetect',
38
38
  socks: 'socksProxy',
39
39
  socks_username: 'socksUsername',
40
- socks_password: 'socksPassword'}.freeze
40
+ socks_password: 'socksPassword',
41
+ socks_version: 'socksVersion'}.freeze
41
42
 
42
- ALLOWED.keys.each { |t| attr_reader t }
43
+ ALLOWED.each_key { |t| attr_reader t }
43
44
 
44
45
  def self.json_create(data)
45
46
  data['proxyType'] = data['proxyType'].downcase.to_sym
@@ -66,6 +67,7 @@ module Selenium
66
67
  end
67
68
 
68
69
  return if not_allowed.empty?
70
+
69
71
  raise ArgumentError, "unknown option#{'s' if not_allowed.size != 1}: #{not_allowed.inspect}"
70
72
  end
71
73
 
@@ -119,10 +121,13 @@ module Selenium
119
121
  @socks_password = value
120
122
  end
121
123
 
124
+ def socks_version=(value)
125
+ self.type = :manual
126
+ @socks_version = value
127
+ end
128
+
122
129
  def type=(type)
123
- unless TYPES.key? type
124
- raise ArgumentError, "invalid proxy type: #{type.inspect}, expected one of #{TYPES.keys.inspect}"
125
- end
130
+ raise ArgumentError, "invalid proxy type: #{type.inspect}, expected one of #{TYPES.keys.inspect}" unless TYPES.key? type
126
131
 
127
132
  if defined?(@type) && type != @type
128
133
  raise ArgumentError, "incompatible proxy type #{type.inspect} (already set to #{@type.inspect})"
@@ -142,7 +147,8 @@ module Selenium
142
147
  'autodetect' => auto_detect,
143
148
  'socksProxy' => socks,
144
149
  'socksUsername' => socks_username,
145
- 'socksPassword' => socks_password
150
+ 'socksPassword' => socks_password,
151
+ 'socksVersion' => socks_version
146
152
  }.delete_if { |_k, v| v.nil? }
147
153
 
148
154
  json_result if json_result.length > 1
@@ -1,5 +1,5 @@
1
- # encoding: utf-8
2
- #
1
+ # frozen_string_literal: true
2
+
3
3
  # Licensed to the Software Freedom Conservancy (SFC) under one
4
4
  # or more contributor license agreements. See the NOTICE file
5
5
  # distributed with this work for additional information
@@ -30,25 +30,29 @@ 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
36
37
 
37
38
  #
38
- # Find the first element matching the given arguments.
39
+ # Find the first element matching the given arguments
39
40
  #
40
41
  # When using Element#find_element with :xpath, be aware that webdriver
41
42
  # follows standard conventions: a search prefixed with "//" will search
42
43
  # the entire document, not just the children of this current node. Use
43
44
  # ".//" to limit your search to the children of the receiving Element.
44
45
  #
45
- # @param [:class, :class_name, :css, :id, :link_text, :link, :partial_link_text, :name, :tag_name, :xpath] how
46
- # @param [String] what
46
+ # @overload find_element(how, what)
47
+ # @param [Symbol, String] how The method to find the element by
48
+ # @param [String] what The locator to use
49
+ # @overload find_element(opts)
50
+ # @param [Hash] opts Find options
51
+ # @option opts [Symbol] :how Key named after the method to find the element by, containing the locator
47
52
  # @return [Element]
48
53
  #
49
54
  # @raise [Error::NoSuchElementError] if the element doesn't exist
50
55
  #
51
- #
52
56
 
53
57
  def find_element(*args)
54
58
  how, what = extract_args(args)
@@ -56,8 +60,8 @@ module Selenium
56
60
  by = FINDERS[how.to_sym]
57
61
  raise ArgumentError, "cannot find element by #{how.inspect}" unless by
58
62
 
59
- bridge.find_element_by by, what.to_s, ref
60
- rescue Selenium::WebDriver::Error::TimeOutError
63
+ bridge.find_element_by by, what, ref
64
+ rescue Selenium::WebDriver::Error::TimeoutError
61
65
  # Implicit Wait times out in Edge
62
66
  raise Selenium::WebDriver::Error::NoSuchElementError
63
67
  end
@@ -67,10 +71,6 @@ module Selenium
67
71
  #
68
72
  # @see SearchContext#find_element
69
73
  #
70
- # @param [:class, :class_name, :css, :id, :link_text, :link, :partial_link_text, :name, :tag_name, :xpath] how
71
- # @param [String] what
72
- # @return [Array<Element>]
73
- #
74
74
 
75
75
  def find_elements(*args)
76
76
  how, what = extract_args(args)
@@ -78,8 +78,8 @@ module Selenium
78
78
  by = FINDERS[how.to_sym]
79
79
  raise ArgumentError, "cannot find elements by #{how.inspect}" unless by
80
80
 
81
- bridge.find_elements_by by, what.to_s, ref
82
- rescue Selenium::WebDriver::Error::TimeOutError
81
+ bridge.find_elements_by by, what, ref
82
+ rescue Selenium::WebDriver::Error::TimeoutError
83
83
  # Implicit Wait times out in Edge
84
84
  []
85
85
  end
@@ -93,15 +93,11 @@ module Selenium
93
93
  when 1
94
94
  arg = args.first
95
95
 
96
- unless arg.respond_to?(:shift)
97
- raise ArgumentError, "expected #{arg.inspect}:#{arg.class} to respond to #shift"
98
- end
96
+ raise ArgumentError, "expected #{arg.inspect}:#{arg.class} to respond to #shift" unless arg.respond_to?(:shift)
99
97
 
100
98
  # this will be a single-entry hash, so use #shift over #first or #[]
101
99
  arr = arg.dup.shift
102
- unless arr.size == 2
103
- raise ArgumentError, "expected #{arr.inspect} to have 2 elements"
104
- end
100
+ raise ArgumentError, "expected #{arr.inspect} to have 2 elements" unless arr.size == 2
105
101
 
106
102
  arr
107
103
  else
@@ -1,5 +1,5 @@
1
- # encoding: utf-8
2
- #
1
+ # frozen_string_literal: true
2
+
3
3
  # Licensed to the Software Freedom Conservancy (SFC) under one
4
4
  # or more contributor license agreements. See the NOTICE file
5
5
  # distributed with this work for additional information
@@ -23,36 +23,77 @@ module Selenium
23
23
  # Base class implementing default behavior of service object,
24
24
  # responsible for starting and stopping driver implementations.
25
25
  #
26
- # Subclasses must implement the following private methods:
27
- # * #start_process
28
- # * #stop_server
29
- # * #cannot_connect_error_text
30
- #
31
- # @api private
32
- #
33
26
 
34
27
  class Service
35
- START_TIMEOUT = 20
28
+ START_TIMEOUT = 20
36
29
  SOCKET_LOCK_TIMEOUT = 45
37
- STOP_TIMEOUT = 20
30
+ STOP_TIMEOUT = 20
31
+
32
+ class << self
33
+ attr_reader :driver_path
34
+
35
+ def chrome(**opts)
36
+ Chrome::Service.new(**opts)
37
+ end
38
+
39
+ def firefox(**opts)
40
+ Firefox::Service.new(**opts)
41
+ end
42
+
43
+ def ie(**opts)
44
+ IE::Service.new(**opts)
45
+ end
46
+ alias_method :internet_explorer, :ie
47
+
48
+ def edge(**opts)
49
+ Edge::Service.new(**opts)
50
+ end
51
+
52
+ def edge_chrome(**opts)
53
+ EdgeChrome::Service.new(**opts)
54
+ end
55
+
56
+ def edge_html(**opts)
57
+ EdgeHtml::Service.new(**opts)
58
+ end
59
+
60
+ def safari(**opts)
61
+ Safari::Service.new(**opts)
62
+ end
63
+
64
+ def driver_path=(path)
65
+ Platform.assert_executable path if path.is_a?(String)
66
+ @driver_path = path
67
+ end
68
+ end
38
69
 
39
70
  attr_accessor :host
71
+ attr_reader :executable_path
72
+
73
+ #
74
+ # End users should use a class method for the desired driver, rather than using this directly.
75
+ #
76
+ # @api private
77
+ #
78
+
79
+ def initialize(path: nil, port: nil, args: nil)
80
+ path ||= self.class.driver_path
81
+ port ||= self.class::DEFAULT_PORT
82
+ args ||= []
83
+
84
+ @executable_path = binary_path(path)
85
+ @host = Platform.localhost
86
+ @port = Integer(port)
40
87
 
41
- def initialize(executable_path, port, *extra_args)
42
- @executable_path = executable_path
43
- @host = Platform.localhost
44
- @port = Integer(port)
45
- @extra_args = extra_args
88
+ @extra_args = args.is_a?(Hash) ? extract_service_args(args) : args
46
89
 
47
90
  raise Error::WebDriverError, "invalid port: #{@port}" if @port < 1
48
91
  end
49
92
 
50
93
  def start
51
- if process_running?
52
- raise "already started: #{uri.inspect} #{@executable_path.inspect}"
53
- end
94
+ raise "already started: #{uri.inspect} #{@executable_path.inspect}" if process_running?
54
95
 
55
- Platform.exit_hook { stop } # make sure we don't leave the server running
96
+ Platform.exit_hook(&method(:stop)) # make sure we don't leave the server running
56
97
 
57
98
  socket_lock.locked do
58
99
  find_free_port
@@ -62,8 +103,12 @@ module Selenium
62
103
  end
63
104
 
64
105
  def stop
65
- return if process_exited?
106
+ return unless self.class::SHUTDOWN_SUPPORTED
107
+
66
108
  stop_server
109
+ @process.poll_for_exit STOP_TIMEOUT
110
+ rescue ChildProcess::TimeoutError
111
+ nil # noop
67
112
  ensure
68
113
  stop_process
69
114
  end
@@ -74,6 +119,29 @@ module Selenium
74
119
 
75
120
  private
76
121
 
122
+ def binary_path(path = nil)
123
+ path = path.call if path.is_a?(Proc)
124
+ path ||= Platform.find_binary(self.class::EXECUTABLE)
125
+
126
+ raise Error::WebDriverError, self.class::MISSING_TEXT unless path
127
+
128
+ Platform.assert_executable path
129
+ path
130
+ end
131
+
132
+ def build_process(*command)
133
+ WebDriver.logger.debug("Executing Process #{command}")
134
+ @process = ChildProcess.build(*command)
135
+ if WebDriver.logger.debug?
136
+ @process.io.stdout = @process.io.stderr = WebDriver.logger.io
137
+ elsif Platform.jruby?
138
+ # Apparently we need to read the output of drivers on JRuby.
139
+ @process.io.stdout = @process.io.stderr = File.new(Platform.null_device, 'w')
140
+ end
141
+
142
+ @process
143
+ end
144
+
77
145
  def connect_to_server
78
146
  Net::HTTP.start(@host, @port) do |http|
79
147
  http.open_timeout = STOP_TIMEOUT / 2
@@ -88,21 +156,30 @@ module Selenium
88
156
  end
89
157
 
90
158
  def start_process
91
- raise NotImplementedError, 'subclass responsibility'
92
- end
93
-
94
- def stop_server
95
- raise NotImplementedError, 'subclass responsibility'
159
+ @process = build_process(@executable_path, "--port=#{@port}", *@extra_args)
160
+ # Note: this is a bug only in Windows 7
161
+ @process.leader = true unless Platform.windows?
162
+ @process.start
96
163
  end
97
164
 
98
165
  def stop_process
99
- @process.poll_for_exit STOP_TIMEOUT
100
- rescue ChildProcess::TimeoutError
166
+ return if process_exited?
167
+
101
168
  @process.stop STOP_TIMEOUT
169
+ @process.io.stdout.close if Platform.jruby? && !WebDriver.logger.debug?
170
+ end
171
+
172
+ def stop_server
173
+ return if process_exited?
174
+
175
+ connect_to_server do |http|
176
+ headers = WebDriver::Remote::Http::Common::DEFAULT_HEADERS.dup
177
+ http.get('/shutdown', headers)
178
+ end
102
179
  end
103
180
 
104
181
  def process_running?
105
- @process && @process.alive?
182
+ defined?(@process) && @process&.alive?
106
183
  end
107
184
 
108
185
  def process_exited?
@@ -112,16 +189,24 @@ module Selenium
112
189
  def connect_until_stable
113
190
  socket_poller = SocketPoller.new @host, @port, START_TIMEOUT
114
191
  return if socket_poller.connected?
192
+
115
193
  raise Error::WebDriverError, cannot_connect_error_text
116
194
  end
117
195
 
118
196
  def cannot_connect_error_text
119
- raise NotImplementedError, 'subclass responsibility'
197
+ "unable to connect to #{self.class::EXECUTABLE} #{@host}:#{@port}"
120
198
  end
121
199
 
122
200
  def socket_lock
123
201
  @socket_lock ||= SocketLock.new(@port - 1, SOCKET_LOCK_TIMEOUT)
124
202
  end
203
+
204
+ protected
205
+
206
+ def extract_service_args(driver_opts)
207
+ driver_opts.key?(:args) ? driver_opts.delete(:args) : []
208
+ end
209
+
125
210
  end # Service
126
211
  end # WebDriver
127
212
  end # Selenium