chrome_for_testing_manager/
session_builder.rs1use crate::ChromeForTestingManagerError;
2use crate::chromedriver::Chromedriver;
3use crate::session::Session;
4use rootcause::prelude::ResultExt;
5use rootcause::{IntoReportCollection, Report, markers::SendSync};
6use thirtyfour::prelude::WebDriverError;
7use thirtyfour::{ChromeCapabilities, WebDriverBuilder};
8
9#[doc(hidden)]
11#[derive(Debug, Clone, Copy)]
12pub struct DefaultCaps;
13
14#[doc(hidden)]
16#[derive(Debug, Clone, Copy)]
17pub struct DefaultConfig;
18
19#[doc(hidden)]
21pub struct CapsSetup<F>(F);
22
23#[doc(hidden)]
25pub struct ConfigSetup<F>(F);
26
27mod sealed {
28 pub trait Sealed {}
29}
30
31#[doc(hidden)]
32pub trait ApplyCaps: sealed::Sealed {
33 fn apply(self, caps: &mut ChromeCapabilities) -> Result<(), WebDriverError>;
34}
35
36impl sealed::Sealed for DefaultCaps {}
37impl ApplyCaps for DefaultCaps {
38 fn apply(self, _caps: &mut ChromeCapabilities) -> Result<(), WebDriverError> {
39 Ok(())
40 }
41}
42
43impl<F> sealed::Sealed for CapsSetup<F> {}
44impl<F> ApplyCaps for CapsSetup<F>
45where
46 F: FnOnce(&mut ChromeCapabilities) -> Result<(), WebDriverError>,
47{
48 fn apply(self, caps: &mut ChromeCapabilities) -> Result<(), WebDriverError> {
49 self.0(caps)
50 }
51}
52
53#[doc(hidden)]
54pub trait ApplyConfig: sealed::Sealed {
55 fn apply(self, builder: WebDriverBuilder) -> WebDriverBuilder;
56}
57
58impl sealed::Sealed for DefaultConfig {}
59impl ApplyConfig for DefaultConfig {
60 fn apply(self, builder: WebDriverBuilder) -> WebDriverBuilder {
61 builder
62 }
63}
64
65impl<F> sealed::Sealed for ConfigSetup<F> {}
66impl<F> ApplyConfig for ConfigSetup<F>
67where
68 F: FnOnce(WebDriverBuilder) -> WebDriverBuilder,
69{
70 fn apply(self, builder: WebDriverBuilder) -> WebDriverBuilder {
71 self.0(builder)
72 }
73}
74
75pub struct SessionBuilder<'a, C, B> {
88 chromedriver: &'a Chromedriver,
89 caps_setup: C,
90 config_setup: B,
91}
92
93impl<'a> SessionBuilder<'a, DefaultCaps, DefaultConfig> {
94 pub(crate) fn new(chromedriver: &'a Chromedriver) -> Self {
95 Self {
96 chromedriver,
97 caps_setup: DefaultCaps,
98 config_setup: DefaultConfig,
99 }
100 }
101}
102
103impl<'a, B> SessionBuilder<'a, DefaultCaps, B> {
104 pub fn with_caps<F>(self, f: F) -> SessionBuilder<'a, CapsSetup<F>, B>
106 where
107 F: FnOnce(&mut ChromeCapabilities) -> Result<(), WebDriverError>,
108 {
109 SessionBuilder {
110 chromedriver: self.chromedriver,
111 caps_setup: CapsSetup(f),
112 config_setup: self.config_setup,
113 }
114 }
115}
116
117impl<'a, C> SessionBuilder<'a, C, DefaultConfig> {
118 pub fn with_config<F>(self, f: F) -> SessionBuilder<'a, C, ConfigSetup<F>>
120 where
121 F: FnOnce(WebDriverBuilder) -> WebDriverBuilder,
122 {
123 SessionBuilder {
124 chromedriver: self.chromedriver,
125 caps_setup: self.caps_setup,
126 config_setup: ConfigSetup(f),
127 }
128 }
129}
130
131impl<C, B> SessionBuilder<'_, C, B>
132where
133 C: ApplyCaps,
134 B: ApplyConfig,
135{
136 pub async fn run<T, E, F>(self, f: F) -> Result<T, Report<ChromeForTestingManagerError>>
148 where
149 F: for<'b> AsyncFnOnce(&'b Session) -> Result<T, E>,
150 E: IntoReportCollection<SendSync>,
151 {
152 use futures::FutureExt;
153
154 let chromedriver = self.chromedriver;
155 let port = chromedriver.port();
156 let mut caps = chromedriver.mgr.prepare_caps(&chromedriver.loaded)?;
157 self.caps_setup
158 .apply(&mut caps)
159 .context(ChromeForTestingManagerError::ConfigureSessionCapabilities)?;
160 let builder = thirtyfour::WebDriver::builder(format!("http://localhost:{port}"), caps);
161 let driver = self
162 .config_setup
163 .apply(builder)
164 .connect()
165 .await
166 .context(ChromeForTestingManagerError::StartWebDriverSession { port })?;
167
168 let session = Session { driver };
169
170 let maybe_panicked = core::panic::AssertUnwindSafe(f(&session))
171 .catch_unwind()
172 .await;
173
174 let user_result = match maybe_panicked {
175 Ok(result) => result.context(ChromeForTestingManagerError::RunSessionCallback),
176 Err(payload) => {
177 if let Err(quit_err) = session.quit().await {
178 tracing::error!(
179 "Failed to quit WebDriver session after user callback panic: {quit_err:?}"
180 );
181 }
182 std::panic::resume_unwind(payload);
183 }
184 };
185
186 let quit_result = session.quit().await;
187
188 match (user_result, quit_result) {
189 (Ok(value), Ok(())) => Ok(value),
190 (Ok(_), Err(quit_err)) => Err(quit_err),
191 (Err(user_err), Ok(())) => Err(user_err),
192 (Err(mut user_err), Err(quit_err)) => {
193 tracing::error!(
194 "Failed to quit WebDriver session after user failure: {quit_err:?}"
195 );
196 user_err
197 .children_mut()
198 .push(quit_err.into_dynamic().into_cloneable());
199 Err(user_err)
200 }
201 }
202 }
203}