async-webdriver 0.10.0 → 0.12.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/context/navigation-timing.md +2 -2
- data/lib/async/webdriver/bridge/chrome.rb +47 -11
- data/lib/async/webdriver/bridge/driver.rb +11 -1
- data/lib/async/webdriver/bridge/firefox.rb +14 -7
- data/lib/async/webdriver/bridge/generic.rb +4 -1
- data/lib/async/webdriver/bridge/pool.rb +31 -1
- data/lib/async/webdriver/bridge/process_group.rb +6 -1
- data/lib/async/webdriver/bridge/safari.rb +12 -6
- data/lib/async/webdriver/bridge.rb +5 -1
- data/lib/async/webdriver/element.rb +3 -1
- data/lib/async/webdriver/error.rb +2 -1
- data/lib/async/webdriver/installer/chrome/installation.rb +193 -0
- data/lib/async/webdriver/installer/chrome/platform.rb +82 -0
- data/lib/async/webdriver/installer/chrome/releases.rb +113 -0
- data/lib/async/webdriver/installer/chrome.rb +66 -0
- data/lib/async/webdriver/installer.rb +37 -0
- data/lib/async/webdriver/scope/printing.rb +38 -6
- data/lib/async/webdriver/scope/window.rb +50 -0
- data/lib/async/webdriver/scope.rb +10 -1
- data/lib/async/webdriver/session.rb +3 -1
- data/lib/async/webdriver/version.rb +4 -2
- data/license.md +1 -1
- data/readme.md +29 -0
- data/releases.md +13 -0
- data.tar.gz.sig +0 -0
- metadata +9 -3
- 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: 06203afae057f1ba3f11ac14dccfd38dde2698a1d56d5ecbae3961b8317c2f98
|
|
4
|
+
data.tar.gz: 07e491fb4ceec6a760eb0ff2e3f301b8f082edb410dafcf885278aa311c34138
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 65e7bafd0d7f1656af4739a55ab37efbb7471d99726cdd38b689e5d4d24f0f3ee9d290cd717ac4c2c39e819953029360b8b6a59b3ed3a7d60bc9610425b068c0
|
|
7
|
+
data.tar.gz: a0baab577a90d809486da2ff3ae12956b07ee11e39131534de2f36e0179ecde07da66f09baffb1e7ed070320ad1520fae4025f1df0b675a4260023af3f84a004
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -84,7 +84,7 @@ The most reliable approach is to use `wait_for_navigation` to wait for the URL o
|
|
|
84
84
|
```ruby
|
|
85
85
|
# ✅ RELIABLE: Wait for URL change
|
|
86
86
|
session.click_button("Submit")
|
|
87
|
-
session.wait_for_navigation
|
|
87
|
+
session.wait_for_navigation{|url| url.end_with?("/success")}
|
|
88
88
|
session.navigate_to("/next-page") # Now safe
|
|
89
89
|
```
|
|
90
90
|
|
|
@@ -96,7 +96,7 @@ For critical operations like authentication, wait for server-side effects to com
|
|
|
96
96
|
# ✅ RELIABLE: Wait for authentication cookie
|
|
97
97
|
session.click_button("Login")
|
|
98
98
|
session.wait_for_navigation do |url, ready_state|
|
|
99
|
-
|
|
99
|
+
ready_state == "complete" && session.cookies.any?{|cookie| cookie["name"] == "auth_token"}
|
|
100
100
|
end
|
|
101
101
|
session.navigate_to("/dashboard") # Now safe
|
|
102
102
|
```
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2023-
|
|
4
|
+
# Copyright, 2023-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require_relative "generic"
|
|
7
7
|
require_relative "process_group"
|
|
@@ -20,20 +20,24 @@ module Async
|
|
|
20
20
|
# end
|
|
21
21
|
# ```
|
|
22
22
|
class Chrome < Generic
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
# @returns [String] The path to the `chromedriver` executable.
|
|
24
|
+
def driver_path
|
|
25
|
+
@options.fetch(:driver_path, "chromedriver")
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
# @returns [String] The version of the `chromedriver` executable.
|
|
28
29
|
def version
|
|
29
|
-
::IO.popen([self.
|
|
30
|
+
::IO.popen([self.driver_path, "--version"]) do |io|
|
|
30
31
|
return io.read
|
|
31
32
|
end
|
|
32
33
|
rescue Errno::ENOENT
|
|
33
34
|
return nil
|
|
34
35
|
end
|
|
35
36
|
|
|
37
|
+
# A locally managed `chromedriver` process.
|
|
36
38
|
class Driver < Bridge::Driver
|
|
39
|
+
# Initialize a managed Chrome driver process.
|
|
40
|
+
# @parameter options [Hash] Driver configuration options.
|
|
37
41
|
def initialize(**options)
|
|
38
42
|
super(**options)
|
|
39
43
|
@process_group = nil
|
|
@@ -42,17 +46,19 @@ module Async
|
|
|
42
46
|
# @returns [Array(String)] The arguments to pass to the `chromedriver` executable.
|
|
43
47
|
def arguments(**options)
|
|
44
48
|
[
|
|
45
|
-
options.fetch(:
|
|
49
|
+
options.fetch(:driver_path, "chromedriver"),
|
|
46
50
|
"--port=#{self.port}",
|
|
47
51
|
].compact
|
|
48
52
|
end
|
|
49
53
|
|
|
54
|
+
# Start the managed Chrome driver process and wait for readiness.
|
|
50
55
|
def start
|
|
51
56
|
@process_group = ProcessGroup.spawn(*arguments(**@options))
|
|
52
57
|
|
|
53
58
|
super
|
|
54
59
|
end
|
|
55
60
|
|
|
61
|
+
# Stop the managed Chrome driver process.
|
|
56
62
|
def close
|
|
57
63
|
if @process_group
|
|
58
64
|
@process_group.close
|
|
@@ -63,21 +69,51 @@ module Async
|
|
|
63
69
|
end
|
|
64
70
|
end
|
|
65
71
|
|
|
66
|
-
# Start the driver
|
|
72
|
+
# Start the driver, forwarding the bridge's own options to the driver process
|
|
73
|
+
# so that a custom `:driver_path` reaches the chromedriver executable.
|
|
67
74
|
def start(**options)
|
|
68
|
-
Driver.new(**options).tap(&:start)
|
|
75
|
+
Driver.new(**@options, **options).tap(&:start)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Ensure the given version of Chrome for Testing is installed and return a
|
|
79
|
+
# fully configured {Chrome} bridge pointing at it.
|
|
80
|
+
#
|
|
81
|
+
# Delegates to {Async::WebDriver::Installer::Chrome.install} for version
|
|
82
|
+
# resolution and download, then wraps the result in a configured bridge.
|
|
83
|
+
#
|
|
84
|
+
# @parameter version [Symbol | String] `:stable`, `:beta`, `:dev`, `:canary`,
|
|
85
|
+
# a major version string like `"148"`, or an exact version like `"148.0.7778.56"`.
|
|
86
|
+
# @parameter cache_path [String] Root of the cache directory.
|
|
87
|
+
# Default: `~/.cache/async-webdriver.rb` (XDG-compliant).
|
|
88
|
+
# @parameter options [Hash] Additional options forwarded to {.new} (e.g. `headless: false`).
|
|
89
|
+
# @returns [Chrome] A configured bridge.
|
|
90
|
+
def self.for(version = :stable, cache_path: Installer.cache_path("chrome"), **options)
|
|
91
|
+
require_relative "../installer/chrome"
|
|
92
|
+
installation = Installer::Chrome.find(version, cache_path: cache_path) || Installer::Chrome.install(version, cache_path: cache_path)
|
|
93
|
+
new(driver_path: installation.driver_path, browser_path: installation.browser_path, **options)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# The path to the Chrome browser executable. If `nil`, ChromeDriver uses its own discovery.
|
|
97
|
+
# @returns [String | Nil]
|
|
98
|
+
def browser_path
|
|
99
|
+
@options[:browser_path]
|
|
69
100
|
end
|
|
70
101
|
|
|
71
102
|
# The default capabilities for the Chrome browser which need to be provided when requesting a new session.
|
|
72
103
|
# @parameter headless [Boolean] Whether to run the browser in headless mode.
|
|
104
|
+
# @parameter browser_path [String | Nil] Path to the Chrome browser executable. Overrides ChromeDriver's default discovery, useful for pointing at a specific Chrome for Testing installation.
|
|
73
105
|
# @returns [Hash] The default capabilities for the Chrome browser.
|
|
74
|
-
def default_capabilities(headless: self.headless
|
|
106
|
+
def default_capabilities(headless: self.headless?, browser_path: self.browser_path)
|
|
107
|
+
chrome_options = {
|
|
108
|
+
args: [headless ? "--headless=new" : nil].compact,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
chrome_options[:binary] = browser_path if browser_path
|
|
112
|
+
|
|
75
113
|
{
|
|
76
114
|
alwaysMatch: {
|
|
77
115
|
browserName: "chrome",
|
|
78
|
-
"goog:chromeOptions":
|
|
79
|
-
args: [headless ? "--headless" : nil].compact,
|
|
80
|
-
},
|
|
116
|
+
"goog:chromeOptions": chrome_options,
|
|
81
117
|
webSocketUrl: true,
|
|
82
118
|
},
|
|
83
119
|
}
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2023, by Samuel Williams.
|
|
4
|
+
# Copyright, 2023-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
module Async
|
|
7
7
|
module WebDriver
|
|
8
8
|
module Bridge
|
|
9
9
|
# Represents an instance of a locally running driver (usually with a process group).
|
|
10
10
|
class Driver
|
|
11
|
+
# Initialize a driver wrapper.
|
|
12
|
+
# @parameter options [Hash] Driver configuration options.
|
|
11
13
|
def initialize(**options)
|
|
12
14
|
@options = options
|
|
13
15
|
@count = 0
|
|
14
16
|
@closed = false
|
|
15
17
|
end
|
|
16
18
|
|
|
19
|
+
# @returns [Integer] The number of concurrent sessions the driver can sustain.
|
|
17
20
|
def concurrency
|
|
18
21
|
@options.fetch(:concurrency, 128)
|
|
19
22
|
end
|
|
@@ -23,18 +26,22 @@ module Async
|
|
|
23
26
|
# @attribute [Hash] The status of the driver after a connection has been established.
|
|
24
27
|
attr :status
|
|
25
28
|
|
|
29
|
+
# @returns [Boolean] Whether the driver can still be used.
|
|
26
30
|
def viable?
|
|
27
31
|
!@closed
|
|
28
32
|
end
|
|
29
33
|
|
|
34
|
+
# @returns [Boolean] Whether the driver has been closed.
|
|
30
35
|
def closed?
|
|
31
36
|
@closed
|
|
32
37
|
end
|
|
33
38
|
|
|
39
|
+
# Mark the driver as closed.
|
|
34
40
|
def close
|
|
35
41
|
@closed = true
|
|
36
42
|
end
|
|
37
43
|
|
|
44
|
+
# @returns [Boolean] Whether the driver may be returned to a pool.
|
|
38
45
|
def reusable?
|
|
39
46
|
@options.fetch(:reusable, !@closed)
|
|
40
47
|
end
|
|
@@ -50,14 +57,17 @@ module Async
|
|
|
50
57
|
end
|
|
51
58
|
end
|
|
52
59
|
|
|
60
|
+
# @returns [Integer] The port the driver listens on.
|
|
53
61
|
def port
|
|
54
62
|
@port ||= @options.fetch(:port, self.ephemeral_port)
|
|
55
63
|
end
|
|
56
64
|
|
|
65
|
+
# @returns [Async::HTTP::Endpoint] The HTTP endpoint exposed by the driver.
|
|
57
66
|
def endpoint
|
|
58
67
|
Async::HTTP::Endpoint.parse("http://localhost", port: self.port)
|
|
59
68
|
end
|
|
60
69
|
|
|
70
|
+
# @returns [Client] A client connected to the driver endpoint.
|
|
61
71
|
def client
|
|
62
72
|
Client.open(self.endpoint)
|
|
63
73
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2023-
|
|
4
|
+
# Copyright, 2023-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require_relative "generic"
|
|
7
7
|
require_relative "process_group"
|
|
@@ -19,43 +19,50 @@ module Async
|
|
|
19
19
|
# bridge&.close
|
|
20
20
|
# end
|
|
21
21
|
class Firefox < Generic
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
# @returns [String] The path to the `geckodriver` executable.
|
|
23
|
+
def driver_path
|
|
24
|
+
@options.fetch(:driver_path, "geckodriver")
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
# @returns [String] The version of the `geckodriver` executable.
|
|
27
28
|
def version
|
|
28
|
-
::IO.popen([self.
|
|
29
|
+
::IO.popen([self.driver_path, "--version"]) do |io|
|
|
29
30
|
return io.read
|
|
30
31
|
end
|
|
31
32
|
rescue Errno::ENOENT
|
|
32
33
|
return nil
|
|
33
34
|
end
|
|
34
35
|
|
|
36
|
+
# A locally managed `geckodriver` process.
|
|
35
37
|
class Driver < Bridge::Driver
|
|
38
|
+
# Initialize a managed Firefox driver process.
|
|
39
|
+
# @parameter options [Hash] Driver configuration options.
|
|
36
40
|
def initialize(**options)
|
|
37
41
|
super(**options)
|
|
38
42
|
@process_group = nil
|
|
39
43
|
end
|
|
40
44
|
|
|
45
|
+
# @returns [Integer] Firefox drivers support one session at a time.
|
|
41
46
|
def concurrency
|
|
42
47
|
1
|
|
43
48
|
end
|
|
44
49
|
|
|
45
|
-
# @returns [Array(String)] The arguments to pass to the `
|
|
50
|
+
# @returns [Array(String)] The arguments to pass to the `geckodriver` executable.
|
|
46
51
|
def arguments(**options)
|
|
47
52
|
[
|
|
48
|
-
options.fetch(:
|
|
53
|
+
options.fetch(:driver_path, "geckodriver"),
|
|
49
54
|
"--port", self.port.to_s,
|
|
50
55
|
].compact
|
|
51
56
|
end
|
|
52
57
|
|
|
58
|
+
# Start the managed Firefox driver process and wait for readiness.
|
|
53
59
|
def start
|
|
54
60
|
@process_group = ProcessGroup.spawn(*arguments(**@options))
|
|
55
61
|
|
|
56
62
|
super
|
|
57
63
|
end
|
|
58
64
|
|
|
65
|
+
# Stop the managed Firefox driver process.
|
|
59
66
|
def close
|
|
60
67
|
if @process_group
|
|
61
68
|
@process_group.close
|
|
@@ -68,7 +75,7 @@ module Async
|
|
|
68
75
|
|
|
69
76
|
# Start the driver.
|
|
70
77
|
def start(**options)
|
|
71
|
-
Driver.new(**options).tap(&:start)
|
|
78
|
+
Driver.new(**@options, **options).tap(&:start)
|
|
72
79
|
end
|
|
73
80
|
|
|
74
81
|
# The default capabilities for the Firefox browser which need to be provided when requesting a new session.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2023-
|
|
4
|
+
# Copyright, 2023-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require "socket"
|
|
7
7
|
require "async/http/endpoint"
|
|
@@ -12,6 +12,8 @@ module Async
|
|
|
12
12
|
module Bridge
|
|
13
13
|
# Generic W3C WebDriver implementation.
|
|
14
14
|
class Generic
|
|
15
|
+
# Initialize a generic bridge wrapper.
|
|
16
|
+
# @parameter options [Hash] Bridge configuration options.
|
|
15
17
|
def initialize(**options)
|
|
16
18
|
@options = options
|
|
17
19
|
end
|
|
@@ -26,6 +28,7 @@ module Async
|
|
|
26
28
|
version != nil
|
|
27
29
|
end
|
|
28
30
|
|
|
31
|
+
# @returns [Boolean] Whether headless mode is enabled by default.
|
|
29
32
|
def headless?
|
|
30
33
|
@options.fetch(:headless, true)
|
|
31
34
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2023-
|
|
4
|
+
# Copyright, 2023-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require "async/actor"
|
|
7
7
|
require "async/pool"
|
|
@@ -24,14 +24,22 @@ module Async
|
|
|
24
24
|
# end
|
|
25
25
|
# ```
|
|
26
26
|
class Pool
|
|
27
|
+
# Controls pooled drivers and cached sessions.
|
|
27
28
|
class BridgeController
|
|
29
|
+
# Initialize the bridge controller.
|
|
30
|
+
# @parameter bridge [Bridge] The bridge used to create drivers.
|
|
31
|
+
# @parameter capabilities [Hash] Capabilities used for new sessions.
|
|
28
32
|
def initialize(bridge, capabilities: bridge.default_capabilities)
|
|
29
33
|
@bridge = bridge
|
|
30
34
|
@capabilities = capabilities
|
|
31
35
|
@pool = Async::Pool::Controller.new(self)
|
|
32
36
|
end
|
|
33
37
|
|
|
38
|
+
# Caches sessions created from a single driver instance.
|
|
34
39
|
class SessionCache
|
|
40
|
+
# Initialize a session cache for one driver instance.
|
|
41
|
+
# @parameter driver [Driver] The driver backing cached sessions.
|
|
42
|
+
# @parameter capabilities [Hash] Capabilities for newly created sessions.
|
|
35
43
|
def initialize(driver, capabilities)
|
|
36
44
|
@driver = driver
|
|
37
45
|
@capabilities = capabilities
|
|
@@ -40,14 +48,17 @@ module Async
|
|
|
40
48
|
@sessions = []
|
|
41
49
|
end
|
|
42
50
|
|
|
51
|
+
# @returns [Boolean] Whether the underlying driver remains usable.
|
|
43
52
|
def viable?
|
|
44
53
|
@driver&.viable?
|
|
45
54
|
end
|
|
46
55
|
|
|
56
|
+
# @returns [Boolean] Whether cached sessions may be reused.
|
|
47
57
|
def reusable?
|
|
48
58
|
@driver&.reusable?
|
|
49
59
|
end
|
|
50
60
|
|
|
61
|
+
# Close the cached sessions, driver, and HTTP client.
|
|
51
62
|
def close
|
|
52
63
|
if @driver
|
|
53
64
|
@driver.close
|
|
@@ -64,10 +75,13 @@ module Async
|
|
|
64
75
|
end
|
|
65
76
|
end
|
|
66
77
|
|
|
78
|
+
# @returns [Integer] The number of concurrently usable sessions.
|
|
67
79
|
def concurrency
|
|
68
80
|
@driver.concurrency
|
|
69
81
|
end
|
|
70
82
|
|
|
83
|
+
# Acquire a cached or newly created session payload.
|
|
84
|
+
# @returns [Hash] A WebDriver session payload.
|
|
71
85
|
def acquire
|
|
72
86
|
if @sessions.empty?
|
|
73
87
|
session = @client.post("session", {capabilities: @capabilities})
|
|
@@ -85,6 +99,8 @@ module Async
|
|
|
85
99
|
end
|
|
86
100
|
end
|
|
87
101
|
|
|
102
|
+
# Return a session payload to the cache.
|
|
103
|
+
# @parameter session [Hash] The session payload to cache.
|
|
88
104
|
def release(session)
|
|
89
105
|
@sessions.push(session)
|
|
90
106
|
end
|
|
@@ -95,12 +111,16 @@ module Async
|
|
|
95
111
|
SessionCache.new(@bridge.start, @capabilities)
|
|
96
112
|
end
|
|
97
113
|
|
|
114
|
+
# Acquire a session payload from the pool.
|
|
115
|
+
# @returns [Hash] The acquired session payload.
|
|
98
116
|
def acquire
|
|
99
117
|
session_cache = @pool.acquire
|
|
100
118
|
|
|
101
119
|
return session_cache.acquire
|
|
102
120
|
end
|
|
103
121
|
|
|
122
|
+
# Return a session payload to the pool.
|
|
123
|
+
# @parameter session [Hash] The session payload to release.
|
|
104
124
|
def release(session)
|
|
105
125
|
session_cache = session[:cache]
|
|
106
126
|
|
|
@@ -109,6 +129,8 @@ module Async
|
|
|
109
129
|
@pool.release(session_cache)
|
|
110
130
|
end
|
|
111
131
|
|
|
132
|
+
# Retire a session payload and its cache from the pool.
|
|
133
|
+
# @parameter session [Hash] The session payload to retire.
|
|
112
134
|
def retire(session)
|
|
113
135
|
session_cache = session[:cache]
|
|
114
136
|
|
|
@@ -117,6 +139,7 @@ module Async
|
|
|
117
139
|
@pool.retire(session_cache)
|
|
118
140
|
end
|
|
119
141
|
|
|
142
|
+
# Close the underlying driver pool.
|
|
120
143
|
def close
|
|
121
144
|
if @pool
|
|
122
145
|
@pool.close
|
|
@@ -136,15 +159,19 @@ module Async
|
|
|
136
159
|
@controller.close
|
|
137
160
|
end
|
|
138
161
|
|
|
162
|
+
# A pooled session wrapper that returns sessions to the cache on close.
|
|
139
163
|
class CachedWrapper < Session
|
|
164
|
+
# @returns [Pool] The pool responsible for reusing this session.
|
|
140
165
|
def pool
|
|
141
166
|
@options[:pool]
|
|
142
167
|
end
|
|
143
168
|
|
|
169
|
+
# @returns [Hash] The raw session payload returned by the bridge.
|
|
144
170
|
def payload
|
|
145
171
|
@options[:payload]
|
|
146
172
|
end
|
|
147
173
|
|
|
174
|
+
# Return the session to the pool when possible.
|
|
148
175
|
def close
|
|
149
176
|
unless self.pool.reuse(self)
|
|
150
177
|
super
|
|
@@ -167,6 +194,9 @@ module Async
|
|
|
167
194
|
end
|
|
168
195
|
end
|
|
169
196
|
|
|
197
|
+
# Reset and return a session to the pool.
|
|
198
|
+
# @parameter session [CachedWrapper] The session to reuse.
|
|
199
|
+
# @returns [Boolean] Always returns `true` once the session is released.
|
|
170
200
|
def reuse(session)
|
|
171
201
|
session.reset!
|
|
172
202
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2023-
|
|
4
|
+
# Copyright, 2023-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require_relative "driver"
|
|
7
7
|
|
|
@@ -75,13 +75,18 @@ module Async
|
|
|
75
75
|
end
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
+
# A driver wrapper that closes an associated process handle.
|
|
78
79
|
class ProcessDriver < Driver
|
|
80
|
+
# Initialize a process-backed driver.
|
|
81
|
+
# @parameter endpoint [Object] Driver options or endpoint information.
|
|
82
|
+
# @parameter process [ProcessGroup] The managed process group.
|
|
79
83
|
def initialize(endpoint, process)
|
|
80
84
|
super(endpoint)
|
|
81
85
|
|
|
82
86
|
@process = process
|
|
83
87
|
end
|
|
84
88
|
|
|
89
|
+
# Close the driver and its process group.
|
|
85
90
|
def close
|
|
86
91
|
super
|
|
87
92
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2024-
|
|
4
|
+
# Copyright, 2024-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require_relative "generic"
|
|
7
7
|
require_relative "process_group"
|
|
@@ -20,20 +20,24 @@ module Async
|
|
|
20
20
|
# end
|
|
21
21
|
# ```
|
|
22
22
|
class Safari < Generic
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
# @returns [String] The path to the `safaridriver` executable.
|
|
24
|
+
def driver_path
|
|
25
|
+
@options.fetch(:driver_path, "safaridriver")
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
# @returns [String] The version of the `safaridriver` executable.
|
|
28
29
|
def version
|
|
29
|
-
::IO.popen([self.
|
|
30
|
+
::IO.popen([self.driver_path, "--version"]) do |io|
|
|
30
31
|
return io.read
|
|
31
32
|
end
|
|
32
33
|
rescue Errno::ENOENT
|
|
33
34
|
return nil
|
|
34
35
|
end
|
|
35
36
|
|
|
37
|
+
# A locally managed `safaridriver` process.
|
|
36
38
|
class Driver < Bridge::Driver
|
|
39
|
+
# Initialize a managed Safari driver process.
|
|
40
|
+
# @parameter options [Hash] Driver configuration options.
|
|
37
41
|
def initialize(**options)
|
|
38
42
|
super(**options)
|
|
39
43
|
@process_group = nil
|
|
@@ -42,17 +46,19 @@ module Async
|
|
|
42
46
|
# @returns [Array(String)] The arguments to pass to the `safaridriver` executable.
|
|
43
47
|
def arguments(**options)
|
|
44
48
|
[
|
|
45
|
-
options.fetch(:
|
|
49
|
+
options.fetch(:driver_path, "safaridriver"),
|
|
46
50
|
"--port=#{self.port}",
|
|
47
51
|
].compact
|
|
48
52
|
end
|
|
49
53
|
|
|
54
|
+
# Start the managed Safari driver process and wait for readiness.
|
|
50
55
|
def start
|
|
51
56
|
@process_group = ProcessGroup.spawn(*arguments(**@options))
|
|
52
57
|
|
|
53
58
|
super
|
|
54
59
|
end
|
|
55
60
|
|
|
61
|
+
# Stop the managed Safari driver process.
|
|
56
62
|
def close
|
|
57
63
|
if @process_group
|
|
58
64
|
@process_group.close
|
|
@@ -65,7 +71,7 @@ module Async
|
|
|
65
71
|
|
|
66
72
|
# Start the driver.
|
|
67
73
|
def start(**options)
|
|
68
|
-
Driver.new(**options).tap(&:start)
|
|
74
|
+
Driver.new(**@options, **options).tap(&:start)
|
|
69
75
|
end
|
|
70
76
|
|
|
71
77
|
# The default capabilities for the Safari browser which need to be provided when requesting a new session.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2023-
|
|
4
|
+
# Copyright, 2023-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require_relative "bridge/chrome"
|
|
7
7
|
require_relative "bridge/firefox"
|
|
@@ -21,6 +21,9 @@ module Async
|
|
|
21
21
|
Bridge::Safari,
|
|
22
22
|
]
|
|
23
23
|
|
|
24
|
+
# Iterate over supported bridge implementations.
|
|
25
|
+
# @yields {|bridge| ...} Each supported bridge class.
|
|
26
|
+
# @parameter bridge [Class] A supported bridge implementation.
|
|
24
27
|
def self.each(&block)
|
|
25
28
|
return enum_for(:each) unless block_given?
|
|
26
29
|
|
|
@@ -45,6 +48,7 @@ module Async
|
|
|
45
48
|
# ```
|
|
46
49
|
ASYNC_WEBDRIVER_BRIDGE_HEADLESS = "ASYNC_WEBDRIVER_BRIDGE_HEADLESS"
|
|
47
50
|
|
|
51
|
+
# Raised when no supported bridge implementation is available.
|
|
48
52
|
class UnsupportedError < Error
|
|
49
53
|
end
|
|
50
54
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2023-
|
|
4
|
+
# Copyright, 2023-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require_relative "request_helper"
|
|
7
7
|
|
|
@@ -20,6 +20,8 @@ module Async
|
|
|
20
20
|
class Attributes
|
|
21
21
|
include Enumerable
|
|
22
22
|
|
|
23
|
+
# Initialize the attribute wrapper for an element.
|
|
24
|
+
# @parameter element [Element] The element whose attributes will be accessed.
|
|
23
25
|
def initialize(element)
|
|
24
26
|
@element = element
|
|
25
27
|
@keys = nil
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2023-
|
|
4
|
+
# Copyright, 2023-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require_relative "version"
|
|
7
7
|
|
|
8
8
|
module Async
|
|
9
9
|
module WebDriver
|
|
10
|
+
# The base class for WebDriver protocol errors.
|
|
10
11
|
class Error < StandardError
|
|
11
12
|
end
|
|
12
13
|
|