use playwright_cdp::options::LaunchOptions;
use playwright_cdp::Playwright;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
async fn http_server(body: &'static str) -> String {
let l = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let port = l.local_addr().unwrap().port();
tokio::spawn(async move {
loop {
let Ok((mut s, _)) = l.accept().await else { break };
tokio::spawn(async move {
let mut buf = [0u8; 4096];
let _ = s.read(&mut buf).await;
let resp = format!(
"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
body.len(),
body
);
let _ = s.write_all(resp.as_bytes()).await;
let _ = s.flush().await;
});
}
});
format!("http://127.0.0.1:{port}/")
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let _ = tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "playwright_cdp=info".into()),
)
.try_init();
let base1 = http_server("<html><body><h1 id=\"a\">page one</h1></body></html>").await;
let base2 = http_server("<html><body><h1 id=\"b\">page two</h1></body></html>").await;
let browser = Playwright::launch()
.await?
.chromium()
.launch_with_options(LaunchOptions::default())
.await?;
println!("browser version: {}", browser.version().await?);
let context = browser.new_context().await?;
let req_count = Arc::new(AtomicU64::new(0));
let first_req_url: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
let first_resp_status: Arc<Mutex<Option<u16>>> = Arc::new(Mutex::new(None));
{
let req_count = Arc::clone(&req_count);
let first_req_url = Arc::clone(&first_req_url);
context.on_request(move |req| {
let n = req_count.fetch_add(1, Ordering::SeqCst) + 1;
if n == 1 {
*first_req_url.lock().unwrap() = Some(req.url().to_string());
}
println!("[ctx] request #{} {} {}", n, req.method(), req.url());
async {}
});
}
{
let first_resp_status = Arc::clone(&first_resp_status);
context.on_response(move |resp| {
let mut slot = first_resp_status.lock().unwrap();
if slot.is_none() {
*slot = Some(resp.status());
}
println!("[ctx] response {} {}", resp.status(), resp.url());
async {}
});
}
let page1 = context.new_page().await?;
page1.goto(&base1, None).await?;
let h1: String = page1.evaluate("document.getElementById('a').textContent").await?;
println!("page1 heading: {h1}");
assert_eq!(h1, "page one");
let page2 = context.new_page().await?;
page2.goto(&base2, None).await?;
let h2: String = page2.evaluate("document.getElementById('b').textContent").await?;
println!("page2 heading: {h2}");
assert_eq!(h2, "page two");
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
let total = req_count.load(Ordering::SeqCst);
println!("total requests captured by the single context handler: {total}");
assert!(
total >= 2,
"context handler should have seen >=2 requests (one per page), got {total}"
);
let first_url = first_req_url.lock().unwrap().clone();
println!("first captured request url: {:?}", first_url);
assert_eq!(first_url.as_deref(), Some(base1.as_str()));
let status = first_resp_status.lock().unwrap();
println!("first captured response status: {:?}", status);
assert_eq!(*status, Some(200));
println!("PASS");
browser.close().await?;
Ok(())
}