use std::time::Duration;
use playwright_cdp::frame::AddScriptTagOptions;
use playwright_cdp::options::LaunchOptions;
use playwright_cdp::Playwright;
#[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 browser = Playwright::launch()
.await?
.chromium()
.launch_with_options(LaunchOptions::default())
.await?;
println!("browser version: {}", browser.version().await?);
let page = browser.new_page().await?;
page.set_content(
r#"<html><body><h1>main</h1><iframe id="f" srcdoc="<input id='name' placeholder='Name'><input type='checkbox' id='c'>"></iframe></body></html>"#,
)
.await?;
let main_id = page.main_frame().frame_id().to_string();
let child = loop {
let frames = page.frames().await?;
let candidate = frames.into_iter().find(|f| {
f.frame_id() != main_id && !f.is_detached() && f.url().contains("about:srcdoc")
});
if let Some(f) = candidate {
break f;
}
tokio::time::sleep(Duration::from_millis(100)).await;
};
println!(
"child frame attached: id={} url={}",
child.frame_id(),
child.url()
);
let child_doc_title: String = child.evaluate("document.title").await?;
println!("child document.title (via Frame::evaluate): {child_doc_title:?}");
assert_eq!(child_doc_title, "", "child frame's document.title should be empty");
child
.evaluate::<serde_json::Value>("window.__childMarker = 'from-child-frame'")
.await?;
let child_marker: String = child.evaluate("window.__childMarker").await?;
assert_eq!(child_marker, "from-child-frame", "child.evaluate reads child window");
let top_marker: Option<String> = page
.evaluate::<serde_json::Value>("window.__childMarker")
.await
.ok()
.and_then(|v| v.as_str().map(String::from));
assert!(
top_marker.is_none(),
"child window marker must NOT leak into the top page's window"
);
println!("child.evaluate correctly scoped to the iframe context");
child
.get_by_placeholder("Name")
.fill("Alice", None)
.await?;
let value = child
.get_by_placeholder("Name")
.input_value(None)
.await?;
println!("get_by_placeholder input value: {value}");
assert_eq!(value, "Alice", "placeholder input should be 'Alice'");
let top_name_count: usize = page
.evaluate::<serde_json::Value>("document.querySelectorAll('#name').length")
.await
.ok()
.and_then(|v| v.as_u64().map(|n| n as usize))
.unwrap_or(0);
assert_eq!(top_name_count, 0, "top document must not contain #name");
let child_name_count: usize = child
.evaluate::<serde_json::Value>("document.querySelectorAll('#name').length")
.await
.ok()
.and_then(|v| v.as_u64().map(|n| n as usize))
.unwrap_or(0);
assert_eq!(child_name_count, 1, "child document contains #name");
child.locator("#c").click(None).await?;
let checked: bool = child.locator("#c").evaluate("el.checked", None).await?;
println!("checkbox checked after click: {checked}");
assert!(checked, "checkbox should be checked after click");
let opts = AddScriptTagOptions::new().source("window.__injected = 42");
assert_eq!(
opts.source.as_deref(),
Some("window.__injected = 42"),
"AddScriptTagOptions builder round-trips the source"
);
child.add_script_tag(Some(opts)).await?;
tokio::time::sleep(Duration::from_millis(50)).await;
let injected: i64 = child
.evaluate::<serde_json::Value>("window.__injected")
.await
.ok()
.and_then(|v| v.as_i64())
.unwrap_or(-1);
println!("injected script set window.__injected = {injected} (in iframe)");
assert_eq!(
injected, 42,
"injected script should set window.__injected = 42 in the iframe"
);
let top_injected: Option<i64> = page
.evaluate::<serde_json::Value>("window.__injected")
.await
.ok()
.and_then(|v| v.as_i64());
assert!(
top_injected.is_none(),
"injected value must NOT leak into the top page"
);
println!("PASS");
browser.close().await?;
println!("done.");
Ok(())
}