Design approved in brainstorming session: uv packaging, Selenium 4 migration, session persistence rebuilt on JSON + session-ID reattach (replacing pickle/dill), configurable pacing, pytest unit+integration tiers, Sphinx docs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3.8 KiB
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:
- Page-object automation with state carried between pages — consumers model each website page as a
Pagesubclass and drive them through a singleBrowserProxy. - 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:
BrowserProxyholds the webdriver plus a current page object and acts as a facade.__getattr__delegates unknown attributes to the current page, sobot.login()invokesbot.page.login();bot[key]resolves page elements.set_page(PageClass)swaps the current page and calls itsverify()gate.perform(name, ...)invokes a page method with exception trapping — any failure flipsself.good = False, after which all further actions are refused (REFUSE_AFTER_EXCEPTION), forcing an explicitreset_good_status().Page(wabot/page.py) is the base class consumers subclass. Pages declare a class-levelelements = {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,CheckFieldwrap elements withget_value/set_value; unknown attribute access falls through to the underlying selenium element.NullFieldis a falsy null-object returned for missing elements so callers canif 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 atlocalhost:4444) are serialized withdilltoappdirs.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 touchingdriver.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 stdliblogginghas notracemethod — the consuming application is expected to register a custom TRACE level; without it those calls raise.appdirsandPilloware imported but missing frominstall_requiresinsetup.py(onlydillandseleniumare declared).wabot/__init__.pystar-exportsapi,page, andfields, 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).