Files
hemx/examples/techdemo/tests/browser_e2e.rs
T
2026-07-13 10:24:36 +02:00

168 lines
5.7 KiB
Rust

use hemx_techdemo::ui::control_center::{self as control, launch_work, simulate_push};
use hemx_test::{
any_root_selector, document_body_selector, handle_button_selector, handle_selector,
island_probe_script, island_readout_selector, keyed_selector, nav_link_selector,
scoped_island_readout_selector, target_selector, TestProcess,
};
use std::process::Command;
use std::time::{Duration, Instant};
use thirtyfour::prelude::*;
const APP_ADDR: &str = "127.0.0.1:3012";
const WEBDRIVER_ADDR: &str = "127.0.0.1:4445";
const STARTUP_TIMEOUT: Duration = Duration::from_secs(8);
#[tokio::test]
async fn browser_drives_typed_product_end_to_end() -> WebDriverResult<()> {
// req: examples/001 req: dx/008 req: form/002 req: page_swap/002 req: push/003
let mut app = Command::new(env!("CARGO_BIN_EXE_hemx-techdemo"));
app.env("HEMX_TECHDEMO_ADDR", APP_ADDR);
let _app = TestProcess::start(app, "hemx-techdemo", APP_ADDR, STARTUP_TIMEOUT)
.expect("start ready hemx-techdemo");
let mut webdriver = Command::new("geckodriver");
webdriver.arg("--port").arg("4445");
let _webdriver = TestProcess::start(webdriver, "geckodriver", WEBDRIVER_ADDR, STARTUP_TIMEOUT)
.expect("start ready geckodriver");
let mut caps = DesiredCapabilities::firefox();
caps.set_headless()?;
let driver = WebDriver::new(&format!("http://{WEBDRIVER_ADDR}"), caps).await?;
let result = async {
driver.goto(&format!("http://{APP_ADDR}/")).await?;
assert_text(
&driver,
"A Linear-class work system without a frontend framework",
)
.await?;
assert_text(&driver, "Compile checked handles").await?;
assert_text(
&driver,
"No selectors. Generated resources address every target.",
)
.await?;
assert_text(&driver, "Opaque island bridge").await?;
wait_for_runtime(&driver).await?;
insert_probe_island(&driver).await?;
wait_for_text(
&driver,
&scoped_island_readout_selector("#probe-island"),
"probe live",
)
.await?;
driver
.find(By::Css(handle_selector(simulate_push)))
.await?
.click()
.await?;
wait_for_text(&driver, &target_selector(control::notice), "Push simulated").await?;
wait_for_text(&driver, island_readout_selector(), "activity rows").await?;
driver
.find(By::Css(handle_button_selector(launch_work)))
.await?
.click()
.await?;
wait_for_text(
&driver,
&keyed_selector(".work-card", 4),
"Ship typed updates",
)
.await?;
assert_text(&driver, "width:77%").await?;
driver
.find(By::Css(handle_selector(simulate_push)))
.await?
.click()
.await?;
wait_for_text(&driver, &target_selector(control::live_feed), "SSE tick").await?;
driver
.find(By::Css(nav_link_selector("/architecture")))
.await?
.click()
.await?;
wait_for_text(&driver, &target_selector(control::inspector), "Page swap").await?;
wait_for_text(&driver, island_readout_selector(), "activity rows").await?;
assert!(driver
.current_url()
.await?
.as_str()
.ends_with("/architecture"));
driver.goto(&format!("http://{APP_ADDR}/")).await?;
wait_for_text(&driver, &target_selector(control::live_feed), "SSE tick").await?;
Ok::<(), WebDriverError>(())
}
.await;
let quit = driver.quit().await;
result.and(quit)
}
async fn insert_probe_island(driver: &WebDriver) -> WebDriverResult<()> {
let root = driver.find(By::Css(any_root_selector())).await?;
let script = island_probe_script(
"probe-island",
"orbit",
"1|1|1|probe waiting",
"7|2|8|probe live",
);
driver.execute(&script, vec![root.to_json()?]).await?;
Ok(())
}
async fn wait_for_runtime(driver: &WebDriver) -> WebDriverResult<()> {
let deadline = Instant::now() + Duration::from_secs(8);
loop {
let loaded = driver
.execute(
"return !!window.hemx && window.hemx.roots().length > 0",
Vec::new(),
)
.await?
.json()
.as_bool()
.unwrap_or(false);
if loaded {
return Ok(());
}
if Instant::now() >= deadline {
panic!("timed out waiting for hemx runtime");
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
}
async fn assert_text(driver: &WebDriver, text: &str) -> WebDriverResult<()> {
wait_for_text(driver, document_body_selector(), text).await
}
async fn wait_for_text(driver: &WebDriver, selector: &str, text: &str) -> WebDriverResult<()> {
let deadline = Instant::now() + Duration::from_secs(8);
let by = By::Css(selector);
loop {
if let Ok(element) = driver.find(by.clone()).await {
let content = element.text().await.unwrap_or_default();
let html = element.inner_html().await.unwrap_or_default();
if content.contains(text) || html.contains(text) {
return Ok(());
}
}
if Instant::now() >= deadline {
let body = driver
.find(By::Css(document_body_selector()))
.await?
.text()
.await
.unwrap_or_default();
panic!("timed out waiting for {text:?} in {selector:?}; body={body:?}");
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
}