Headless Core & JS Blur & MultiThreading Addet
This commit is contained in:
parent
a41f765211
commit
5b99621044
4 changed files with 235 additions and 68 deletions
|
|
@ -1,12 +1,12 @@
|
|||
use std::{path::Path, time::Duration};
|
||||
use std::{path::Path, time::Duration, fs};
|
||||
use futures::StreamExt;
|
||||
use chromiumoxide::browser::{Browser, BrowserConfig};
|
||||
use chromiumoxide::cdp::browser_protocol::page::NavigateParams;
|
||||
use chromiumoxide::cdp::browser_protocol::input::InsertTextParams;
|
||||
use chromiumoxide::cdp::js_protocol::runtime::EvaluateParams;
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
use crate::state::{SharedState, HeadlessPhase};
|
||||
use crate::config::read_config;
|
||||
|
||||
pub async fn start(state: SharedState) -> Result<(), Box<dyn std::error::Error>> {
|
||||
{
|
||||
|
|
@ -109,6 +109,93 @@ impl PageHandle {
|
|||
elem.click().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn click_and_wait_navigation(&self, selector: &str, timeout_ms: u64) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Future zum Warten auf die nächste Navigation vorbereiten
|
||||
let wait_nav = self.page.wait_for_navigation();
|
||||
|
||||
// Klick ausführen
|
||||
let elem = self.page.find_element(selector).await?;
|
||||
elem.click().await?;
|
||||
|
||||
// Auf Navigation warten (mit Timeout absichern)
|
||||
match tokio::time::timeout(std::time::Duration::from_millis(timeout_ms), wait_nav).await {
|
||||
Ok(Ok(_)) => Ok(()), // Navigation erfolgreich abgeschlossen
|
||||
Ok(Err(e)) => Err(Box::new(e)), // Navigation-Error
|
||||
Err(_) => Err("navigation timeout".into()), // Timeout
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn wait_for_dom_quiet(
|
||||
&self,
|
||||
timeout_ms: u64,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
use tokio::time::{sleep, Duration, Instant};
|
||||
|
||||
// Heuristik: HTML-Größe stabilisiert sich für zwei aufeinanderfolgende Messungen
|
||||
let deadline = Instant::now() + Duration::from_millis(timeout_ms);
|
||||
let mut last_len = 0usize;
|
||||
let mut stable = 0u8;
|
||||
|
||||
while Instant::now() < deadline {
|
||||
let html = self.page.content().await.unwrap_or_default();
|
||||
let len = html.len();
|
||||
if len == last_len {
|
||||
stable += 1;
|
||||
if stable >= 2 {
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
stable = 0;
|
||||
last_len = len;
|
||||
}
|
||||
sleep(Duration::from_millis(250)).await;
|
||||
}
|
||||
|
||||
Err("wait_for_dom_quiet: timeout".into())
|
||||
}
|
||||
|
||||
/// Setzt den Wert eines Inputs per JS (löscht zuerst), feuert input/change/blur.
|
||||
pub async fn set_input_value_by_id(&self, id: &str, value: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let js = format!(r#"
|
||||
(function() {{
|
||||
var el = document.getElementById("{id}");
|
||||
if (!el) return false;
|
||||
el.scrollIntoView({{block:'center', inline:'center'}});
|
||||
el.focus();
|
||||
el.value = "";
|
||||
el.dispatchEvent(new Event('input', {{ bubbles: true }}));
|
||||
el.value = "{value}";
|
||||
el.dispatchEvent(new Event('input', {{ bubbles: true }}));
|
||||
el.dispatchEvent(new Event('change', {{ bubbles: true }}));
|
||||
// Blur auslösen, viele Form-UIs übernehmen dann erst
|
||||
el.blur && el.blur();
|
||||
return true;
|
||||
}})()
|
||||
"#);
|
||||
// v0.7: evaluate akzeptiert &str direkt
|
||||
let _ = self.page.evaluate(js.as_str()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
//Debug
|
||||
pub async fn dump_all_ids(&self, filename: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Gesamtes DOM als HTML ziehen
|
||||
let html = self.page.content().await?;
|
||||
|
||||
// IDs per Regex herausziehen (einfach & robust)
|
||||
let re = regex::Regex::new(r#"id\s*=\s*"([^"]+)""#)?;
|
||||
let mut out = String::new();
|
||||
for cap in re.captures_iter(&html) {
|
||||
out.push_str(&cap[1]);
|
||||
out.push('\n');
|
||||
}
|
||||
|
||||
std::fs::write(filename, out)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_test(ip: &str, log: broadcast::Sender<String>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
|
@ -122,23 +209,37 @@ pub async fn run_test(ip: &str, log: broadcast::Sender<String>) -> Result<(), Bo
|
|||
let manager = HeadlessManager::new(chrome_exe, &cfg.chrome.args).await?;
|
||||
|
||||
let page = manager.new_page().await?;
|
||||
let url_https = format!("https://{ip}/");
|
||||
let url_http = format!("http://{ip}/");
|
||||
|
||||
let _ = log.send(format!("[Headless] goto {url_https}"));
|
||||
if page.goto(&url_https).await.is_err() {
|
||||
let _ = log.send(format!("[Headless] HTTPS fehlgeschlagen, versuche HTTP"));
|
||||
let _ = log.send(format!("[Headless] goto {url_http}"));
|
||||
page.goto(&url_http).await?;
|
||||
}
|
||||
page.goto("https://192.168.178.240/hp/device/SignIn/Index").await?;
|
||||
page.wait_for_selector("#PasswordTextBox", 5000).await?;
|
||||
let _ = log.send("PasswordTextBox gefunden".to_string());
|
||||
|
||||
let _ = log.send("[Headless] warte auf <h1> …".to_string());
|
||||
let _ = page.wait_for_selector("h1", 5000).await?;
|
||||
page.type_text("#PasswordTextBox", "Pa55w.rt").await?;
|
||||
let _ = log.send("Passwort erfolgreich eingetragen".to_string());
|
||||
|
||||
match page.get_text("h1").await? {
|
||||
Some(h1) => { let _ = log.send(format!("[Headless] H1: {h1}")); }
|
||||
None => { let _ = log.send("[Headless] Keine H1 gefunden".to_string()); }
|
||||
}
|
||||
page.wait_for_selector("#signInOk", 5000).await?;
|
||||
let _ = log.send("Login-Button gefunden, klicke …".to_string());
|
||||
|
||||
page.click_and_wait_navigation("#signInOk", 15_000).await?;
|
||||
let _ = log.send("Login erfolgreich, warte auf Menü".to_string());
|
||||
page.wait_for_dom_quiet(8_000).await?;
|
||||
|
||||
|
||||
|
||||
|
||||
page.goto("https://192.168.178.240/hp/device/SleepSchedule/Index").await?;
|
||||
page.wait_for_selector("#SleepDelayTimeLimit", 5000).await?;
|
||||
let _ = log.send("Eingabefeld 'SleepDelayTimeLimit' gefunden".to_string());
|
||||
|
||||
page.set_input_value_by_id("SleepDelayTimeLimit", "15").await?;
|
||||
let _ = log.send("SleepDelayTimeLimit = 15 eingetragen".to_string());
|
||||
|
||||
page.wait_for_selector("#FormButtonSubmit", 5000).await?;
|
||||
let _ = log.send("Submit-Button gefunden, sende Formular …".to_string());
|
||||
|
||||
page.click_and_wait_navigation("#FormButtonSubmit", 15_000).await?;
|
||||
let _ = log.send("Formular erfolgreich gesendet ✅".to_string());
|
||||
page.wait_for_dom_quiet(8_000).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue