137 lines
No EOL
4.6 KiB
Rust
137 lines
No EOL
4.6 KiB
Rust
use std::{path::Path, time::Duration};
|
||
use futures::StreamExt;
|
||
use chromiumoxide::browser::{Browser, BrowserConfig};
|
||
use chromiumoxide::cdp::browser_protocol::page::NavigateParams;
|
||
use chromiumoxide::cdp::browser_protocol::input::InsertTextParams;
|
||
|
||
use crate::state::{SharedState, HeadlessPhase};
|
||
use crate::config::read_config;
|
||
|
||
pub async fn start(state: SharedState) -> Result<(), Box<dyn std::error::Error>> {
|
||
{
|
||
let mut s = state.lock().unwrap();
|
||
s.headless = HeadlessPhase::Starting;
|
||
}
|
||
|
||
// Hier später: chromiumoxide Browser-Launch + Healthcheck
|
||
// Für jetzt: kurze simulierte Init-Zeit
|
||
tokio::time::sleep(std::time::Duration::from_millis(400)).await;
|
||
|
||
{
|
||
let mut s = state.lock().unwrap();
|
||
s.headless = HeadlessPhase::Ready;
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
pub struct HeadlessManager {
|
||
browser: chromiumoxide::Browser,
|
||
_handler_task: tokio::task::JoinHandle<()>,
|
||
}
|
||
|
||
pub struct PageHandle {
|
||
page: chromiumoxide::Page,
|
||
}
|
||
|
||
impl HeadlessManager {
|
||
/// Startet Chromium (für Tests ohne Headless: wir filtern --headless raus)
|
||
pub async fn new(chrome_exe: &Path, extra_args: &[String]) -> Result<Self, Box<dyn std::error::Error>> {
|
||
let mut cfg = BrowserConfig::builder();
|
||
cfg = cfg.chrome_executable(chrome_exe); // v0.7 API
|
||
|
||
for a in extra_args {
|
||
// fürs Testen: Headless-Flag unterdrücken, damit ein Fenster sichtbar ist
|
||
if a.starts_with("--headless") { continue; }
|
||
cfg = cfg.arg(a);
|
||
}
|
||
|
||
let (browser, mut handler) = Browser::launch(cfg.build()?).await?;
|
||
|
||
// Event-Handler-Loop muss laufen
|
||
let handler_task = tokio::spawn(async move {
|
||
while let Some(_evt) = handler.next().await {
|
||
// hier ggf. Logging
|
||
}
|
||
});
|
||
|
||
Ok(Self { browser, _handler_task: handler_task })
|
||
}
|
||
|
||
pub async fn new_page(&self) -> Result<PageHandle, Box<dyn std::error::Error>> {
|
||
let page = self.browser.new_page("about:blank").await?;
|
||
Ok(PageHandle { page })
|
||
}
|
||
}
|
||
|
||
impl PageHandle {
|
||
pub async fn goto(&self, url: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||
self.page.goto(NavigateParams::builder().url(url).build()?).await?;
|
||
// manche UIs brauchen einen Moment
|
||
self.page.wait_for_navigation().await.ok();
|
||
Ok(())
|
||
}
|
||
|
||
/// Wartet auf einen CSS-Selector, true wenn gefunden, false bei Timeout
|
||
pub async fn wait_for_selector(&self, selector: &str, timeout_ms: u64) -> Result<bool, Box<dyn std::error::Error>> {
|
||
match tokio::time::timeout(
|
||
Duration::from_millis(timeout_ms),
|
||
self.page.find_element(selector),
|
||
).await {
|
||
Ok(Ok(_elem)) => Ok(true),
|
||
_ => Ok(false),
|
||
}
|
||
}
|
||
|
||
/// Liest innerText eines Elements, None falls nicht gefunden/Fehler
|
||
pub async fn get_text(&self, selector: &str) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||
let elem = match self.page.find_element(selector).await {
|
||
Ok(e) => e,
|
||
Err(_) => return Ok(None),
|
||
};
|
||
match elem.inner_text().await {
|
||
Ok(opt) => Ok(opt), // opt ist bereits Option<String>
|
||
Err(_) => Ok(None),
|
||
}
|
||
}
|
||
|
||
/// Tippt Text in ein (vorher angeklicktes) Eingabefeld
|
||
pub async fn type_text(&self, selector: &str, text: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||
let elem = self.page.find_element(selector).await?;
|
||
elem.click().await?;
|
||
// Fokus ist im Feld – jetzt Text am Stück einfügen
|
||
self.page.execute(InsertTextParams::new(text.to_string())).await?;
|
||
Ok(())
|
||
}
|
||
|
||
pub async fn click(&self, selector: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||
let elem = self.page.find_element(selector).await?;
|
||
elem.click().await?;
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
pub async fn run_test(ip: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||
println!("[Headless] Starte Test für {ip}");
|
||
|
||
// config lesen, um Pfad zu Chrome zu bekommen
|
||
let exe_dir = std::env::current_exe()?.parent().unwrap().to_path_buf();
|
||
let cfg = read_config(&exe_dir)?;
|
||
let chrome_exe = Path::new(&cfg.chrome.path);
|
||
|
||
let manager = HeadlessManager::new(chrome_exe, &cfg.chrome.args).await?;
|
||
let page = manager.new_page().await?;
|
||
|
||
// Zielseite öffnen
|
||
let url = format!("https://{ip}/");
|
||
page.goto(&url).await?;
|
||
|
||
// kurz warten & Text auslesen
|
||
let _ = page.wait_for_selector("h1", 5000).await?;
|
||
if let Some(h1) = page.get_text("h1").await? {
|
||
println!("[Headless] H1: {}", h1);
|
||
} else {
|
||
println!("[Headless] Keine H1 gefunden");
|
||
}
|
||
|
||
Ok(())
|
||
} |