#![cfg(feature = "integration-tests")]
#![allow(clippy::panic, clippy::unwrap_used)]
use std::time::Duration;
use serial_test::serial;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
use zendriver::Browser;
const INTERCEPT_SETTLE: Duration = Duration::from_millis(150);
#[tokio::test]
#[serial]
async fn interception_block_rule_prevents_request() {
let mock = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/"))
.respond_with(
ResponseTemplate::new(200).set_body_raw(
r#"<!doctype html><html><body>
<script>
window.fetchErr = null;
fetch('/blocked/x.json')
.then(r => r.text())
.then(t => { window.fetchResult = t; })
.catch(e => { window.fetchErr = String(e); });
</script>
</body></html>"#
.as_bytes()
.to_vec(),
"text/html",
),
)
.mount(&mock)
.await;
Mock::given(method("GET"))
.and(path("/blocked/x.json"))
.respond_with(ResponseTemplate::new(200).set_body_string("should-not-arrive"))
.expect(0)
.mount(&mock)
.await;
let browser = Browser::builder().headless(true).launch().await.unwrap();
let tab = browser.main_tab();
let _intercept = tab.intercept().block("*/blocked/*").unwrap().start();
tokio::time::sleep(INTERCEPT_SETTLE).await;
tab.goto(&mock.uri()).await.unwrap();
tab.wait_for_load().await.unwrap();
tokio::time::sleep(Duration::from_millis(500)).await;
let err: Option<String> = tab.evaluate_main("window.fetchErr").await.unwrap_or(None);
assert!(
err.is_some(),
"fetch to /blocked/x.json should have errored; got window.fetchErr = {err:?}"
);
browser.close().await.unwrap();
drop(mock);
}
#[tokio::test]
#[serial]
async fn interception_respond_serves_fake_body() {
let mock = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/"))
.respond_with(
ResponseTemplate::new(200).set_body_raw(
r#"<!doctype html><html><body>
<script>
window.r = null;
fetch('/api/health').then(r => r.text()).then(t => { window.r = t; });
</script>
</body></html>"#
.as_bytes()
.to_vec(),
"text/html",
),
)
.mount(&mock)
.await;
let browser = Browser::builder().headless(true).launch().await.unwrap();
let tab = browser.main_tab();
let body = b"hello-from-respond".to_vec();
let _intercept = tab
.intercept()
.respond(
"*/api/health",
200,
vec![("content-type".into(), "text/plain".into())],
body,
)
.unwrap()
.start();
tokio::time::sleep(INTERCEPT_SETTLE).await;
tab.goto(&mock.uri()).await.unwrap();
tab.wait_for_load().await.unwrap();
let deadline = std::time::Instant::now() + Duration::from_secs(2);
let r: String = loop {
let v: Option<String> = tab.evaluate_main("window.r").await.unwrap_or(None);
if let Some(s) = v {
break s;
}
if std::time::Instant::now() >= deadline {
panic!("respond rule never delivered a body to window.r within 2s");
}
tokio::time::sleep(Duration::from_millis(50)).await;
};
assert_eq!(r, "hello-from-respond");
browser.close().await.unwrap();
}
#[tokio::test]
#[serial]
async fn expect_response_returns_matched() {
let mock = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/api/data"))
.respond_with(ResponseTemplate::new(200).set_body_string(r#"{"ok":true}"#))
.mount(&mock)
.await;
Mock::given(method("GET"))
.and(path("/"))
.respond_with(
ResponseTemplate::new(200).set_body_raw(
r#"<!doctype html><html><body>
<script>
setTimeout(() => { fetch('/api/data'); }, 200);
</script>
</body></html>"#
.as_bytes()
.to_vec(),
"text/html",
),
)
.mount(&mock)
.await;
let browser = Browser::builder().headless(true).launch().await.unwrap();
let tab = browser.main_tab();
let expectation = tab
.expect_response("/api/data")
.timeout(Duration::from_secs(5));
tab.goto(&mock.uri()).await.unwrap();
let matched = expectation.await.expect("expect_response should resolve");
assert!(
matched.url.contains("/api/data"),
"matched URL should contain /api/data; got {}",
matched.url
);
assert_eq!(matched.status, 200);
browser.close().await.unwrap();
}
#[tokio::test]
#[serial]
#[ignore = "headless=new suppresses Page.javascriptDialogOpened for script-driven alert()"]
async fn expect_dialog_resolves_on_alert() {
let mock = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/"))
.respond_with(
ResponseTemplate::new(200).set_body_raw(
r#"<!doctype html><html><body>
<script>setTimeout(() => alert('hi'), 100);</script>
</body></html>"#
.as_bytes()
.to_vec(),
"text/html",
),
)
.mount(&mock)
.await;
let browser = Browser::builder().headless(true).launch().await.unwrap();
let tab = browser.main_tab();
let expectation = tab.expect_dialog().timeout(Duration::from_secs(5));
tab.goto(&mock.uri()).await.unwrap();
let matched = expectation.await.expect("expect_dialog should resolve");
assert_eq!(matched.message, "hi");
matched.accept(None).await.unwrap();
browser.close().await.unwrap();
}
#[tokio::test]
#[serial]
async fn cloudflare_is_challenge_present_returns_false_on_normal_page() {
let mock = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/"))
.respond_with(
ResponseTemplate::new(200).set_body_raw(
r#"<!doctype html><html><body><h1>plain page</h1></body></html>"#
.as_bytes()
.to_vec(),
"text/html",
),
)
.mount(&mock)
.await;
let browser = Browser::builder().headless(true).launch().await.unwrap();
let tab = browser.main_tab();
tab.goto(&mock.uri()).await.unwrap();
tab.wait_for_load().await.unwrap();
let present = tab
.cloudflare()
.is_challenge_present()
.await
.expect("is_challenge_present should not error on a normal page");
assert!(
!present,
"vanilla page must not be reported as carrying a Cloudflare challenge"
);
browser.close().await.unwrap();
}