Skip to main content

chrome_for_testing_manager/
error.rs

1use crate::{Port, VersionRequest};
2use chrome_for_testing::{Platform, Version};
3use std::{
4    fmt::{Display, Formatter},
5    path::PathBuf,
6    time::Duration,
7};
8use thiserror::Error;
9use tokio::runtime::RuntimeFlavor;
10
11/// Convenience alias for `Result<T, rootcause::Report<ChromeForTestingManagerError>>`.
12///
13/// Use this in your application's signatures to avoid spelling out the wrapped error type:
14///
15/// ```no_run
16/// use chrome_for_testing_manager::{Chromedriver, ChromedriverRunConfig, Result};
17///
18/// async fn launch() -> Result<Chromedriver> {
19///     Chromedriver::run(ChromedriverRunConfig::default()).await
20/// }
21/// ```
22pub type Result<T> = std::result::Result<T, rootcause::Report<ChromeForTestingManagerError>>;
23
24/// The chrome-for-testing artifact involved in an operation.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26#[non_exhaustive]
27pub enum ChromeForTestingArtifact {
28    /// The Chrome browser binary package.
29    Chrome,
30
31    /// The Chrome Headless Shell binary package.
32    ChromeHeadlessShell,
33
34    /// The Chromedriver package.
35    ChromeDriver,
36}
37
38impl Display for ChromeForTestingArtifact {
39    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
40        match self {
41            Self::Chrome => f.write_str("chrome"),
42            Self::ChromeHeadlessShell => f.write_str("chrome-headless-shell"),
43            Self::ChromeDriver => f.write_str("chromedriver"),
44        }
45    }
46}
47
48/// Error contexts reported by chrome-for-testing-manager operations.
49#[derive(Debug, Error)]
50#[non_exhaustive]
51pub enum ChromeForTestingManagerError {
52    /* Runtime and platform. */
53    /// The current Tokio runtime does not support async drop cleanup.
54    #[error("chromedriver requires a multi-threaded Tokio runtime; detected {runtime_flavor:?}")]
55    UnsupportedRuntime {
56        /// The detected runtime flavor.
57        runtime_flavor: RuntimeFlavor,
58    },
59
60    /// The current platform is unsupported by chrome-for-testing.
61    #[error("unsupported chrome-for-testing platform")]
62    UnsupportedPlatform,
63
64    // Cache and version resolution.
65    /// The cache directory could not be determined.
66    #[error("failed to determine cache directory; is $HOME set?")]
67    DetermineCacheDir,
68
69    /// The cache directory could not be created.
70    #[error("failed to create cache directory {}", .cache_dir.display())]
71    CreateCacheDir {
72        /// The cache directory path.
73        cache_dir: PathBuf,
74    },
75
76    /// The cache directory could not be removed.
77    #[error("failed to remove cache directory {}", .cache_dir.display())]
78    RemoveCacheDir {
79        /// The cache directory path.
80        cache_dir: PathBuf,
81    },
82
83    /// The cache directory could not be recreated.
84    #[error("failed to recreate cache directory {}", .cache_dir.display())]
85    RecreateCacheDir {
86        /// The cache directory path.
87        cache_dir: PathBuf,
88    },
89
90    /// The known-good version manifest could not be requested.
91    #[error("failed to request versions for {version_request:?}")]
92    RequestVersions {
93        /// The requested version selection.
94        version_request: VersionRequest,
95    },
96
97    /// No known-good version matched the requested selection.
98    #[error("could not determine a version for {version_request:?}")]
99    NoMatchingVersion {
100        /// The requested version selection.
101        version_request: VersionRequest,
102    },
103
104    /// No Chrome binary was requested for a download operation.
105    #[error("at least one Chrome binary must be requested")]
106    EmptyChromeBinaryDownloadRequest,
107
108    /// No Chrome download exists for the selected version and platform.
109    #[error("no chrome download for version {version} on {platform}")]
110    NoChromeDownload {
111        /// The selected Chrome version.
112        version: Version,
113        /// The detected platform.
114        platform: Platform,
115    },
116
117    /// No Chrome Headless Shell download exists for the selected version and platform.
118    #[error("no chrome-headless-shell download for version {version} on {platform}")]
119    NoChromeHeadlessShellDownload {
120        /// The selected Chrome version.
121        version: Version,
122        /// The detected platform.
123        platform: Platform,
124    },
125
126    /// No Chromedriver download exists for the selected version and platform.
127    #[error("no chromedriver download for version {version} on {platform}")]
128    NoChromedriverDownload {
129        /// The selected Chrome version.
130        version: Version,
131        /// The detected platform.
132        platform: Platform,
133    },
134
135    /// The platform-specific package directory could not be created.
136    #[error("failed to create platform directory {}", .platform_dir.display())]
137    CreatePlatformDir {
138        /// The platform-specific package directory.
139        platform_dir: PathBuf,
140    },
141
142    /* Downloads and archives. */
143    /// The download request failed or returned a non-success status.
144    #[error("failed to download {artifact} from {url}")]
145    Download {
146        /// The artifact being downloaded.
147        artifact: ChromeForTestingArtifact,
148        /// The download URL.
149        url: String,
150    },
151
152    /// The downloaded archive file could not be created.
153    #[error("failed to create {artifact} download file {}", .path.display())]
154    CreateDownloadFile {
155        /// The artifact being downloaded.
156        artifact: ChromeForTestingArtifact,
157        /// The archive path.
158        path: PathBuf,
159    },
160
161    /// A chunk could not be written into the downloaded archive.
162    #[error("failed to write {artifact} download chunk")]
163    WriteDownloadFile {
164        /// The artifact being downloaded.
165        artifact: ChromeForTestingArtifact,
166    },
167
168    /// The downloaded archive file could not be flushed.
169    #[error("failed to flush {artifact} download file")]
170    FlushDownloadFile {
171        /// The artifact being downloaded.
172        artifact: ChromeForTestingArtifact,
173    },
174
175    /// The download stalled for too long.
176    #[error(
177        "{artifact} download timed out after {consecutive_stalls} consecutive stalls of {chunk_timeout:?}"
178    )]
179    DownloadStalled {
180        /// The artifact being downloaded.
181        artifact: ChromeForTestingArtifact,
182        /// The number of consecutive stalls observed.
183        consecutive_stalls: u32,
184        /// The timeout for each stall.
185        chunk_timeout: Duration,
186    },
187
188    /// The downloaded archive could not be opened.
189    #[error("failed to open downloaded ZIP archive {}", .path.display())]
190    OpenDownloadedZip {
191        /// The archive path.
192        path: PathBuf,
193    },
194
195    /// The downloaded archive was not a valid ZIP file.
196    #[error("downloaded file {} is not a valid ZIP archive", .path.display())]
197    InvalidZip {
198        /// The archive path.
199        path: PathBuf,
200    },
201
202    /// The downloaded archive exceeded the decompressed size safety limit.
203    #[error(
204        "downloaded ZIP archive {} decompressed size {size} exceeds safety limit {max_size}",
205        .path.display()
206    )]
207    ZipTooLarge {
208        /// The archive path.
209        path: PathBuf,
210        /// The reported decompressed size in bytes.
211        size: u128,
212        /// The configured maximum decompressed size in bytes.
213        max_size: u128,
214    },
215
216    /// The downloaded archive could not be extracted.
217    #[error(
218        "failed to extract ZIP archive {} to {}",
219        .path.display(),
220        .unpack_dir.display()
221    )]
222    ExtractZip {
223        /// The archive path.
224        path: PathBuf,
225        /// The destination directory.
226        unpack_dir: PathBuf,
227    },
228
229    /// The downloaded archive could not be removed after extraction.
230    #[error("failed to remove downloaded ZIP archive {}", .path.display())]
231    RemoveDownloadedZip {
232        /// The archive path.
233        path: PathBuf,
234    },
235
236    /* Chromedriver process lifecycle. */
237    /// The chromedriver process could not be spawned.
238    #[error("failed to spawn chromedriver process {}", .path.display())]
239    SpawnChromedriver {
240        /// The chromedriver executable path.
241        path: PathBuf,
242    },
243
244    /// The browser process could not be spawned.
245    #[error("failed to spawn browser process {}", .path.display())]
246    SpawnBrowser {
247        /// The browser executable path.
248        path: PathBuf,
249    },
250
251    /// A Chrome Headless Shell session was configured with an unsupported remote debugging arg.
252    #[error(
253        "Chrome Headless Shell sessions require a TCP remote debugging port; unsupported argument {arg:?}"
254    )]
255    UnsupportedHeadlessShellRemoteDebuggingArg {
256        /// The unsupported browser argument.
257        arg: String,
258    },
259
260    /// A Chrome Headless Shell session was configured with an invalid remote debugging port arg.
261    #[error(
262        "Chrome Headless Shell sessions require --remote-debugging-port=<0-65535>; invalid argument {arg:?}"
263    )]
264    InvalidHeadlessShellRemoteDebuggingPortArg {
265        /// The invalid browser argument.
266        arg: String,
267    },
268
269    /// A Chrome Headless Shell session was configured with multiple remote debugging port args.
270    #[error(
271        "Chrome Headless Shell sessions require exactly one TCP remote debugging port; conflicting arguments {first_arg:?} and {second_arg:?}"
272    )]
273    ConflictingHeadlessShellRemoteDebuggingArgs {
274        /// The first configured remote debugging port argument.
275        first_arg: String,
276        /// The second configured remote debugging port argument.
277        second_arg: String,
278    },
279
280    /// Chromedriver did not report startup before the timeout.
281    #[error("failed while waiting for chromedriver {} to start", .path.display())]
282    WaitForChromedriverStartup {
283        /// The chromedriver executable path.
284        path: PathBuf,
285    },
286
287    /// The browser process did not report `DevTools` startup before the timeout.
288    #[error("failed while waiting for browser {} to expose DevTools", .path.display())]
289    WaitForBrowserStartup {
290        /// The browser executable path.
291        path: PathBuf,
292    },
293
294    /// An initial browser page could not be created through the `DevTools` endpoint.
295    #[error("failed to create initial browser page through DevTools at {debugger_address}")]
296    CreateInitialBrowserPage {
297        /// The `DevTools` HTTP endpoint address.
298        debugger_address: String,
299    },
300
301    /// The chromedriver process could not be terminated.
302    #[error("failed to terminate chromedriver process on port {port}")]
303    TerminateChromedriver {
304        /// The chromedriver port.
305        port: Port,
306    },
307
308    /// The browser process could not be terminated.
309    #[error("failed to terminate browser process attached at {debugger_address}")]
310    TerminateBrowser {
311        /// The `DevTools` HTTP endpoint address.
312        debugger_address: String,
313    },
314
315    /* Session lifecycle. */
316    /// Chrome capabilities could not be prepared.
317    #[error(
318        "failed to prepare Chrome capabilities for {}",
319        .browser_executable.display()
320    )]
321    PrepareChromeCapabilities {
322        /// The browser executable path.
323        browser_executable: PathBuf,
324    },
325
326    /// User-provided capability setup failed.
327    #[error("failed to configure Chrome capabilities")]
328    ConfigureSessionCapabilities,
329
330    /// The `WebDriver` session could not be started.
331    #[error("failed to start WebDriver session on port {port}")]
332    StartWebDriverSession {
333        /// The chromedriver port.
334        port: Port,
335    },
336
337    /// User-provided session callback returned an error.
338    #[error("session callback failed")]
339    RunSessionCallback,
340
341    /// The `WebDriver` session could not be closed.
342    #[error("failed to quit WebDriver session")]
343    QuitSession,
344}