200 lines
6.2 KiB
Rust
200 lines
6.2 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,
|
|
};
|
|
use std::process::{Child, Command, Stdio};
|
|
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";
|
|
|
|
struct ChildProcess {
|
|
child: Child,
|
|
}
|
|
|
|
impl ChildProcess {
|
|
fn spawn(mut command: Command) -> Self {
|
|
let child = command
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null())
|
|
.spawn()
|
|
.expect("spawn child process");
|
|
Self { child }
|
|
}
|
|
}
|
|
|
|
impl Drop for ChildProcess {
|
|
fn drop(&mut self) {
|
|
let _ = self.child.kill();
|
|
let _ = self.child.wait();
|
|
}
|
|
}
|
|
|
|
#[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 = ChildProcess::spawn(app);
|
|
wait_for_tcp(APP_ADDR);
|
|
|
|
let mut webdriver = Command::new("geckodriver");
|
|
webdriver.arg("--port").arg("4445");
|
|
let _webdriver = ChildProcess::spawn(webdriver);
|
|
wait_for_tcp(WEBDRIVER_ADDR);
|
|
|
|
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)
|
|
}
|
|
|
|
fn wait_for_tcp(addr: &str) {
|
|
let deadline = Instant::now() + Duration::from_secs(8);
|
|
while Instant::now() < deadline {
|
|
if std::net::TcpStream::connect(addr).is_ok() {
|
|
return;
|
|
}
|
|
std::thread::sleep(Duration::from_millis(25));
|
|
}
|
|
panic!("timed out waiting for {addr}");
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|