selenium-webdriver 2.47.1 → 2.48.0

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 (31) hide show
  1. data/CHANGES +18 -0
  2. data/Gemfile.lock +1 -1
  3. data/lib/selenium/client/base.rb +3 -3
  4. data/lib/selenium/client/extensions.rb +15 -18
  5. data/lib/selenium/client/idiomatic.rb +26 -26
  6. data/lib/selenium/client/javascript_expression_builder.rb +8 -8
  7. data/lib/selenium/webdriver/common.rb +1 -1
  8. data/lib/selenium/webdriver/common/driver.rb +5 -1
  9. data/lib/selenium/webdriver/common/error.rb +5 -8
  10. data/lib/selenium/webdriver/common/search_context.rb +6 -0
  11. data/lib/selenium/webdriver/common/target_locator.rb +4 -0
  12. data/lib/selenium/webdriver/common/w3c_error.rb +194 -0
  13. data/lib/selenium/webdriver/firefox.rb +3 -0
  14. data/lib/selenium/webdriver/firefox/binary.rb +13 -0
  15. data/lib/selenium/webdriver/firefox/extension/prefs.json +2 -1
  16. data/lib/selenium/webdriver/firefox/extension/webdriver.xpi +0 -0
  17. data/lib/selenium/webdriver/firefox/service.rb +120 -0
  18. data/lib/selenium/webdriver/firefox/w3c_bridge.rb +97 -0
  19. data/lib/selenium/webdriver/remote.rb +3 -0
  20. data/lib/selenium/webdriver/remote/bridge.rb +17 -10
  21. data/lib/selenium/webdriver/remote/capabilities.rb +1 -2
  22. data/lib/selenium/webdriver/remote/commands.rb +5 -0
  23. data/lib/selenium/webdriver/remote/response.rb +5 -6
  24. data/lib/selenium/webdriver/remote/w3c_bridge.rb +676 -0
  25. data/lib/selenium/webdriver/remote/w3c_capabilities.rb +208 -0
  26. data/lib/selenium/webdriver/remote/w3c_commands.rb +133 -0
  27. data/lib/selenium/webdriver/safari/resources/client.js +653 -629
  28. data/selenium-webdriver.gemspec +1 -1
  29. metadata +8 -4
  30. data/lib/selenium/webdriver/common/core_ext/string.rb +0 -24
  31. data/lib/selenium/webdriver/safari/resources/SafariDriver.safariextz +0 -0
@@ -28,6 +28,9 @@ require 'selenium/webdriver/firefox/profiles_ini'
28
28
  require 'selenium/webdriver/firefox/profile'
29
29
  require 'selenium/webdriver/firefox/launcher'
30
30
  require 'selenium/webdriver/firefox/bridge'
31
+ require 'selenium/webdriver/firefox/w3c_bridge'
32
+ require 'selenium/webdriver/firefox/binary'
33
+ require 'selenium/webdriver/firefox/service'
31
34
 
32
35
  module Selenium
33
36
  module WebDriver
@@ -149,6 +149,19 @@ module Selenium
149
149
  @path
150
150
  end
151
151
 
152
+ def version
153
+ @version = case Platform.os
154
+ when :macosx
155
+ `#{path} -v`.strip[/[^\s]*$/][/^\d+/].to_i
156
+ when :windows
157
+ `\"#{path}\" -v | more`.strip[/[^\s]*$/][/^\d+/].to_i
158
+ when :linux
159
+ `#{path} -v`.strip[/[^\s]*$/][/^\d+/].to_i
160
+ else
161
+ 0
162
+ end
163
+ end
164
+
152
165
  private
153
166
 
154
167
  def windows_path
@@ -52,7 +52,8 @@
52
52
  "toolkit.networkmanager.disable": true,
53
53
  "toolkit.telemetry.prompted": 2,
54
54
  "toolkit.telemetry.enabled": false,
55
- "toolkit.telemetry.rejected": true
55
+ "toolkit.telemetry.rejected": true,
56
+ "xpinstall.signatures.required": false
56
57
  },
57
58
  "mutable": {
58
59
  "browser.dom.window.dump.enabled": true,
@@ -0,0 +1,120 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Licensed to the Software Freedom Conservancy (SFC) under one
4
+ # or more contributor license agreements. See the NOTICE file
5
+ # distributed with this work for additional information
6
+ # regarding copyright ownership. The SFC licenses this file
7
+ # to you under the Apache License, Version 2.0 (the
8
+ # "License"); you may not use this file except in compliance
9
+ # with the License. You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing,
14
+ # software distributed under the License is distributed on an
15
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ # KIND, either express or implied. See the License for the
17
+ # specific language governing permissions and limitations
18
+ # under the License.
19
+
20
+ module Selenium
21
+ module WebDriver
22
+ module Firefox
23
+
24
+ #
25
+ # @api private
26
+ #
27
+ class Service
28
+ START_TIMEOUT = 20
29
+ SOCKET_LOCK_TIMEOUT = 45
30
+ STOP_TIMEOUT = 5
31
+ DEFAULT_PORT = 4444
32
+ MISSING_TEXT = "Unable to find Mozilla Wires. Please download the executable from https://github.com/jgraham/wires/releases"
33
+
34
+ def self.executable_path
35
+ @executable_path ||= (
36
+ path = Platform.find_binary "wires"
37
+ path or raise Error::WebDriverError, MISSING_TEXT
38
+ Platform.assert_executable path
39
+
40
+ path
41
+ )
42
+ end
43
+
44
+ def self.executable_path=(path)
45
+ Platform.assert_executable path
46
+ @executable_path = path
47
+ end
48
+
49
+ def self.default_service(*extra_args)
50
+ new executable_path, DEFAULT_PORT, *extra_args
51
+ end
52
+
53
+ def initialize(executable_path, port, *extra_args)
54
+ @executable_path = executable_path
55
+ @host = Platform.localhost
56
+ @port = Integer(port)
57
+
58
+ raise Error::WebDriverError, "invalid port: #{@port}" if @port < 1
59
+
60
+ @extra_args = extra_args
61
+ end
62
+
63
+ def start
64
+ Platform.exit_hook { stop } # make sure we don't leave the server running
65
+
66
+ socket_lock.locked do
67
+ find_free_port
68
+ start_process
69
+ connect_until_stable
70
+ end
71
+ end
72
+
73
+ def stop
74
+ return if @process.nil? || @process.exited?
75
+
76
+ Net::HTTP.start(@host, @port) do |http|
77
+ http.open_timeout = STOP_TIMEOUT / 2
78
+ http.read_timeout = STOP_TIMEOUT / 2
79
+
80
+ http.head("/shutdown")
81
+ end
82
+
83
+ @process.poll_for_exit STOP_TIMEOUT
84
+ rescue ChildProcess::TimeoutError
85
+ # ok, force quit
86
+ @process.stop STOP_TIMEOUT
87
+ end
88
+
89
+ def uri
90
+ URI.parse "http://#{@host}:#{@port}"
91
+ end
92
+
93
+ def find_free_port
94
+ @port = PortProber.above @port
95
+ end
96
+
97
+ def start_process
98
+ server_command = [@executable_path, "--binary=#{Firefox::Binary.path}", "--webdriver-port=#{@port}", *@extra_args]
99
+ @process = ChildProcess.build(*server_command)
100
+
101
+ @process.io.inherit! if $DEBUG || Platform.os == :windows
102
+ @process.start
103
+ end
104
+
105
+ def connect_until_stable
106
+ @socket_poller = SocketPoller.new @host, @port, START_TIMEOUT
107
+
108
+ unless @socket_poller.connected?
109
+ raise Error::WebDriverError, "unable to connect to Mozilla Wires #{@host}:#{@port}"
110
+ end
111
+ end
112
+
113
+ def socket_lock
114
+ @socket_lock ||= SocketLock.new(@port - 1, SOCKET_LOCK_TIMEOUT)
115
+ end
116
+
117
+ end # Service
118
+ end # Firefox
119
+ end # WebDriver
120
+ end # Service
@@ -0,0 +1,97 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Licensed to the Software Freedom Conservancy (SFC) under one
4
+ # or more contributor license agreements. See the NOTICE file
5
+ # distributed with this work for additional information
6
+ # regarding copyright ownership. The SFC licenses this file
7
+ # to you under the Apache License, Version 2.0 (the
8
+ # "License"); you may not use this file except in compliance
9
+ # with the License. You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing,
14
+ # software distributed under the License is distributed on an
15
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ # KIND, either express or implied. See the License for the
17
+ # specific language governing permissions and limitations
18
+ # under the License.
19
+
20
+ module Selenium
21
+ module WebDriver
22
+ module Firefox
23
+
24
+ # @api private
25
+ class W3CBridge < Remote::W3CBridge
26
+
27
+ def initialize(opts = {})
28
+ http_client = opts.delete(:http_client)
29
+
30
+ if opts.has_key?(:url)
31
+ url = opts.delete(:url)
32
+ else
33
+ @service = Service.default_service(*extract_service_args(opts))
34
+
35
+ if @service.instance_variable_get("@host") == "127.0.0.1"
36
+ @service.instance_variable_set("@host", 'localhost')
37
+ end
38
+
39
+ @service.start
40
+
41
+ url = @service.uri
42
+ end
43
+
44
+ caps = create_capabilities(opts)
45
+
46
+ remote_opts = {
47
+ :url => url,
48
+ :desired_capabilities => caps
49
+ }
50
+
51
+ remote_opts.merge!(:http_client => http_client) if http_client
52
+ super(remote_opts)
53
+ end
54
+
55
+ def browser
56
+ :firefox
57
+ end
58
+
59
+ def driver_extensions
60
+ [
61
+ DriverExtensions::TakesScreenshot,
62
+ DriverExtensions::HasInputDevices
63
+ ]
64
+ end
65
+
66
+ def quit
67
+ super
68
+ ensure
69
+ @service.stop if @service
70
+ end
71
+
72
+ private
73
+
74
+ def create_capabilities(opts)
75
+ caps = opts.delete(:desired_capabilities) { Remote::W3CCapabilities.firefox }
76
+
77
+ unless opts.empty?
78
+ raise ArgumentError, "unknown option#{'s' if opts.size != 1}: #{opts.inspect}"
79
+ end
80
+
81
+ caps
82
+ end
83
+
84
+ def extract_service_args(opts)
85
+ args = []
86
+
87
+ if opts.has_key?(:service_log_path)
88
+ args << "--log-path=#{opts.delete(:service_log_path)}"
89
+ end
90
+
91
+ args
92
+ end
93
+
94
+ end # W3CBridge
95
+ end # Firefox
96
+ end # WebDriver
97
+ end # Selenium
@@ -20,10 +20,13 @@
20
20
  require 'uri'
21
21
 
22
22
  require 'selenium/webdriver/remote/capabilities'
23
+ require 'selenium/webdriver/remote/w3c_capabilities'
23
24
  require 'selenium/webdriver/remote/bridge'
25
+ require 'selenium/webdriver/remote/w3c_bridge'
24
26
  require 'selenium/webdriver/remote/server_error'
25
27
  require 'selenium/webdriver/remote/response'
26
28
  require 'selenium/webdriver/remote/commands'
29
+ require 'selenium/webdriver/remote/w3c_commands'
27
30
  require 'selenium/webdriver/remote/http/common'
28
31
  require 'selenium/webdriver/remote/http/default'
29
32
 
@@ -21,8 +21,6 @@ module Selenium
21
21
  module WebDriver
22
22
  module Remote
23
23
 
24
- COMMANDS = {}
25
-
26
24
  #
27
25
  # Low level bridge to the remote server, through which the rest of the API works.
28
26
  #
@@ -32,6 +30,8 @@ module Selenium
32
30
  class Bridge
33
31
  include BridgeHelper
34
32
 
33
+ COMMANDS = {}
34
+
35
35
  #
36
36
  # Defines a wrapper method for a command, which ultimately calls #execute.
37
37
  #
@@ -158,19 +158,23 @@ module Selenium
158
158
  end
159
159
 
160
160
  def acceptAlert
161
- execute :acceptAlert
161
+ command = :acceptAlert
162
+ execute command
162
163
  end
163
164
 
164
165
  def dismissAlert
165
- execute :dismissAlert
166
+ command = :dismissAlert
167
+ execute command
166
168
  end
167
169
 
168
170
  def setAlertValue(keys)
169
- execute :setAlertValue, {}, :text => keys.to_s
171
+ command = capabilities.browser_name == 'MicrosoftEdge' ? :setAlertValueW3C : :setAlertValue
172
+ execute command, {}, :text => keys.to_s
170
173
  end
171
174
 
172
175
  def getAlertText
173
- execute :getAlertText
176
+ command = :getAlertText
177
+ execute command
174
178
  end
175
179
 
176
180
  #
@@ -206,7 +210,11 @@ module Selenium
206
210
  end
207
211
 
208
212
  def switchToWindow(name)
209
- execute :switchToWindow, {}, :name => name
213
+ if capabilities.browser_name == 'MicrosoftEdge'
214
+ execute :switchToWindow, {}, :handle => name
215
+ else
216
+ execute :switchToWindow, {}, :name => name
217
+ end
210
218
  end
211
219
 
212
220
  def switchToFrame(id)
@@ -619,9 +627,8 @@ module Selenium
619
627
  private
620
628
 
621
629
  def assert_javascript_enabled
622
- unless capabilities.javascript_enabled?
623
- raise Error::UnsupportedOperationError, "underlying webdriver instance does not support javascript"
624
- end
630
+ return if capabilities.browser_name == 'MicrosoftEdge' || capabilities.javascript_enabled?
631
+ raise Error::UnsupportedOperationError, "underlying webdriver instance does not support javascript"
625
632
  end
626
633
 
627
634
  #
@@ -80,9 +80,8 @@ module Selenium
80
80
 
81
81
  def edge(opts = {})
82
82
  new({
83
- :browser_name => "edge",
83
+ :browser_name => "MicrosoftEdge",
84
84
  :platform => :windows,
85
- :javascript_enabled => true
86
85
  }.merge(opts))
87
86
  end
88
87
 
@@ -78,6 +78,11 @@ class Selenium::WebDriver::Remote::Bridge
78
78
  command :getAlertText, :get, "session/:session_id/alert_text"
79
79
  command :setAlertValue, :post, "session/:session_id/alert_text"
80
80
 
81
+ command :dismissAlertW3C, :post, "session/:session_id/alert/dismiss"
82
+ command :acceptAlertW3C, :post, "session/:session_id/alert/accept"
83
+ command :getAlertTextW3C, :get, "session/:session_id/alert/text"
84
+ command :setAlertValueW3C, :post, "session/:session_id/alert/text"
85
+
81
86
  #
82
87
  # target locator
83
88
  #
@@ -52,13 +52,12 @@ module Selenium
52
52
  msg = val['message'] or return "unknown error"
53
53
  msg << ": #{val['alert']['text'].inspect}" if val['alert'].kind_of?(Hash) && val['alert']['text']
54
54
  msg << " (#{ val['class'] })" if val['class']
55
+ msg
55
56
  when String
56
- msg = val
57
+ val
57
58
  else
58
- msg = "unknown error, status=#{status}: #{val.inspect}"
59
+ "unknown error, status=#{status}: #{val.inspect}"
59
60
  end
60
-
61
- msg
62
61
  end
63
62
 
64
63
  def [](key)
@@ -104,11 +103,11 @@ module Selenium
104
103
  end
105
104
 
106
105
  def status
107
- @payload['status']
106
+ @payload['status'] || @payload['error']
108
107
  end
109
108
 
110
109
  def value
111
- @payload['value']
110
+ @payload['value'] || @payload['message']
112
111
  end
113
112
 
114
113
  end # Response
@@ -0,0 +1,676 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Licensed to the Software Freedom Conservancy (SFC) under one
4
+ # or more contributor license agreements. See the NOTICE file
5
+ # distributed with this work for additional information
6
+ # regarding copyright ownership. The SFC licenses this file
7
+ # to you under the Apache License, Version 2.0 (the
8
+ # "License"); you may not use this file except in compliance
9
+ # with the License. You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing,
14
+ # software distributed under the License is distributed on an
15
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ # KIND, either express or implied. See the License for the
17
+ # specific language governing permissions and limitations
18
+ # under the License.
19
+
20
+ module Selenium
21
+ module WebDriver
22
+ module Remote
23
+
24
+ #
25
+ # Low level bridge to the remote server, through which the rest of the API works.
26
+ #
27
+ # @api private
28
+ #
29
+
30
+ class W3CBridge
31
+ include BridgeHelper
32
+
33
+ COMMANDS = {}
34
+
35
+ #
36
+ # Defines a wrapper method for a command, which ultimately calls #execute.
37
+ #
38
+ # @param name [Symbol]
39
+ # name of the resulting method
40
+ # @param verb [Symbol]
41
+ # the appropriate http verb, such as :get, :post, or :delete
42
+ # @param url [String]
43
+ # a URL template, which can include some arguments, much like the definitions on the server.
44
+ # the :session_id parameter is implicitly handled, but the remainder will become required method arguments.
45
+ #
46
+
47
+ def self.command(name, verb, url)
48
+ COMMANDS[name] = [verb, url.freeze]
49
+ end
50
+
51
+ attr_accessor :context, :http, :file_detector
52
+ attr_reader :capabilities
53
+
54
+ #
55
+ # Initializes the bridge with the given server URL.
56
+ #
57
+ # @param url [String] url for the remote server
58
+ # @param http_client [Object] an HTTP client instance that implements the same protocol as Http::Default
59
+ # @param desired_capabilities [Capabilities] an instance of Remote::Capabilities describing the capabilities you want
60
+ #
61
+
62
+ def initialize(opts = {})
63
+ opts = opts.dup
64
+
65
+ http_client = opts.delete(:http_client) { Http::Default.new }
66
+ desired_capabilities = opts.delete(:desired_capabilities) { W3CCapabilities.firefox }
67
+ url = opts.delete(:url) { "http://#{Platform.localhost}:4444/wd/hub" }
68
+
69
+ unless opts.empty?
70
+ raise ArgumentError, "unknown option#{'s' if opts.size != 1}: #{opts.inspect}"
71
+ end
72
+
73
+ if desired_capabilities.kind_of?(Symbol)
74
+ unless W3CCapabilities.respond_to?(desired_capabilities)
75
+ raise Error::WebDriverError, "invalid desired capability: #{desired_capabilities.inspect}"
76
+ end
77
+
78
+ desired_capabilities = W3CCapabilities.send(desired_capabilities)
79
+ end
80
+
81
+ uri = url.kind_of?(URI) ? url : URI.parse(url)
82
+ uri.path += "/" unless uri.path =~ /\/$/
83
+
84
+ http_client.server_url = uri
85
+
86
+ @http = http_client
87
+ @capabilities = create_session(desired_capabilities)
88
+
89
+ @file_detector = nil
90
+ end
91
+
92
+ def browser
93
+ @browser ||= (
94
+ name = @capabilities.browser_name
95
+ name ? name.gsub(" ", "_").to_sym : 'unknown'
96
+ )
97
+ end
98
+
99
+ def driver_extensions
100
+ [
101
+ DriverExtensions::HasInputDevices,
102
+ DriverExtensions::UploadsFiles,
103
+ DriverExtensions::TakesScreenshot,
104
+ DriverExtensions::HasSessionId,
105
+ DriverExtensions::Rotatable,
106
+ DriverExtensions::HasTouchScreen,
107
+ DriverExtensions::HasLocation,
108
+ DriverExtensions::HasNetworkConnection,
109
+ DriverExtensions::HasRemoteStatus
110
+ ]
111
+ end
112
+
113
+ #
114
+ # Returns the current session ID.
115
+ #
116
+
117
+ def session_id
118
+ @session_id || raise(Error::WebDriverError, "no current session exists")
119
+ end
120
+
121
+ def create_session(desired_capabilities)
122
+ resp = raw_execute :newSession, {}, :desiredCapabilities => desired_capabilities
123
+ @session_id = resp['sessionId'] or raise Error::WebDriverError, 'no sessionId in returned payload'
124
+
125
+ W3CCapabilities.json_create resp['value']
126
+ end
127
+
128
+ def status
129
+ execute :status
130
+ end
131
+
132
+ def get(url)
133
+ execute :get, {}, :url => url
134
+ end
135
+
136
+ def getCapabilities
137
+ W3CCapabilities.json_create execute(:getCapabilities)
138
+ end
139
+
140
+ def setImplicitWaitTimeout(milliseconds)
141
+ setTimeout('implicit', milliseconds)
142
+ end
143
+
144
+ def setScriptTimeout(milliseconds)
145
+ setTimeout('script', milliseconds)
146
+ end
147
+
148
+ def setTimeout(type, milliseconds)
149
+ execute :setTimeout, {}, :type => type, :ms => milliseconds
150
+ end
151
+
152
+ #
153
+ # alerts
154
+ #
155
+
156
+ def getAlert
157
+ execute :getAlert
158
+ end
159
+
160
+ def acceptAlert
161
+ execute :acceptAlert
162
+ end
163
+
164
+ def dismissAlert
165
+ execute :dismissAlert
166
+ end
167
+
168
+ def setAlertValue(keys)
169
+ execute :sendAlertText, {}, {:handler => 'prompt', :message => keys}
170
+ end
171
+
172
+ def getAlertText
173
+ execute :getAlertText
174
+ end
175
+
176
+ #
177
+ # navigation
178
+ #
179
+
180
+ def goBack
181
+ execute :back
182
+ end
183
+
184
+ def goForward
185
+ execute :forward
186
+ end
187
+
188
+ def getCurrentUrl
189
+ execute :getCurrentUrl
190
+ end
191
+
192
+ def getTitle
193
+ execute :getTitle
194
+ end
195
+
196
+ def getPageSource
197
+ execute :getPageSource
198
+ end
199
+
200
+ def getVisible
201
+ execute :getVisible
202
+ end
203
+
204
+ def setVisible(bool)
205
+ execute :setVisible, {}, bool
206
+ end
207
+
208
+ def switchToWindow(name)
209
+ execute :switchToWindow, {}, :handle => name
210
+ end
211
+
212
+ def switchToFrame(id)
213
+ locator = case id
214
+ when String
215
+ find_element_by('id', id)
216
+ when Hash
217
+ find_element_by(id.keys.first.to_s, id.values.first)
218
+ else
219
+ id
220
+ end
221
+
222
+ execute :switchToFrame, {}, :id => locator
223
+ end
224
+
225
+ def switchToParentFrame
226
+ execute :switchToParentFrame
227
+ end
228
+
229
+ def switchToDefaultContent
230
+ switchToFrame nil
231
+ end
232
+
233
+ QUIT_ERRORS = [IOError]
234
+
235
+ def quit
236
+ execute :deleteSession
237
+ http.close
238
+ rescue *QUIT_ERRORS
239
+ end
240
+
241
+ def close
242
+ execute :closeWindow
243
+ end
244
+
245
+ def refresh
246
+ execute :refresh
247
+ end
248
+
249
+ #
250
+ # window handling
251
+ #
252
+
253
+ def getWindowHandles
254
+ execute :getWindowHandles
255
+ end
256
+
257
+ def getCurrentWindowHandle
258
+ execute :getWindowHandle
259
+ end
260
+
261
+ # TODO - These Commands might require checking for being
262
+ # current window before performing
263
+ def setWindowSize(width, height, handle = :current)
264
+ execute :setWindowSize, {}, {:width => width,
265
+ :height => height}
266
+ end
267
+
268
+ def maximizeWindow(handle = :current)
269
+ execute :maximizeWindow
270
+ end
271
+
272
+ def getWindowSize(handle = :current)
273
+ data = execute :getWindowSize
274
+
275
+ Dimension.new data['width'], data['height']
276
+ end
277
+
278
+ def setWindowPosition(x, y, handle = :current)
279
+ execute :setWindowPosition, :x => x, :y => y
280
+ end
281
+
282
+ def getWindowPosition(handle = :current)
283
+ data = execute :getWindowPosition
284
+
285
+ Point.new data['x'], data['y']
286
+ end
287
+
288
+ def getScreenshot
289
+ execute :takeScreenshot
290
+ end
291
+
292
+ #
293
+ # HTML 5
294
+ #
295
+
296
+ def getLocalStorageItem(key)
297
+ execute :getLocalStorageItem, :key => key
298
+ end
299
+
300
+ def removeLocalStorageItem(key)
301
+ execute :removeLocalStorageItem, :key => key
302
+ end
303
+
304
+ def getLocalStorageKeys
305
+ execute :getLocalStorageKeys
306
+ end
307
+
308
+ def setLocalStorageItem(key, value)
309
+ execute :setLocalStorageItem, {}, :key => key, :value => value
310
+ end
311
+
312
+ def clearLocalStorage
313
+ execute :clearLocalStorage
314
+ end
315
+
316
+ def getLocalStorageSize
317
+ execute :getLocalStorageSize
318
+ end
319
+
320
+ def getSessionStorageItem(key)
321
+ execute :getSessionStorageItem, :key => key
322
+ end
323
+
324
+ def removeSessionStorageItem(key)
325
+ execute :removeSessionStorageItem, :key => key
326
+ end
327
+
328
+ def getSessionStorageKeys
329
+ execute :getSessionStorageKeys
330
+ end
331
+
332
+ def setSessionStorageItem(key, value)
333
+ execute :setSessionStorageItem, {}, :key => key, :value => value
334
+ end
335
+
336
+ def clearSessionStorage
337
+ execute :clearSessionStorage
338
+ end
339
+
340
+ def getSessionStorageSize
341
+ execute :getSessionStorageSize
342
+ end
343
+
344
+ def getLocation
345
+ obj = execute(:getLocation) || {} # android returns null
346
+ Location.new obj['latitude'], obj['longitude'], obj['altitude']
347
+ end
348
+
349
+ def setLocation(lat, lon, alt)
350
+ loc = {:latitude => lat, :longitude => lon, :altitude => alt}
351
+ execute :setLocation, {}, :location => loc
352
+ end
353
+
354
+ def getNetworkConnection
355
+ execute :getNetworkConnection
356
+ end
357
+
358
+ def setNetworkConnection(type)
359
+ execute :setNetworkConnection, {}, :parameters => {:type => type}
360
+ end
361
+
362
+ #
363
+ # javascript execution
364
+ #
365
+
366
+ def executeScript(script, *args)
367
+ result = execute :executeScript, {}, :script => script, :args => args
368
+ unwrap_script_result result
369
+ end
370
+
371
+ def executeAsyncScript(script, *args)
372
+ result = execute :executeAsyncScript, {}, :script => script, :args => args
373
+ unwrap_script_result result
374
+ end
375
+
376
+ #
377
+ # cookies
378
+ #
379
+
380
+ def addCookie(cookie)
381
+ execute :addCookie, {}, cookie
382
+ end
383
+
384
+ def deleteCookie(name)
385
+ execute :deleteCookie, :name => name
386
+ end
387
+
388
+ # TODO - write specs
389
+ def getCookie(name)
390
+ execute :getCookie, :name => name
391
+ end
392
+
393
+ def getAllCookies
394
+ execute :getAllCookies
395
+ end
396
+
397
+ def deleteAllCookies
398
+ getAllCookies.each { |cookie| deleteCookie(cookie['name'])}
399
+ end
400
+
401
+ #
402
+ # actions
403
+ #
404
+
405
+ def clickElement(element)
406
+ execute :elementClick, :id => element
407
+ end
408
+
409
+ def click
410
+ execute :click, {}, :button => 0
411
+ end
412
+
413
+ def doubleClick
414
+ execute :doubleClick
415
+ end
416
+
417
+ def contextClick
418
+ execute :click, {}, :button => 2
419
+ end
420
+
421
+ def mouseDown
422
+ execute :mouseDown
423
+ end
424
+
425
+ def mouseUp
426
+ execute :mouseUp
427
+ end
428
+
429
+ def mouseMoveTo(element, x = nil, y = nil)
430
+ params = { :element => element }
431
+
432
+ if x && y
433
+ params.merge! :xoffset => x, :yoffset => y
434
+ end
435
+
436
+ execute :mouseMoveTo, {}, params
437
+ end
438
+
439
+ def sendKeysToActiveElement(keys)
440
+ sendKeysToElement(getActiveElement, keys)
441
+ end
442
+
443
+ def sendKeysToElement(element, keys)
444
+ execute :elementSendKeys, {:id => element}, {:value => keys.join('').split(//)}
445
+ end
446
+
447
+ def upload(local_file)
448
+ unless File.file?(local_file)
449
+ raise Error::WebDriverError, "you may only upload files: #{local_file.inspect}"
450
+ end
451
+
452
+ execute :uploadFile, {}, :file => Zipper.zip_file(local_file)
453
+ end
454
+
455
+ def clearElement(element)
456
+ execute :elementClear, :id => element
457
+ end
458
+
459
+
460
+ def submitElement(element)
461
+ executeScript("var e = arguments[0].ownerDocument.createEvent('Event');" +
462
+ "e.initEvent('submit', true, true);" +
463
+ "if (arguments[0].dispatchEvent(e)) { arguments[0].submit() }", element)
464
+ end
465
+
466
+ def dragElement(element, right_by, down_by)
467
+ execute :dragElement, {:id => element}, :x => right_by, :y => down_by
468
+ end
469
+
470
+ def touchSingleTap(element)
471
+ execute :touchSingleTap, {}, :element => element
472
+ end
473
+
474
+ def touchDoubleTap(element)
475
+ execute :touchDoubleTap, {}, :element => element
476
+ end
477
+
478
+ def touchLongPress(element)
479
+ execute :touchLongPress, {}, :element => element
480
+ end
481
+
482
+ def touchDown(x, y)
483
+ execute :touchDown, {}, :x => x, :y => y
484
+ end
485
+
486
+ def touchUp(x, y)
487
+ execute :touchUp, {}, :x => x, :y => y
488
+ end
489
+
490
+ def touchMove(x, y)
491
+ execute :touchMove, {}, :x => x, :y => y
492
+ end
493
+
494
+ def touchScroll(element, x, y)
495
+ if element
496
+ execute :touchScroll, {}, :element => element,
497
+ :xoffset => x,
498
+ :yoffset => y
499
+ else
500
+ execute :touchScroll, {}, :xoffset => x, :yoffset => y
501
+ end
502
+ end
503
+
504
+ def touchFlick(xspeed, yspeed)
505
+ execute :touchFlick, {}, :xspeed => xspeed, :yspeed => yspeed
506
+ end
507
+
508
+ def touchElementFlick(element, right_by, down_by, speed)
509
+ execute :touchFlick, {}, :element => element,
510
+ :xoffset => right_by,
511
+ :yoffset => down_by,
512
+ :speed => speed
513
+
514
+ end
515
+
516
+ def setScreenOrientation(orientation)
517
+ execute :setScreenOrientation, {}, :orientation => orientation
518
+ end
519
+
520
+ def getScreenOrientation
521
+ execute :getScreenOrientation
522
+ end
523
+
524
+ #
525
+ # element properties
526
+ #
527
+
528
+ def getElementTagName(element)
529
+ execute :getElementTagName, :id => element
530
+ end
531
+
532
+ def getElementAttribute(element, name)
533
+ execute :getElementAttribute, :id => element, :name => name
534
+ end
535
+
536
+ def getElementValue(element)
537
+ execute :getElementProperty, :id => element, :name => 'value'
538
+ end
539
+
540
+ def getElementText(element)
541
+ execute :getElementText, :id => element
542
+ end
543
+
544
+ def getElementLocation(element)
545
+ data = execute :getElementLocation, :id => element
546
+
547
+ Point.new data['x'], data['y']
548
+ end
549
+
550
+ def getElementLocationOnceScrolledIntoView(element)
551
+ data = execute :getElementLocationOnceScrolledIntoView, :id => element
552
+
553
+ Point.new data['x'], data['y']
554
+ end
555
+
556
+ def getElementSize(element)
557
+ data = execute :getElementSize, :id => element
558
+
559
+ Dimension.new data['width'], data['height']
560
+ end
561
+
562
+ def isElementEnabled(element)
563
+ execute :isElementEnabled, :id => element
564
+ end
565
+
566
+ def isElementSelected(element)
567
+ execute :isElementSelected, :id => element
568
+ end
569
+
570
+ def isElementDisplayed(element)
571
+ execute :isElementDisplayed, :id => element
572
+ end
573
+ def getElementValueOfCssProperty(element, prop)
574
+ execute :getElementCssValue, :id => element, :property_name => prop
575
+ end
576
+
577
+ def elementEquals(element, other)
578
+ element.ref == other.ref
579
+ end
580
+
581
+ #
582
+ # finding elements
583
+ #
584
+
585
+ def getActiveElement
586
+ Element.new self, element_id_from(execute(:getActiveElement))
587
+ end
588
+ alias_method :switchToActiveElement, :getActiveElement
589
+
590
+ def find_element_by(how, what, parent = nil)
591
+ how, what = convert_locators(how, what)
592
+
593
+ if parent
594
+ id = execute :findChildElement, {:id => parent}, {:using => how, :value => what}
595
+ else
596
+ id = execute :findElement, {}, {:using => how, :value => what}
597
+ end
598
+
599
+ Element.new self, element_id_from(id)
600
+ end
601
+
602
+ def find_elements_by(how, what, parent = nil)
603
+ how, what = convert_locators(how, what)
604
+
605
+ if parent
606
+ ids = execute :findChildElements, {:id => parent}, {:using => how, :value => what}
607
+ else
608
+ ids = execute :findElements, {}, {:using => how, :value => what}
609
+ end
610
+
611
+ ids.map { |id| Element.new self, element_id_from(id) }
612
+ end
613
+
614
+ private
615
+
616
+ def convert_locators(how, what)
617
+ case how
618
+ when 'class name'
619
+ how = 'css selector'
620
+ what = ".#{what}"
621
+ when 'id'
622
+ how = 'css selector'
623
+ what = "##{what}"
624
+ when 'name'
625
+ how = 'css selector'
626
+ what = "*[name='#{what}']"
627
+ when 'tag name'
628
+ how = 'css selector'
629
+ end
630
+ return how, what
631
+ end
632
+
633
+ #
634
+ # executes a command on the remote server.
635
+ #
636
+ #
637
+ # Returns the 'value' of the returned payload
638
+ #
639
+
640
+ def execute(*args)
641
+ result = raw_execute(*args)
642
+ result.payload.key?('value') ? result['value'] : result
643
+ end
644
+
645
+ #
646
+ # executes a command on the remote server.
647
+ #
648
+ # @return [WebDriver::Remote::Response]
649
+ #
650
+
651
+ def raw_execute(command, opts = {}, command_hash = nil)
652
+ verb, path = COMMANDS[command] || raise(ArgumentError, "unknown command: #{command.inspect}")
653
+ path = path.dup
654
+
655
+ path[':session_id'] = @session_id if path.include?(":session_id")
656
+
657
+ begin
658
+ opts.each { |key, value|
659
+ path[key.inspect] = escaper.escape(value.to_s)
660
+ }
661
+ rescue IndexError
662
+ raise ArgumentError, "#{opts.inspect} invalid for #{command.inspect}"
663
+ end
664
+
665
+ puts "-> #{verb.to_s.upcase} #{path}" if $DEBUG
666
+ http.call verb, path, command_hash
667
+ end
668
+
669
+ def escaper
670
+ @escaper ||= defined?(URI::Parser) ? URI::Parser.new : URI
671
+ end
672
+
673
+ end # W3CBridge
674
+ end # Remote
675
+ end # WebDriver
676
+ end # Selenium