Contributing
Thanks for helping. Plainwright welcomes bug reports, fixes, new test engines, provider support, docs and translations of confusing error messages into clear ones. This page gets you from a fresh clone to a pull request.
- Found a bug? Open an issue with what you did, what you expected and what happened. Include the run’s error and, if you can, the steps from the run page, but never secrets or API keys.
- Want to build something bigger? Open an issue first to agree on the approach. Check the Roadmap to see whether it’s already planned.
- Found a security problem? Don’t open a public issue. Use GitHub’s private vulnerability reporting.
Repository layout
Section titled “Repository layout”Directorybackend/ FastAPI API, RQ worker and scheduler (Python 3.12)
Directoryapp/
- main.py app, router mounts
Directoryapi/ thin routers, one file per area
- …
Directorycore/ settings, models (database.py), auth, crypto, migrations
- …
Directoryservices/
Directoryengines/ one engine per test kind
- …
Directoryintegrations/ Jira, Linear, GitHub, Figma, Notion, IMAP
- …
Directoryquality/ axe, performance, links, SEO checks
- …
Directorytools/ HTTP and one-time-code tools for the agent
- …
- ai_service.py agent loop, toolsets, MCP client
- runner_service.py the run job
- …
Directorymigrations/ Alembic revisions
- …
Directorytests/ pytest, one folder per area
- …
- requirements.txt
- requirements-dev.txt
Directoryfrontend/ React 18, Vite, Tailwind
Directorysrc/
Directoryapi/ axios clients, one file per router
- …
Directoryui/ the UI kit (buttons, drawers, tables, …)
- …
Directorycomponents/ app shell
- …
Directoryfeatures/ feature components, with tests next to them
- …
Directorypages/ one file per route
- …
Directorydocs-site/ this documentation (Astro Starlight)
- …
- docs/deploy.md deployment notes
- nginx/nginx.conf reverse proxy
Directoryscripts/ OpenAPI export and other helpers
- …
Directorytest-files/ uploaded project files (mounted into the browsers)
- …
Directory.github/workflows/ CI examples and the docs deploy
- …
- docker-compose.yml the whole stack
- .env.example every server setting, documented
Architecture explains how these pieces fit together at run time.
Prerequisites
Section titled “Prerequisites”| Tool | Version | For |
|---|---|---|
| Python | 3.12 | The backend and its tests |
| Node.js | 20 or newer (22.12+ for the docs site) | The frontend and the docs |
| Docker with Compose 2.24+ | recent | Running the full stack, or just Postgres and Redis |
You don’t need Docker or an AI provider key to run the unit tests.
Backend
Section titled “Backend”-
Create a virtual environment and install the dependencies, including the test tools:
Terminal window cd backendpython3.12 -m venv .venv.venv/bin/pip install -r requirements-dev.txtTerminal window cd backendpy -3.12 -m venv .venv.venv\Scripts\pip install -r requirements-dev.txt -
Run the test suite:
Terminal window .venv/bin/python -m pytest -q tests # everything.venv/bin/python -m pytest -q tests/flaky # one area.venv/bin/python -m pytest -q -k quarantine # by nameTerminal window .venv\Scripts\python -m pytest -q tests.venv\Scripts\python -m pytest -q tests/flaky.venv\Scripts\python -m pytest -q -k quarantineThe suite runs on an in-memory SQLite database with a fake LLM and no network, and takes a few minutes. Some tests are skipped unless you opt in:
Marker Enable with Needs postgresPLAINWRIGHT_TEST_PG=postgresql://user:pass@localhost:5432/dbA throwaway Postgres (migration tests) mcpPLAINWRIGHT_TEST_MCP=http://localhost:8931A running Playwright MCP server -
To run the API and a worker on your machine, start Postgres and Redis (for example
docker compose up -d postgres redis), point the settings at them, and run each process frombackend/:Terminal window export DATABASE_URL=postgresql://testai:testai_secret@localhost:5432/testai_dbexport REDIS_URL=redis://localhost:6379.venv/bin/uvicorn app.main:app --reload --port 8000 # API; runs migrations on start.venv/bin/rq worker --url redis://localhost:6379 test_runs.venv/bin/python -m app.services.scheduler # only if you need cron runsA worker needs a Playwright MCP server for browser tests. The simplest way to get the whole stack is
docker compose up --build; see Docker Compose.
Frontend
Section titled “Frontend”cd frontendnpm installnpx vitest run # all component tests (jsdom)npx vitest run src/pages/Triage.test.jsxnpm run dev # Vite dev server on http://localhost:5173npm run build # production bundle in dist/The dev server proxies /api and /screenshots to http://backend:8000, the Compose service name.
When your backend runs on the host instead, change the proxy target in vite.config.js to
http://localhost:8000 locally (don’t commit that). The app always calls the relative /api; never
hard-code a host in frontend code.
Docs site
Section titled “Docs site”cd docs-sitenpm installnpm run dev # http://localhost:4321npm run build # production build for https://saibolisetty.github.io/Plainwright/DOCS_BASE=/ npm run build # root build plus the broken link/anchor checknpm run openapi # refresh the REST API reference after backend changesOn Windows, run the link check as $env:DOCS_BASE='/'; npm run build in PowerShell, or as
MSYS_NO_PATHCONV=1 DOCS_BASE=/ npm run build in Git Bash (which otherwise rewrites / into a
Windows path).
Page ownership and the writing conventions are in docs-site/OWNERS.md.
Search only works on a built site (npm run build then npm run preview).
Code style
Section titled “Code style”Backend
- Keep routers thin: validate input, check access, call a service, serialize. Business logic lives
in
app/services/. - Check access with the helpers in
app.core.auth(require_project_access,require_owned_test_case,require_owned_run, …) and the lowest role that makes sense. - Serialize with hand-written
_serialize…helpers.str()every UUID, and read enums with thex.value if hasattr(x, "value") else xidiom. - Never log, return or send a secret value, API key or token. Mask on read; use the secret
resolver’s
scrubon anything stored or shown. - Every server-side request to a user-supplied URL must go through
targets.check_url_allowed. - Don’t add an automatic LLM call where an on-demand button works, and cap loops (turns, trials, rows).
- Schema changes need an Alembic revision in
backend/migrations/versions/. Prefer the existing JSON columns (TestCase.config,TestRun.extras,Project.settings) for feature data.
Frontend
- Build pages from the UI kit in
src/ui/and its colour tokens. Both light and dark themes must work, and so must a 375 px wide screen with no horizontal scrolling. - Separate sections with hairlines and whitespace rather than nested bordered boxes. Show status as an icon and text, never colour alone. Put details in a right-side drawer rather than a modal.
- Add API calls to the matching module in
src/api/.
Tests
- Every change comes with tests. Backend tests go in
backend/tests/<area>/test_*.py; frontend tests sit next to the component as*.test.jsx. - Unit tests never call a real LLM, network service or Docker. Use the
fake_llmfixture (orllm.set_backend),respxfor HTTP, the fakes intests/core/fakes.py, and thedb,clientand factory helpers fromtests/conftest.pyandtests/factories.py. Mock API modules in frontend tests withvi.mock.
Common changes
Section titled “Common changes”| To add | Touch |
|---|---|
| An endpoint | A router in backend/app/api/ (mount new routers in main.py), a call in frontend/src/api/, tests on both sides, then npm run openapi in docs-site/ |
| A setting | backend/app/core/config.py and .env.example (a test keeps them in sync), plus Configuration |
| An AI provider | The enum in core/database.py (with a migration), base URL rules in services/llm.py and ai_service.py, the model list in api/ai_configs.py, and the provider list in the frontend. Every model must support tool calling. |
| A test kind | An engine module in services/engines/ registered with @register("kind"), the kind in TEST_KINDS, a config editor in frontend/src/features/tests/, and docs |
Pull request checklist
Section titled “Pull request checklist”- The change is focused, and the PR description says what it does and why.
- Backend tests pass:
python -m pytest -q testsinbackend/. - Frontend tests pass and the app builds:
npx vitest runandnpm run buildinfrontend/. - New behaviour has tests, with no real LLM, network or Docker calls.
- UI changes are checked in light and dark themes and at phone width, with screenshots in the PR.
- No secrets, keys or personal data in code, tests, fixtures or screenshots.
- Schema changes have a migration; new settings are in
.env.example. - Docs are updated, the API reference is regenerated if endpoints changed, and
DOCS_BASE=/ npm run buildindocs-site/passes. - User-facing changes have a line in the Changelog.
- Every commit is signed off (
git commit -s), as described below.
License and Developer Certificate of Origin
Section titled “License and Developer Certificate of Origin”Plainwright is licensed under the Apache License 2.0. As section 5 of the license says, any contribution you submit is licensed under the same Apache-2.0 terms, without any additional terms or conditions.
We use the Developer Certificate of Origin (DCO) instead of a
contributor license agreement. By signing off a commit you certify that you wrote the change, or
otherwise have the right to submit it under the project’s license. Sign off every commit with
git commit -s (or --signoff), which adds a line with your name and email:
Signed-off-by: Jane Doe <jane@example.com>Use your real name and an email address you can be reached at. Forgot on the last commit? Run
git commit --amend -s --no-edit; for several commits, git rebase --signoff main. Then
force-push the branch.