async-webdriver 0.1.2 → 0.2.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/async/webdriver/bridge/chrome.rb +81 -0
- data/lib/async/webdriver/bridge/firefox.rb +80 -0
- data/lib/async/webdriver/bridge/generic.rb +91 -0
- data/lib/async/webdriver/bridge/pool.rb +99 -0
- data/lib/async/webdriver/bridge/process_group.rb +77 -0
- data/lib/async/webdriver/bridge.rb +30 -0
- data/lib/async/webdriver/client.rb +71 -26
- data/lib/async/webdriver/element.rb +270 -17
- data/lib/async/webdriver/error.rb +214 -0
- data/lib/async/webdriver/locator.rb +127 -0
- data/lib/async/webdriver/request_helper.rb +120 -0
- data/lib/async/webdriver/scope/alerts.rb +40 -0
- data/lib/async/webdriver/scope/cookies.rb +43 -0
- data/lib/async/webdriver/scope/document.rb +41 -0
- data/lib/async/webdriver/scope/elements.rb +111 -0
- data/lib/async/webdriver/scope/fields.rb +66 -0
- data/lib/async/webdriver/scope/frames.rb +33 -0
- data/lib/async/webdriver/scope/navigation.rb +50 -0
- data/lib/async/webdriver/scope/printing.rb +22 -0
- data/lib/async/webdriver/scope/screen_capture.rb +23 -0
- data/lib/async/webdriver/scope/timeouts.rb +63 -0
- data/lib/async/webdriver/scope.rb +15 -0
- data/lib/async/webdriver/session.rb +107 -65
- data/lib/async/webdriver/version.rb +8 -3
- data/lib/async/webdriver/xpath.rb +29 -0
- data/lib/async/webdriver.rb +7 -12
- data/{LICENSE.txt → license.md} +6 -6
- data/readme.md +37 -0
- data.tar.gz.sig +0 -0
- metadata +71 -149
- metadata.gz.sig +0 -0
- data/.gitignore +0 -11
- data/.rspec +0 -3
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/.travis.yml +0 -7
- data/Gemfile +0 -6
- data/Gemfile.lock +0 -103
- data/Guardfile +0 -45
- data/README.md +0 -3
- data/Rakefile +0 -6
- data/async-webdriver.gemspec +0 -37
- data/bin/console +0 -12
- data/bin/setup +0 -8
- data/examples/multiple_sessions.rb +0 -29
- data/lib/async/webdriver/connection.rb +0 -78
- data/lib/async/webdriver/connection_path.rb +0 -25
- data/lib/async/webdriver/execute.rb +0 -29
- data/lib/async/webdriver/session_creator.rb +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ee23c04114676fa39b6838b560ad843f0d011e8c89ac1de1038c66c2f7bd5e26
|
4
|
+
data.tar.gz: aa95efc428c3cdfcb362c8a9f6fbc80aaa4cec6258630adb2f057479ba051537
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf32864d5c72d2529151f4de5f1f72d6e4a5a3731d0598e49df758d97371f176ec34504d9922d6f56499855c9d21726fb7fba641b08c19f2ae084960a35ec90f
|
7
|
+
data.tar.gz: 67f0fd5bdba22e79cba73c2db37e69da65ddc3dc22a8603584a59d1cae158902ac1ca501a7d80304683e56f831aa621ca852acd3b31079c4c7a2b19d2a36711c
|
checksums.yaml.gz.sig
ADDED
Binary file
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2023, by Samuel Williams.
|
5
|
+
|
6
|
+
require_relative 'generic'
|
7
|
+
require_relative 'process_group'
|
8
|
+
|
9
|
+
module Async
|
10
|
+
module WebDriver
|
11
|
+
module Bridge
|
12
|
+
# A bridge to the Chrome browser using `chromedriver`.
|
13
|
+
#
|
14
|
+
# ``` ruby
|
15
|
+
# begin
|
16
|
+
# bridge = Async::WebDriver::Bridge::Chrome.start
|
17
|
+
# client = Async::WebDriver::Client.open(bridge.endpoint)
|
18
|
+
# ensure
|
19
|
+
# bridge&.close
|
20
|
+
# end
|
21
|
+
# ```
|
22
|
+
class Chrome < Generic
|
23
|
+
# Create a new bridge to Chrome.
|
24
|
+
# @parameter path [String] The path to the `chromedriver` executable.
|
25
|
+
def initialize(path: "chromedriver")
|
26
|
+
super()
|
27
|
+
|
28
|
+
@path = path
|
29
|
+
@process = nil
|
30
|
+
end
|
31
|
+
|
32
|
+
# @returns [String] The version of the `chromedriver` executable.
|
33
|
+
def version
|
34
|
+
::IO.popen([@path, "--version"]) do |io|
|
35
|
+
return io.read
|
36
|
+
end
|
37
|
+
rescue Errno::ENOENT
|
38
|
+
return nil
|
39
|
+
end
|
40
|
+
|
41
|
+
# @returns [Array(String)] The arguments to pass to the `chromedriver` executable.
|
42
|
+
def arguments
|
43
|
+
[
|
44
|
+
"--port=#{self.port}",
|
45
|
+
].compact
|
46
|
+
end
|
47
|
+
|
48
|
+
# Start the driver.
|
49
|
+
def start
|
50
|
+
@process ||= ProcessGroup.spawn(@path, *arguments)
|
51
|
+
|
52
|
+
super
|
53
|
+
end
|
54
|
+
|
55
|
+
# Close the driver.
|
56
|
+
def close
|
57
|
+
super
|
58
|
+
|
59
|
+
if @process
|
60
|
+
@process.close
|
61
|
+
@process = nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# The default capabilities for the Chrome browser which need to be provided when requesting a new session.
|
66
|
+
# @parameter headless [Boolean] Whether to run the browser in headless mode.
|
67
|
+
# @returns [Hash] The default capabilities for the Chrome browser.
|
68
|
+
def default_capabilities(headless: true)
|
69
|
+
{
|
70
|
+
alwaysMatch: {
|
71
|
+
browserName: "chrome",
|
72
|
+
"goog:chromeOptions": {
|
73
|
+
args: [headless ? "--headless" : nil].compact,
|
74
|
+
}
|
75
|
+
},
|
76
|
+
}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2023, by Samuel Williams.
|
5
|
+
|
6
|
+
require_relative 'generic'
|
7
|
+
require_relative 'process_group'
|
8
|
+
|
9
|
+
module Async
|
10
|
+
module WebDriver
|
11
|
+
module Bridge
|
12
|
+
# A bridge to the Firefox browser using `geckodriver`.
|
13
|
+
#
|
14
|
+
# ``` ruby
|
15
|
+
# begin
|
16
|
+
# bridge = Async::WebDriver::Bridge::Firefox.start
|
17
|
+
# client = Async::WebDriver::Client.open(bridge.endpoint)
|
18
|
+
# ensure
|
19
|
+
# bridge&.close
|
20
|
+
# end
|
21
|
+
class Firefox < Generic
|
22
|
+
# Create a new bridge to Firefox.
|
23
|
+
# @parameter path [String] The path to the `geckodriver` executable.
|
24
|
+
def initialize(path: "geckodriver")
|
25
|
+
super()
|
26
|
+
|
27
|
+
@path = path
|
28
|
+
@process = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
# @returns [String] The version of the `geckodriver` executable.
|
32
|
+
def version
|
33
|
+
::IO.popen([@path, "--version"]) do |io|
|
34
|
+
return io.read
|
35
|
+
end
|
36
|
+
rescue Errno::ENOENT
|
37
|
+
return nil
|
38
|
+
end
|
39
|
+
|
40
|
+
# @returns [Array(String)] The arguments to pass to the `geckodriver` executable.
|
41
|
+
def arguments
|
42
|
+
[
|
43
|
+
"--port", self.port.to_s,
|
44
|
+
].compact
|
45
|
+
end
|
46
|
+
|
47
|
+
# Start the driver.
|
48
|
+
def start
|
49
|
+
@process ||= ProcessGroup.spawn(@path, *arguments)
|
50
|
+
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
# Close the driver.
|
55
|
+
def close
|
56
|
+
super
|
57
|
+
|
58
|
+
if @process
|
59
|
+
@process.close
|
60
|
+
@process = nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# The default capabilities for the Firefox browser which need to be provided when requesting a new session.
|
65
|
+
# @parameter headless [Boolean] Whether to run the browser in headless mode.
|
66
|
+
# @returns [Hash] The default capabilities for the Firefox browser.
|
67
|
+
def default_capabilities(headless: true)
|
68
|
+
{
|
69
|
+
alwaysMatch: {
|
70
|
+
browserName: "firefox",
|
71
|
+
"moz:firefoxOptions": {
|
72
|
+
"args": [headless ? "-headless" : nil].compact,
|
73
|
+
}
|
74
|
+
}
|
75
|
+
}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2023, by Samuel Williams.
|
5
|
+
|
6
|
+
require 'socket'
|
7
|
+
require 'async/http/endpoint'
|
8
|
+
require 'async/http/client'
|
9
|
+
|
10
|
+
module Async
|
11
|
+
module WebDriver
|
12
|
+
module Bridge
|
13
|
+
# Generic W3C WebDriver implementation.
|
14
|
+
class Generic
|
15
|
+
# Start the driver and return a new instance.
|
16
|
+
def self.start(**options)
|
17
|
+
self.new(**options).tap do |bridge|
|
18
|
+
bridge.start
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Initialize the driver.
|
23
|
+
# @parameter port [Integer] The port to listen on.
|
24
|
+
def initialize(port: nil)
|
25
|
+
@port = port
|
26
|
+
@status = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
# @attribute [String] The status of the driver after a connection has been established.
|
30
|
+
attr :status
|
31
|
+
|
32
|
+
# @returns [String | Nil] The version of the driver.
|
33
|
+
def version
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
|
37
|
+
# @returns [Boolean] Is the driver supported/working?
|
38
|
+
def supported?
|
39
|
+
version != nil
|
40
|
+
end
|
41
|
+
|
42
|
+
# Start the driver.
|
43
|
+
# @parameter retries [Integer] The number of times to retry before giving up.
|
44
|
+
def start(retries: 100)
|
45
|
+
Console.debug(self, "Waiting for driver to start...")
|
46
|
+
count = 0
|
47
|
+
|
48
|
+
Async::HTTP::Client.open(endpoint) do |client|
|
49
|
+
begin
|
50
|
+
response = client.get("/status")
|
51
|
+
@status = JSON.parse(response.read)["value"]
|
52
|
+
Console.debug(self, "Successfully connected to driver.", status: @status)
|
53
|
+
rescue Errno::ECONNREFUSED
|
54
|
+
if count < retries
|
55
|
+
count += 1
|
56
|
+
sleep(0.001 * count)
|
57
|
+
Console.debug(self, "Driver not ready, retrying...")
|
58
|
+
retry
|
59
|
+
else
|
60
|
+
raise
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Close the driver and any associated resources.
|
67
|
+
def close
|
68
|
+
end
|
69
|
+
|
70
|
+
# Generate a port number for the driver to listen on if it was not specified.
|
71
|
+
# @returns [Integer] The port the driver is listening on.
|
72
|
+
def port
|
73
|
+
unless @port
|
74
|
+
address = ::Addrinfo.tcp("localhost", 0)
|
75
|
+
address.bind do |socket|
|
76
|
+
# We assume that it's unlikely the port will be reused any time soon...
|
77
|
+
@port = socket.local_address.ip_port
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
return @port
|
82
|
+
end
|
83
|
+
|
84
|
+
# @returns [Async::HTTP::Endpoint] The endpoint the driver is listening on.
|
85
|
+
def endpoint
|
86
|
+
Async::HTTP::Endpoint.parse("http://localhost:#{port}")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2023, by Samuel Williams.
|
5
|
+
|
6
|
+
module Async
|
7
|
+
module WebDriver
|
8
|
+
module Bridge
|
9
|
+
# A pool of sessions.
|
10
|
+
#
|
11
|
+
# ``` ruby
|
12
|
+
# begin
|
13
|
+
# bridge = Async::WebDriver::Bridge::Pool.start(Async::WebDriver::Bridge::Chrome.new)
|
14
|
+
# session = bridge.session
|
15
|
+
# ensure
|
16
|
+
# bridge&.close
|
17
|
+
# end
|
18
|
+
# ```
|
19
|
+
class Pool
|
20
|
+
# Create a new session pool and start it.
|
21
|
+
# @parameter bridge [Bridge] The bridge to use to create sessions.
|
22
|
+
# @parameter capabilities [Hash] The capabilities to use when creating sessions.
|
23
|
+
# @returns [Pool] The pool.
|
24
|
+
def self.start(bridge, **options)
|
25
|
+
self.new(bridge, **options).tap do |pool|
|
26
|
+
pool.start
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Initialize the session pool.
|
31
|
+
# @parameter bridge [Bridge] The bridge to use to create sessions.
|
32
|
+
# @parameter capabilities [Hash] The capabilities to use when creating sessions.
|
33
|
+
# @parameter minimum [Integer] The minimum number of sessions to keep open.
|
34
|
+
def initialize(bridge, capabilities: bridge.default_capabilities, minimum: 2)
|
35
|
+
@bridge = bridge
|
36
|
+
@capabilities = capabilities
|
37
|
+
@minimum = minimum
|
38
|
+
|
39
|
+
@thread = nil
|
40
|
+
|
41
|
+
@waiting = Thread::Queue.new
|
42
|
+
@sessions = Thread::Queue.new
|
43
|
+
end
|
44
|
+
|
45
|
+
# Close the session pool.
|
46
|
+
def close
|
47
|
+
if @waiting
|
48
|
+
@waiting.close
|
49
|
+
end
|
50
|
+
|
51
|
+
if @thread
|
52
|
+
@thread.join
|
53
|
+
@thread = nil
|
54
|
+
end
|
55
|
+
|
56
|
+
if @sessions
|
57
|
+
@sessions.close
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private def prepare_session(client)
|
62
|
+
client.post("session", {capabilities: @capabilities})
|
63
|
+
end
|
64
|
+
|
65
|
+
# Start the session pool.
|
66
|
+
def start
|
67
|
+
@thread ||= Thread.new do
|
68
|
+
Sync do
|
69
|
+
@bridge.start
|
70
|
+
|
71
|
+
client = Client.open(@bridge.endpoint)
|
72
|
+
|
73
|
+
@minimum.times do
|
74
|
+
@waiting << true
|
75
|
+
end
|
76
|
+
|
77
|
+
while @waiting.pop
|
78
|
+
session = prepare_session(client)
|
79
|
+
@sessions << session
|
80
|
+
end
|
81
|
+
ensure
|
82
|
+
client&.close
|
83
|
+
@bridge.close
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Open a session.
|
89
|
+
def session(&block)
|
90
|
+
@waiting << true
|
91
|
+
|
92
|
+
reply = @sessions.pop
|
93
|
+
|
94
|
+
Session.open(@bridge.endpoint, reply["sessionId"], reply["capabilities"], &block)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2023, by Samuel Williams.
|
5
|
+
|
6
|
+
module Async
|
7
|
+
module WebDriver
|
8
|
+
module Bridge
|
9
|
+
# A group of processes that are all killed when the group is closed.
|
10
|
+
class ProcessGroup
|
11
|
+
# Spawn a new process group with a given command.
|
12
|
+
# @parameter arguments [Array] The command to execute.
|
13
|
+
def self.spawn(*arguments)
|
14
|
+
# This might be problematic...
|
15
|
+
self.new(
|
16
|
+
::Process.spawn(*arguments, pgroup: true, out: File::NULL, err: File::NULL)
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Create a new process group from an existing process id.
|
21
|
+
# @parameter pid [Integer] The process id.
|
22
|
+
def initialize(pid)
|
23
|
+
@pid = pid
|
24
|
+
|
25
|
+
@status_task = Async(transient: true) do
|
26
|
+
@status = ::Process.wait(@pid)
|
27
|
+
|
28
|
+
unless @status.success?
|
29
|
+
Console.error(self, "Process exited unexpectedly: #{@status}")
|
30
|
+
end
|
31
|
+
ensure
|
32
|
+
self.close
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Close the process group.
|
37
|
+
def close
|
38
|
+
if @status_task
|
39
|
+
@status_task.stop
|
40
|
+
@status_task = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
if @pid
|
44
|
+
::Process.kill("INT", -@pid)
|
45
|
+
|
46
|
+
Async do |task|
|
47
|
+
task.with_timeout(1) do
|
48
|
+
::Process.wait(@pid)
|
49
|
+
rescue Errno::ECHILD
|
50
|
+
# Done.
|
51
|
+
rescue Async::TimeoutError
|
52
|
+
Console.info(self, "Killing pid #{@pid}...")
|
53
|
+
::Process.kill("KILL", -@pid)
|
54
|
+
end
|
55
|
+
end.wait
|
56
|
+
|
57
|
+
wait_all(-@pid)
|
58
|
+
@pid = nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
# Wait for all processes in the group to exit.
|
65
|
+
def wait_all(pgid)
|
66
|
+
while true
|
67
|
+
pid, status = ::Process.wait2(pgid, ::Process::WNOHANG)
|
68
|
+
|
69
|
+
break unless pid
|
70
|
+
end
|
71
|
+
rescue Errno::ECHILD
|
72
|
+
# Done.
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2023, by Samuel Williams.
|
5
|
+
|
6
|
+
require_relative 'bridge/chrome'
|
7
|
+
require_relative 'bridge/firefox'
|
8
|
+
|
9
|
+
module Async
|
10
|
+
module WebDriver
|
11
|
+
# A bridge is a process that can be used to communicate with a browser.
|
12
|
+
# It is not needed in all cases, but is useful when you want to run integration tests without any external drivers/dependencies.
|
13
|
+
# As starting a bridge can be slow, it is recommended to use a shared bridge when possible.
|
14
|
+
module Bridge
|
15
|
+
ALL = [
|
16
|
+
Bridge::Chrome,
|
17
|
+
Bridge::Firefox,
|
18
|
+
]
|
19
|
+
|
20
|
+
def self.each(&block)
|
21
|
+
return enum_for(:each) unless block_given?
|
22
|
+
|
23
|
+
ALL.each do |klass|
|
24
|
+
next unless klass.new.supported?
|
25
|
+
yield klass
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -1,30 +1,75 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
class Client
|
6
|
-
def initialize(endpoint:, desired_capabilities: {})
|
7
|
-
@connection = Connection.new endpoint: endpoint
|
8
|
-
@desired_capabilities = desired_capabilities
|
9
|
-
end
|
10
|
-
|
11
|
-
def session
|
12
|
-
SessionCreator.new connection: @connection, desired_capabilities: @desired_capabilities
|
13
|
-
end
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2023, by Samuel Williams.
|
14
5
|
|
15
|
-
|
16
|
-
|
17
|
-
end
|
6
|
+
require_relative 'request_helper'
|
7
|
+
require_relative 'session'
|
18
8
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
9
|
+
module Async
|
10
|
+
module WebDriver
|
11
|
+
# A client for the WebDriver protocol.
|
12
|
+
#
|
13
|
+
# If you have a running web driver server, you can connect to it like so (assuming it is running on port 4444):
|
14
|
+
#
|
15
|
+
# ``` ruby
|
16
|
+
# begin
|
17
|
+
# client = Async::WebDriver::Client.open(Async::HTTP::Endpoint.parse("http://localhost:4444"))
|
18
|
+
# session = client.session
|
19
|
+
# ensure
|
20
|
+
# client&.close
|
21
|
+
# end
|
22
|
+
# ```
|
23
|
+
class Client
|
24
|
+
include RequestHelper
|
25
|
+
|
26
|
+
# Open a new session.
|
27
|
+
# @parameter endpoint [Async::HTTP::Endpoint] The endpoint to connect to.
|
28
|
+
# @yields {|client| ...} The client will be closed automatically if you provide a block.
|
29
|
+
# @parameter client [Client] The client.
|
30
|
+
# @returns [Client] The client if no block is given.
|
31
|
+
def self.open(endpoint, **options)
|
32
|
+
client = self.new(
|
33
|
+
Async::HTTP::Client.open(endpoint, **options)
|
34
|
+
)
|
35
|
+
|
36
|
+
return client unless block_given?
|
37
|
+
|
38
|
+
begin
|
39
|
+
yield client
|
40
|
+
ensure
|
41
|
+
client.close
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Initialize the client.
|
46
|
+
# @parameter delegate [Protocol::HTTP::Middleware] The underlying HTTP client (or wrapper).
|
47
|
+
def initialize(delegate)
|
48
|
+
@delegate = delegate
|
49
|
+
end
|
50
|
+
|
51
|
+
# Close the client.
|
52
|
+
def close
|
53
|
+
@delegate.close
|
54
|
+
end
|
55
|
+
|
56
|
+
# Request a new session.
|
57
|
+
# @returns [Session] The session if no block is given.
|
58
|
+
# @yields {|session| ...} The session will be closed automatically if you provide a block.
|
59
|
+
# @parameter session [Session] The session.
|
60
|
+
def session(capabilities, &block)
|
61
|
+
reply = post("session", {capabilities: capabilities})
|
62
|
+
|
63
|
+
session = Session.new(@delegate, reply["sessionId"], reply["value"])
|
64
|
+
|
65
|
+
return session unless block_given?
|
66
|
+
|
67
|
+
begin
|
68
|
+
yield session
|
69
|
+
ensure
|
70
|
+
session.close
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
30
75
|
end
|