use std::future::Future;
use std::sync::Arc;
use crate::{Client, Doco, Result, Session, TestCase};
pub struct TestRunner {
rt: tokio::runtime::Runtime,
doco: Doco,
selenium: Arc<testcontainers::ContainerAsync<testcontainers::GenericImage>>,
}
impl TestRunner {
pub fn new(init: impl Future<Output = Doco>) -> Self {
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("failed to build tokio runtime");
let doco = rt.block_on(init);
println!("Initializing ephemeral test environment...");
let selenium = rt
.block_on(Session::start_selenium())
.expect("failed to initialize the test runner");
Self {
rt,
doco,
selenium: Arc::new(selenium),
}
}
pub fn run(self) {
let runner = Arc::new(self);
let args = libtest_mimic::Arguments::from_args();
let tests: Vec<libtest_mimic::Trial> = inventory::iter::<TestCase>
.into_iter()
.map(|tc| {
let r = Arc::clone(&runner);
let handle = runner.rt.handle().clone();
let func = tc.function;
libtest_mimic::Trial::test(tc.name, move || {
handle.block_on(r.run_test(func)).map_err(|e| e.into())
})
})
.collect();
libtest_mimic::run(&args, tests).exit();
}
pub async fn run_test(&self, test: fn(Client) -> Result<()>) -> Result<()> {
let session = Session::with_selenium(&self.doco, Arc::clone(&self.selenium)).await?;
let client = session.client().clone();
test(client)?;
session.close().await?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use axum::routing::get;
use axum::Router;
use tokio::net::TcpListener;
use crate::test_utils::*;
use crate::Result;
use super::*;
#[tokio::test]
async fn selenium_can_access_host() -> Result<()> {
let listener = TcpListener::bind("0.0.0.0:0").await?;
let port = listener.local_addr()?.port();
let app = Router::new().route("/", get(|| async { "hello from the test" }));
tokio::spawn(async { axum::serve(listener, app).await });
let selenium = Session::start_selenium().await?;
let driver = thirtyfour::WebDriver::new(
&format!(
"http://{}:{}",
selenium.get_host().await?,
selenium.get_host_port_ipv4(4444).await?
),
thirtyfour::DesiredCapabilities::firefox(),
)
.await
.expect("failed to connect to WebDriver");
driver
.goto(&format!("http://host.docker.internal:{port}/"))
.await?;
let body = driver.source().await?;
assert!(body.contains("hello from the test"));
driver.quit().await.ok();
Ok(())
}
#[tokio::test]
async fn headless_browser_can_navigate() -> Result<()> {
let listener = TcpListener::bind("0.0.0.0:0").await?;
let port = listener.local_addr()?.port();
let app = Router::new().route("/", get(|| async { "headless works" }));
tokio::spawn(async { axum::serve(listener, app).await });
let selenium = Session::start_selenium().await?;
let mut caps = thirtyfour::DesiredCapabilities::firefox();
caps.set_headless()?;
let driver = thirtyfour::WebDriver::new(
&format!(
"http://{}:{}",
selenium.get_host().await?,
selenium.get_host_port_ipv4(4444).await?
),
caps,
)
.await
.expect("failed to connect to headless WebDriver");
driver
.goto(&format!("http://host.docker.internal:{port}/"))
.await?;
let body = driver.source().await?;
assert!(body.contains("headless works"));
driver.quit().await.ok();
Ok(())
}
#[tokio::test]
async fn viewport_sets_window_dimensions() -> Result<()> {
let selenium = Session::start_selenium().await?;
let driver = thirtyfour::WebDriver::new(
&format!(
"http://{}:{}",
selenium.get_host().await?,
selenium.get_host_port_ipv4(4444).await?
),
thirtyfour::DesiredCapabilities::firefox(),
)
.await
.expect("failed to connect to WebDriver");
let viewport = crate::Viewport::new(1280, 720);
driver
.set_window_rect(0, 0, viewport.width(), viewport.height())
.await?;
let inner_width: u64 = driver
.execute("return window.innerWidth", vec![])
.await?
.json()
.as_u64()
.unwrap();
let inner_height: u64 = driver
.execute("return window.innerHeight", vec![])
.await?
.json()
.as_u64()
.unwrap();
assert!(inner_width > 0, "innerWidth should be positive");
assert!(inner_height > 0, "innerHeight should be positive");
let rect = driver.get_window_rect().await?;
assert_eq!(rect.width, 1280);
assert_eq!(rect.height, 720);
driver.quit().await.ok();
Ok(())
}
#[test]
fn list_flag_prints_test_names() {
let trials = vec![
libtest_mimic::Trial::test("alpha_test", || Ok(())),
libtest_mimic::Trial::test("beta_test", || Ok(())),
];
let args = libtest_mimic::Arguments {
list: true,
..Default::default()
};
let conclusion = libtest_mimic::run(&args, trials);
assert_eq!(conclusion.num_passed, 0);
assert_eq!(conclusion.num_failed, 0);
}
#[test]
fn filter_selects_matching_tests() {
let trials = vec![
libtest_mimic::Trial::test("alpha_test", || Ok(())),
libtest_mimic::Trial::test("beta_test", || Err("should not run".into())),
];
let args = libtest_mimic::Arguments {
filter: Some("alpha".into()),
..Default::default()
};
let conclusion = libtest_mimic::run(&args, trials);
assert_eq!(conclusion.num_passed, 1);
assert_eq!(conclusion.num_failed, 0);
assert_eq!(conclusion.num_filtered_out, 1);
}
#[test]
fn trait_send() {
assert_send::<TestRunner>();
}
#[test]
fn trait_sync() {
assert_sync::<TestRunner>();
}
#[test]
fn trait_unpin() {
assert_unpin::<TestRunner>();
}
}