otree-server / TESTING.md
BPEL Bot
Initial Hugging Face deployment
e40dce0
# Testing Guide (ibe-pp)
This project ships with a complete test suite covering cloned apps and full-session flows. Tests never modify oTree core and run against the local project only.
## Test Types
- Unit-lite: config and import sanity checks (no DB writes).
- App bots (oTree): app-level flows in each cloned app (`tests.py` inside the app).
- E2E sessions (pytest): spins up full sessions via `otree test <session>` using fresh SQLite DBs.
- Matrix/infra checks: participant-count matrix, rooms/labels, static assets.
## Layout
- `policy_*/tests.py` – Bot specs per app:
- `policy_public_goods_defaults`, `policy_trust_framed`, `policy_dictator_norms`, `policy_guess_anchoring`, `policy_survey_biases`
- Patterns used: `expect()` to assert payoffs and `SubmissionMustFail(...)` to test validation failures (see `policy_survey_biases`).
- `payment_info/tests.py` – No-op bot to satisfy multi-app sessions.
- `tests/test_integration_settings.py` – Verifies session configs and app folders.
- `tests/test_e2e_bots.py` – Runs E2E bots for configured sessions (`policy_nudges`, `anchoring_demo`, `survey_biases_full`).
Survey Biases (policy_survey_biases) modules and tests
- Module A (Information and Views): randomized order `order_a ∈ {info_before, info_after}` with a comprehension check. Bots cover both branches and include a negative case via `SubmissionMustFail` for a wrong comprehension answer.
- Module B (Statements and Evidence): randomized order `order_b ∈ {support_first, eval_first}`. Bots cover both branches.
- Module C (Topics and News): randomized order `order_c ∈ {recall_first, opinion_first}` with validation requiring ~20 characters for each recall text. Bots assert negative cases (short input) then submit valid responses.
- Results page safely handles optional fields using `field_maybe_none(...)` to avoid null access during rendering.
Running just the survey biases suite
- `make otree-test-survey` (fresh SQLite DB) or:
- `source scripts/activate_venv.sh && cd otree-projects/ibe-pp && OTREE_DATABASE_URL=sqlite:///tmp_survey.sqlite3 otree test survey_biases_full`
- `tests/test_session_matrix_and_rooms.py` – Participant-count matrix (valid/invalid), rooms label file presence, and static file presence.
- `tests/test_static_http.py` – Optional HTTP check for `/_static/custom.css` when `RUN_OTREE_HTTP_TESTS=1`.
## How To Run
- Quick run (recommended):
- `make test-ibe-pp` (pytest over `tests/`)
- `make test-otree-e2e` (serial bot runs across key sessions)
- Manual (from project root):
- `source scripts/activate_venv.sh`
- `cd otree-projects/ibe-pp && pytest -q tests`
- Single E2E:
- `cd otree-projects/ibe-pp && pytest -q tests/test_e2e_bots.py::test_bots_policy_nudges_sequence`
- Optional HTTP static check:
- `RUN_OTREE_HTTP_TESTS=1 cd otree-projects/ibe-pp && pytest -q tests/test_static_http.py`
## Dependencies
- Added to root `requirements.txt`: `pytest`, `requests`, `httpx`, `starlette==0.14.1`.
- Pin rationale: oTree 5.11.4 bots expect Starlette 0.14.x; newer Starlette breaks bot TestClient.
- If running FastAPI tooling simultaneously, consider a dedicated test requirements file or separate venv.
## E2E Strategy
- Tests set `OTREE_DATABASE_URL=sqlite:///test_db_<name>.sqlite3` to isolate DBs.
- If you run `otree test` manually and see “delete your database”, remove `db.sqlite3` or set `OTREE_DATABASE_URL`.
- Participant-count matrix asserts success/failure for group-size compatibility.
## Adding Tests
- New app: add `tests.py` alongside `__init__.py` with a `PlayerBot(Bot)` and yields for each page.
- Use `expect()` to assert key state (e.g., payoffs), and `SubmissionMustFail(...)` for invalid input branches.
- New session: add a test in `tests/test_e2e_bots.py` invoking `run_otree_test('<session_name>')`.
## Common Issues
- Missing dependencies for bots: install `requests` and `httpx` (already in `requirements.txt`).
- Name collisions: cloned apps set unique `NAME_IN_URL` values; keep this when adding apps.
- Timeouts: `prisoner.Introduction` now uses `get_timeout_seconds`; override with `session.config['prisoner_intro_timeout']` in `settings.py` if needed, for example:
```python
SESSION_CONFIGS = [
dict(
name='classic_baseline',
app_sequence=['sequence_welcome', 'prisoner', 'trust_simple', 'public_goods_simple', 'payment_info'],
num_demo_participants=6,
prisoner_intro_timeout=30, # seconds (optional)
),
]
```