playwright update

This commit is contained in:
Simon
2026-09-07 15:59:03 +00:00
parent 86c8faca44
commit 16219e1ddf
2 changed files with 36 additions and 27 deletions

View File

@@ -45,7 +45,7 @@ aes-gcm = "0.10"
cbc = { version = "0.1", features = ["alloc"] }
hex = "0.4"
chromiumoxide = { version = "0.7", features = ["tokio-runtime"] }
playwright = "0.0.20"
playwright-rs = "0.7"
[lints.rust]
warnings = "warn"

View File

@@ -1,5 +1,4 @@
use playwright::Playwright;
use playwright::api::Page;
use playwright_rs::{LaunchOptions, Page, Playwright};
use std::path::Path;
use std::time::Duration;
use tokio::time::{sleep, timeout};
@@ -52,24 +51,29 @@ pub async fn wait_for_attribute(
attribute: &str,
wait_timeout: Duration,
) -> Option<String> {
// _pw must be kept alive — dropping it kills the driver process.
let (_pw, page) = open_page(url).await?;
// _browser is kept alive (Arc inside Playwright) — the page borrows from it.
// The Page holds its own server reference, so dropping our local Playwright
// handle does not shut the driver down.
let (page, _browser, _playwright) = open_page(url).await?;
let xpath_js = serde_json::to_string(xpath).unwrap_or_default();
let attr_js = serde_json::to_string(attribute).unwrap_or_default();
// playwright-rs's evaluate_value takes a single JS expression, so wrap the
// multi-statement lookup in an IIFE. The return value is auto-converted to
// a String by the Playwright protocol layer.
let js = format!(
r#"() => {{
r#"(() => {{
var r = document.evaluate({xpath_js}, document, null,
XPathResult.FIRST_ORDERED_NODE_TYPE, null);
var el = r.singleNodeValue;
if (!el) return '';
return el.getAttribute({attr_js}) || el[{attr_js}] || '';
}}"#
}})()"#
);
let result = timeout(wait_timeout, async {
loop {
match page.evaluate::<(), String>(&js, ()).await {
match page.evaluate_value(&js).await {
Ok(s) if !s.is_empty() => return Some(s),
Ok(_) => {}
Err(e) => eprintln!("[playwright] evaluate error: {e}"),
@@ -86,43 +90,48 @@ pub async fn wait_for_attribute(
// ── internals ─────────────────────────────────────────────────────────────────
async fn open_page(url: &str) -> Option<(Playwright, Page)> {
let pw = Playwright::initialize()
/// Launches Playwright (Node server + bundled Chromium), opens a page on
/// `url`, and returns a handle. Caller is responsible for keeping the
/// returned `Playwright` and `Browser` handles alive while the page is in
/// use — dropping the browser closes the page and driver.
async fn open_page(
url: &str,
) -> Option<(Page, playwright_rs::Browser, Playwright)> {
let playwright = Playwright::launch()
.await
.map_err(|e| eprintln!("[playwright] init error: {e}"))
.ok()?;
let args: Vec<String> = CHROME_ARGS.iter().map(|s| s.to_string()).collect();
let browser = pw
.chromium()
.launcher()
.executable(Path::new("/usr/bin/google-chrome"))
let options = LaunchOptions::new()
.headless(true)
.args(&args)
.launch()
.chromium_sandbox(false)
.args(args)
.executable_path(
Path::new("/usr/bin/google-chrome")
.to_str()
.unwrap_or("/usr/bin/google-chrome")
.to_string(),
);
let browser = playwright
.chromium()
.launch_with_options(options)
.await
.map_err(|e| eprintln!("[playwright] launch error: {e}"))
.ok()?;
let context = browser
.context_builder()
.build()
.await
.map_err(|e| eprintln!("[playwright] context error: {e}"))
.ok()?;
let page = context
let page = browser
.new_page()
.await
.map_err(|e| eprintln!("[playwright] new_page error: {e}"))
.ok()?;
page.goto_builder(url)
.goto()
page.goto(url, None)
.await
.map_err(|e| eprintln!("[playwright] goto error: {e}"))
.ok()?;
Some((pw, page))
Some((page, browser, playwright))
}