Skip to content

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

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.

  1. Create a virtual environment and install the dependencies, including the test tools:

    Terminal window
    cd backend
    python3.12 -m venv .venv
    .venv/bin/pip install -r requirements-dev.txt
  2. 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 name

    The 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
    postgres PLAINWRIGHT_TEST_PG=postgresql://user:pass@localhost:5432/db A throwaway Postgres (migration tests)
    mcp PLAINWRIGHT_TEST_MCP=http://localhost:8931 A running Playwright MCP server
  3. 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 from backend/:

    Terminal window
    export DATABASE_URL=postgresql://testai:testai_secret@localhost:5432/testai_db
    export 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 runs

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

Terminal window
cd frontend
npm install
npx vitest run # all component tests (jsdom)
npx vitest run src/pages/Triage.test.jsx
npm run dev # Vite dev server on http://localhost:5173
npm 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.

Terminal window
cd docs-site
npm install
npm run dev # http://localhost:4321
npm run build # production build for https://saibolisetty.github.io/Plainwright/
DOCS_BASE=/ npm run build # root build plus the broken link/anchor check
npm run openapi # refresh the REST API reference after backend changes

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

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 the x.value if hasattr(x, "value") else x idiom.
  • Never log, return or send a secret value, API key or token. Mask on read; use the secret resolver’s scrub on 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_llm fixture (or llm.set_backend), respx for HTTP, the fakes in tests/core/fakes.py, and the db, client and factory helpers from tests/conftest.py and tests/factories.py. Mock API modules in frontend tests with vi.mock.
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
  • The change is focused, and the PR description says what it does and why.
  • Backend tests pass: python -m pytest -q tests in backend/.
  • Frontend tests pass and the app builds: npx vitest run and npm run build in frontend/.
  • 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 build in docs-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.