#![allow(clippy::expect_used, clippy::unwrap_used, clippy::missing_panics_doc)]
use plumb_cdp::{CdpError, ChromiumOptions, PersistentBrowser, Target};
use plumb_core::ViewportKey;
fn target(url: &str) -> Target {
Target {
url: url.into(),
viewport: ViewportKey::new("desktop"),
width: 1280,
height: 800,
device_pixel_ratio: 1.0,
..Target::default()
}
}
#[cfg(feature = "e2e-chromium")]
fn fixture_url(name: &str) -> std::io::Result<String> {
let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fixtures")
.join(name);
let canonical = path.canonicalize()?;
Ok(format!("file://{}", canonical.display()))
}
#[cfg(feature = "e2e-chromium")]
const SKIP_ENV_VAR: &str = "PLUMB_E2E_CHROMIUM_SKIP";
#[cfg(feature = "e2e-chromium")]
fn host_missing_chromium(err: &CdpError) -> bool {
let unavailable = matches!(
err,
CdpError::ChromiumNotFound { .. } | CdpError::UnsupportedChromium { .. }
);
unavailable && std::env::var_os(SKIP_ENV_VAR).is_some()
}
#[test]
fn target_helper_builds_desktop_viewport() {
let t = target("https://example.com");
assert_eq!(t.viewport.as_str(), "desktop");
assert_eq!(t.width, 1280);
assert_eq!(t.height, 800);
}
#[tokio::test]
async fn persistent_browser_rejects_missing_executable() {
let result = PersistentBrowser::launch(ChromiumOptions {
executable_path: Some(std::path::PathBuf::from(
"/definitely/not/a/chromium/binary",
)),
..ChromiumOptions::default()
})
.await;
assert!(matches!(result, Err(CdpError::ChromiumNotFound { .. })));
}
#[cfg(feature = "e2e-chromium")]
fn isolated_options() -> std::io::Result<(ChromiumOptions, tempfile::TempDir)> {
let dir = tempfile::tempdir()?;
Ok((
ChromiumOptions {
executable_path: None,
user_data_dir: Some(dir.path().to_path_buf()),
..ChromiumOptions::default()
},
dir,
))
}
#[cfg(feature = "e2e-chromium")]
#[tokio::test]
#[allow(clippy::disallowed_methods)]
async fn persistent_browser_warm_call_is_faster_than_cold() {
let url = match fixture_url("static_page.html") {
Ok(u) => u,
Err(err) => panic!("fixture path: {err}"),
};
let (options, _profile) = isolated_options().expect("tempdir");
let cold_start = std::time::Instant::now();
let browser = match PersistentBrowser::launch(options).await {
Ok(b) => b,
Err(err) if host_missing_chromium(&err) => return,
Err(err) => panic!("launch failed: {err}"),
};
let _first = match browser.snapshot(target(&url)).await {
Ok(s) => s,
Err(err) if host_missing_chromium(&err) => {
let _ = browser.shutdown().await;
return;
}
Err(err) => panic!("first snapshot: {err}"),
};
let cold = cold_start.elapsed();
let warm_start = std::time::Instant::now();
let _second = match browser.snapshot(target(&url)).await {
Ok(s) => s,
Err(err) => panic!("second snapshot: {err}"),
};
let warm = warm_start.elapsed();
let _ = browser.shutdown().await;
assert!(
cold * 10 > warm * 13,
"warm call should be meaningfully faster than cold (cold={cold:?}, warm={warm:?}); regression guard expects cold > warm * 1.3"
);
}
#[cfg(feature = "e2e-chromium")]
#[tokio::test]
async fn persistent_browser_isolates_state_between_calls() {
let url = match fixture_url("stateful_page.html") {
Ok(u) => u,
Err(err) => panic!("fixture path: {err}"),
};
let (options, _profile) = isolated_options().expect("tempdir");
let browser = match PersistentBrowser::launch(options).await {
Ok(b) => b,
Err(err) if host_missing_chromium(&err) => return,
Err(err) => panic!("launch failed: {err}"),
};
let first = match browser.snapshot(target(&url)).await {
Ok(s) => s,
Err(err) if host_missing_chromium(&err) => {
let _ = browser.shutdown().await;
return;
}
Err(err) => panic!("first snapshot: {err}"),
};
let second = match browser.snapshot(target(&url)).await {
Ok(s) => s,
Err(err) => panic!("second snapshot: {err}"),
};
let _ = browser.shutdown().await;
let first_marker = state_marker(&first);
let second_marker = state_marker(&second);
assert_eq!(
first_marker, "state-fresh",
"first snapshot must render `state-fresh` — the fixture writes \
localStorage on every visit, so a value other than `state-fresh` \
here means the read-before-write surfaced state from a prior call"
);
assert_eq!(
second_marker, "state-fresh",
"second snapshot must render `state-fresh` — observing \
`state-leaked` would mean call 1's localStorage write was still \
visible to call 2, proving the incognito BrowserContext was reused"
);
let first_json = serde_json::to_string(&first).expect("serialize first");
let second_json = serde_json::to_string(&second).expect("serialize second");
assert_eq!(
first_json, second_json,
"back-to-back snapshots over fresh incognito contexts must be byte-identical"
);
}
#[cfg(feature = "e2e-chromium")]
fn state_marker(snap: &plumb_core::PlumbSnapshot) -> String {
snap.nodes
.iter()
.find(|n| n.tag == "main")
.and_then(|n| n.attrs.get("data-marker").cloned())
.unwrap_or_else(|| "state-missing".to_string())
}
#[cfg(feature = "e2e-chromium")]
#[tokio::test]
async fn persistent_browser_shutdown_is_idempotent() {
let (options, _profile) = isolated_options().expect("tempdir");
let browser = match PersistentBrowser::launch(options).await {
Ok(b) => b,
Err(err) if host_missing_chromium(&err) => return,
Err(err) => panic!("launch failed: {err}"),
};
browser.shutdown().await.expect("first shutdown ok");
browser
.shutdown()
.await
.expect("second shutdown must remain idempotent");
}