selenium-webdriver 2.53.4 → 3.0.0.beta1

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 (122) hide show
  1. data/CHANGES +24 -18
  2. data/README.md +2 -3
  3. data/lib/selenium/server.rb +64 -68
  4. data/lib/selenium/webdriver.rb +5 -9
  5. data/lib/selenium/webdriver/chrome.rb +18 -3
  6. data/lib/selenium/webdriver/chrome/bridge.rb +13 -16
  7. data/lib/selenium/webdriver/chrome/profile.rb +7 -9
  8. data/lib/selenium/webdriver/chrome/service.rb +8 -84
  9. data/lib/selenium/webdriver/common.rb +1 -2
  10. data/lib/selenium/webdriver/common/action_builder.rb +28 -38
  11. data/lib/selenium/webdriver/common/alert.rb +7 -10
  12. data/lib/selenium/webdriver/common/bridge_helper.rb +10 -15
  13. data/lib/selenium/webdriver/common/driver.rb +19 -28
  14. data/lib/selenium/webdriver/common/driver_extensions/has_input_devices.rb +0 -3
  15. data/lib/selenium/webdriver/common/driver_extensions/has_location.rb +4 -6
  16. data/lib/selenium/webdriver/common/driver_extensions/has_network_connection.rb +4 -5
  17. data/lib/selenium/webdriver/common/driver_extensions/has_remote_status.rb +0 -2
  18. data/lib/selenium/webdriver/common/driver_extensions/has_session_id.rb +0 -2
  19. data/lib/selenium/webdriver/common/driver_extensions/has_touch_screen.rb +0 -2
  20. data/lib/selenium/webdriver/common/driver_extensions/has_web_storage.rb +0 -3
  21. data/lib/selenium/webdriver/common/driver_extensions/rotatable.rb +3 -6
  22. data/lib/selenium/webdriver/common/driver_extensions/takes_screenshot.rb +2 -5
  23. data/lib/selenium/webdriver/common/driver_extensions/uploads_files.rb +2 -5
  24. data/lib/selenium/webdriver/common/element.rb +27 -29
  25. data/lib/selenium/webdriver/common/error.rb +17 -20
  26. data/lib/selenium/webdriver/common/file_reaper.rb +3 -9
  27. data/lib/selenium/webdriver/common/html5/local_storage.rb +6 -8
  28. data/lib/selenium/webdriver/common/html5/session_storage.rb +6 -8
  29. data/lib/selenium/webdriver/common/html5/shared_web_storage.rb +6 -15
  30. data/lib/selenium/webdriver/common/keyboard.rb +7 -12
  31. data/lib/selenium/webdriver/common/keys.rb +67 -69
  32. data/lib/selenium/webdriver/common/log_entry.rb +3 -4
  33. data/lib/selenium/webdriver/common/logs.rb +2 -4
  34. data/lib/selenium/webdriver/common/mouse.rb +9 -12
  35. data/lib/selenium/webdriver/common/navigation.rb +2 -4
  36. data/lib/selenium/webdriver/common/options.rb +16 -19
  37. data/lib/selenium/webdriver/common/platform.rb +61 -90
  38. data/lib/selenium/webdriver/common/port_prober.rb +1 -2
  39. data/lib/selenium/webdriver/common/profile_helper.rb +5 -8
  40. data/lib/selenium/webdriver/common/proxy.rb +58 -70
  41. data/lib/selenium/webdriver/common/search_context.rb +15 -19
  42. data/lib/selenium/webdriver/common/service.rb +127 -0
  43. data/lib/selenium/webdriver/common/socket_lock.rb +5 -11
  44. data/lib/selenium/webdriver/common/socket_poller.rb +4 -9
  45. data/lib/selenium/webdriver/common/target_locator.rb +11 -13
  46. data/lib/selenium/webdriver/common/timeouts.rb +4 -6
  47. data/lib/selenium/webdriver/common/touch_action_builder.rb +2 -4
  48. data/lib/selenium/webdriver/common/touch_screen.rb +15 -18
  49. data/lib/selenium/webdriver/common/w3c_error.rb +3 -6
  50. data/lib/selenium/webdriver/common/wait.rb +6 -11
  51. data/lib/selenium/webdriver/common/window.rb +12 -15
  52. data/lib/selenium/webdriver/common/zipper.rb +6 -8
  53. data/lib/selenium/webdriver/edge.rb +18 -3
  54. data/lib/selenium/webdriver/edge/bridge.rb +11 -16
  55. data/lib/selenium/webdriver/edge/legacy_support.rb +38 -39
  56. data/lib/selenium/webdriver/edge/service.rb +8 -82
  57. data/lib/selenium/webdriver/firefox.rb +25 -6
  58. data/lib/selenium/webdriver/firefox/binary.rb +37 -53
  59. data/lib/selenium/webdriver/firefox/bridge.rb +3 -6
  60. data/lib/selenium/webdriver/firefox/extension.rb +4 -6
  61. data/lib/selenium/webdriver/firefox/extension/prefs.json +1 -10
  62. data/lib/selenium/webdriver/firefox/extension/webdriver.xpi +0 -0
  63. data/lib/selenium/webdriver/firefox/launcher.rb +8 -11
  64. data/lib/selenium/webdriver/firefox/profile.rb +40 -42
  65. data/lib/selenium/webdriver/firefox/profiles_ini.rb +8 -15
  66. data/lib/selenium/webdriver/firefox/service.rb +23 -79
  67. data/lib/selenium/webdriver/firefox/util.rb +0 -2
  68. data/lib/selenium/webdriver/firefox/w3c_bridge.rb +2 -4
  69. data/lib/selenium/webdriver/ie.rb +16 -7
  70. data/lib/selenium/webdriver/ie/bridge.rb +16 -23
  71. data/lib/selenium/webdriver/{iphone.rb → ie/service.rb} +26 -4
  72. data/lib/selenium/webdriver/phantomjs.rb +8 -3
  73. data/lib/selenium/webdriver/phantomjs/bridge.rb +9 -11
  74. data/lib/selenium/webdriver/phantomjs/service.rb +17 -81
  75. data/lib/selenium/webdriver/remote.rb +0 -2
  76. data/lib/selenium/webdriver/remote/bridge.rb +193 -191
  77. data/lib/selenium/webdriver/remote/capabilities.rb +60 -90
  78. data/lib/selenium/webdriver/remote/commands.rb +197 -192
  79. data/lib/selenium/webdriver/remote/http/common.rb +15 -13
  80. data/lib/selenium/webdriver/remote/http/curb.rb +5 -9
  81. data/lib/selenium/webdriver/remote/http/default.rb +32 -37
  82. data/lib/selenium/webdriver/remote/http/persistent.rb +4 -6
  83. data/lib/selenium/webdriver/remote/response.rb +13 -21
  84. data/lib/selenium/webdriver/remote/server_error.rb +1 -3
  85. data/lib/selenium/webdriver/remote/w3c_bridge.rb +200 -195
  86. data/lib/selenium/webdriver/remote/w3c_capabilities.rb +38 -46
  87. data/lib/selenium/webdriver/remote/w3c_commands.rb +116 -113
  88. data/lib/selenium/webdriver/safari.rb +23 -7
  89. data/lib/selenium/{client/javascript_frameworks/jquery.rb → webdriver/safari/apple_bridge.rb} +28 -9
  90. data/lib/selenium/webdriver/safari/browser.rb +0 -2
  91. data/lib/selenium/webdriver/safari/{bridge.rb → legacy_bridge.rb} +12 -9
  92. data/lib/selenium/webdriver/safari/options.rb +3 -4
  93. data/lib/selenium/webdriver/safari/resources/client.js +56 -7255
  94. data/lib/selenium/webdriver/safari/server.rb +18 -24
  95. data/lib/selenium/{client/javascript_frameworks/prototype.rb → webdriver/safari/service.rb} +27 -9
  96. data/lib/selenium/webdriver/support.rb +1 -0
  97. data/lib/selenium/webdriver/support/abstract_event_listener.rb +17 -2
  98. data/lib/selenium/webdriver/support/block_event_listener.rb +1 -3
  99. data/lib/selenium/webdriver/support/color.rb +55 -38
  100. data/lib/selenium/webdriver/{android.rb → support/escaper.rb} +19 -4
  101. data/lib/selenium/webdriver/support/event_firing_bridge.rb +36 -38
  102. data/lib/selenium/webdriver/support/select.rb +33 -84
  103. data/selenium-webdriver.gemspec +23 -23
  104. metadata +19 -30
  105. data/lib/selenium-client.rb +0 -21
  106. data/lib/selenium/client.rb +0 -57
  107. data/lib/selenium/client/base.rb +0 -151
  108. data/lib/selenium/client/driver.rb +0 -29
  109. data/lib/selenium/client/errors.rb +0 -28
  110. data/lib/selenium/client/extensions.rb +0 -132
  111. data/lib/selenium/client/idiomatic.rb +0 -507
  112. data/lib/selenium/client/javascript_expression_builder.rb +0 -135
  113. data/lib/selenium/client/legacy_driver.rb +0 -1722
  114. data/lib/selenium/client/protocol.rb +0 -123
  115. data/lib/selenium/client/selenium_helper.rb +0 -49
  116. data/lib/selenium/rake/server_task.rb +0 -176
  117. data/lib/selenium/webdriver/android/bridge.rb +0 -68
  118. data/lib/selenium/webdriver/common/core_ext/base64.rb +0 -28
  119. data/lib/selenium/webdriver/common/core_ext/dir.rb +0 -61
  120. data/lib/selenium/webdriver/common/html5/location.rb +0 -19
  121. data/lib/selenium/webdriver/ie/server.rb +0 -133
  122. data/lib/selenium/webdriver/iphone/bridge.rb +0 -64
@@ -23,8 +23,8 @@ module Selenium
23
23
  module Http
24
24
  class Common
25
25
  MAX_REDIRECTS = 20 # same as chromium/gecko
26
- CONTENT_TYPE = "application/json"
27
- DEFAULT_HEADERS = { "Accept" => CONTENT_TYPE }
26
+ CONTENT_TYPE = 'application/json'.freeze
27
+ DEFAULT_HEADERS = {'Accept' => CONTENT_TYPE}.freeze
28
28
 
29
29
  attr_accessor :timeout
30
30
  attr_writer :server_url
@@ -38,22 +38,22 @@ module Selenium
38
38
  end
39
39
 
40
40
  def call(verb, url, command_hash)
41
- url = server_url.merge(url) unless url.kind_of?(URI)
41
+ url = server_url.merge(url) unless url.is_a?(URI)
42
42
  headers = DEFAULT_HEADERS.dup
43
- headers['Cache-Control'] = "no-cache" if verb == :get
43
+ headers['Cache-Control'] = 'no-cache' if verb == :get
44
44
 
45
45
  if command_hash
46
46
  payload = JSON.generate(command_hash)
47
- headers["Content-Type"] = "#{CONTENT_TYPE}; charset=utf-8"
48
- headers["Content-Length"] = payload.bytesize.to_s if [:post, :put].include?(verb)
47
+ headers['Content-Type'] = "#{CONTENT_TYPE}; charset=utf-8"
48
+ headers['Content-Length'] = payload.bytesize.to_s if [:post, :put].include?(verb)
49
49
 
50
50
  if $DEBUG
51
51
  puts " >>> #{url} | #{payload}"
52
52
  puts " > #{headers.inspect}"
53
53
  end
54
54
  elsif verb == :post
55
- payload = "{}"
56
- headers["Content-Length"] = "2"
55
+ payload = '{}'
56
+ headers['Content-Length'] = '2'
57
57
  end
58
58
 
59
59
  request verb, url, headers, payload
@@ -62,15 +62,18 @@ module Selenium
62
62
  private
63
63
 
64
64
  def server_url
65
- @server_url or raise Error::WebDriverError, "server_url not set"
65
+ return @server_url if @server_url
66
+ raise Error::WebDriverError, 'server_url not set'
66
67
  end
67
68
 
68
- def request(verb, url, headers, payload)
69
- raise NotImplementedError, "subclass responsibility"
69
+ def request(*)
70
+ raise NotImplementedError, 'subclass responsibility'
70
71
  end
71
72
 
72
73
  def create_response(code, body, content_type)
73
- code, body, content_type = code.to_i, body.to_s.strip, content_type.to_s
74
+ code = code.to_i
75
+ body = body.to_s.strip
76
+ content_type = content_type.to_s
74
77
  puts "<- #{body}\n" if $DEBUG
75
78
 
76
79
  if content_type.include? CONTENT_TYPE
@@ -85,7 +88,6 @@ module Selenium
85
88
  raise Error::WebDriverError, msg
86
89
  end
87
90
  end
88
-
89
91
  end # Common
90
92
  end # Http
91
93
  end # Remote
@@ -22,12 +22,10 @@ require 'curb'
22
22
  module Selenium
23
23
  module WebDriver
24
24
  module Remote
25
-
26
25
  # added for rescue
27
26
  Bridge::QUIT_ERRORS << Curl::Err::RecvError
28
27
 
29
28
  module Http
30
-
31
29
  #
32
30
  # An alternative to the default Net::HTTP client.
33
31
  #
@@ -42,15 +40,14 @@ module Selenium
42
40
  #
43
41
 
44
42
  class Curb < Common
45
-
46
43
  private
47
44
 
48
45
  def request(verb, url, headers, payload)
49
- client.url = url.to_s
46
+ client.url = url.to_s
50
47
 
51
48
  # workaround for http://github.com/taf2/curb/issues/issue/40
52
49
  # curb will handle this for us anyway
53
- headers.delete "Content-Length"
50
+ headers.delete 'Content-Length'
54
51
 
55
52
  client.headers = headers
56
53
 
@@ -62,10 +59,10 @@ module Selenium
62
59
  when :get
63
60
  client.http_get
64
61
  when :post
65
- client.post_body = payload || ""
62
+ client.post_body = payload || ''
66
63
  client.http_post
67
64
  when :put
68
- client.put_data = payload || ""
65
+ client.put_data = payload || ''
69
66
  client.http_put
70
67
  when :delete
71
68
  client.http_delete
@@ -85,12 +82,11 @@ module Selenium
85
82
  c.max_redirects = MAX_REDIRECTS
86
83
  c.follow_location = true
87
84
  c.timeout = @timeout if @timeout
88
- c.verbose = !!$DEBUG
85
+ c.verbose = $DEBUG
89
86
 
90
87
  c
91
88
  )
92
89
  end
93
-
94
90
  end # Curb
95
91
  end # Http
96
92
  end # Remote
@@ -24,7 +24,6 @@ module Selenium
24
24
  module WebDriver
25
25
  module Remote
26
26
  module Http
27
-
28
27
  # @api private
29
28
  class Default < Common
30
29
  attr_accessor :proxy
@@ -33,18 +32,18 @@ module Selenium
33
32
 
34
33
  def http
35
34
  @http ||= (
36
- http = new_http_client
37
- if server_url.scheme == "https"
38
- http.use_ssl = true
39
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
40
- end
35
+ http = new_http_client
36
+ if server_url.scheme == 'https'
37
+ http.use_ssl = true
38
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
39
+ end
41
40
 
42
- if @timeout
43
- http.open_timeout = @timeout
44
- http.read_timeout = @timeout
45
- end
41
+ if @timeout
42
+ http.open_timeout = @timeout
43
+ http.read_timeout = @timeout
44
+ end
46
45
 
47
- http
46
+ http
48
47
  )
49
48
  end
50
49
 
@@ -76,15 +75,12 @@ module Selenium
76
75
  retry
77
76
 
78
77
  rescue Errno::ECONNREFUSED => ex
79
- if use_proxy?
80
- raise ex.class, "using proxy: #{proxy.http}"
81
- else
82
- raise
83
- end
78
+ raise ex.class, "using proxy: #{proxy.http}" if use_proxy?
79
+ raise
84
80
  end
85
81
 
86
- if response.kind_of? Net::HTTPRedirection
87
- raise Error::WebDriverError, "too many redirects" if redirects >= MAX_REDIRECTS
82
+ if response.is_a? Net::HTTPRedirection
83
+ raise Error::WebDriverError, 'too many redirects' if redirects >= MAX_REDIRECTS
88
84
  request(:get, URI.parse(response['Location']), DEFAULT_HEADERS.dup, nil, redirects + 1)
89
85
  else
90
86
  create_response response.code, response.body, response.content_type
@@ -109,7 +105,8 @@ module Selenium
109
105
 
110
106
  def new_http_client
111
107
  if use_proxy?
112
- unless proxy.respond_to?(:http) && url = @proxy.http
108
+ url = @proxy.http
109
+ unless proxy.respond_to?(:http) && url
113
110
  raise Error::WebDriverError, "expected HTTP proxy, got #{@proxy.inspect}"
114
111
  end
115
112
 
@@ -124,13 +121,13 @@ module Selenium
124
121
 
125
122
  def proxy
126
123
  @proxy ||= (
127
- proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
128
- no_proxy = ENV['no_proxy'] || ENV['NO_PROXY']
124
+ proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
125
+ no_proxy = ENV['no_proxy'] || ENV['NO_PROXY']
129
126
 
130
- if proxy
131
- proxy = "http://#{proxy}" unless proxy.start_with?("http://")
132
- Proxy.new(:http => proxy, :no_proxy => no_proxy)
133
- end
127
+ if proxy
128
+ proxy = "http://#{proxy}" unless proxy.start_with?('http://')
129
+ Proxy.new(http: proxy, no_proxy: no_proxy)
130
+ end
134
131
  )
135
132
  end
136
133
 
@@ -138,24 +135,22 @@ module Selenium
138
135
  return false if proxy.nil?
139
136
 
140
137
  if proxy.no_proxy
141
- ignored = proxy.no_proxy.split(",").any? do |host|
142
- host == "*" ||
143
- host == server_url.host || (
144
- begin
145
- IPAddr.new(host).include?(server_url.host)
146
- rescue ArgumentError
147
- false
148
- end
149
- )
150
-
138
+ ignored = proxy.no_proxy.split(',').any? do |host|
139
+ host == '*' ||
140
+ host == server_url.host || (
141
+ begin
142
+ IPAddr.new(host).include?(server_url.host)
143
+ rescue ArgumentError
144
+ false
145
+ end
146
+ )
151
147
  end
152
148
 
153
- not ignored
149
+ !ignored
154
150
  else
155
151
  true
156
152
  end
157
153
  end
158
-
159
154
  end # Default
160
155
  end # Http
161
156
  end # Remote
@@ -23,10 +23,8 @@ module Selenium
23
23
  module WebDriver
24
24
  module Remote
25
25
  module Http
26
-
27
26
  # @api private
28
27
  class Persistent < Default
29
-
30
28
  def close
31
29
  @http.shutdown if @http
32
30
  end
@@ -37,19 +35,19 @@ module Selenium
37
35
  proxy = nil
38
36
 
39
37
  if @proxy
40
- unless @proxy.respond_to?(:http) && url = @proxy.http
41
- raise Error::WebDriverError, "expected HTTP proxy, got #{@proxy.inspect}"
38
+ unless @proxy.respond_to?(:http)
39
+ url = @proxy.http
40
+ raise Error::WebDriverError, "expected HTTP proxy, got #{@proxy.inspect}" unless url
42
41
  end
43
42
  proxy = URI.parse(url)
44
43
  end
45
44
 
46
- Net::HTTP::Persistent.new "webdriver", proxy
45
+ Net::HTTP::Persistent.new 'webdriver', proxy
47
46
  end
48
47
 
49
48
  def response_for(request)
50
49
  http.request server_url, request
51
50
  end
52
-
53
51
  end # Persistent
54
52
  end # Http
55
53
  end # Remote
@@ -20,10 +20,8 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  module Remote
23
-
24
23
  # @api private
25
24
  class Response
26
-
27
25
  attr_reader :code, :payload
28
26
  attr_writer :payload
29
27
 
@@ -49,9 +47,10 @@ module Selenium
49
47
 
50
48
  case val
51
49
  when Hash
52
- msg = val['message'] or return "unknown error"
53
- msg << ": #{val['alert']['text'].inspect}" if val['alert'].kind_of?(Hash) && val['alert']['text']
54
- msg << " (#{ val['class'] })" if val['class']
50
+ msg = val['message']
51
+ return 'unknown error' unless msg
52
+ msg << ": #{val['alert']['text'].inspect}" if val['alert'].is_a?(Hash) && val['alert']['text']
53
+ msg << " (#{val['class']})" if val['class']
55
54
  msg
56
55
  when String
57
56
  val
@@ -67,34 +66,28 @@ module Selenium
67
66
  private
68
67
 
69
68
  def assert_ok
70
- if e = error()
71
- raise e
72
- elsif @code.nil? || @code >= 400
73
- raise Error::ServerError, self
74
- end
69
+ e = error
70
+ raise e if e
71
+ return unless @code.nil? || @code >= 400
72
+ raise Error::ServerError, self
75
73
  end
76
74
 
77
75
  def add_backtrace(ex)
78
- unless value.kind_of?(Hash) && value['stackTrace']
79
- return
80
- end
76
+ return unless value.is_a?(Hash) && value['stackTrace']
81
77
 
82
78
  server_trace = value['stackTrace']
83
79
 
84
80
  backtrace = server_trace.map do |frame|
85
- next unless frame.kind_of?(Hash)
81
+ next unless frame.is_a?(Hash)
86
82
 
87
83
  file = frame['fileName']
88
84
  line = frame['lineNumber']
89
85
  meth = frame['methodName']
90
86
 
91
- if class_name = frame['className']
92
- file = "#{class_name}(#{file})"
93
- end
87
+ class_name = frame['className']
88
+ file = "#{class_name}(#{file})" if class_name
94
89
 
95
- if meth.nil? || meth.empty?
96
- meth = 'unknown'
97
- end
90
+ meth = 'unknown' if meth.nil? || meth.empty?
98
91
 
99
92
  "[remote server] #{file}:#{line}:in `#{meth}'"
100
93
  end.compact
@@ -109,7 +102,6 @@ module Selenium
109
102
  def value
110
103
  @payload['value'] || @payload['message']
111
104
  end
112
-
113
105
  end # Response
114
106
  end # Remote
115
107
  end # WebDriver
@@ -21,15 +21,13 @@ module Selenium
21
21
  module WebDriver
22
22
  module Error
23
23
  class ServerError < StandardError
24
-
25
24
  def initialize(response)
26
- if response.kind_of? String
25
+ if response.is_a? String
27
26
  super(response)
28
27
  else
29
28
  super("status code #{response.code}")
30
29
  end
31
30
  end
32
-
33
31
  end # ServerError
34
32
  end # Error
35
33
  end # WebDriver
@@ -22,7 +22,6 @@ require 'json'
22
22
  module Selenium
23
23
  module WebDriver
24
24
  module Remote
25
-
26
25
  #
27
26
  # Low level bridge to the remote server, through which the rest of the API works.
28
27
  #
@@ -32,6 +31,7 @@ module Selenium
32
31
  class W3CBridge
33
32
  include BridgeHelper
34
33
 
34
+ # TODO: constant shouldn't be modified in class
35
35
  COMMANDS = {}
36
36
 
37
37
  #
@@ -62,16 +62,13 @@ module Selenium
62
62
  #
63
63
 
64
64
  def initialize(opts = {})
65
- if opts.fetch(:desired_capabilities, {})[:browser_name] == 'MicrosoftEdge'
66
- require_relative '../edge/legacy_support'
67
- extend Edge::LegacySupport
68
- end
65
+ edge_check(opts)
69
66
 
70
67
  opts = opts.dup
71
68
 
72
- http_client = opts.delete(:http_client) { Http::Default.new }
69
+ http_client = opts.delete(:http_client) { Http::Default.new }
73
70
  desired_capabilities = opts.delete(:desired_capabilities) { W3CCapabilities.firefox }
74
- url = opts.delete(:url) { "http://#{Platform.localhost}:4444/wd/hub" }
71
+ url = opts.delete(:url) { "http://#{Platform.localhost}:4444/wd/hub" }
75
72
 
76
73
  desired_capabilities = W3CCapabilities.send(desired_capabilities) if desired_capabilities.is_a? Symbol
77
74
 
@@ -81,23 +78,30 @@ module Selenium
81
78
  raise ArgumentError, "unknown option#{'s' if opts.size != 1}: #{opts.inspect}"
82
79
  end
83
80
 
84
- uri = url.kind_of?(URI) ? url : URI.parse(url)
85
- uri.path += "/" unless uri.path =~ /\/$/
81
+ uri = url.is_a?(URI) ? url : URI.parse(url)
82
+ uri.path += '/' unless uri.path =~ %r{\/$}
86
83
 
87
84
  http_client.server_url = uri
88
85
 
89
- @http = http_client
90
- @capabilities = create_session(desired_capabilities)
86
+ @http = http_client
87
+ @capabilities = create_session(desired_capabilities)
91
88
  @file_detector = nil
92
89
  end
93
90
 
94
91
  def browser
95
92
  @browser ||= (
96
- name = @capabilities.browser_name
97
- name ? name.gsub(" ", "_").to_sym : 'unknown'
93
+ name = @capabilities.browser_name
94
+ name ? name.tr(' ', '_').to_sym : 'unknown'
98
95
  )
99
96
  end
100
97
 
98
+ def edge_check(opts)
99
+ caps = opts[:desired_capabilities]
100
+ return unless caps && caps[:browser_name] && caps[:browser_name] == 'MicrosoftEdge'
101
+ require_relative '../edge/legacy_support'
102
+ extend Edge::LegacySupport
103
+ end
104
+
101
105
  def driver_extensions
102
106
  [
103
107
  DriverExtensions::HasInputDevices,
@@ -116,14 +120,15 @@ module Selenium
116
120
  #
117
121
 
118
122
  def session_id
119
- @session_id || raise(Error::WebDriverError, "no current session exists")
123
+ @session_id || raise(Error::WebDriverError, 'no current session exists')
120
124
  end
121
125
 
122
126
  def create_session(desired_capabilities)
123
- resp = raw_execute :newSession, {}, :desiredCapabilities => desired_capabilities
124
- @session_id = resp['sessionId'] or raise Error::WebDriverError, 'no sessionId in returned payload'
127
+ resp = raw_execute :newSession, {}, {desiredCapabilities: desired_capabilities}
128
+ @session_id = resp['sessionId']
129
+ return W3CCapabilities.json_create resp['value'] if @session_id
125
130
 
126
- W3CCapabilities.json_create resp['value']
131
+ raise Error::WebDriverError, 'no sessionId in returned payload'
127
132
  end
128
133
 
129
134
  def status
@@ -133,38 +138,38 @@ module Selenium
133
138
  end
134
139
 
135
140
  def get(url)
136
- execute :get, {}, :url => url
141
+ execute :get, {}, {url: url}
137
142
  end
138
143
 
139
- def setImplicitWaitTimeout(milliseconds)
140
- setTimeout('implicit', milliseconds)
144
+ def implicit_wait_timeout=(milliseconds)
145
+ timeout('implicit', milliseconds)
141
146
  end
142
147
 
143
- def setScriptTimeout(milliseconds)
144
- setTimeout('script', milliseconds)
148
+ def script_timeout=(milliseconds)
149
+ timeout('script', milliseconds)
145
150
  end
146
151
 
147
- def setTimeout(type, milliseconds)
148
- execute :setTimeout, {}, :type => type, :ms => milliseconds
152
+ def timeout(type, milliseconds)
153
+ execute :setTimeout, {}, {type: type, ms: milliseconds}
149
154
  end
150
155
 
151
156
  #
152
157
  # alerts
153
158
  #
154
159
 
155
- def acceptAlert
160
+ def accept_alert
156
161
  execute :acceptAlert
157
162
  end
158
163
 
159
- def dismissAlert
164
+ def dismiss_alert
160
165
  execute :dismissAlert
161
166
  end
162
167
 
163
- def setAlertValue(keys)
164
- execute :sendAlertText, {}, {:handler => 'prompt', :message => keys}
168
+ def alert=(keys)
169
+ execute :sendAlertText, {}, {handler: 'prompt', text: keys}
165
170
  end
166
171
 
167
- def getAlertText
172
+ def alert_text
168
173
  execute :getAlertText
169
174
  end
170
175
 
@@ -172,46 +177,46 @@ module Selenium
172
177
  # navigation
173
178
  #
174
179
 
175
- def goBack
180
+ def go_back
176
181
  execute :back
177
182
  end
178
183
 
179
- def goForward
184
+ def go_forward
180
185
  execute :forward
181
186
  end
182
187
 
183
- def getCurrentUrl
188
+ def url
184
189
  execute :getCurrentUrl
185
190
  end
186
191
 
187
- def getTitle
192
+ def title
188
193
  execute :getTitle
189
194
  end
190
195
 
191
- def getPageSource
192
- executeScript("var source = document.documentElement.outerHTML;" +
193
- "if (!source) { source = new XMLSerializer().serializeToString(document); }" +
194
- "return source;")
196
+ def page_source
197
+ execute_script('var source = document.documentElement.outerHTML;' \
198
+ 'if (!source) { source = new XMLSerializer().serializeToString(document); }' \
199
+ 'return source;')
195
200
  end
196
201
 
197
- def switchToWindow(name)
198
- execute :switchToWindow, {}, :handle => name
202
+ def switch_to_window(name)
203
+ execute :switchToWindow, {}, {handle: name}
199
204
  end
200
205
 
201
- def switchToFrame(id)
206
+ def switch_to_frame(id)
202
207
  id = find_element_by('id', id) if id.is_a? String
203
- execute :switchToFrame, {}, :id => id
208
+ execute :switchToFrame, {}, {id: id}
204
209
  end
205
210
 
206
- def switchToParentFrame
211
+ def switch_to_parent_frame
207
212
  execute :switchToParentFrame
208
213
  end
209
214
 
210
- def switchToDefaultContent
211
- switchToFrame nil
215
+ def switch_to_default_content
216
+ switch_to_frame nil
212
217
  end
213
218
 
214
- QUIT_ERRORS = [IOError]
219
+ QUIT_ERRORS = [IOError].freeze
215
220
 
216
221
  def quit
217
222
  execute :deleteSession
@@ -231,34 +236,34 @@ module Selenium
231
236
  # window handling
232
237
  #
233
238
 
234
- def getWindowHandles
239
+ def window_handles
235
240
  execute :getWindowHandles
236
241
  end
237
242
 
238
- def getCurrentWindowHandle
243
+ def window_handle
239
244
  execute :getWindowHandle
240
245
  end
241
246
 
242
- def setWindowSize(width, height, handle = :current)
247
+ def resize_window(width, height, handle = :current)
243
248
  unless handle == :current
244
249
  raise Error::WebDriverError, 'Switch to desired window before changing its size'
245
250
  end
246
- execute :setWindowSize, {}, {:width => width,
247
- :height => height}
251
+ execute :setWindowSize, {}, {width: width,
252
+ height: height}
248
253
  end
249
254
 
250
- def maximizeWindow(handle = :current)
255
+ def maximize_window(handle = :current)
251
256
  unless handle == :current
252
257
  raise Error::UnsupportedOperationError, 'Switch to desired window before changing its size'
253
258
  end
254
259
  execute :maximizeWindow
255
260
  end
256
261
 
257
- def fullscreenWindow
262
+ def full_screen_window
258
263
  execute :fullscreenWindow
259
264
  end
260
265
 
261
- def getWindowSize(handle = :current)
266
+ def window_size(handle = :current)
262
267
  unless handle == :current
263
268
  raise Error::UnsupportedOperationError, 'Switch to desired window before getting its size'
264
269
  end
@@ -267,15 +272,15 @@ module Selenium
267
272
  Dimension.new data['width'], data['height']
268
273
  end
269
274
 
270
- def setWindowPosition(_x, _y, _handle = nil)
275
+ def reposition_window(_x, _y, _handle = nil)
271
276
  raise Error::UnsupportedOperationError, 'The W3C standard does not currently support setting the Window Position'
272
277
  end
273
278
 
274
- def getWindowPosition(_handle = nil)
279
+ def window_position(_handle = nil)
275
280
  raise Error::UnsupportedOperationError, 'The W3C standard does not currently support getting the Window Position'
276
281
  end
277
282
 
278
- def getScreenshot
283
+ def screenshot
279
284
  execute :takeScreenshot
280
285
  end
281
286
 
@@ -283,67 +288,67 @@ module Selenium
283
288
  # HTML 5
284
289
  #
285
290
 
286
- def getLocalStorageItem(key)
287
- executeScript("return localStorage.getItem('#{key}')")
288
- end
289
-
290
- def removeLocalStorageItem(key)
291
- executeScript("localStorage.removeItem('#{key}')")
292
- end
293
-
294
- def getLocalStorageKeys
295
- executeScript("return Object.keys(localStorage)")
291
+ def local_storage_item(key, value = nil)
292
+ if value
293
+ execute_script("localStorage.setItem('#{key}', '#{value}')")
294
+ else
295
+ execute_script("return localStorage.getItem('#{key}')")
296
+ end
296
297
  end
297
298
 
298
- def setLocalStorageItem(key, value)
299
- executeScript("localStorage.setItem('#{key}', '#{value}')")
299
+ def remove_local_storage_item(key)
300
+ execute_script("localStorage.removeItem('#{key}')")
300
301
  end
301
302
 
302
- def clearLocalStorage
303
- executeScript("localStorage.clear()")
303
+ def local_storage_keys
304
+ execute_script('return Object.keys(localStorage)')
304
305
  end
305
306
 
306
- def getLocalStorageSize
307
- executeScript("return localStorage.length")
307
+ def clear_local_storage
308
+ execute_script('localStorage.clear()')
308
309
  end
309
310
 
310
- def getSessionStorageItem(key)
311
- executeScript("return sessionStorage.getItem('#{key}')")
311
+ def local_storage_size
312
+ execute_script('return localStorage.length')
312
313
  end
313
314
 
314
- def removeSessionStorageItem(key)
315
- executeScript("sessionStorage.removeItem('#{key}')")
315
+ def session_storage_item(key, value = nil)
316
+ if value
317
+ execute_script("sessionStorage.setItem('#{key}', '#{value}')")
318
+ else
319
+ execute_script("return sessionStorage.getItem('#{key}')")
320
+ end
316
321
  end
317
322
 
318
- def getSessionStorageKeys
319
- executeScript("return Object.keys(sessionStorage)")
323
+ def remove_session_storage_item(key)
324
+ execute_script("sessionStorage.removeItem('#{key}')")
320
325
  end
321
326
 
322
- def setSessionStorageItem(key, value)
323
- executeScript("sessionStorage.setItem('#{key}', '#{value}')")
327
+ def session_storage_keys
328
+ execute_script('return Object.keys(sessionStorage)')
324
329
  end
325
330
 
326
- def clearSessionStorage
327
- executeScript("sessionStorage.clear()")
331
+ def clear_session_storage
332
+ execute_script('sessionStorage.clear()')
328
333
  end
329
334
 
330
- def getSessionStorageSize
331
- executeScript("return sessionStorage.length")
335
+ def session_storage_size
336
+ execute_script('return sessionStorage.length')
332
337
  end
333
338
 
334
- def getLocation
339
+ def location
335
340
  raise Error::UnsupportedOperationError, 'The W3C standard does not currently support getting location'
336
341
  end
337
342
 
338
- def setLocation(_lat, _lon, _alt)
343
+ def set_location(_lat, _lon, _alt)
339
344
  raise Error::UnsupportedOperationError, 'The W3C standard does not currently support setting location'
340
345
  end
341
346
 
342
- def getNetworkConnection
347
+ def network_connection
343
348
  raise Error::UnsupportedOperationError, 'The W3C standard does not currently support getting network connection'
344
349
  end
345
350
 
346
- def setNetworkConnection(_type)
351
+ def network_connection=(_type)
347
352
  raise Error::UnsupportedOperationError, 'The W3C standard does not currently support setting network connection'
348
353
  end
349
354
 
@@ -351,13 +356,13 @@ module Selenium
351
356
  # javascript execution
352
357
  #
353
358
 
354
- def executeScript(script, *args)
355
- result = execute :executeScript, {}, :script => script, :args => args
359
+ def execute_script(script, *args)
360
+ result = execute :executeScript, {}, {script: script, args: args}
356
361
  unwrap_script_result result
357
362
  end
358
363
 
359
- def executeAsyncScript(script, *args)
360
- result = execute :executeAsyncScript, {}, :script => script, :args => args
364
+ def execute_async_script(script, *args)
365
+ result = execute :executeAsyncScript, {}, {script: script, args: args}
361
366
  unwrap_script_result result
362
367
  end
363
368
 
@@ -365,139 +370,139 @@ module Selenium
365
370
  # cookies
366
371
  #
367
372
 
368
- def addCookie(cookie)
369
- execute :addCookie, {}, :cookie => cookie
373
+ def add_cookie(cookie)
374
+ execute :addCookie, {}, {cookie: cookie}
370
375
  end
371
376
 
372
- def deleteCookie(name)
373
- execute :deleteCookie, :name => name
377
+ def delete_cookie(name)
378
+ execute :deleteCookie, name: name
374
379
  end
375
380
 
376
- # TODO - write specs
377
- def getCookie(name)
378
- execute :getCookie, :name => name
381
+ # TODO: - write specs
382
+ def cookie(name)
383
+ execute :getCookie, name: name
379
384
  end
380
385
 
381
- def getAllCookies
386
+ def cookies
382
387
  execute :getAllCookies
383
388
  end
384
389
 
385
- def deleteAllCookies
386
- getAllCookies.each { |cookie| deleteCookie(cookie['name'])}
390
+ def delete_all_cookies
391
+ cookies.each { |cookie| delete_cookie(cookie['name']) }
387
392
  end
388
393
 
389
394
  #
390
395
  # actions
391
396
  #
392
397
 
393
- def clickElement(element)
394
- execute :elementClick, :id => element
398
+ def click_element(element)
399
+ execute :elementClick, id: element
395
400
  end
396
401
 
397
402
  def click
398
- execute :click, {}, :button => 0
403
+ execute :click, {}, {button: 0}
399
404
  end
400
405
 
401
- def doubleClick
406
+ def double_click
402
407
  execute :doubleClick
403
408
  end
404
409
 
405
- def contextClick
406
- execute :click, {}, :button => 2
410
+ def context_click
411
+ execute :click, {}, {button: 2}
407
412
  end
408
413
 
409
- def mouseDown
414
+ def mouse_down
410
415
  execute :mouseDown
411
416
  end
412
417
 
413
- def mouseUp
418
+ def mouse_up
414
419
  execute :mouseUp
415
420
  end
416
421
 
417
- def mouseMoveTo(element, x = nil, y = nil)
418
- params = { :element => element }
422
+ def mouse_move_to(element, x = nil, y = nil)
423
+ params = {element: element}
419
424
 
420
425
  if x && y
421
- params.merge! :xoffset => x, :yoffset => y
426
+ params[:xoffset] = x
427
+ params[:yoffset] = y
422
428
  end
423
429
 
424
430
  execute :mouseMoveTo, {}, params
425
431
  end
426
432
 
427
- def sendKeysToActiveElement(keys)
428
- sendKeysToElement(getActiveElement, keys)
433
+ def send_keys_to_active_element(keys)
434
+ send_keys_to_element(active_element, keys)
429
435
  end
430
436
 
431
- # TODO - Implement file verification
432
- def sendKeysToElement(element, keys)
433
- execute :elementSendKeys, {:id => element}, {:value => keys.join('').split(//)}
437
+ # TODO: - Implement file verification
438
+ def send_keys_to_element(element, keys)
439
+ execute :elementSendKeys, {id: element}, {value: keys.join('').split(//)}
434
440
  end
435
441
 
436
- def clearElement(element)
437
- execute :elementClear, :id => element
442
+ def clear_element(element)
443
+ execute :elementClear, id: element
438
444
  end
439
445
 
440
- def submitElement(element)
441
- executeScript("var e = arguments[0].ownerDocument.createEvent('Event');" +
442
- "e.initEvent('submit', true, true);" +
443
- "if (arguments[0].dispatchEvent(e)) { arguments[0].submit() }", element)
446
+ def submit_element(element)
447
+ execute_script("var e = arguments[0].ownerDocument.createEvent('Event');" \
448
+ "e.initEvent('submit', true, true);" \
449
+ 'if (arguments[0].dispatchEvent(e)) { arguments[0].submit() }', element)
444
450
  end
445
451
 
446
- def dragElement(element, right_by, down_by)
447
- execute :dragElement, {:id => element}, :x => right_by, :y => down_by
452
+ def drag_element(element, right_by, down_by)
453
+ execute :dragElement, {id: element}, {x: right_by, y: down_by}
448
454
  end
449
455
 
450
- def touchSingleTap(element)
451
- execute :touchSingleTap, {}, :element => element
456
+ def touch_single_tap(element)
457
+ execute :touchSingleTap, {}, {element: element}
452
458
  end
453
459
 
454
- def touchDoubleTap(element)
455
- execute :touchDoubleTap, {}, :element => element
460
+ def touch_double_tap(element)
461
+ execute :touchDoubleTap, {}, {element: element}
456
462
  end
457
463
 
458
- def touchLongPress(element)
459
- execute :touchLongPress, {}, :element => element
464
+ def touch_long_press(element)
465
+ execute :touchLongPress, {}, {element: element}
460
466
  end
461
467
 
462
- def touchDown(x, y)
463
- execute :touchDown, {}, :x => x, :y => y
468
+ def touch_down(x, y)
469
+ execute :touchDown, {}, {x: x, y: y}
464
470
  end
465
471
 
466
- def touchUp(x, y)
467
- execute :touchUp, {}, :x => x, :y => y
472
+ def touch_up(x, y)
473
+ execute :touchUp, {}, {x: x, y: y}
468
474
  end
469
475
 
470
- def touchMove(x, y)
471
- execute :touchMove, {}, :x => x, :y => y
476
+ def touch_move(x, y)
477
+ execute :touchMove, {}, {x: x, y: y}
472
478
  end
473
479
 
474
- def touchScroll(element, x, y)
480
+ def touch_scroll(element, x, y)
475
481
  if element
476
- execute :touchScroll, {}, :element => element,
477
- :xoffset => x,
478
- :yoffset => y
482
+ execute :touchScroll, {}, {element: element,
483
+ xoffset: x,
484
+ yoffset: y}
479
485
  else
480
- execute :touchScroll, {}, :xoffset => x, :yoffset => y
486
+ execute :touchScroll, {}, {xoffset: x, yoffset: y}
481
487
  end
482
488
  end
483
489
 
484
- def touchFlick(xspeed, yspeed)
485
- execute :touchFlick, {}, :xspeed => xspeed, :yspeed => yspeed
490
+ def touch_flick(xspeed, yspeed)
491
+ execute :touchFlick, {}, {xspeed: xspeed, yspeed: yspeed}
486
492
  end
487
493
 
488
- def touchElementFlick(element, right_by, down_by, speed)
489
- execute :touchFlick, {}, :element => element,
490
- :xoffset => right_by,
491
- :yoffset => down_by,
492
- :speed => speed
493
-
494
+ def touch_element_flick(element, right_by, down_by, speed)
495
+ execute :touchFlick, {}, {element: element,
496
+ xoffset: right_by,
497
+ yoffset: down_by,
498
+ speed: speed}
494
499
  end
495
500
 
496
- def setScreenOrientation(orientation)
497
- execute :setScreenOrientation, {}, :orientation => orientation
501
+ def screen_orientation=(orientation)
502
+ execute :setScreenOrientation, {}, {orientation: orientation}
498
503
  end
499
504
 
500
- def getScreenOrientation
505
+ def screen_orientation
501
506
  execute :getScreenOrientation
502
507
  end
503
508
 
@@ -505,74 +510,75 @@ module Selenium
505
510
  # element properties
506
511
  #
507
512
 
508
- def getElementTagName(element)
509
- execute :getElementTagName, :id => element
513
+ def element_tag_name(element)
514
+ execute :getElementTagName, id: element
510
515
  end
511
516
 
512
- def getElementAttribute(element, name)
513
- execute :getElementAttribute, :id => element, :name => name
517
+ def element_attribute(element, name)
518
+ execute :getElementAttribute, id: element, name: name
514
519
  end
515
520
 
516
- def getElementValue(element)
517
- execute :getElementProperty, :id => element, :name => 'value'
521
+ def element_value(element)
522
+ execute :getElementProperty, id: element, name: 'value'
518
523
  end
519
524
 
520
- def getElementText(element)
521
- execute :getElementText, :id => element
525
+ def element_text(element)
526
+ execute :getElementText, id: element
522
527
  end
523
528
 
524
- def getElementLocation(element)
525
- data = execute :getElementRect, :id => element
529
+ def element_location(element)
530
+ data = execute :getElementRect, id: element
526
531
 
527
532
  Point.new data['x'], data['y']
528
533
  end
529
534
 
530
- def getElementLocationOnceScrolledIntoView(element)
531
- sendKeysToElement(element, [''])
532
- getElementLocation(element)
535
+ def element_location_once_scrolled_into_view(element)
536
+ send_keys_to_element(element, [''])
537
+ element_location(element)
533
538
  end
534
539
 
535
- def getElementSize(element)
536
- data = execute :getElementRect, :id => element
540
+ def element_size(element)
541
+ data = execute :getElementRect, id: element
537
542
 
538
543
  Dimension.new data['width'], data['height']
539
544
  end
540
545
 
541
- def isElementEnabled(element)
542
- execute :isElementEnabled, :id => element
546
+ def element_enabled?(element)
547
+ execute :isElementEnabled, id: element
543
548
  end
544
549
 
545
- def isElementSelected(element)
546
- execute :isElementSelected, :id => element
550
+ def element_selected?(element)
551
+ execute :isElementSelected, id: element
547
552
  end
548
553
 
549
- def isElementDisplayed(element)
554
+ def element_displayed?(element)
550
555
  jwp = Selenium::WebDriver::Remote::Bridge::COMMANDS[:isElementDisplayed]
551
556
  self.class.command(:isElementDisplayed, jwp.first, jwp.last)
552
- execute :isElementDisplayed, :id => element
557
+ execute :isElementDisplayed, id: element
553
558
  end
554
559
 
555
- def getElementValueOfCssProperty(element, prop)
556
- execute :getElementCssValue, :id => element, :property_name => prop
560
+ def element_value_of_css_property(element, prop)
561
+ execute :getElementCssValue, id: element, property_name: prop
557
562
  end
558
563
 
559
564
  #
560
565
  # finding elements
561
566
  #
562
567
 
563
- def getActiveElement
568
+ def active_element
564
569
  Element.new self, element_id_from(execute(:getActiveElement))
565
570
  end
566
- alias_method :switchToActiveElement, :getActiveElement
571
+
572
+ alias_method :switch_to_active_element, :active_element
567
573
 
568
574
  def find_element_by(how, what, parent = nil)
569
575
  how, what = convert_locators(how, what)
570
576
 
571
- if parent
572
- id = execute :findChildElement, {:id => parent}, {:using => how, :value => what}
573
- else
574
- id = execute :findElement, {}, {:using => how, :value => what}
575
- end
577
+ id = if parent
578
+ execute :findChildElement, {id: parent}, {using: how, value: what}
579
+ else
580
+ execute :findElement, {}, {using: how, value: what}
581
+ end
576
582
 
577
583
  Element.new self, element_id_from(id)
578
584
  end
@@ -580,11 +586,11 @@ module Selenium
580
586
  def find_elements_by(how, what, parent = nil)
581
587
  how, what = convert_locators(how, what)
582
588
 
583
- if parent
584
- ids = execute :findChildElements, {:id => parent}, {:using => how, :value => what}
585
- else
586
- ids = execute :findElements, {}, {:using => how, :value => what}
587
- end
589
+ ids = if parent
590
+ execute :findChildElements, {id: parent}, {using: how, value: what}
591
+ else
592
+ execute :findElements, {}, {using: how, value: what}
593
+ end
588
594
 
589
595
  ids.map { |id| Element.new self, element_id_from(id) }
590
596
  end
@@ -605,7 +611,7 @@ module Selenium
605
611
  when 'tag name'
606
612
  how = 'css selector'
607
613
  end
608
- return how, what
614
+ [how, what]
609
615
  end
610
616
 
611
617
  #
@@ -628,14 +634,14 @@ module Selenium
628
634
 
629
635
  def raw_execute(command, opts = {}, command_hash = nil)
630
636
  verb, path = COMMANDS[command] || raise(ArgumentError, "unknown command: #{command.inspect}")
631
- path = path.dup
637
+ path = path.dup
632
638
 
633
- path[':session_id'] = @session_id if path.include?(":session_id")
639
+ path[':session_id'] = @session_id if path.include?(':session_id')
634
640
 
635
641
  begin
636
- opts.each { |key, value|
642
+ opts.each do |key, value|
637
643
  path[key.inspect] = escaper.escape(value.to_s)
638
- }
644
+ end
639
645
  rescue IndexError
640
646
  raise ArgumentError, "#{opts.inspect} invalid for #{command.inspect}"
641
647
  end
@@ -661,7 +667,6 @@ module Selenium
661
667
 
662
668
  string
663
669
  end
664
-
665
670
  end # W3CBridge
666
671
  end # Remote
667
672
  end # WebDriver