#![cfg(feature = "integration")]
use std::sync::Arc;
use std::sync::Once;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::time::Duration;
use viewpoint_core::{Browser, DocumentLoadState};
use viewpoint_js::js;
static TRACING_INIT: Once = Once::new();
fn init_tracing() {
TRACING_INIT.call_once(|| {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::from_default_env()
.add_directive(tracing::Level::INFO.into()),
)
.with_test_writer()
.try_init()
.ok();
});
}
#[tokio::test]
async fn test_route_abort() {
init_tracing();
let browser = Browser::launch()
.headless(true)
.launch()
.await
.expect("Failed to launch browser");
let context = browser
.new_context()
.await
.expect("Failed to create context");
let page = context.new_page().await.expect("Failed to create page");
context
.route("**/*.png", |route| {
Box::pin(async move {
route.abort().await?;
Ok(())
})
})
.await
.expect("Failed to set route");
page.set_content(
r#"
<html><body>
<img id="img" src="https://example.com/image.png" onerror="window.imageError = true">
</body></html>
"#,
)
.set()
.await
.expect("Failed to set content");
tokio::time::sleep(Duration::from_millis(500)).await;
let error: bool = page
.evaluate(js! { window.imageError || false })
.await
.expect("Failed to check error");
assert!(error, "Image request should have been aborted");
browser.close().await.expect("Failed to close browser");
}
#[tokio::test]
async fn test_route_fulfill_custom() {
init_tracing();
let browser = Browser::launch()
.headless(true)
.launch()
.await
.expect("Failed to launch browser");
let context = browser
.new_context()
.await
.expect("Failed to create context");
let page = context.new_page().await.expect("Failed to create page");
context
.route("**/api/data", |route| {
Box::pin(async move {
route
.fulfill()
.status(200)
.content_type("application/json")
.body(r#"{"mocked": true, "value": 42}"#)
.send()
.await?;
Ok(())
})
})
.await
.expect("Failed to set route");
page.goto("https://example.com")
.wait_until(DocumentLoadState::Load)
.goto()
.await
.expect("Failed to navigate");
let result: serde_json::Value = page
.evaluate(js! {
fetch("/api/data").then(r => r.json())
})
.await
.expect("Failed to fetch");
assert_eq!(result["mocked"], true);
assert_eq!(result["value"], 42);
browser.close().await.expect("Failed to close browser");
}
#[tokio::test]
async fn test_route_continue_with_headers() {
init_tracing();
let browser = Browser::launch()
.headless(true)
.launch()
.await
.expect("Failed to launch browser");
let context = browser
.new_context()
.await
.expect("Failed to create context");
let page = context.new_page().await.expect("Failed to create page");
context
.route("**/*", |route| {
Box::pin(async move {
route
.continue_()
.header("X-Custom-Header", "test-value")
.await?;
Ok(())
})
})
.await
.expect("Failed to set route");
let response = page
.goto("https://httpbin.org/headers")
.wait_until(DocumentLoadState::Load)
.goto()
.await
.expect("Failed to navigate");
assert_eq!(response.status(), Some(200));
let content = page.content().await.expect("Failed to get content");
assert!(
content.contains("X-Custom-Header") || content.contains("x-custom-header"),
"Custom header should be echoed back"
);
browser.close().await.expect("Failed to close browser");
}
#[tokio::test]
async fn test_route_fetch_basic() {
init_tracing();
let browser = Browser::launch()
.headless(true)
.launch()
.await
.expect("Failed to launch browser");
let context = browser
.new_context()
.await
.expect("Failed to create context");
let page = context.new_page().await.expect("Failed to create page");
let route_called = Arc::new(AtomicBool::new(false));
let route_called_clone = route_called.clone();
page.route("**/get", move |route| {
let called = route_called_clone.clone();
async move {
called.store(true, Ordering::SeqCst);
let response = route.fetch().await?;
assert!(
response.status >= 200 && response.status < 300,
"Should get OK status"
);
response.fulfill().await
}
})
.await
.expect("Failed to set route");
page.goto("https://httpbin.org/get")
.wait_until(DocumentLoadState::Load)
.goto()
.await
.expect("Failed to navigate");
assert!(
route_called.load(Ordering::SeqCst),
"Route should have been called"
);
browser.close().await.expect("Failed to close browser");
}
#[tokio::test]
async fn test_route_fetch_with_headers() {
init_tracing();
let browser = Browser::launch()
.headless(true)
.launch()
.await
.expect("Failed to launch browser");
let context = browser
.new_context()
.await
.expect("Failed to create context");
let page = context.new_page().await.expect("Failed to create page");
let route_called = Arc::new(AtomicBool::new(false));
let route_called_clone = route_called.clone();
page.route("**/headers", move |route| {
let called = route_called_clone.clone();
async move {
called.store(true, Ordering::SeqCst);
let response = route
.fetch()
.header("X-Custom-Header", "test-value")
.await?;
response.fulfill().await
}
})
.await
.expect("Failed to set route");
page.goto("https://httpbin.org/headers")
.wait_until(DocumentLoadState::Load)
.goto()
.await
.expect("Failed to navigate");
assert!(
route_called.load(Ordering::SeqCst),
"Route should have been called"
);
browser.close().await.expect("Failed to close browser");
}
#[tokio::test]
async fn test_context_route_propagation() {
init_tracing();
let browser = Browser::launch()
.headless(true)
.launch()
.await
.expect("Failed to launch browser");
let context = browser
.new_context()
.await
.expect("Failed to create context");
let call_count = Arc::new(AtomicU32::new(0));
let call_count_clone = call_count.clone();
context
.route("**/*.png", move |route| {
let count = call_count_clone.clone();
async move {
count.fetch_add(1, Ordering::SeqCst);
route.abort().await
}
})
.await
.expect("Failed to set context route");
let page1 = context.new_page().await.expect("Failed to create page 1");
page1
.set_content(
r#"
<html><body>
<img src="https://example.com/test.png" onerror="window.imgError = true">
</body></html>
"#,
)
.set()
.await
.expect("Failed to set content");
tokio::time::sleep(Duration::from_millis(300)).await;
assert!(
call_count.load(Ordering::SeqCst) >= 1,
"Context route should be applied to first page"
);
let page2 = context.new_page().await.expect("Failed to create page 2");
let count_before = call_count.load(Ordering::SeqCst);
page2
.set_content(
r#"
<html><body>
<img src="https://example.com/another.png" onerror="window.imgError = true">
</body></html>
"#,
)
.set()
.await
.expect("Failed to set content on page 2");
tokio::time::sleep(Duration::from_millis(300)).await;
assert!(
call_count.load(Ordering::SeqCst) > count_before,
"Context route should be applied to second page"
);
browser.close().await.expect("Failed to close browser");
}
#[tokio::test]
async fn test_context_route_multiple_pages() {
init_tracing();
let browser = Browser::launch()
.headless(true)
.launch()
.await
.expect("Failed to launch browser");
let context = browser
.new_context()
.await
.expect("Failed to create context");
context
.route("**/api/**", |route| {
Box::pin(async move {
route
.fulfill()
.status(200)
.body("context intercepted")
.send()
.await?;
Ok(())
})
})
.await
.expect("Failed to set up context route");
let page1 = context.new_page().await.expect("Failed to create page1");
let page2 = context.new_page().await.expect("Failed to create page2");
page1
.goto("https://example.com")
.goto()
.await
.expect("Failed to navigate page1");
page2
.goto("https://example.com")
.goto()
.await
.expect("Failed to navigate page2");
let result1: String = page1
.evaluate(js! {
fetch("/api/test").then(r => r.text())
})
.await
.expect("Failed to fetch on page1");
assert_eq!(
result1, "context intercepted",
"Context route should work on page1"
);
let result2: String = page2
.evaluate(js! {
fetch("/api/test").then(r => r.text())
})
.await
.expect("Failed to fetch on page2");
assert_eq!(
result2, "context intercepted",
"Context route should work on page2"
);
browser.close().await.expect("Failed to close browser");
}
#[tokio::test]
async fn test_wait_for_request() {
init_tracing();
let browser = Browser::launch()
.headless(true)
.launch()
.await
.expect("Failed to launch browser");
let context = browser
.new_context()
.await
.expect("Failed to create context");
let page = context.new_page().await.expect("Failed to create page");
page.goto("https://example.com")
.goto()
.await
.expect("Failed to navigate");
let _waiter = page.wait_for_request("**/api/**".to_string());
browser.close().await.expect("Failed to close browser");
}
#[tokio::test]
async fn test_wait_for_response() {
init_tracing();
let browser = Browser::launch()
.headless(true)
.launch()
.await
.expect("Failed to launch browser");
let context = browser
.new_context()
.await
.expect("Failed to create context");
let page = context.new_page().await.expect("Failed to create page");
page.goto("https://example.com")
.goto()
.await
.expect("Failed to navigate");
let _waiter = page.wait_for_response("**/*".to_string());
browser.close().await.expect("Failed to close browser");
}