use std::ffi::OsStr; use std::time::{Duration, Instant}; fn which(cmd: &str) -> Option { which::which(cmd).ok() } fn chrome_path() -> Option { let mac = std::path::PathBuf::from("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"); if mac.exists() { return Some(mac); } which("google-chrome") .or_else(|| which("google-chrome-stable")) .or_else(|| which("chromium")) } fn click_css(tab: &headless_chrome::Tab, css: &str) -> anyhow::Result<()> { tab.wait_for_element(css)?.click()?; Ok(()) } fn wait_for_text( tab: &headless_chrome::Tab, needle: &str, timeout: Duration, ) -> anyhow::Result<()> { let deadline = Instant::now() + timeout; while Instant::now() < deadline { let js = format!( r#"(function() {{ return document.body && (document.body.innerText || '').includes({n}); }})();"#, n = serde_json::to_string(needle).unwrap() ); let v = tab.evaluate(&js, false)?; if v.value.and_then(|v| v.as_bool()).unwrap_or(false) { return Ok(()); } std::thread::sleep(Duration::from_millis(200)); } anyhow::bail!("timed out waiting for text: {needle}"); } fn wait_for_blob_video(tab: &headless_chrome::Tab, timeout: Duration) -> anyhow::Result<()> { let deadline = Instant::now() + timeout; while Instant::now() < deadline { let js = r#"(function() { let v = document.querySelector('video'); if (!v) return false; if (typeof v.src !== 'string') return false; return v.src.startsWith('blob:'); })();"#; let v = tab.evaluate(js, false)?; if v.value.and_then(|v| v.as_bool()).unwrap_or(false) { return Ok(()); } std::thread::sleep(Duration::from_millis(200)); } anyhow::bail!("timed out waiting for video blob src"); } fn wait_for_video_element(tab: &headless_chrome::Tab, timeout: Duration) -> anyhow::Result<()> { let deadline = Instant::now() + timeout; while Instant::now() < deadline { let js = r#"(function() { return !!document.querySelector('video'); })();"#; let v = tab.evaluate(js, false)?; if v.value.and_then(|v| v.as_bool()).unwrap_or(false) { return Ok(()); } std::thread::sleep(Duration::from_millis(200)); } anyhow::bail!("timed out waiting for