chrome_for_testing_manager/
chromedriver.rs1use crate::mgr::{ChromeForTestingManager, LoadedChromePackage, VersionRequest};
2use crate::port::{Port, PortRequest};
3use anyhow::anyhow;
4use chrome_for_testing::api::channel::Channel;
5use std::fmt::{Debug, Formatter};
6use std::process::ExitStatus;
7use std::time::Duration;
8use tokio::runtime::RuntimeFlavor;
9use tokio_process_tools::broadcast::BroadcastOutputStream;
10use tokio_process_tools::{TerminateOnDrop, TerminationError};
11
12pub struct Chromedriver {
20 mgr: ChromeForTestingManager,
22
23 loaded: LoadedChromePackage,
25
26 chromedriver_process: Option<TerminateOnDrop<BroadcastOutputStream>>,
31
32 chromedriver_port: Port,
34}
35
36impl Debug for Chromedriver {
37 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
38 f.debug_struct("Chromedriver")
39 .field("mgr", &self.mgr)
40 .field("loaded", &self.loaded)
41 .field("chromedriver_process", &self.chromedriver_process)
42 .field("chromedriver_port", &self.chromedriver_port)
43 .finish()
44 }
45}
46
47impl Chromedriver {
48 pub async fn run(version: VersionRequest, port: PortRequest) -> anyhow::Result<Chromedriver> {
49 match tokio::runtime::Handle::current().runtime_flavor() {
53 RuntimeFlavor::MultiThread => { }
54 unsupported_flavor => {
55 return Err(anyhow!(indoc::formatdoc! {r#"
56 The Chromedriver type requires a multithreaded tokio runtime,
57 as we rely on async-drop functionality not available on a single-threaded runtime.
58
59 Detected runtime flavor: {unsupported_flavor:?}.
60
61 If you are writing a test, annotate it with `#[tokio::test(flavor = "multi_thread")]`.
62 "#}));
63 }
64 }
65
66 let mgr = ChromeForTestingManager::new();
67 let selected = mgr.resolve_version(version).await?;
68 let loaded = mgr.download(selected).await?;
69 let (chromedriver_process, chromedriver_port) =
70 mgr.launch_chromedriver(&loaded, port).await?;
71 Ok(Chromedriver {
72 chromedriver_process: Some(
73 chromedriver_process
74 .terminate_on_drop(Duration::from_secs(3), Duration::from_secs(3)),
75 ),
76 chromedriver_port,
77 loaded,
78 mgr,
79 })
80 }
81
82 pub async fn run_latest_stable() -> anyhow::Result<Chromedriver> {
83 Self::run(VersionRequest::LatestIn(Channel::Stable), PortRequest::Any).await
84 }
85
86 pub async fn run_latest_beta() -> anyhow::Result<Chromedriver> {
87 Self::run(VersionRequest::LatestIn(Channel::Beta), PortRequest::Any).await
88 }
89
90 pub async fn run_latest_dev() -> anyhow::Result<Chromedriver> {
91 Self::run(VersionRequest::LatestIn(Channel::Dev), PortRequest::Any).await
92 }
93
94 pub async fn run_latest_canary() -> anyhow::Result<Chromedriver> {
95 Self::run(VersionRequest::LatestIn(Channel::Canary), PortRequest::Any).await
96 }
97
98 pub async fn terminate(self) -> Result<ExitStatus, TerminationError> {
99 self.terminate_with_timeouts(Duration::from_secs(3), Duration::from_secs(3))
100 .await
101 }
102
103 pub async fn terminate_with_timeouts(
104 mut self,
105 interrupt_timeout: Duration,
106 terminate_timeout: Duration,
107 ) -> Result<ExitStatus, TerminationError> {
108 self.chromedriver_process
109 .take()
110 .expect("present")
111 .terminate(interrupt_timeout, terminate_timeout)
112 .await
113 }
114
115 #[cfg(feature = "thirtyfour")]
118 pub async fn with_session(
119 &self,
120 f: impl AsyncFnOnce(&crate::session::Session) -> Result<(), crate::session::SessionError>,
121 ) -> anyhow::Result<()> {
122 self.with_custom_session(|_caps| Ok(()), f).await
123 }
124
125 #[cfg(feature = "thirtyfour")]
128 pub async fn with_custom_session<F>(
129 &self,
130 setup: impl Fn(
131 &mut thirtyfour::ChromeCapabilities,
132 ) -> Result<(), thirtyfour::prelude::WebDriverError>,
133 f: F,
134 ) -> anyhow::Result<()>
135 where
136 F: for<'a> AsyncFnOnce(
137 &'a crate::session::Session,
138 ) -> Result<(), crate::session::SessionError>,
139 {
140 use crate::session::Session;
141 use anyhow::Context;
142 use futures::FutureExt;
143
144 let mut caps = self.mgr.prepare_caps(&self.loaded).await?;
145 setup(&mut caps).context("Failed to set up chrome capabilities.")?;
146 let driver = thirtyfour::WebDriver::new(
147 format!("http://localhost:{}", self.chromedriver_port),
148 caps,
149 )
150 .await?;
151
152 let session = Session { driver };
153
154 let maybe_panicked = core::panic::AssertUnwindSafe(f(&session))
156 .catch_unwind()
157 .await;
158
159 session.quit().await?;
161
162 let result = maybe_panicked.map_err(|err| {
164 let err = anyhow::anyhow!("{err:?}");
165 crate::session::SessionError::Panic {
166 reason: err.to_string(),
167 }
168 })?;
169
170 result.map_err(Into::into)
172 }
173}