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.
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require "fileutils"
7
+ require "tempfile"
8
+ require_relative "platform"
9
+ require_relative "releases"
10
+
11
+ module Async
12
+ module WebDriver
13
+ module Installer
14
+ module Chrome
15
+ # Represents a Chrome for Testing installation on disk, and provides class-level
16
+ # methods for resolving, locating, and downloading installations.
17
+ #
18
+ # Installations are stored under the cache_path directory, organised as:
19
+ #
20
+ # {cache_path}/{platform}/{version}/
21
+ # chrome/ ← extracted chrome zip contents
22
+ # chromedriver/ ← extracted chromedriver zip contents
23
+ #
24
+ # Channel names (e.g. `stable`) are stored as symlinks pointing at the
25
+ # specific version directory, so that {find} can resolve them without
26
+ # hitting the network. {install} always re-checks the API and updates
27
+ # the symlink if the channel has moved on to a newer version.
28
+ class Installation
29
+ # Look up an existing installation, or download and install a fresh one.
30
+ #
31
+ # For channel specifiers (`:stable`, `:beta`, etc.), always hits the
32
+ # Chrome for Testing API to resolve the current version, downloads if
33
+ # needed, and updates the channel symlink. For exact versions, checks
34
+ # the local cache only.
35
+ #
36
+ # @parameter version [Symbol | String] Channel or version specifier.
37
+ # @parameter cache_path [String] Root of the cache directory.
38
+ # @returns [Installation]
39
+ def self.install(version, cache_path:)
40
+ platform = Platform.current
41
+ release = Releases.resolve(version, platform)
42
+
43
+ unless installation = find(release[:version], platform, cache_path: cache_path)
44
+ Console.info(self, "Installing Chrome for Testing #{release[:version]}...", platform: platform)
45
+
46
+ dir = installation_dir(release[:version], platform, cache_path: cache_path)
47
+ FileUtils.mkdir_p(dir)
48
+
49
+ begin
50
+ download_and_extract(release[:chrome_url], File.join(dir, "chrome"))
51
+ download_and_extract(release[:chromedriver_url], File.join(dir, "chromedriver"))
52
+
53
+ installation = find(release[:version], platform, cache_path: cache_path) or
54
+ raise "Installation failed: binaries not found after extraction"
55
+
56
+ Console.info(self, "Installed Chrome for Testing #{release[:version]}.", platform: platform)
57
+ rescue
58
+ FileUtils.rm_rf(dir)
59
+ raise
60
+ end
61
+ end
62
+
63
+ # Update the channel symlink so subsequent find(:stable) calls
64
+ # resolve locally without a network request.
65
+ if channel = channel_name(version)
66
+ update_channel_symlink(channel, release[:version], platform, cache_path: cache_path)
67
+ end
68
+
69
+ return installation
70
+ end
71
+
72
+ # Find an already-installed version or channel, without hitting the network.
73
+ #
74
+ # For channel names (`:stable`, `"stable"`, etc.), resolves the local
75
+ # symlink. For exact versions, checks the installation directory directly.
76
+ #
77
+ # @parameter version [Symbol | String] Channel or exact version string.
78
+ # @parameter platform [String] Platform string, e.g. `"mac-arm64"`.
79
+ # @parameter cache_path [String] Root of the cache directory.
80
+ # @returns [Installation | Nil]
81
+ def self.find(version, platform, cache_path:)
82
+ if channel = channel_name(version)
83
+ find_channel(channel, platform, cache_path: cache_path)
84
+ else
85
+ find_version(version, platform, cache_path: cache_path)
86
+ end
87
+ end
88
+
89
+ # @parameter browser_path [String] Absolute path to the Chrome browser executable.
90
+ # @parameter driver_path [String] Absolute path to the chromedriver executable.
91
+ # @parameter version [String] Exact version string.
92
+ # @parameter platform [String] Platform string.
93
+ def initialize(browser_path:, driver_path:, version:, platform:)
94
+ @browser_path = browser_path
95
+ @driver_path = driver_path
96
+ @version = version
97
+ @platform = platform
98
+ end
99
+
100
+ # @attribute [String] Absolute path to the Chrome browser executable.
101
+ attr :browser_path
102
+
103
+ # @attribute [String] Absolute path to the chromedriver executable.
104
+ attr :driver_path
105
+
106
+ # @attribute [String] Exact installed version, e.g. `"148.0.7778.56"`.
107
+ attr :version
108
+
109
+ # @attribute [String] Platform, e.g. `"mac-arm64"`.
110
+ attr :platform
111
+
112
+ private_class_method def self.channel_name(version)
113
+ Releases::CHANNELS.key(version.to_s.capitalize) && version.to_s.downcase
114
+ end
115
+
116
+ private_class_method def self.find_channel(channel, platform, cache_path:)
117
+ symlink = channel_symlink(channel, platform, cache_path: cache_path)
118
+ return nil unless File.symlink?(symlink)
119
+
120
+ # Derive the version from the symlink target name.
121
+ version = File.basename(File.readlink(symlink))
122
+ find_version(version, platform, cache_path: cache_path)
123
+ end
124
+
125
+ private_class_method def self.find_version(version, platform, cache_path:)
126
+ dir = installation_dir(version, platform, cache_path: cache_path)
127
+
128
+ browser_path = File.join(dir, "chrome", Platform.chrome_binary(platform))
129
+ driver_path = File.join(dir, "chromedriver", Platform.chromedriver_binary(platform))
130
+
131
+ return nil unless File.exist?(browser_path) && File.exist?(driver_path)
132
+
133
+ new(
134
+ browser_path: browser_path,
135
+ driver_path: driver_path,
136
+ version: version,
137
+ platform: platform,
138
+ )
139
+ end
140
+
141
+ private_class_method def self.update_channel_symlink(channel, version, platform, cache_path:)
142
+ symlink = channel_symlink(channel, platform, cache_path: cache_path)
143
+ target = installation_dir(version, platform, cache_path: cache_path)
144
+
145
+ # Remove stale symlink if it points elsewhere.
146
+ if File.symlink?(symlink) && File.readlink(symlink) != target
147
+ File.unlink(symlink)
148
+ end
149
+
150
+ File.symlink(target, symlink) unless File.symlink?(symlink)
151
+ end
152
+
153
+ private_class_method def self.channel_symlink(channel, platform, cache_path:)
154
+ File.join(cache_path, platform, channel.to_s)
155
+ end
156
+
157
+ private_class_method def self.installation_dir(version, platform, cache_path:)
158
+ File.join(cache_path, platform, version)
159
+ end
160
+
161
+ private_class_method def self.download_and_extract(url, dest)
162
+ require "async/http/internet"
163
+
164
+ Tempfile.create(["async-webdriver-", ".zip"]) do |tmp|
165
+ tmp.binmode
166
+
167
+ Sync do
168
+ internet = Async::HTTP::Internet.new
169
+ begin
170
+ Console.debug(self, "Downloading...", url: url)
171
+ response = internet.get(url)
172
+ tmp.write(response.read)
173
+ tmp.flush
174
+ ensure
175
+ internet.close
176
+ end
177
+ end
178
+
179
+ FileUtils.mkdir_p(dest)
180
+ system("unzip", "-q", "-o", tmp.path, "-d", dest) or
181
+ raise "Failed to extract #{url}"
182
+
183
+ # Remove macOS quarantine attributes added to files downloaded via code.
184
+ if RUBY_PLATFORM.include?("darwin")
185
+ system("xattr", "-r", "-d", "com.apple.quarantine", dest)
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ module Async
7
+ module WebDriver
8
+ module Installer
9
+ module Chrome
10
+ # Platform detection for Chrome for Testing downloads.
11
+ #
12
+ # Maps Ruby's `RUBY_PLATFORM` to the platform strings used by the
13
+ # Chrome for Testing JSON API and zip file naming conventions.
14
+ module Platform
15
+ # Ordered list of (pattern, platform) pairs. First match wins.
16
+ PLATFORM_MAP = [
17
+ [/arm.*darwin|darwin.*arm|aarch64.*darwin|darwin.*aarch64/, "mac-arm64"],
18
+ [/darwin/, "mac-x64"],
19
+ [/aarch64.*linux|linux.*aarch64/, "linux-arm64"],
20
+ [/linux/, "linux64"],
21
+ [/x64.*mingw|mingw.*x64/, "win64"],
22
+ [/mingw/, "win32"],
23
+ ].freeze
24
+
25
+ # Detect the current platform.
26
+ # @returns [String] e.g. `"mac-arm64"`, `"linux64"`.
27
+ # @raises [RuntimeError] If the platform is not recognised.
28
+ def self.current
29
+ PLATFORM_MAP.each do |pattern, platform|
30
+ return platform if RUBY_PLATFORM.match?(pattern)
31
+ end
32
+ raise "Unsupported platform: #{RUBY_PLATFORM}"
33
+ end
34
+
35
+ # Relative path to the Chrome binary inside the extracted chrome zip.
36
+ # @parameter platform [String]
37
+ # @returns [String]
38
+ def self.chrome_binary(platform)
39
+ case platform
40
+ when "mac-arm64"
41
+ "chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"
42
+ when "mac-x64"
43
+ "chrome-mac-x64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"
44
+ when "linux64"
45
+ "chrome-linux64/chrome"
46
+ when "linux-arm64"
47
+ "chrome-linux-arm64/chrome"
48
+ when "win64"
49
+ "chrome-win64/chrome.exe"
50
+ when "win32"
51
+ "chrome-win32/chrome.exe"
52
+ else
53
+ raise "Unknown platform: #{platform}"
54
+ end
55
+ end
56
+
57
+ # Relative path to the chromedriver binary inside the extracted chromedriver zip.
58
+ # @parameter platform [String]
59
+ # @returns [String]
60
+ def self.chromedriver_binary(platform)
61
+ case platform
62
+ when "mac-arm64"
63
+ "chromedriver-mac-arm64/chromedriver"
64
+ when "mac-x64"
65
+ "chromedriver-mac-x64/chromedriver"
66
+ when "linux64"
67
+ "chromedriver-linux64/chromedriver"
68
+ when "linux-arm64"
69
+ "chromedriver-linux-arm64/chromedriver"
70
+ when "win64"
71
+ "chromedriver-win64/chromedriver.exe"
72
+ when "win32"
73
+ "chromedriver-win32/chromedriver.exe"
74
+ else
75
+ raise "Unknown platform: #{platform}"
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require "json"
7
+
8
+ module Async
9
+ module WebDriver
10
+ module Installer
11
+ module Chrome
12
+ # Resolves Chrome for Testing version specifiers and download URLs using the
13
+ # public Chrome for Testing JSON API.
14
+ module Releases
15
+ # Returns the latest known-good version for each release channel.
16
+ CHANNELS_URL = "https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json"
17
+
18
+ # Returns every known-good version with its download URLs.
19
+ VERSIONS_URL = "https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json"
20
+
21
+ # Maps symbolic channel names to the API's title-case keys.
22
+ CHANNELS = {
23
+ stable: "Stable",
24
+ beta: "Beta",
25
+ dev: "Dev",
26
+ canary: "Canary",
27
+ }.freeze
28
+
29
+ # Resolve a version specifier and platform to a version string and download URLs.
30
+ #
31
+ # @parameter version [Symbol | String] `:stable`, `:beta`, `:dev`, `:canary`,
32
+ # a major version string like `"148"`, or an exact version like `"148.0.7778.56"`.
33
+ # @parameter platform [String] A Chrome for Testing platform string, e.g. `"mac-arm64"`.
34
+ # @returns [Hash] `{ version:, chrome_url:, chromedriver_url: }`
35
+ def self.resolve(version, platform)
36
+ case version
37
+ when Symbol then resolve_channel(version, platform)
38
+ when /\A(stable|beta|dev|canary)\z/ then resolve_channel(version.to_sym, platform)
39
+ when /\A\d+\z/ then resolve_major(version, platform)
40
+ else resolve_exact(version, platform)
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def self.fetch_json(url)
47
+ require "async/http/internet"
48
+
49
+ Sync do
50
+ internet = Async::HTTP::Internet.new
51
+ begin
52
+ response = internet.get(url)
53
+ JSON.parse(response.read)
54
+ ensure
55
+ internet.close
56
+ end
57
+ end
58
+ end
59
+
60
+ def self.resolve_channel(channel, platform)
61
+ key = CHANNELS.fetch(channel) do
62
+ raise ArgumentError, "Unknown channel #{channel.inspect}. Expected one of: #{CHANNELS.keys.inspect}"
63
+ end
64
+
65
+ data = fetch_json(CHANNELS_URL)
66
+ entry = data.dig("channels", key) or raise "Channel #{key} not found in API response"
67
+
68
+ extract(entry, platform)
69
+ end
70
+
71
+ def self.resolve_major(major, platform)
72
+ data = fetch_json(VERSIONS_URL)
73
+
74
+ entry = data["versions"]
75
+ .select{|v| v["version"].start_with?("#{major}.")}
76
+ .max_by{|v| Gem::Version.new(v["version"])}
77
+
78
+ raise "No version found for major version #{major}" unless entry
79
+
80
+ extract(entry, platform)
81
+ end
82
+
83
+ def self.resolve_exact(version, platform)
84
+ data = fetch_json(VERSIONS_URL)
85
+
86
+ entry = data["versions"].find{|v| v["version"] == version}
87
+ raise "Version #{version} not found" unless entry
88
+
89
+ extract(entry, platform)
90
+ end
91
+
92
+ def self.extract(entry, platform)
93
+ version = entry["version"]
94
+ downloads = entry["downloads"]
95
+
96
+ chrome_url = downloads["chrome"]
97
+ &.find{|d| d["platform"] == platform}
98
+ &.dig("url")
99
+
100
+ chromedriver_url = downloads["chromedriver"]
101
+ &.find{|d| d["platform"] == platform}
102
+ &.dig("url")
103
+
104
+ raise "No Chrome download for platform #{platform} in version #{version}" unless chrome_url
105
+ raise "No ChromeDriver download for platform #{platform} in version #{version}" unless chromedriver_url
106
+
107
+ {version: version, chrome_url: chrome_url, chromedriver_url: chromedriver_url}
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require_relative "chrome/platform"
7
+ require_relative "chrome/releases"
8
+ require_relative "chrome/installation"
9
+
10
+ module Async
11
+ module WebDriver
12
+ module Installer
13
+ # Installer for Chrome for Testing, the purpose-built Chrome variant
14
+ # designed for automated testing.
15
+ #
16
+ # Versions can be specified as:
17
+ # - A channel symbol: `:stable`, `:beta`, `:dev`, `:canary`
18
+ # - A major version string: `"148"` (resolves to the latest patch)
19
+ # - An exact version string: `"148.0.7778.56"`
20
+ #
21
+ # Installations are cached in `~/.cache/async-webdriver.rb/` by default
22
+ # (respects `$XDG_CACHE_HOME`).
23
+ #
24
+ # ## Example
25
+ #
26
+ # ``` ruby
27
+ # installation = Async::WebDriver::Installer::Chrome.install(:stable)
28
+ # bridge = Async::WebDriver::Bridge::Chrome.new(
29
+ # driver_path: installation.driver_path,
30
+ # browser_path: installation.browser_path,
31
+ # )
32
+ # ```
33
+ #
34
+ # Or via the convenience shorthand on the bridge:
35
+ #
36
+ # ``` ruby
37
+ # bridge = Async::WebDriver::Bridge::Chrome.for(:stable)
38
+ # ```
39
+ module Chrome
40
+ # Default cache directory, following the XDG Base Directory Specification.
41
+
42
+
43
+ # Ensure the given version is installed and return an {Installation}.
44
+ #
45
+ # Checks the local cache first; downloads from the Chrome for Testing
46
+ # infrastructure only when the version is not already present.
47
+ #
48
+ # @parameter version [Symbol | String] Version specifier.
49
+ # @parameter cache_path [String] Root of the cache directory.
50
+ # @returns [Installation]
51
+ def self.install(version = :stable, cache_path: Installer.cache_path("chrome"))
52
+ Installation.install(version, cache_path: cache_path)
53
+ end
54
+
55
+ # Find an already-installed version or channel without hitting the network.
56
+ #
57
+ # @parameter version [Symbol | String] Channel or exact version string.
58
+ # @parameter cache_path [String] Root of the cache directory.
59
+ # @returns [Installation | Nil]
60
+ def self.find(version, cache_path: Installer.cache_path("chrome"))
61
+ Installation.find(version, Platform.current, cache_path: cache_path)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require_relative "installer/chrome"
7
+
8
+ module Async
9
+ module WebDriver
10
+ # Browser installation and management for automated testing.
11
+ #
12
+ # Each browser has its own sub-module with browser-specific platform detection,
13
+ # version resolution, and download logic:
14
+ #
15
+ # - {Installer::Chrome} — Chrome for Testing, via the Chrome for Testing JSON API.
16
+ module Installer
17
+ # Resolve the cache path for the given sub-directory.
18
+ #
19
+ # Follows the XDG Base Directory Specification, using `$XDG_CACHE_HOME`
20
+ # (default: `~/.cache`) as the root, with `async-webdriver.rb` as the
21
+ # application directory.
22
+ #
23
+ # @parameter subdirectory [String | Nil] Optional sub-directory, e.g. `"chrome"`.
24
+ # @parameter env [Hash] Environment to read `XDG_CACHE_HOME` from. Default: `ENV`.
25
+ # @returns [String] Absolute path.
26
+ def self.cache_path(subdirectory = nil, env = ENV)
27
+ path = File.expand_path("async-webdriver.rb", env.fetch("XDG_CACHE_HOME", "~/.cache"))
28
+
29
+ if subdirectory
30
+ path = File.join(path, subdirectory)
31
+ end
32
+
33
+ return path
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,20 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023-2025, by Samuel Williams.
4
+ # Copyright, 2023-2026, by Samuel Williams.
5
5
 
6
6
  require "base64"
7
7
 
8
8
  module Async
9
9
  module WebDriver
10
10
  module Scope
11
- # Helpers for working with printing.
11
+ # Helpers for printing the current page to PDF.
12
12
  module Printing
13
- # Print the current page and return the result as a Base64 encoded string containing a PDF representation of the paginated document.
14
- def print(page_ranges: nil, total_pages: nil)
15
- reply = session.post("print", {pageRanges: page_ranges, totalPages: total_pages}.compact)
13
+ # Print the current page as a PDF and return the raw binary data.
14
+ #
15
+ # All margin and page measurements are in centimetres. The W3C WebDriver
16
+ # default page size is US Letter (21.59 × 27.94 cm) with 1 cm margins.
17
+ #
18
+ # @parameter orientation [String | Nil] `"portrait"` or `"landscape"`. Default: `"portrait"`.
19
+ # @parameter scale [Float | Nil] Scaling factor between 0.1 and 2.0. Default: `1.0`.
20
+ # @parameter background [Boolean | Nil] Whether to print background graphics and colours. Default: `false`.
21
+ # @parameter page [Hash | Nil] Page dimensions in cm. Keys: `:width`, `:height`.
22
+ # @parameter margin [Hash | Nil] Page margins in cm. Keys: `:top`, `:bottom`, `:left`, `:right`.
23
+ # @parameter page_ranges [Array(String) | Nil] Page ranges to print, e.g. `["1-5", "8"]`. Default: all pages.
24
+ # @parameter shrink_to_fit [Boolean | Nil] Whether to shrink content to fit the page. Default: `true`.
25
+ # @returns [String] The raw PDF binary data.
26
+ def print(orientation: nil, scale: nil, background: nil, page: nil, margin: nil, page_ranges: nil, shrink_to_fit: nil)
27
+ parameters = {
28
+ orientation: orientation,
29
+ scale: scale,
30
+ background: background,
31
+ page: page,
32
+ margin: margin,
33
+ pageRanges: page_ranges,
34
+ shrinkToFit: shrink_to_fit,
35
+ }.compact
16
36
 
17
- return Base64.decode64(reply["value"])
37
+ # Synchronise with Chrome's rendering pipeline before issuing the print
38
+ # command. The underlying CDP call (Page.printToPDF) is synchronous: if
39
+ # the renderer process has not yet fully initialised its print pipeline
40
+ # by the time the command arrives, Chrome returns JSON-RPC error -32000
41
+ # ("Printing failed") with no retry. A JavaScript round-trip forces
42
+ # ChromeDriver to wait for the renderer to be live (a JS execution
43
+ # context must exist), which also guarantees the print pipeline is ready.
44
+ # Without this, fast-loading pages can trigger the race intermittently.
45
+ session.execute("return document.readyState")
46
+
47
+ reply = session.post("print", parameters)
48
+
49
+ return Base64.decode64(reply)
18
50
  end
19
51
  end
20
52
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ module Async
7
+ module WebDriver
8
+ module Scope
9
+ # Helpers for managing the browser window size and position.
10
+ module Window
11
+ # Get the current window rect (position and size).
12
+ # @returns [Hash] The window rect with keys `"x"`, `"y"`, `"width"`, `"height"`.
13
+ def window_rect
14
+ session.get("window/rect")
15
+ end
16
+
17
+ # Set the window rect (position and/or size).
18
+ # @parameter x [Integer | Nil] The x position of the window.
19
+ # @parameter y [Integer | Nil] The y position of the window.
20
+ # @parameter width [Integer | Nil] The width of the window in CSS pixels.
21
+ # @parameter height [Integer | Nil] The height of the window in CSS pixels.
22
+ def set_window_rect(x: nil, y: nil, width: nil, height: nil)
23
+ session.post("window/rect", {x: x, y: y, width: width, height: height}.compact)
24
+ end
25
+
26
+ # Resize the browser window to the given dimensions.
27
+ # @parameter width [Integer] The new width in CSS pixels.
28
+ # @parameter height [Integer] The new height in CSS pixels.
29
+ def resize_window(width, height)
30
+ set_window_rect(width: width, height: height)
31
+ end
32
+
33
+ # Maximize the browser window.
34
+ def maximize_window
35
+ session.post("window/maximize")
36
+ end
37
+
38
+ # Minimize the browser window.
39
+ def minimize_window
40
+ session.post("window/minimize")
41
+ end
42
+
43
+ # Make the browser window fullscreen.
44
+ def fullscreen_window
45
+ session.post("window/fullscreen")
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023-2025, by Samuel Williams.
4
+ # Copyright, 2023-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "scope/alerts"
7
7
  require_relative "scope/cookies"
@@ -13,3 +13,12 @@ require_relative "scope/navigation"
13
13
  require_relative "scope/printing"
14
14
  require_relative "scope/screen_capture"
15
15
  require_relative "scope/timeouts"
16
+ require_relative "scope/window"
17
+
18
+ module Async
19
+ module WebDriver
20
+ # @namespace
21
+ module Scope
22
+ end
23
+ end
24
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023-2025, by Samuel Williams.
4
+ # Copyright, 2023-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "request_helper"
7
7
  require_relative "element"
@@ -56,6 +56,7 @@ module Async
56
56
  @options = options
57
57
  end
58
58
 
59
+ # @returns [String] A concise representation of the session.
59
60
  def inspect
60
61
  "\#<#{self.class} id=#{@id.inspect}>"
61
62
  end
@@ -126,6 +127,7 @@ module Async
126
127
  include Scope::Printing
127
128
  include Scope::ScreenCapture
128
129
  include Scope::Timeouts
130
+ include Scope::Window
129
131
 
130
132
  # Reset the session to a clean state.
131
133
  def reset!
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023-2025, by Samuel Williams.
4
+ # Copyright, 2023-2026, by Samuel Williams.
5
5
 
6
+ # @namespace
6
7
  module Async
8
+ # @namespace
7
9
  module WebDriver
8
- VERSION = "0.10.0"
10
+ VERSION = "0.12.0"
9
11
  end
10
12
  end
data/license.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
 
3
- Copyright, 2023-2025, by Samuel Williams.
3
+ Copyright, 2023-2026, by Samuel Williams.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal