use std::time::Duration;
use drission::prelude::*;
const PNG_MAGIC: &[u8] = &[0x89, 0x50, 0x4E, 0x47];
fn data_url_html(html: &str) -> String {
let mut enc = String::with_capacity(html.len() * 2);
for &b in html.as_bytes() {
if b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_' | b'.' | b'~') {
enc.push(b as char);
} else {
enc.push('%');
enc.push_str(&format!("{b:02X}"));
}
}
format!("data:text/html,{enc}")
}
#[tokio::main]
async fn main() -> drission::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "warn".into()),
)
.init();
println!("[*] 启动 BrowserServer(headless)…");
let server = BrowserServer::launch(BrowserOptions::new().headless(true)).await?;
let endpoint = server.ws_endpoint().to_string();
println!("[*] ws 端点: {endpoint}");
let endpoint_ok = endpoint.starts_with("ws://127.0.0.1:")
&& endpoint.rsplit('/').next().is_some_and(|t| !t.is_empty());
let page = "<!doctype html><meta charset=utf-8><title>WSOK</title><h1 id=x>hello-ws</h1>";
let url = data_url_html(page);
println!("[*] 客户端1 connect…");
let (title_ok, ele_ok, js_ok, shot_ok, concurrent_rejected) = {
let b1 = Browser::connect(&endpoint).await?;
let t1 = b1.latest_tab().await?;
t1.get(&url).await?;
t1.wait()
.ele_displayed("#x", Some(Duration::from_secs(5)))
.await?;
let title = t1.title().await?;
let txt = t1.ele("#x").await?.text().await?;
let js = t1.run_js("6*7").await?;
let shot = t1.screenshot_bytes(false).await?;
let concurrent_rejected = Browser::connect(&endpoint).await.is_err();
println!(
"[客户端1] title={title:?} ele={txt:?} js={js} png={}B 并发再连被拒={concurrent_rejected}",
shot.len()
);
(
title == "WSOK",
txt.trim() == "hello-ws",
js.as_i64() == Some(42),
shot.starts_with(PNG_MAGIC),
concurrent_rejected,
)
};
tokio::time::sleep(Duration::from_millis(800)).await;
println!("[*] 客户端2 connect(验证接管语义)…");
let reconnect_ok = {
let b2 = Browser::connect(&endpoint).await?;
let t2 = b2.latest_tab().await?;
t2.get(&url).await?;
t2.wait()
.ele_displayed("#x", Some(Duration::from_secs(5)))
.await?;
let title2 = t2.title().await?;
println!("[客户端2] title={title2:?}");
title2 == "WSOK"
};
let bad = format!("ws://127.0.0.1:{}/definitely-wrong-token", server.port());
let bad_rejected = Browser::connect(&bad).await.is_err();
println!("[错误token] 拒绝={bad_rejected}");
let checks = [
("端点格式 ws://127.0.0.1:port/token", endpoint_ok),
("ws 驱动:title=WSOK", title_ok),
("ws 驱动:元素文本=hello-ws", ele_ok),
("ws 驱动:run_js 6*7=42", js_ok),
("ws 驱动:截图为 PNG", shot_ok),
("单实例:并发再连被拒", concurrent_rejected),
("接管语义:断开后可再次接管", reconnect_ok),
("错误 token 被拒", bad_rejected),
];
let pass = checks.iter().all(|&(_, ok)| ok);
for (name, ok) in checks {
println!(" [{}] {name}", if ok { "PASS" } else { "FAIL" });
}
println!(
"\n==== {} ====",
if pass {
"ALL CHECKS PASSED"
} else {
"SOME CHECKS FAILED"
}
);
server.stop().await?;
pass.then_some(())
.ok_or_else(|| drission::Error::msg("ws_connect 自验证未通过"))
}