2024-12-23 17:36:12 -03:00
|
|
|
use std::time::Duration;
|
|
|
|
use chromiumoxide::browser::{Browser, BrowserConfig};
|
|
|
|
use chromiumoxide::cdp::browser_protocol;
|
|
|
|
use chromiumoxide::page::Page;
|
|
|
|
use futures_util::StreamExt;
|
|
|
|
use gb_core::{Error, Result};
|
|
|
|
use tracing::instrument;
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Element {
|
|
|
|
inner: chromiumoxide::element::Element,
|
|
|
|
}
|
2024-12-22 20:56:52 -03:00
|
|
|
|
|
|
|
pub struct WebAutomation {
|
2024-12-23 17:36:12 -03:00
|
|
|
browser: Browser,
|
2024-12-22 20:56:52 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl WebAutomation {
|
|
|
|
#[instrument]
|
|
|
|
pub async fn new() -> Result<Self> {
|
2024-12-23 17:36:12 -03:00
|
|
|
let (browser, mut handler) = Browser::launch(BrowserConfig::default())
|
2024-12-22 20:56:52 -03:00
|
|
|
.await
|
2024-12-23 17:36:12 -03:00
|
|
|
.map_err(|e| Error::internal(e.to_string()))?;
|
|
|
|
|
2024-12-22 20:56:52 -03:00
|
|
|
tokio::spawn(async move {
|
|
|
|
while let Some(h) = handler.next().await {
|
|
|
|
if let Err(e) = h {
|
2024-12-23 17:36:12 -03:00
|
|
|
tracing::error!("Browser handler error: {}", e);
|
2024-12-22 20:56:52 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-12-23 17:36:12 -03:00
|
|
|
Ok(Self { browser })
|
2024-12-22 20:56:52 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
#[instrument(skip(self))]
|
|
|
|
pub async fn new_page(&self) -> Result<Page> {
|
2024-12-23 17:36:12 -03:00
|
|
|
let params = browser_protocol::page::CreateTarget::new();
|
|
|
|
let page = self.browser.new_page(params)
|
2024-12-22 20:56:52 -03:00
|
|
|
.await
|
2024-12-23 17:36:12 -03:00
|
|
|
.map_err(|e| Error::internal(e.to_string()))?;
|
2024-12-22 20:56:52 -03:00
|
|
|
Ok(page)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[instrument(skip(self))]
|
|
|
|
pub async fn navigate(&self, page: &Page, url: &str) -> Result<()> {
|
|
|
|
page.goto(url)
|
|
|
|
.await
|
2024-12-23 17:36:12 -03:00
|
|
|
.map_err(|e| Error::internal(e.to_string()))?;
|
2024-12-22 20:56:52 -03:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[instrument(skip(self))]
|
|
|
|
pub async fn get_element(&self, page: &Page, selector: &str) -> Result<Element> {
|
|
|
|
let element = page.find_element(selector)
|
|
|
|
.await
|
2024-12-23 17:36:12 -03:00
|
|
|
.map_err(|e| Error::internal(e.to_string()))?;
|
2024-12-22 20:56:52 -03:00
|
|
|
Ok(Element { inner: element })
|
|
|
|
}
|
|
|
|
|
|
|
|
#[instrument(skip(self))]
|
2024-12-23 17:36:12 -03:00
|
|
|
pub async fn screenshot(&self, page: &Page, _path: &str) -> Result<Vec<u8>> {
|
|
|
|
let screenshot_params = browser_protocol::page::CaptureScreenshot::new();
|
|
|
|
let data = page.screenshot(screenshot_params)
|
2024-12-22 20:56:52 -03:00
|
|
|
.await
|
2024-12-23 17:36:12 -03:00
|
|
|
.map_err(|e| Error::internal(e.to_string()))?;
|
|
|
|
Ok(data)
|
2024-12-22 20:56:52 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
#[instrument(skip(self))]
|
|
|
|
pub async fn wait_for_selector(&self, page: &Page, selector: &str) -> Result<()> {
|
2024-12-23 17:36:12 -03:00
|
|
|
page.find_element(selector)
|
2024-12-22 20:56:52 -03:00
|
|
|
.await
|
2024-12-23 17:36:12 -03:00
|
|
|
.map_err(|e| Error::internal(e.to_string()))?;
|
2024-12-22 20:56:52 -03:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[instrument(skip(self))]
|
|
|
|
pub async fn wait_for_network_idle(&self, page: &Page) -> Result<()> {
|
2024-12-23 17:36:12 -03:00
|
|
|
page.evaluate("() => new Promise(resolve => setTimeout(resolve, 1000))")
|
|
|
|
.await
|
|
|
|
.map_err(|e| Error::internal(e.to_string()))?;
|
|
|
|
Ok(())
|
2024-12-22 20:56:52 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use rstest::*;
|
|
|
|
|
|
|
|
#[fixture]
|
|
|
|
async fn automation() -> WebAutomation {
|
|
|
|
WebAutomation::new().await.unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[rstest]
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_navigation(automation: WebAutomation) -> Result<()> {
|
|
|
|
let page = automation.new_page().await?;
|
|
|
|
automation.navigate(&page, "https://example.com").await?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[rstest]
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_element_interaction(automation: WebAutomation) -> Result<()> {
|
|
|
|
let page = automation.new_page().await?;
|
|
|
|
automation.navigate(&page, "https://example.com").await?;
|
|
|
|
let element = automation.get_element(&page, "h1").await?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[rstest]
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_screenshot(automation: WebAutomation) -> Result<()> {
|
|
|
|
let page = automation.new_page().await?;
|
|
|
|
automation.navigate(&page, "https://example.com").await?;
|
|
|
|
let screenshot = automation.screenshot(&page, "test.png").await?;
|
|
|
|
Ok(())
|
|
|
|
}
|
2024-12-23 17:36:12 -03:00
|
|
|
}
|