#![allow(dead_code)]
use std::{
net::SocketAddr,
sync::{Arc, OnceLock},
thread::JoinHandle,
};
use rstest::fixture;
use thirtyfour::manager::WebDriverManager;
use thirtyfour::prelude::*;
use thirtyfour::support::block_on;
use thirtyfour::{ChromeCapabilities, FirefoxCapabilities};
use tokio::sync::{Semaphore, SemaphorePermit};
static SERVER: OnceLock<Arc<JoinHandle<()>>> = OnceLock::new();
static LOGINIT: OnceLock<()> = OnceLock::new();
const ASSETS_DIR: &str = "tests/test_html";
const PORT: u16 = 8081;
fn make_chrome_caps() -> ChromeCapabilities {
let mut caps = DesiredCapabilities::chrome();
caps.set_headless().unwrap();
caps.set_no_sandbox().unwrap();
caps.set_disable_gpu().unwrap();
caps.set_disable_dev_shm_usage().unwrap();
caps.add_arg("--no-sandbox").unwrap();
caps
}
fn make_firefox_caps() -> FirefoxCapabilities {
let mut caps = DesiredCapabilities::firefox();
caps.set_headless().unwrap();
caps
}
pub fn start_server() -> Arc<JoinHandle<()>> {
SERVER
.get_or_init(|| {
let handle = std::thread::spawn(move || {
let rt =
tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
rt.block_on(async {
tracing::debug!("starting web server on http://localhost:{PORT}");
let addr = SocketAddr::from(([127, 0, 0, 1], PORT));
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
let app = axum::Router::new()
.fallback_service(tower_http::services::ServeDir::new(ASSETS_DIR));
axum::serve(listener, app).await.unwrap();
});
});
Arc::new(handle)
})
.clone()
}
pub fn init_logging() {
LOGINIT.get_or_init(|| {
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
tracing_subscriber::registry()
.with(fmt::layer())
.with(EnvFilter::from_default_env())
.init();
});
}
pub fn get_limiter() -> &'static Semaphore {
static LIMITER: Semaphore = Semaphore::const_new(1);
&LIMITER
}
pub async fn lock_firefox(browser: &str) -> Option<SemaphorePermit<'static>> {
if browser == "firefox" {
Some(get_limiter().acquire().await.unwrap())
} else {
None
}
}
fn manager_for(browser: &str) -> &'static Arc<WebDriverManager> {
static CHROME: OnceLock<Arc<WebDriverManager>> = OnceLock::new();
static FIREFOX: OnceLock<Arc<WebDriverManager>> = OnceLock::new();
let cell = match browser {
"chrome" => &CHROME,
"firefox" => &FIREFOX,
b => unimplemented!("unsupported browser backend {b}"),
};
cell.get_or_init(|| WebDriverManager::builder().build())
}
pub async fn launch_browser(browser: &str) -> WebDriver {
tracing::debug!("launching browser {browser}");
let mgr = manager_for(browser);
match browser {
"chrome" => mgr.launch(make_chrome_caps()).await.expect("Failed to launch chrome"),
"firefox" => mgr.launch(make_firefox_caps()).await.expect("Failed to launch firefox"),
b => unimplemented!("unsupported browser backend {b}"),
}
}
pub async fn launch_managed_chrome(caps: ChromeCapabilities) -> WebDriverResult<WebDriver> {
let mgr = manager_for("chrome");
mgr.launch(caps).await
}
pub async fn launch_managed_firefox(caps: FirefoxCapabilities) -> WebDriverResult<WebDriver> {
let mgr = manager_for("firefox");
mgr.launch(caps).await
}
pub async fn launch_managed_bidi() -> WebDriverResult<WebDriver> {
let browser = std::env::var("THIRTYFOUR_BROWSER").unwrap_or_else(|_| "chrome".to_string());
match browser.as_str() {
"firefox" => {
let mut caps = DesiredCapabilities::firefox();
caps.set_headless()?;
caps.enable_bidi()?;
launch_managed_firefox(caps).await
}
_ => {
let mut caps = DesiredCapabilities::chrome();
caps.set_headless()?;
caps.set_no_sandbox()?;
caps.set_disable_gpu()?;
caps.set_disable_dev_shm_usage()?;
caps.add_arg("--no-sandbox")?;
caps.enable_bidi()?;
launch_managed_chrome(caps).await
}
}
}
pub fn bidi_browser() -> String {
std::env::var("THIRTYFOUR_BROWSER").unwrap_or_else(|_| "chrome".to_string())
}
pub struct TestHarness {
browser: String,
server: Arc<JoinHandle<()>>,
driver: Option<WebDriver>,
guard: Option<SemaphorePermit<'static>>,
}
impl TestHarness {
pub async fn new(browser: &str) -> Self {
let server = start_server();
let guard = lock_firefox(browser).await;
let driver = Some(launch_browser(browser).await);
Self {
browser: browser.to_string(),
server,
driver,
guard,
}
}
pub fn browser(&self) -> &str {
&self.browser
}
pub fn driver(&self) -> &WebDriver {
self.driver.as_ref().expect("the driver to still be active")
}
pub fn disable_auto_close(mut self) -> Self {
if let Some(driver) = self.driver.take() {
let _ = driver.leak();
}
self
}
}
#[fixture]
pub fn test_harness() -> TestHarness {
let browser = std::env::var("THIRTYFOUR_BROWSER").unwrap_or_else(|_| "chrome".to_string());
init_logging();
block_on(TestHarness::new(&browser))
}
pub fn sample_page_url() -> String {
format!("http://localhost:{PORT}/sample_page.html")
}
pub fn other_page_url() -> String {
format!("http://localhost:{PORT}/other_page.html")
}
pub fn drag_to_url() -> String {
format!("http://localhost:{PORT}/drag_to.html")
}