async-webdriver 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/async/webdriver/bridge/chrome.rb +35 -28
- data/lib/async/webdriver/bridge/driver.rb +93 -0
- data/lib/async/webdriver/bridge/firefox.rb +38 -28
- data/lib/async/webdriver/bridge/generic.rb +3 -59
- data/lib/async/webdriver/bridge/pool.rb +117 -61
- data/lib/async/webdriver/bridge/process_group.rb +19 -0
- data/lib/async/webdriver/bridge.rb +27 -0
- data/lib/async/webdriver/client.rb +1 -1
- data/lib/async/webdriver/error.rb +0 -30
- data/lib/async/webdriver/session.rb +6 -3
- data/lib/async/webdriver/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +47 -4
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2885f18a964ec45c687168efd25b89326eb2a7cdf30e2458a8d348a6613d22ff
|
4
|
+
data.tar.gz: 58de67040f73f43aa73e2877acab7c7637575721613d63941127bb69a38e3899
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ddfea03fb616984f3f48d2bf6199918119c6750a84e53f012d9d3e3a9c6231508cb911578c19cfd7cd7a88a4fe15965d5a8a8502fd7fc3148af0c8aa409f1fd1
|
7
|
+
data.tar.gz: 7a309bf300d64898d622da707e9fa98e37876cd1752e9ce749b45ed74d0648b7d3f5030d3bad9b123f5bfec468faee57271c45e596af7936868acc34c62fa4b4
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
@@ -20,48 +20,54 @@ module Async
|
|
20
20
|
# end
|
21
21
|
# ```
|
22
22
|
class Chrome < Generic
|
23
|
-
|
24
|
-
|
25
|
-
def initialize(path: "chromedriver")
|
26
|
-
super()
|
27
|
-
|
28
|
-
@path = path
|
29
|
-
@process = nil
|
23
|
+
def path
|
24
|
+
@options.fetch(:path, "chromedriver")
|
30
25
|
end
|
31
26
|
|
32
27
|
# @returns [String] The version of the `chromedriver` executable.
|
33
28
|
def version
|
34
|
-
::IO.popen([
|
29
|
+
::IO.popen([self.path, "--version"]) do |io|
|
35
30
|
return io.read
|
36
31
|
end
|
37
32
|
rescue Errno::ENOENT
|
38
33
|
return nil
|
39
34
|
end
|
40
35
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
47
|
-
|
48
|
-
# Start the driver.
|
49
|
-
def start
|
50
|
-
@process ||= ProcessGroup.spawn(@path, *arguments)
|
36
|
+
class Driver < Bridge::Driver
|
37
|
+
def initialize(**options)
|
38
|
+
super(**options)
|
39
|
+
@process_group = nil
|
40
|
+
end
|
51
41
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
42
|
+
# @returns [Array(String)] The arguments to pass to the `chromedriver` executable.
|
43
|
+
def arguments(**options)
|
44
|
+
[
|
45
|
+
options.fetch(:path, "chromedriver"),
|
46
|
+
"--port=#{self.port}",
|
47
|
+
].compact
|
48
|
+
end
|
49
|
+
|
50
|
+
def start
|
51
|
+
@process_group = ProcessGroup.spawn(*arguments(**@options))
|
52
|
+
|
53
|
+
super
|
54
|
+
end
|
58
55
|
|
59
|
-
|
60
|
-
@
|
61
|
-
|
56
|
+
def close
|
57
|
+
if @process_group
|
58
|
+
@process_group.close
|
59
|
+
@process_group = nil
|
60
|
+
end
|
61
|
+
|
62
|
+
super
|
62
63
|
end
|
63
64
|
end
|
64
65
|
|
66
|
+
# Start the driver.
|
67
|
+
def start(**options)
|
68
|
+
Driver.new(**options).tap(&:start)
|
69
|
+
end
|
70
|
+
|
65
71
|
# The default capabilities for the Chrome browser which need to be provided when requesting a new session.
|
66
72
|
# @parameter headless [Boolean] Whether to run the browser in headless mode.
|
67
73
|
# @returns [Hash] The default capabilities for the Chrome browser.
|
@@ -71,7 +77,8 @@ module Async
|
|
71
77
|
browserName: "chrome",
|
72
78
|
"goog:chromeOptions": {
|
73
79
|
args: [headless ? "--headless" : nil].compact,
|
74
|
-
}
|
80
|
+
},
|
81
|
+
webSocketUrl: true,
|
75
82
|
},
|
76
83
|
}
|
77
84
|
end
|
@@ -0,0 +1,93 @@
|
|
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
|
+
# Represents an instance of a locally running driver (usually with a process group).
|
10
|
+
class Driver
|
11
|
+
def initialize(**options)
|
12
|
+
@options = options
|
13
|
+
@count = 0
|
14
|
+
@closed = false
|
15
|
+
end
|
16
|
+
|
17
|
+
def concurrency
|
18
|
+
@options.fetch(:concurrency, 128)
|
19
|
+
end
|
20
|
+
|
21
|
+
attr :count
|
22
|
+
|
23
|
+
# @attribute [Hash] The status of the driver after a connection has been established.
|
24
|
+
attr :status
|
25
|
+
|
26
|
+
def viable?
|
27
|
+
!@closed
|
28
|
+
end
|
29
|
+
|
30
|
+
def closed?
|
31
|
+
@closed
|
32
|
+
end
|
33
|
+
|
34
|
+
def close
|
35
|
+
@closed = true
|
36
|
+
end
|
37
|
+
|
38
|
+
def reusable?
|
39
|
+
@options.fetch(:reusable, !@closed)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Generate a port number for the driver to listen on if it was not specified.
|
43
|
+
# @returns [Integer] An ephemeral port number.
|
44
|
+
def ephemeral_port
|
45
|
+
address = ::Addrinfo.tcp("localhost", 0)
|
46
|
+
|
47
|
+
address.bind do |socket|
|
48
|
+
# We assume that it's unlikely the port will be reused any time soon...
|
49
|
+
return socket.local_address.ip_port
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def port
|
54
|
+
@port ||= @options.fetch(:port, self.ephemeral_port)
|
55
|
+
end
|
56
|
+
|
57
|
+
def endpoint
|
58
|
+
Async::HTTP::Endpoint.parse("http://localhost", port: self.port)
|
59
|
+
end
|
60
|
+
|
61
|
+
def client
|
62
|
+
Client.open(self.endpoint)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Start the driver.
|
66
|
+
# @parameter retries [Integer] The number of times to retry before giving up.
|
67
|
+
def start(retries: 100)
|
68
|
+
endpoint = self.endpoint
|
69
|
+
|
70
|
+
Console.debug(self, "Waiting for driver to start...", endpoint: endpoint)
|
71
|
+
count = 0
|
72
|
+
|
73
|
+
Async::HTTP::Client.open(endpoint) do |client|
|
74
|
+
begin
|
75
|
+
response = client.get("/status")
|
76
|
+
@status = JSON.parse(response.read)["value"]
|
77
|
+
Console.debug(self, "Successfully connected to driver.", status: @status)
|
78
|
+
rescue Errno::ECONNREFUSED
|
79
|
+
if count < retries
|
80
|
+
count += 1
|
81
|
+
sleep(0.01 * count)
|
82
|
+
Console.debug(self, "Driver not ready, retrying...")
|
83
|
+
retry
|
84
|
+
else
|
85
|
+
raise
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -19,46 +19,56 @@ module Async
|
|
19
19
|
# bridge&.close
|
20
20
|
# end
|
21
21
|
class Firefox < Generic
|
22
|
-
|
23
|
-
|
24
|
-
def initialize(path: "geckodriver")
|
25
|
-
super()
|
26
|
-
|
27
|
-
@path = path
|
28
|
-
@process = nil
|
22
|
+
def path
|
23
|
+
@options.fetch(:path, "geckodriver")
|
29
24
|
end
|
30
25
|
|
31
26
|
# @returns [String] The version of the `geckodriver` executable.
|
32
27
|
def version
|
33
|
-
::IO.popen([
|
28
|
+
::IO.popen([self.path, "--version"]) do |io|
|
34
29
|
return io.read
|
35
30
|
end
|
36
31
|
rescue Errno::ENOENT
|
37
32
|
return nil
|
38
33
|
end
|
39
34
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
end
|
46
|
-
|
47
|
-
# Start the driver.
|
48
|
-
def start
|
49
|
-
@process ||= ProcessGroup.spawn(@path, *arguments)
|
35
|
+
class Driver < Bridge::Driver
|
36
|
+
def initialize(**options)
|
37
|
+
super(**options)
|
38
|
+
@process_group = nil
|
39
|
+
end
|
50
40
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
# Close the driver.
|
55
|
-
def close
|
56
|
-
super
|
41
|
+
def concurrency
|
42
|
+
1
|
43
|
+
end
|
57
44
|
|
58
|
-
|
59
|
-
|
60
|
-
|
45
|
+
# @returns [Array(String)] The arguments to pass to the `chromedriver` executable.
|
46
|
+
def arguments(**options)
|
47
|
+
[
|
48
|
+
options.fetch(:path, "geckodriver"),
|
49
|
+
"--port", self.port.to_s,
|
50
|
+
].compact
|
61
51
|
end
|
52
|
+
|
53
|
+
def start
|
54
|
+
@process_group = ProcessGroup.spawn(*arguments(**@options))
|
55
|
+
|
56
|
+
super
|
57
|
+
end
|
58
|
+
|
59
|
+
def close
|
60
|
+
if @process_group
|
61
|
+
@process_group.close
|
62
|
+
@process_group = nil
|
63
|
+
end
|
64
|
+
|
65
|
+
super
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Start the driver.
|
70
|
+
def start(**options)
|
71
|
+
Driver.new(**options).tap(&:start)
|
62
72
|
end
|
63
73
|
|
64
74
|
# The default capabilities for the Firefox browser which need to be provided when requesting a new session.
|
@@ -69,7 +79,7 @@ module Async
|
|
69
79
|
alwaysMatch: {
|
70
80
|
browserName: "firefox",
|
71
81
|
"moz:firefoxOptions": {
|
72
|
-
|
82
|
+
args: [headless ? "-headless" : nil].compact,
|
73
83
|
}
|
74
84
|
}
|
75
85
|
}
|
@@ -12,23 +12,14 @@ module Async
|
|
12
12
|
module Bridge
|
13
13
|
# Generic W3C WebDriver implementation.
|
14
14
|
class Generic
|
15
|
-
# Start the driver and return a new instance.
|
16
15
|
def self.start(**options)
|
17
|
-
self.new(**options).
|
18
|
-
bridge.start
|
19
|
-
end
|
16
|
+
self.new(**options).start
|
20
17
|
end
|
21
18
|
|
22
|
-
|
23
|
-
|
24
|
-
def initialize(port: nil)
|
25
|
-
@port = port
|
26
|
-
@status = nil
|
19
|
+
def initialize(**options)
|
20
|
+
@options = options
|
27
21
|
end
|
28
22
|
|
29
|
-
# @attribute [String] The status of the driver after a connection has been established.
|
30
|
-
attr :status
|
31
|
-
|
32
23
|
# @returns [String | Nil] The version of the driver.
|
33
24
|
def version
|
34
25
|
nil
|
@@ -38,53 +29,6 @@ module Async
|
|
38
29
|
def supported?
|
39
30
|
version != nil
|
40
31
|
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
32
|
end
|
89
33
|
end
|
90
34
|
end
|
@@ -3,10 +3,15 @@
|
|
3
3
|
# Released under the MIT License.
|
4
4
|
# Copyright, 2023, by Samuel Williams.
|
5
5
|
|
6
|
+
require 'async/actor'
|
7
|
+
require 'async/pool'
|
8
|
+
|
9
|
+
require_relative '../session'
|
10
|
+
|
6
11
|
module Async
|
7
12
|
module WebDriver
|
8
13
|
module Bridge
|
9
|
-
# A pool of sessions.
|
14
|
+
# A pool of sessions, constructed from a bridge, which instantiates drivers as needed. Drivers are capable of supporting 1 ore more sessions.
|
10
15
|
#
|
11
16
|
# ``` ruby
|
12
17
|
# begin
|
@@ -17,90 +22,139 @@ module Async
|
|
17
22
|
# end
|
18
23
|
# ```
|
19
24
|
class Pool
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
class BridgeController
|
26
|
+
def initialize(bridge, capabilities: bridge.default_capabilities)
|
27
|
+
@bridge = bridge
|
28
|
+
@capabilities = capabilities
|
29
|
+
@pool = Async::Pool::Controller.new(self)
|
30
|
+
end
|
31
|
+
|
32
|
+
class SessionCache
|
33
|
+
def initialize(driver, capabilities)
|
34
|
+
@driver = driver
|
35
|
+
@capabilities = capabilities
|
36
|
+
|
37
|
+
@client = driver.client
|
38
|
+
@sessions = []
|
39
|
+
end
|
40
|
+
|
41
|
+
def viable?
|
42
|
+
@driver&.viable?
|
43
|
+
end
|
44
|
+
|
45
|
+
def reusable?
|
46
|
+
@driver&.reusable?
|
47
|
+
end
|
48
|
+
|
49
|
+
def close
|
50
|
+
if @driver
|
51
|
+
@driver.close
|
52
|
+
@driver = nil
|
53
|
+
end
|
54
|
+
|
55
|
+
if @client
|
56
|
+
@client.close
|
57
|
+
@client = nil
|
58
|
+
end
|
59
|
+
|
60
|
+
if @sessions
|
61
|
+
@sessions = nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def concurrency
|
66
|
+
@driver.concurrency
|
67
|
+
end
|
68
|
+
|
69
|
+
def acquire
|
70
|
+
if @sessions.empty?
|
71
|
+
session = @client.post("session", {capabilities: @capabilities})
|
72
|
+
session[:cache] = self
|
73
|
+
session[:endpoint] = @driver.endpoint
|
74
|
+
|
75
|
+
return session
|
76
|
+
else
|
77
|
+
return @sessions.pop
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def release(session)
|
82
|
+
@sessions.push(session)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Constructor for the pool.
|
87
|
+
def call
|
88
|
+
SessionCache.new(@bridge.start, @capabilities)
|
89
|
+
end
|
90
|
+
|
91
|
+
def acquire
|
92
|
+
session_cache = @pool.acquire
|
93
|
+
|
94
|
+
return session_cache.acquire
|
95
|
+
end
|
96
|
+
|
97
|
+
def release(session)
|
98
|
+
session_cache = session[:cache]
|
99
|
+
|
100
|
+
session_cache.release(session)
|
101
|
+
|
102
|
+
@pool.release(session_cache)
|
103
|
+
end
|
104
|
+
|
105
|
+
def retire(session)
|
106
|
+
session_cache = session[:cache]
|
107
|
+
|
108
|
+
session_cache.release(session)
|
109
|
+
|
110
|
+
@pool.retire(session_cache)
|
111
|
+
end
|
112
|
+
|
113
|
+
def close
|
114
|
+
if @pool
|
115
|
+
@pool.close
|
116
|
+
@pool = nil
|
117
|
+
end
|
27
118
|
end
|
28
119
|
end
|
29
120
|
|
30
121
|
# Initialize the session pool.
|
31
122
|
# @parameter bridge [Bridge] The bridge to use to create sessions.
|
32
|
-
|
33
|
-
|
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
|
123
|
+
def initialize(...)
|
124
|
+
@controller = Async::Actor.new(BridgeController.new(...))
|
43
125
|
end
|
44
126
|
|
45
127
|
# Close the session pool.
|
46
128
|
def close
|
47
|
-
|
48
|
-
|
129
|
+
@controller.close
|
130
|
+
end
|
131
|
+
|
132
|
+
class CachedWrapper < Session
|
133
|
+
def pool
|
134
|
+
@options[:pool]
|
49
135
|
end
|
50
136
|
|
51
|
-
|
52
|
-
@
|
53
|
-
@thread = nil
|
137
|
+
def payload
|
138
|
+
@options[:payload]
|
54
139
|
end
|
55
140
|
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
141
|
+
def close
|
142
|
+
unless self.pool.reuse(self)
|
143
|
+
super
|
84
144
|
end
|
85
145
|
end
|
86
146
|
end
|
87
147
|
|
88
148
|
# Open a session.
|
89
149
|
def session(&block)
|
90
|
-
|
150
|
+
payload = @controller.acquire
|
91
151
|
|
92
|
-
|
93
|
-
|
94
|
-
session = Session.open(@bridge.endpoint, reply["sessionId"], reply["capabilities"])
|
152
|
+
session = CachedWrapper.open(payload[:endpoint], payload["sessionId"], payload["capabilities"], pool: self, payload: payload)
|
95
153
|
|
96
154
|
return session unless block_given?
|
97
155
|
|
98
156
|
begin
|
99
157
|
yield session
|
100
|
-
|
101
|
-
# Try to reuse the session for extreme performance:
|
102
|
-
reuse(session)
|
103
|
-
session = nil
|
104
158
|
ensure
|
105
159
|
session&.close
|
106
160
|
end
|
@@ -109,7 +163,9 @@ module Async
|
|
109
163
|
def reuse(session)
|
110
164
|
session.reset!
|
111
165
|
|
112
|
-
@
|
166
|
+
@controller.release(session.payload)
|
167
|
+
|
168
|
+
return true
|
113
169
|
end
|
114
170
|
end
|
115
171
|
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
# Released under the MIT License.
|
4
4
|
# Copyright, 2023, by Samuel Williams.
|
5
5
|
|
6
|
+
require_relative 'driver'
|
7
|
+
|
6
8
|
module Async
|
7
9
|
module WebDriver
|
8
10
|
module Bridge
|
@@ -72,6 +74,23 @@ module Async
|
|
72
74
|
# Done.
|
73
75
|
end
|
74
76
|
end
|
77
|
+
|
78
|
+
class ProcessDriver < Driver
|
79
|
+
def initialize(endpoint, process)
|
80
|
+
super(endpoint)
|
81
|
+
|
82
|
+
@process = process
|
83
|
+
end
|
84
|
+
|
85
|
+
def close
|
86
|
+
super
|
87
|
+
|
88
|
+
if @process
|
89
|
+
@process.close
|
90
|
+
@process = nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
75
94
|
end
|
76
95
|
end
|
77
96
|
end
|
@@ -6,6 +6,8 @@
|
|
6
6
|
require_relative 'bridge/chrome'
|
7
7
|
require_relative 'bridge/firefox'
|
8
8
|
|
9
|
+
require_relative 'error'
|
10
|
+
|
9
11
|
module Async
|
10
12
|
module WebDriver
|
11
13
|
# A bridge is a process that can be used to communicate with a browser.
|
@@ -25,6 +27,31 @@ module Async
|
|
25
27
|
yield klass
|
26
28
|
end
|
27
29
|
end
|
30
|
+
|
31
|
+
# The environment variable used to select a bridge.
|
32
|
+
#
|
33
|
+
# ```
|
34
|
+
# ASYNC_WEBDRIVER_BRIDGE=Chrome
|
35
|
+
# ASYNC_WEBDRIVER_BRIDGE=Firefox
|
36
|
+
# ```
|
37
|
+
ASYNC_WEBDRIVER_BRIDGE = 'ASYNC_WEBDRIVER_BRIDGE'
|
38
|
+
|
39
|
+
class UnsupportedError < Error
|
40
|
+
end
|
41
|
+
|
42
|
+
# @returns [Bridge] The default bridge to use.
|
43
|
+
def self.default(env = ENV)
|
44
|
+
if name = env[ASYNC_WEBDRIVER_BRIDGE]
|
45
|
+
self.const_get(name).new
|
46
|
+
else
|
47
|
+
ALL.each do |klass|
|
48
|
+
instance = klass.new
|
49
|
+
return instance if instance.supported?
|
50
|
+
end
|
51
|
+
|
52
|
+
raise UnsupportedError, "No supported bridge found!"
|
53
|
+
end
|
54
|
+
end
|
28
55
|
end
|
29
56
|
end
|
30
57
|
end
|
@@ -60,7 +60,7 @@ module Async
|
|
60
60
|
def session(capabilities, &block)
|
61
61
|
reply = post("session", {capabilities: capabilities})
|
62
62
|
|
63
|
-
session = Session.new(@delegate, reply["sessionId"], reply["
|
63
|
+
session = Session.new(@delegate, reply["sessionId"], reply["capabilities"])
|
64
64
|
|
65
65
|
return session unless block_given?
|
66
66
|
|
@@ -7,36 +7,6 @@ require_relative 'version'
|
|
7
7
|
|
8
8
|
module Async
|
9
9
|
module WebDriver
|
10
|
-
# Error Code HTTP Status JSON Error Code Description
|
11
|
-
# element click intercepted 400 element click intercepted The Element Click command could not be completed because the element receiving the events is obscuring the element that was requested clicked.
|
12
|
-
# element not interactable 400 element not interactable A command could not be completed because the element is not pointer- or keyboard interactable.
|
13
|
-
# insecure certificate 400 insecure certificate Navigation caused the user agent to hit a certificate warning, which is usually the result of an expired or invalid TLS certificate.
|
14
|
-
# invalid argument 400 invalid argument The arguments passed to a command are either invalid or malformed.
|
15
|
-
# invalid cookie domain 400 invalid cookie domain An illegal attempt was made to set a cookie under a different domain than the current page.
|
16
|
-
# invalid element state 400 invalid element state A command could not be completed because the element is in an invalid state, e.g. attempting to clear an element that isn’t both editable and resettable.
|
17
|
-
# invalid selector 400 invalid selector Argument was an invalid selector.
|
18
|
-
# invalid session id 404 invalid session id Occurs if the given session id is not in the list of active sessions, meaning the session either does not exist or that it’s not active.
|
19
|
-
# javascript error 500 javascript error An error occurred while executing JavaScript supplied by the user.
|
20
|
-
# move target out of bounds 500 move target out of bounds The target for mouse interaction is not in the browser’s viewport and cannot be brought into that viewport.
|
21
|
-
# no such alert 404 no such alert An attempt was made to operate on a modal dialog when one was not open.
|
22
|
-
# no such cookie 404 no such cookie No cookie matching the given path name was found amongst the associated cookies of the current browsing context’s active document.
|
23
|
-
# no such element 404 no such element An element could not be located on the page using the given search parameters.
|
24
|
-
# no such frame 404 no such frame A command to switch to a frame could not be satisfied because the frame could not be found.
|
25
|
-
# no such window 404 no such window A command to switch to a window could not be satisfied because the window could not be found.
|
26
|
-
# no such shadow root 404 no such shadow root The element does not have a shadow root.
|
27
|
-
# script timeout error 500 script timeout A script did not complete before its timeout expired.
|
28
|
-
# session not created 500 session not created A new session could not be created.
|
29
|
-
# stale element reference 404 stale element reference A command failed because the referenced element is no longer attached to the DOM.
|
30
|
-
# detached shadow root 404 detached shadow root A command failed because the referenced shadow root is no longer attached to the DOM.
|
31
|
-
# timeout 500 timeout An operation did not complete before its timeout expired.
|
32
|
-
# unable to set cookie 500 unable to set cookie A command to set a cookie’s value could not be satisfied.
|
33
|
-
# unable to capture screen 500 unable to capture screen A screen capture was made impossible.
|
34
|
-
# unexpected alert open 500 unexpected alert open A modal dialog was open, blocking this operation.
|
35
|
-
# unknown command 404 unknown command A command could not be executed because the remote end is not aware of it.
|
36
|
-
# unknown error 500 unknown error An unknown error occurred in the remote end while processing the command.
|
37
|
-
# unknown method 405 unknown method The requested command matched a known URL but did not match any method for that URL.
|
38
|
-
# unsupported operation 500 unsupported operation Indicates that a command that should have executed properly cannot be supported for some reason.
|
39
|
-
|
40
10
|
class Error < StandardError
|
41
11
|
end
|
42
12
|
|
@@ -30,8 +30,9 @@ module Async
|
|
30
30
|
# @returns [Session] The session if no block is given.
|
31
31
|
def self.open(endpoint, *arguments, **options)
|
32
32
|
client = self.new(
|
33
|
-
Async::HTTP::Client.open(endpoint
|
34
|
-
*arguments
|
33
|
+
Async::HTTP::Client.open(endpoint),
|
34
|
+
*arguments,
|
35
|
+
**options
|
35
36
|
)
|
36
37
|
|
37
38
|
return client unless block_given?
|
@@ -47,10 +48,12 @@ module Async
|
|
47
48
|
# @parameter delegate [Protocol::HTTP::Middleware] The underlying HTTP client (or wrapper).
|
48
49
|
# @parameter id [String] The session identifier.
|
49
50
|
# @parameter capabilities [Hash] The capabilities of the session.
|
50
|
-
def initialize(delegate, id, capabilities)
|
51
|
+
def initialize(delegate, id, capabilities, **options)
|
51
52
|
@delegate = delegate
|
52
53
|
@id = id
|
53
54
|
@capabilities = capabilities
|
55
|
+
|
56
|
+
@options = options
|
54
57
|
end
|
55
58
|
|
56
59
|
def inspect
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: async-webdriver
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
@@ -37,7 +37,7 @@ cert_chain:
|
|
37
37
|
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
38
38
|
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
39
39
|
-----END CERTIFICATE-----
|
40
|
-
date: 2023-12-
|
40
|
+
date: 2023-12-08 00:00:00.000000000 Z
|
41
41
|
dependencies:
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: async-http
|
@@ -45,14 +45,56 @@ dependencies:
|
|
45
45
|
requirements:
|
46
46
|
- - "~>"
|
47
47
|
- !ruby/object:Gem::Version
|
48
|
-
version: '0.
|
48
|
+
version: '0.61'
|
49
49
|
type: :runtime
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
53
|
- - "~>"
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version: '0.
|
55
|
+
version: '0.61'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: async-websocket
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0.25'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0.25'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: async-actor
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0.1'
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0.1'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: async-pool
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0.4'
|
91
|
+
type: :runtime
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0.4'
|
56
98
|
description:
|
57
99
|
email:
|
58
100
|
executables: []
|
@@ -62,6 +104,7 @@ files:
|
|
62
104
|
- lib/async/webdriver.rb
|
63
105
|
- lib/async/webdriver/bridge.rb
|
64
106
|
- lib/async/webdriver/bridge/chrome.rb
|
107
|
+
- lib/async/webdriver/bridge/driver.rb
|
65
108
|
- lib/async/webdriver/bridge/firefox.rb
|
66
109
|
- lib/async/webdriver/bridge/generic.rb
|
67
110
|
- lib/async/webdriver/bridge/pool.rb
|
metadata.gz.sig
CHANGED
Binary file
|