Development¶
How to set up BrowserControl for local development, run tests, lint, and contribute.
Quick start¶
git clone https://github.com/adityasasidhar/browsercontrol
cd browsercontrol
# Install deps + Playwright + Chromium
uv sync
uv run playwright install chromium --with-deps
# Run the server in dev mode (FastMCP inspector UI)
uv run fastmcp dev browsercontrol/server.py
# Run tests
uv run pytest
# Lint, format, security checks
uv run ruff check .
uv run ruff format .
uv run pre-commit run --all-files
Project layout¶
browsercontrol/
├── __init__.py # Public exports (`mcp`)
├── __main__.py # `python -m browsercontrol` entry point
├── server.py # FastMCP server definition + tool registration
├── browser.py # BrowserManager — Playwright lifecycle + SoM renderer
├── config.py # Env-var configuration
└── tools/
├── navigation.py # navigate_to, go_back, go_forward, refresh, scroll
├── interaction.py # click, click_at, type_text, press_key, hover, scroll_to_element, wait
├── forms.py # select_option, check_checkbox, upload_file
├── content.py # get_page_content, get_text, get_page_info, run_javascript, screenshot
├── devtools.py # console, network, errors, inspect, perf, cookies, viewport
├── recording.py # start/stop_recording, take_snapshot, list_recordings
└── tabs.py # create_tab, switch_tab, close_tab, list_tabs
tests/ # ~1,700 lines of pytest tests (mocked Playwright)
├── conftest.py # Shared fixtures: mock_page, mock_context, mock_browser_manager…
└── test_*.py # One file per module
Running tests¶
# All tests
uv run pytest
# Single file
uv run pytest tests/test_interaction.py
# Single test
uv run pytest tests/test_interaction.py::test_click
# Verbose
uv run pytest -v
# With coverage
uv run pytest --cov=browsercontrol --cov-report=term-missing
The tests use mocked Playwright objects (see tests/conftest.py) so they run fast and don't need a real browser.
Code style¶
- Ruff for linting and formatting (configured in
pyproject.toml). - mypy --strict for type checking.
- Pre-commit runs ruff, mypy, and other checks on every commit.
# Run everything
uv run pre-commit run --all-files
# Just ruff
uv run ruff check .
uv run ruff format .
# Just mypy
uv run mypy browsercontrol
CI runs the full suite on Ubuntu, Windows, and macOS with ruff, mypy --strict, bandit, and pytest.
Adding a new tool¶
- Pick the right module. Most tools belong in an existing category (
interaction.py,devtools.py, etc.). If your tool is genuinely new, add a new file underbrowsercontrol/tools/. - Use the
@mcp.tool()decorator. The signature is auto-exposed as the MCP tool signature. - Return
(text, Image)if the tool changes the page (so the model gets a fresh screenshot). Returnstrfor read-only tools. - Use
browser.ensure_started()at the top of the tool body. - Add a docstring with
Args:andReturns:— these become the tool's description. - Add tests in
tests/test_<module>.py. - Update the docs. Add the new tool to the relevant
docs/tools/*.mdpage.
Example skeleton:
@mcp.tool()
async def my_new_tool(element_id: int) -> tuple[str, Image]:
"""
One-line summary of what the tool does.
Args:
element_id: The number label shown on the element in the screenshot
Returns:
Description of the return value
"""
try:
await browser.ensure_started()
elem_map = get_element_map()
if element_id not in elem_map:
image, summary = await _get_screenshot_with_summary()
return f"Error: Element {element_id} not found.\n\n{summary}", image
# ... your logic ...
image, summary = await _get_screenshot_with_summary()
return f"Did the thing to element {element_id}\n\n{summary}", image
except Exception as e:
logger.error(f"my_new_tool failed: {e}")
try:
image, summary = await _get_screenshot_with_summary()
return f"Error: {e}\n\n{summary}", image
except Exception:
raise RuntimeError(f"my_new_tool failed: {e}")
Building the docs¶
The docs site uses MkDocs Material, installed via the docs extra.
One-time setup¶
Make sure your project's virtual environment has MkDocs Material installed:
uv sync (run as part of normal setup per CONTRIBUTING.md) already covers this — --extra docs is only required if you've previously synced without it.
Preview locally¶
Then open http://localhost:8000.
Why uv run?
Do not run a bare mkdocs serve. On many systems a stray /usr/bin/mkdocs (Python 3.x with no Material theme) takes precedence over the project venv and fails with cannot find module 'material.extensions.emoji'. uv run always resolves mkdocs from .venv/bin/.
Build the static site (CI does this)¶
The output goes to site/ (gitignored).
Good first contributions¶
Looking for something to work on? Start with one of these:
- Firefox / WebKit support
- DOM diffing (detect changes between snapshots)
- Accessibility audit tools
- Mobile emulation presets (iPhone, Pixel, iPad)
- Cookie import/export (Netscape format)
-
--record-videoMP4 output (in addition to traces)
See also¶
- Architecture — how the components fit together
- Set of Marks — the technique this is built on
- CONTRIBUTING.md — contribution workflow
- CODE_QUALITY.md — toolchain conventions