aboutsummaryrefslogtreecommitdiff
path: root/src/cerberus/automation/selenium_engine.py
blob: 3fbdaac8b31dc87d54f0478f344ca9d11ac72063 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
from typing import Optional, Dict, Any

try:
    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.chrome.options import Options as ChromeOptions
    SELENIUM_AVAILABLE = True
except Exception:  # pragma: no cover - optional dependency
    SELENIUM_AVAILABLE = False


class SeleniumEngine:
    def __init__(self):
        if not SELENIUM_AVAILABLE:
            raise RuntimeError("Selenium is not installed. Install with: pip install selenium")
        self._driver: Optional[webdriver.Chrome] = None

    def start(self, headless: bool = True, user_data_dir: Optional[str] = None) -> None:
        options = ChromeOptions()
        if headless:
            options.add_argument("--headless=new")
        if user_data_dir:
            options.add_argument(f"--user-data-dir={user_data_dir}")
        self._driver = webdriver.Chrome(options=options)

    def stop(self) -> None:
        if self._driver:
            self._driver.quit()
            self._driver = None

    def goto(self, url: str, wait_until: str = "networkidle") -> None:
        assert self._driver is not None
        self._driver.get(url)

    def type(self, selector: str, value: str, clear: bool = True) -> None:
        assert self._driver is not None
        elem = self._driver.find_element(By.CSS_SELECTOR, selector)
        if clear:
            elem.clear()
        elem.send_keys(value)

    def click(self, selector: str) -> None:
        assert self._driver is not None
        elem = self._driver.find_element(By.CSS_SELECTOR, selector)
        elem.click()

    def wait_for(self, selector: str, timeout_ms: int = 15000) -> None:
        assert self._driver is not None
        # Simple polling; users can replace with WebDriverWait if desired
        import time
        end = time.time() + timeout_ms / 1000.0
        while time.time() < end:
            try:
                self._driver.find_element(By.CSS_SELECTOR, selector)
                return
            except Exception:
                time.sleep(0.1)
        raise TimeoutError(f"Timeout waiting for selector: {selector}")

    def evaluate(self, script: str, arg: Optional[Dict[str, Any]] = None) -> Any:
        assert self._driver is not None
        return self._driver.execute_script(script, arg)