use std::time::Duration;
use rust_drission::{BrowserConfig, ChromiumPage};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let user_data_dir = std::env::temp_dir().join("drission_demo_page_wait");
let config = BrowserConfig::new()
.user_data_dir(user_data_dir.to_string_lossy().to_string())
.headless(true);
let mut page = ChromiumPage::new(config)?;
demo_wait_element_appears(&page)?;
demo_wait_visible_for_modal(&page)?;
demo_wait_hidden_for_spinner(&page)?;
demo_wait_with_different_locators(&page)?;
demo_timeout_handling(&page)?;
println!("\nAll demos completed.");
page.close_browser();
Ok(())
}
fn demo_wait_element_appears(page: &ChromiumPage) -> Result<(), Box<dyn std::error::Error>> {
println!("=== 1. 等待动态内容加载 ===");
let html = build_html("<div id='app'></div>");
page.get(&to_data_url(&html))?;
page.run_js(
"setTimeout(() => { \
const app = document.getElementById('app'); \
app.innerHTML = '<ul class=\"item-list\"><li>Item A</li><li>Item B</li></ul>'; \
}, 1000)",
)?;
match page.wait(".item-list", Duration::from_secs(5)) {
Ok(el) => {
let items = el.elements("li")?;
println!(" 列表已加载,包含 {} 项", items.len());
for (i, item) in items.iter().enumerate() {
println!(" [{}] {}", i, item.text().unwrap_or_default());
}
}
Err(e) => println!(" 加载超时: {}", e),
}
Ok(())
}
fn demo_wait_visible_for_modal(page: &ChromiumPage) -> Result<(), Box<dyn std::error::Error>> {
println!("\n=== 2. 等待弹窗出现(wait_visible) ===");
let html = build_html(
"<div id='modal' style='display:none'>\
<p class='modal-text'>确认删除?</p>\
<button class='btn-confirm'>确定</button>\
</div>\
<button id='trigger-btn' onclick=\"document.getElementById('modal').style.display='block'\">打开弹窗</button>",
);
page.get(&to_data_url(&html))?;
page.run_js(
"setTimeout(() => document.getElementById('trigger-btn').click(), 300)",
)?;
match page.tab().wait_visible("#modal", Duration::from_secs(5)) {
Ok(modal) => {
let text = modal.element(".modal-text")?.map(|e| e.text().unwrap_or_default());
println!(" 弹窗已出现: {:?}", text);
}
Err(e) => println!(" 弹窗未出现: {}", e),
}
Ok(())
}
fn demo_wait_hidden_for_spinner(page: &ChromiumPage) -> Result<(), Box<dyn std::error::Error>> {
println!("\n=== 3. 等待加载动画消失(wait_hidden) ===");
let html = build_html(
"<div id='spinner' class='loading'>Loading...</div>\
<div id='content' style='display:none'>实际内容</div>",
);
page.get(&to_data_url(&html))?;
page.run_js(
"setTimeout(() => { \
document.getElementById('spinner').style.display = 'none'; \
document.getElementById('content').style.display = 'block'; \
}, 800)",
)?;
match page.tab().wait_hidden("#spinner", Duration::from_secs(5)) {
Ok(()) => {
println!(" spinner 已消失");
let content_el = page.ele("#content")?;
println!(
" 内容已显示: {}",
content_el.map(|e| e.text().unwrap_or_default()).unwrap_or_default()
);
}
Err(e) => println!(" spinner 超时未消失: {}", e),
}
Ok(())
}
fn demo_wait_with_different_locators(
page: &ChromiumPage,
) -> Result<(), Box<dyn std::error::Error>> {
println!("\n=== 4. 各种定位器类型 ===");
let html = build_html("<div id='container'></div>");
page.get(&to_data_url(&html))?;
page.run_js(
"setTimeout(() => { \
const c = document.getElementById('container'); \
c.innerHTML = [ \
'<button id=\"my-btn\" class=\"action-btn\">点击我</button>', \
'<span data-role=\"badge\">99+</span>', \
'<p>这是一段提示文本</p>' \
].join(''); \
}, 500)",
)?;
if let Ok(el) = page.wait(".action-btn", Duration::from_secs(5)) {
println!(" CSS(.action-btn): {}", el.text().unwrap_or_default());
}
if let Ok(el) = page.wait("id:my-btn", Duration::from_secs(5)) {
println!(" ID(my-btn): tag={}", el.tag().unwrap_or_default());
}
if let Ok(el) = page.wait("text:提示", Duration::from_secs(5)) {
println!(" Text(提示): {}", el.text().unwrap_or_default());
}
if let Ok(el) = page.wait("xpath://span[@data-role='badge']", Duration::from_secs(5)) {
println!(" XPath(badge): {}", el.text().unwrap_or_default());
}
if let Ok(el) = page.wait("tag:button", Duration::from_secs(5)) {
println!(" Tag(button): {}", el.text().unwrap_or_default());
}
Ok(())
}
fn demo_timeout_handling(page: &ChromiumPage) -> Result<(), Box<dyn std::error::Error>> {
println!("\n=== 5. 超时处理策略 ===");
let html = build_html("<div id='app'></div>");
page.get(&to_data_url(&html))?;
println!(" [策略 A] 等待关键元素...");
page.run_js(
"setTimeout(() => { \
const el = document.createElement('div'); \
el.id = 'critical'; \
el.textContent = 'ready'; \
document.getElementById('app').appendChild(el); \
}, 300)",
)?;
match page.wait("#critical", Duration::from_secs(10)) {
Ok(_) => println!(" 关键元素就绪,继续流程"),
Err(e) => {
println!(" 关键元素缺失,中止: {}", e);
return Err(e.into());
}
}
println!(" [策略 B] 等待可选元素...");
match page.wait("#optional-banner", Duration::from_secs(1)) {
Ok(el) => println!(" banner 已显示: {}", el.text().unwrap_or_default()),
Err(_) => println!(" banner 未出现,跳过(降级处理)"),
}
println!(" [策略 C] wait_element 快捷方法...");
page.run_js(
"setTimeout(() => { \
const el = document.createElement('span'); \
el.id = 'quick-el'; \
el.textContent = 'quick'; \
document.getElementById('app').appendChild(el); \
}, 200)",
)?;
match page.tab().wait_element("#quick-el") {
Ok(el) => println!(" 元素出现: {}", el.text().unwrap_or_default()),
Err(e) => println!(" 超时: {}", e),
}
Ok(())
}
fn build_html(body: &str) -> String {
format!(
"<!DOCTYPE html><html><head><meta charset='utf-8'></head><body>{}</body></html>",
body
)
}
fn to_data_url(html: &str) -> String {
format!("data:text/html,{}", urlencoding(html))
}
fn urlencoding(s: &str) -> String {
s.replace('%', "%25")
.replace('\n', "%0A")
.replace('\r', "%0D")
.replace(' ', "%20")
.replace('"', "%22")
.replace('#', "%23")
.replace('<', "%3C")
.replace('>', "%3E")
.replace('[', "%5B")
.replace(']', "%5D")
.replace('{', "%7B")
.replace('}', "%7D")
.replace('|', "%7C")
.replace('\\', "%5C")
.replace('^', "%5E")
.replace('`', "%60")
.replace(';', "%3B")
.replace('/', "%2F")
.replace('?', "%3F")
.replace(':', "%3A")
.replace('@', "%40")
.replace('=', "%3D")
.replace('&', "%26")
}