# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## What this is WABot is a Python library wrapping Selenium for stateful browser automation (login flows, multi-page form filling, scraping). Its two distinguishing features: 1. **Page-object automation with state carried between pages** — consumers model each website page as a `Page` subclass and drive them through a single `BrowserProxy`. 2. **Browser sessions that survive the Python process** — a remote webdriver reference is persisted to disk so a *different* Python process can reattach to the same still-open browser. There is currently no test suite, linter, or CI. Packaging is a legacy `setup.py` (`pip install -e .` to develop). The code is pinned to Selenium 3-era APIs (`desired_capabilities`, `browser_profile`, `switch_to_alert`); it does not run against Selenium 4+ without migration. ## Architecture Consumer code talks to one object, `BrowserProxy` (`wabot/api.py`), which composes everything else: - **`BrowserProxy`** holds the webdriver plus a *current page* object and acts as a facade. `__getattr__` delegates unknown attributes to the current page, so `bot.login()` invokes `bot.page.login()`; `bot[key]` resolves page elements. `set_page(PageClass)` swaps the current page and calls its `verify()` gate. `perform(name, ...)` invokes a page method with exception trapping — any failure flips `self.good = False`, after which all further actions are refused (`REFUSE_AFTER_EXCEPTION`), forcing an explicit `reset_good_status()`. - **`Page`** (`wabot/page.py`) is the base class consumers subclass. Pages declare a class-level `elements = {key: (type, accessors)}` map; lookup walks the MRO, so subclasses inherit and override parent element maps. `page[key]` returns a typed field wrapper, never a raw WebElement. - **Field wrappers** (`wabot/fields.py`): `TextField`, `SelectField`, `CheckField` wrap elements with `get_value`/`set_value`; unknown attribute access falls through to the underlying selenium element. `NullField` is a falsy null-object returned for missing elements so callers can `if el:` instead of catching exceptions. - **`CreateBrowser`** (`wabot/create_browser.py`) is the driver factory. Local drivers (Chrome/Firefox) are plain. Remote drivers (Selenium server expected at `localhost:4444`) are serialized with `dill` to `appdirs.user_data_dir('wabot')/saved_browser_instances.pickle`, keyed by session name with a timestamp (refs older than 3 days are purged). On the next request for the same session name, the pickled driver is loaded and validated by touching `driver.current_url` — that is the reattach mechanism. **Human-like behavior is intentional, not accidental slowness**: clicks land at Gaussian-random coordinates within the element instead of Selenium's default (0,0), and field setters `sleep(random.uniform(...))` between actions. Chrome full-page screenshots are stitched from viewport tiles with PIL to work around chromedriver capturing only the visible area. Don't "optimize" these away. ## Gotchas - `LOGGER.trace(...)` is called throughout, but stdlib `logging` has no `trace` method — the consuming application is expected to register a custom TRACE level; without it those calls raise. - `appdirs` and `Pillow` are imported but missing from `install_requires` in `setup.py` (only `dill` and `selenium` are declared). - `wabot/__init__.py` star-exports `api`, `page`, and `fields`, so anything public in those modules is public API. - Remote driver types require an external Selenium server already running on port 4444; nothing in this repo starts one. ## Git Remote is Gitea (`gitea@git.zavage.net:Zavage-Software/wabot.git`); default branch is `master`. Do feature work on branches — pushing is done manually by the user (no SSH auth from inside the Claude shell).