BrowserOps is a small automation service built to answer a practical question: what should happen when a browser script is expected to run unattended, but the website, login session or underlying record changes?
The project pairs a Playwright worker with a deliberately imperfect local operations portal. The worker replies to routine messages and renews eligible adverts. SQLite keeps the queue, idempotency keys, retry state and audit history outside the browser, while a FastAPI console gives an operator somewhere to inspect and resolve anything uncertain.
Nothing here targets a real website. The mock portal, test data and failure controls are bundled with the repository.
- Browser login with reusable Playwright storage state and automatic recovery from an expired session
- Durable idempotency, so replaying a run cannot send the same reply twice
- Optimistic revision checks before every write
- Manual holds that automation will not overwrite
- Bounded retries with exponential backoff for temporary HTTP failures
- Selector-drift detection that stops work and records a screenshot
- A human-review queue with retry, dismiss and completed-manually controls
- Append-only audit events for runs, decisions and recovery
- Health endpoints, a background scheduler and one-command Docker startup
The mock data includes routine requests, a safety-sensitive request that must be reviewed, a manually protected record, eligible and scheduled adverts, and a manually managed advert.
flowchart LR
S["Scheduler or operator"] --> W["Playwright worker"]
W -->|"UI interactions only"| P["Local mock portal"]
P --> D[("SQLite")]
W --> D
W --> A["Failure screenshots"]
D --> C["FastAPI operator console"]
A --> C
C -->|"retry or resolve"| D
The target website is treated as an unreliable boundary. Discovery happens through the rendered UI, but the worker never relies on a browser tab to remember what it has done. Before changing a record it compares the current revision with the one captured during discovery. After acting, it looks for an explicit success signal in the UI before marking the queue item complete.
See the architecture notes for the action lifecycle and recovery decisions.
Docker includes Chromium and is the simplest clean setup:
docker compose up --buildOpen:
- Operator console: http://127.0.0.1:8000
- Local mock portal: http://127.0.0.1:8000/mock
- API documentation: http://127.0.0.1:8000/docs
The service creates and seeds var/browserops.db on first start. The console's Reset all demo data control returns it to the original state.
Python 3.12 or newer is required.
python -m venv .venv
# Windows: .venv\Scripts\activate
# macOS/Linux: source .venv/bin/activate
pip install -e ".[dev]"
playwright install chromium
browserops serveCopy .env.example to .env if you want to change the database path, schedule, retry limits or local credentials.
- Start the service and open the operator console.
- Select Run automation. Two ordinary messages receive a saved reply, one eligible advert is renewed, and protected work enters review.
- Run it again. The completed actions are suppressed by their idempotency keys.
- Under Demo controls, arm a transient failure. Reset the demo first if you want fresh actionable records, then run the worker and watch the affected action enter retry wait.
- Enable selector drift and run again. The worker stops discovery for the changed area, opens a review item and attaches a full-page screenshot.
- Reset the demo, arm session expiry and run once more. The audit log records re-authentication before processing continues.
All of those cases are also automated in the test suite.
| Method | Path | Purpose |
|---|---|---|
GET |
/health/live |
Process liveness |
GET |
/health/ready |
Database/schema readiness and worker state |
GET |
/api/overview |
Queue, portal and scheduler summary |
GET |
/api/actions |
Durable action queue |
GET |
/api/runs |
Recent execution history |
GET |
/api/audit |
Audit events |
POST |
/api/runs/trigger |
Run one automation cycle |
POST |
/api/actions/{id}/retry |
Return a failed or reviewed action to the queue |
POST |
/api/actions/{id}/resolve |
Record an operator decision |
GET |
/api/actions/{id}/screenshot |
Retrieve failure evidence |
POST |
/api/scenarios |
Configure local failure simulation |
POST |
/api/demo/reset |
Reseed the local demonstration |
ruff check .
pytestThe browser tests start the FastAPI application on a local ephemeral port and use a real Chromium instance. They cover normal processing, duplicate suppression, expired sessions, temporary failures, selector drift, manual overrides and recovery of an action left running by an interrupted worker.
This repository is intentionally limited to a local portal under the developer's control. It does not contain CAPTCHA solving, stealth plugins, browser fingerprint manipulation, proxy rotation or techniques for avoiding platform controls. Credentials in .env.example belong only to the seeded mock portal.
A real deployment would also need written permission from the target-system owner, an assessment of the site's terms and supported APIs, least-privilege credentials, encrypted secret storage, operator authentication and a documented shutdown path. More detail is in SECURITY.md.
This is a portfolio-sized service, not a multi-tenant automation platform. SQLite is a sensible fit for one local worker but should be replaced by PostgreSQL when multiple worker processes need to claim work concurrently. The in-process scheduler should move to a dedicated job runner in a horizontally scaled deployment. Screenshots are stored on disk, the operator console has no login, and observability is limited to structured application logs and the audit table.
The mock portal uses stable test identifiers until the drift scenario is enabled. On a real approved integration, selectors would be based on the most durable accessible roles or product-owned attributes available, backed by contract monitoring and a controlled release process.
MIT
