|
<div class="docs-content" data-render>
|
|
# Test framework and rules
|
|
|
|
The structure of the correctness suite, the fixtures that back it, and the mandatory patterns every test must follow. See also [Testing overview](/docs/testing.html) and [Running tests via make](/docs/testing-make.html).
|
|
|
|
## Playwright, not pytest-playwright
|
|
|
|
The suite uses **raw Playwright** driven by plain `pytest`, not the `pytest-playwright` plugin. The two conflict over fixture names. If `pytest-playwright` is installed, uninstall it before running the suite.
|
|
|
|
## Fixture stack
|
|
|
|
All fixtures live in `tests/conftest.py`. They layer from a shared server up to per-test isolation:
|
|
|
|
| Fixture | Scope | Purpose |
|
|
|---------|-------|---------|
|
|
| `app_server` | session | Spawns Uvicorn as a subprocess on a per-worker port with a tempfile SQLite database, then waits for it to answer. |
|
|
| `playwright_instance` / `browser` | session | One Playwright runtime and one Chromium browser for the whole run. |
|
|
| `browser_context` | module | One shared browser context. |
|
|
| `page` | function | Clears cookies on the shared context, then yields a fresh page. |
|
|
| `seeded_db` | session | Seeds baseline users and content once. |
|
|
| `alice` / `bob` | function | Logged-in seeded users (`alice_test` / `bob_test`); `bob` gets its own context for multi-user tests. Each returns `(page, user_dict)`. |
|
|
|
|
In-process tests skip the browser entirely and call a FastAPI `TestClient` or run coroutines through the `run_async` helper, which executes them on a worker thread and refreshes the database snapshot afterward.
|
|
|
|
## Isolation and ports
|
|
|
|
Each xdist worker runs its own server on its own port, derived from the worker index: master uses `10501`, `gw0` uses `10501`, `gw1` uses `10502`, and so on. Every worker gets a separate tempfile database. Background services are disabled in tests via `DEVPLACE_DISABLE_SERVICES=1`, and the rate limit is seeded to `1000000` so the suite is never throttled.
|
|
|
|
## Mandatory patterns
|
|
|
|
These are not style preferences. Violating them produces flaky or hanging tests:
|
|
|
|
- **Always pass `wait_until="domcontentloaded"`** to every `page.goto(...)` and `page.wait_for_url(...)`. CDN libraries and avatars make the default `"load"` event time out.
|
|
- **Prefer `page.locator(...).wait_for(state="visible")`** over `wait_for_selector`.
|
|
- **Scope ambiguous selectors.** Examples: comment Delete is `.comment-action-btn:has-text('Delete')`; the create-post Post button is `#create-post-modal button.btn-primary:has-text('Post')`; the comment textarea is `.comment-form textarea[name='content']`.
|
|
- **Restore global state in `try/finally`.** The server is shared session-wide, so a test that flips a `site_settings` value (maintenance mode, registration, rate limit, session length) MUST restore it. Rate-limit unit tests patch `get_int_setting` rather than the fallback constant.
|
|
|
|
## Failure diagnostics
|
|
|
|
A `pytest_runtest_makereport` hook saves a screenshot to `/tmp/devplace_test_screenshots/` whenever a Playwright test fails. In CI those screenshots are uploaded as a `failure-screenshots` artifact.
|
|
|
|
## When to write which test
|
|
|
|
- **UI or browser behavior** (rendering, clicks, forms, navigation, JS): end-to-end Playwright test. This is required for every user-facing feature.
|
|
- **JSON response shape, auth guards, data helpers, pure logic**: in-process `TestClient` or `asyncio` test.
|
|
|
|
Many features need both: the in-process test pins the JSON contract and the E2E test proves the page works.
|
|
|
|
## Running a single test
|
|
|
|
```bash
|
|
python -m pytest tests/test_feed.py::test_name -v --tb=line -x
|
|
```
|
|
|
|
The full suite is run through the `make` targets documented in [Running tests via make](/docs/testing-make.html).
|
|
</div>
|