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.
- data/CHANGES +18 -0
- data/Gemfile.lock +1 -1
- data/lib/selenium/client/base.rb +3 -3
- data/lib/selenium/client/extensions.rb +15 -18
- data/lib/selenium/client/idiomatic.rb +26 -26
- data/lib/selenium/client/javascript_expression_builder.rb +8 -8
- data/lib/selenium/webdriver/common.rb +1 -1
- data/lib/selenium/webdriver/common/driver.rb +5 -1
- data/lib/selenium/webdriver/common/error.rb +5 -8
- data/lib/selenium/webdriver/common/search_context.rb +6 -0
- data/lib/selenium/webdriver/common/target_locator.rb +4 -0
- data/lib/selenium/webdriver/common/w3c_error.rb +194 -0
- data/lib/selenium/webdriver/firefox.rb +3 -0
- data/lib/selenium/webdriver/firefox/binary.rb +13 -0
- data/lib/selenium/webdriver/firefox/extension/prefs.json +2 -1
- data/lib/selenium/webdriver/firefox/extension/webdriver.xpi +0 -0
- data/lib/selenium/webdriver/firefox/service.rb +120 -0
- data/lib/selenium/webdriver/firefox/w3c_bridge.rb +97 -0
- data/lib/selenium/webdriver/remote.rb +3 -0
- data/lib/selenium/webdriver/remote/bridge.rb +17 -10
- data/lib/selenium/webdriver/remote/capabilities.rb +1 -2
- data/lib/selenium/webdriver/remote/commands.rb +5 -0
- data/lib/selenium/webdriver/remote/response.rb +5 -6
- data/lib/selenium/webdriver/remote/w3c_bridge.rb +676 -0
- data/lib/selenium/webdriver/remote/w3c_capabilities.rb +208 -0
- data/lib/selenium/webdriver/remote/w3c_commands.rb +133 -0
- data/lib/selenium/webdriver/safari/resources/client.js +653 -629
- data/selenium-webdriver.gemspec +1 -1
- metadata +8 -4
- data/lib/selenium/webdriver/common/core_ext/string.rb +0 -24
- 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,
|
Binary file
|
@@ -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
|
-
|
161
|
+
command = :acceptAlert
|
162
|
+
execute command
|
162
163
|
end
|
163
164
|
|
164
165
|
def dismissAlert
|
165
|
-
|
166
|
+
command = :dismissAlert
|
167
|
+
execute command
|
166
168
|
end
|
167
169
|
|
168
170
|
def setAlertValue(keys)
|
169
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
623
|
-
|
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
|
#
|
@@ -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
|
-
|
57
|
+
val
|
57
58
|
else
|
58
|
-
|
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
|