wabot/CLAUDE.md
Mathew Sir Guest the best 12ef5d7eee docs: add CLAUDE.md and modernization design spec
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>
2026-07-07 20:07:07 -06:00

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:

  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).