use std::fmt::Formatter;
use std::future::{Future, IntoFuture};
use std::ops::Deref;
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
use http::HeaderValue;
use crate::Capabilities;
use crate::common::config::{WebDriverConfig, WebDriverConfigBuilder};
use crate::error::WebDriverResult;
use crate::extensions::query::IntoElementPoller;
use crate::prelude::WebDriverError;
use crate::session::create::start_session;
use crate::session::handle::SessionHandle;
use crate::session::http::HttpClient;
#[cfg(feature = "reqwest")]
use crate::session::http::create_reqwest_client;
#[derive(Debug, Clone)]
pub struct WebDriver {
pub(crate) handle: Arc<SessionHandle>,
}
#[derive(Debug, thiserror::Error)]
#[error("Webdriver has already quit, can't leak an already quit driver")]
pub struct AlreadyQuit(pub(crate) ());
impl WebDriver {
pub async fn new<S, C>(server_url: S, capabilities: C) -> WebDriverResult<Self>
where
S: Into<String>,
C: Into<Capabilities>,
{
Self::builder(server_url, capabilities).await
}
pub fn builder<S, C>(server_url: S, capabilities: C) -> WebDriverBuilder
where
S: Into<String>,
C: Into<Capabilities>,
{
WebDriverBuilder::new(server_url, capabilities)
}
pub fn handle(&self) -> &Arc<SessionHandle> {
&self.handle
}
pub fn clone_with_config(&self, config: WebDriverConfig) -> Self {
Self {
handle: Arc::new(self.handle.clone_with_config(config)),
}
}
#[cfg(feature = "manager")]
pub fn managed<C>(capabilities: C) -> crate::manager::WebDriverManagerBuilder
where
C: Into<Capabilities>,
{
let mut builder = crate::manager::WebDriverManager::builder();
builder.preloaded_caps = Some(capabilities.into());
builder
}
#[cfg(feature = "bidi")]
pub async fn bidi(&self) -> WebDriverResult<crate::bidi::BiDi> {
let handle = self.handle.clone();
let cell = self.handle.bidi_cell().clone();
let bidi = cell
.get_or_try_init(|| async move { crate::bidi::BiDi::connect(handle).await })
.await?;
Ok(bidi.clone())
}
#[cfg(feature = "cdp")]
pub fn cdp(&self) -> crate::cdp::Cdp {
crate::cdp::Cdp::new(self.handle.clone())
}
#[cfg(feature = "manager")]
pub fn driver_id(&self) -> Option<crate::manager::DriverId> {
let guard = self.handle.driver_guard()?;
let session_guard =
guard.as_any().downcast_ref::<crate::manager::manager_internal::SessionGuard>()?;
Some(session_guard.driver.driver_id)
}
#[cfg(feature = "manager")]
pub fn on_driver_log<F>(&self, f: F) -> Option<crate::manager::DriverLogSubscription>
where
F: Fn(&crate::manager::DriverLogLine) + Send + Sync + 'static,
{
let guard = self.handle.driver_guard()?;
let session_guard =
guard.as_any().downcast_ref::<crate::manager::manager_internal::SessionGuard>()?;
Some(session_guard.driver.subscribe_log(f))
}
pub async fn quit(self) -> WebDriverResult<()> {
self.handle.quit().await
}
pub fn leak(self) -> Result<(), AlreadyQuit> {
self.handle.leak()
}
}
impl Deref for WebDriver {
type Target = Arc<SessionHandle>;
fn deref(&self) -> &Self::Target {
&self.handle
}
}
pub struct WebDriverBuilder {
server_url: String,
capabilities: Capabilities,
config: WebDriverConfigBuilder,
client: Option<Box<dyn HttpClient>>,
}
impl std::fmt::Debug for WebDriverBuilder {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WebDriverBuilder")
.field("server_url", &self.server_url)
.field("capabilities", &self.capabilities)
.field("config", &self.config)
.field("client", &self.client.as_ref().map(|_| "<custom>"))
.finish()
}
}
impl WebDriverBuilder {
fn new<S, C>(server_url: S, capabilities: C) -> Self
where
S: Into<String>,
C: Into<Capabilities>,
{
Self {
server_url: server_url.into(),
capabilities: capabilities.into(),
config: WebDriverConfig::builder(),
client: None,
}
}
pub fn keep_alive(mut self, keep_alive: bool) -> Self {
self.config = self.config.keep_alive(keep_alive);
self
}
pub fn poller(mut self, poller: Arc<dyn IntoElementPoller + Send + Sync>) -> Self {
self.config = self.config.poller(poller);
self
}
pub fn user_agent<V>(mut self, user_agent: V) -> Self
where
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<WebDriverError>,
{
self.config = self.config.user_agent(user_agent);
self
}
pub fn request_timeout(mut self, timeout: Duration) -> Self {
self.config = self.config.request_timeout(timeout);
self
}
pub fn client(mut self, client: impl HttpClient + 'static) -> Self {
self.client = Some(Box::new(client));
self
}
pub async fn connect(self) -> WebDriverResult<WebDriver> {
let config = self.config.build()?;
let client: Arc<dyn HttpClient> = match self.client {
Some(c) => Arc::from(c),
None => {
#[cfg(feature = "reqwest")]
{
Arc::new(create_reqwest_client(config.request_timeout))
}
#[cfg(not(feature = "reqwest"))]
{
Arc::new(crate::session::http::null_client::create_null_client())
}
}
};
let server_url = self
.server_url
.parse()
.map_err(|e| WebDriverError::ParseError(format!("invalid url: {e}")))?;
let started =
start_session(client.as_ref(), &server_url, &config, self.capabilities).await?;
let handle = SessionHandle::new_with_config_guard_and_caps(
client,
server_url,
started.session_id,
config,
None,
started.capabilities,
)?;
Ok(WebDriver {
handle: Arc::new(handle),
})
}
}
impl IntoFuture for WebDriverBuilder {
type Output = WebDriverResult<WebDriver>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(self.connect())
}
}