#![cfg(feature = "integration")]
mod common;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use tokio::sync::Mutex;
use viewpoint_core::Browser;
async fn setup() -> (Browser, viewpoint_core::BrowserContext) {
common::init_tracing();
let browser = common::launch_browser().await;
let context = browser
.new_context()
.await
.expect("Failed to create context");
(browser, context)
}
#[tokio::test]
async fn test_window_open_creates_tracked_page() {
let (browser, context) = setup().await;
let page_received = Arc::new(AtomicBool::new(false));
let page_received_clone = page_received.clone();
context
.on_page(move |_page| {
let received = page_received_clone.clone();
async move {
received.store(true, Ordering::SeqCst);
}
})
.await;
let page = context.new_page().await.expect("Failed to create page");
page.set_content(
r#"
<!DOCTYPE html>
<html>
<body>
<button id="open-popup" onclick="window.open('about:blank', '_blank', 'width=400,height=300')">Open Popup</button>
</body>
</html>
"#,
)
.set()
.await
.expect("Failed to set content");
page_received.store(false, Ordering::SeqCst);
page.locator("#open-popup")
.click()
.await
.expect("Failed to click button");
tokio::time::sleep(Duration::from_millis(500)).await;
assert!(
page_received.load(Ordering::SeqCst),
"on_page event should have been triggered for the popup"
);
let pages = context.pages().await.expect("Failed to get pages");
assert!(
pages.len() >= 2,
"Should have at least 2 pages (original + popup), got {}",
pages.len()
);
browser.close().await.expect("Failed to close browser");
}
#[tokio::test]
async fn test_target_blank_link_creates_tracked_page() {
let (browser, context) = setup().await;
let pages_received = Arc::new(Mutex::new(Vec::<String>::new()));
let pages_received_clone = pages_received.clone();
context
.on_page(move |page| {
let received = pages_received_clone.clone();
async move {
if let Ok(url) = page.url().await {
received.lock().await.push(url);
}
}
})
.await;
let page = context.new_page().await.expect("Failed to create page");
pages_received.lock().await.clear();
page.set_content(
r#"
<!DOCTYPE html>
<html>
<body>
<a id="external-link" href="about:blank" target="_blank">Open in new tab</a>
</body>
</html>
"#,
)
.set()
.await
.expect("Failed to set content");
page.locator("#external-link")
.click()
.await
.expect("Failed to click link");
tokio::time::sleep(Duration::from_millis(500)).await;
let received = pages_received.lock().await;
assert!(
!received.is_empty(),
"on_page event should have been triggered for the new tab"
);
browser.close().await.expect("Failed to close browser");
}
#[tokio::test]
async fn test_pages_includes_externally_opened() {
let (browser, context) = setup().await;
let page = context.new_page().await.expect("Failed to create page");
let initial_pages = context.pages().await.expect("Failed to get pages");
let initial_count = initial_pages.len();
page.set_content(
r#"
<!DOCTYPE html>
<html>
<body>
<button id="open-popup" onclick="window.open('about:blank')">Open Popup</button>
</body>
</html>
"#,
)
.set()
.await
.expect("Failed to set content");
page.locator("#open-popup")
.click()
.await
.expect("Failed to click button");
tokio::time::sleep(Duration::from_millis(500)).await;
let final_pages = context.pages().await.expect("Failed to get pages");
assert!(
final_pages.len() > initial_count,
"Pages count should increase after popup, was {} now {}",
initial_count,
final_pages.len()
);
browser.close().await.expect("Failed to close browser");
}
#[tokio::test]
async fn test_no_duplicate_events_for_new_page() {
let (browser, context) = setup().await;
let call_count = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let call_count_clone = call_count.clone();
context
.on_page(move |_page| {
let count = call_count_clone.clone();
async move {
count.fetch_add(1, Ordering::SeqCst);
}
})
.await;
let _page = context.new_page().await.expect("Failed to create page");
tokio::time::sleep(Duration::from_millis(200)).await;
let count = call_count.load(Ordering::SeqCst);
assert_eq!(
count, 1,
"on_page should be called exactly once for new_page(), got {}",
count
);
browser.close().await.expect("Failed to close browser");
}
#[tokio::test]
async fn test_wait_for_popup_compatibility() {
let (browser, context) = setup().await;
let page = context.new_page().await.expect("Failed to create page");
page.set_content(
r#"
<!DOCTYPE html>
<html>
<body>
<button id="open-popup" onclick="window.open('about:blank', 'popup', 'width=400,height=300')">Open Popup</button>
</body>
</html>
"#,
)
.set()
.await
.expect("Failed to set content");
let popup = page
.wait_for_popup(|| async {
page.locator("#open-popup")
.click()
.await
.expect("Failed to click button");
Ok(())
})
.wait()
.await
.expect("wait_for_popup should succeed");
let popup_url = popup.url().await.expect("Failed to get popup URL");
assert!(
popup_url.contains("blank"),
"Popup should be at about:blank, got {}",
popup_url
);
browser.close().await.expect("Failed to close browser");
}
#[tokio::test]
async fn test_page_activated_handler_registration() {
let (browser, context) = setup().await;
let activation_count = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let activation_count_clone = activation_count.clone();
let handler_id = context
.on_page_activated(move |_page| {
let count = activation_count_clone.clone();
async move {
count.fetch_add(1, Ordering::SeqCst);
}
})
.await;
let _page = context.new_page().await.expect("Failed to create page");
tokio::time::sleep(Duration::from_millis(200)).await;
let removed = context.off_page_activated(handler_id).await;
assert!(
removed,
"Handler should have been successfully registered and removed"
);
let removed_again = context.off_page_activated(handler_id).await;
assert!(!removed_again, "Handler should not be found after removal");
browser.close().await.expect("Failed to close browser");
}
#[tokio::test]
async fn test_multiple_page_activated_handlers() {
let (browser, context) = setup().await;
let handler_id1 = context.on_page_activated(|_page| async {}).await;
let handler_id2 = context.on_page_activated(|_page| async {}).await;
let handler_id3 = context.on_page_activated(|_page| async {}).await;
assert_ne!(handler_id1, handler_id2);
assert_ne!(handler_id2, handler_id3);
assert_ne!(handler_id1, handler_id3);
assert!(context.off_page_activated(handler_id1).await);
assert!(context.off_page_activated(handler_id2).await);
assert!(context.off_page_activated(handler_id3).await);
assert!(!context.off_page_activated(handler_id1).await);
assert!(!context.off_page_activated(handler_id2).await);
assert!(!context.off_page_activated(handler_id3).await);
browser.close().await.expect("Failed to close browser");
}
#[tokio::test]
async fn test_page_activated_only_for_own_context() {
let (browser, _) = setup().await;
let context_a = browser
.new_context()
.await
.expect("Failed to create context A");
let context_b = browser
.new_context()
.await
.expect("Failed to create context B");
let context_a_activations = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let context_a_activations_clone = context_a_activations.clone();
context_a
.on_page_activated(move |_page| {
let count = context_a_activations_clone.clone();
async move {
count.fetch_add(1, Ordering::SeqCst);
}
})
.await;
let _page_a = context_a
.new_page()
.await
.expect("Failed to create page in context A");
let page_b = context_b
.new_page()
.await
.expect("Failed to create page in context B");
tokio::time::sleep(Duration::from_millis(200)).await;
context_a_activations.store(0, Ordering::SeqCst);
page_b
.bring_to_front()
.await
.expect("Failed to bring page B to front");
tokio::time::sleep(Duration::from_millis(500)).await;
let count = context_a_activations.load(Ordering::SeqCst);
assert_eq!(
count, 0,
"Context A's handler should not be triggered by context B's page activation"
);
browser.close().await.expect("Failed to close browser");
}
#[tokio::test]
async fn test_closed_page_removed_from_tracking() {
let (browser, context) = setup().await;
let page = context.new_page().await.expect("Failed to create page");
page.set_content(
r#"
<!DOCTYPE html>
<html>
<body>
<button id="open-popup" onclick="window.open('about:blank')">Open Popup</button>
</body>
</html>
"#,
)
.set()
.await
.expect("Failed to set content");
let mut popup = page
.wait_for_popup(|| async {
page.locator("#open-popup")
.click()
.await
.expect("Failed to click button");
Ok(())
})
.wait()
.await
.expect("wait_for_popup should succeed");
let pages_with_popup = context.pages().await.expect("Failed to get pages");
let count_with_popup = pages_with_popup.len();
popup.close().await.expect("Failed to close popup");
tokio::time::sleep(Duration::from_millis(200)).await;
let pages_after_close = context.pages().await.expect("Failed to get pages");
let count_after_close = pages_after_close.len();
assert!(
count_after_close < count_with_popup,
"Pages count should decrease after popup close, was {} now {}",
count_with_popup,
count_after_close
);
browser.close().await.expect("Failed to close browser");
}