Development#
If you haven’t set up e-Babylab yet, follow the steps in Getting Started first.
Additional Prerequisites#
Pre-commit Hooks#
The project uses pre-commit to run linting and formatting checks before each commit. Install the hooks once after cloning:
uv tool install pre-commit
pre-commit install
Running Tests#
Python (pytest)#
Run the Python test suite inside the Docker container:
docker compose -f docker-compose.dev.yml exec web uv run pytest
Run a specific test file:
docker compose -f docker-compose.dev.yml exec web uv run pytest /usr/src/tests/test_unit/test_config.py
JavaScript (Vitest) and End-to-End Tests (Playwright)#
JS tests run directly on your machine (no Docker needed). Requires Node.js.
Install dependencies once (installs both Vitest and Playwright deps):
cd tests
npm install
npx playwright install chromium firefox webkit msedge --with-deps
Run tests:
cd tests
npm run test:unit # Vitest unit tests only (no dev server needed)
npm run test:e2e # Playwright e2e tests (dev server must be running)
npm test # both suites in sequence
Watch mode (re-runs unit tests on file change):
cd tests && npm run test:watch
E2e tests require the dev server to be running (see Try It Locally).
Run a single browser:
cd tests/e2e && npx playwright test --project=chromium
Run a single spec:
cd tests/e2e && npx playwright test specs/experiment.spec.js --project=chromium
Interactive UI mode:
cd tests/e2e && npm run test:ui
Browser coverage:
Project |
Browser |
Platform |
|---|---|---|
|
Chrome |
Desktop |
|
Firefox |
Desktop |
|
Safari (approximate) |
Desktop |
|
Edge |
Desktop |
|
Chrome |
Android (Pixel 5) |
|
Safari |
iOS (iPhone 14) |
Note
mobile-safari uses WebKit with iPhone 14 device emulation. For the most accurate Safari results, run this project on macOS (CI uses a macOS runner; locally, it works on any platform but macOS gives the closest match to real Safari).
Verify Production Setup Locally#
To test the production stack locally without a domain, use DOMAIN=localhost with a self-signed certificate:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout server.key -out cert.pem -subj '/CN=localhost'
Then set the paths in your .env file:
DOMAIN=localhost
SSL_CERT_PATH=./cert.pem
SSL_KEY_PATH=./server.key
Start the production stack and visit https://localhost/admin/. Your browser will warn about the self-signed certificate — this is expected.
docker compose up -d
Database Admin (pgAdmin)#
The development environment includes pgAdmin for easy access to the database. It is accessible at http://localhost:5050. The default port can be changed by updating the 5050:80 port mapping under the pgadmin service in docker-compose.dev.yml. Login credentials are set via PGADMIN_EMAIL and PGADMIN_PASSWORD in .env.
Django Commands#
Run Django management commands inside the container:
docker compose -f docker-compose.dev.yml exec web uv run python manage.py <command> [options]
All available commands can be found in the Django documentation.
Managing Dependencies#
Python#
pyproject.toml is not mounted inside the container, so dependency changes must be made outside it using uv. The venv_cache Docker volume persists the virtual environment across container restarts — simply rebuilding the image is not enough, because the volume overrides the image’s venv on startup. You must clear the volume so Docker re-initialises it from the freshly built image.
# 1. Add or remove the package (updates pyproject.toml and uv.lock)
uv add <package>
uv remove <package>
# 2. Stop containers and remove the venv volume (keeps database intact)
docker compose -f docker-compose.dev.yml down
docker volume rm e-babylab_venv_cache
# 3. Rebuild the image and restart
docker compose -f docker-compose.dev.yml up -d --build
# 4. If the package adds Django apps with migrations, apply them
docker compose -f docker-compose.dev.yml exec web uv run python manage.py migrate
JavaScript#
Whenever you add, remove, or upgrade a JS dependency, commit the updated package-lock.json alongside your package.json changes:
cd tests && npm install
git add tests/package.json tests/js/package.json tests/e2e/package.json tests/package-lock.json
git commit -m "Update JS dependencies"
Data Model Changes#
If you make changes to the data models, you will need to create and apply migration files:
docker compose -f docker-compose.dev.yml exec web uv run python manage.py makemigrations
docker compose -f docker-compose.dev.yml exec web uv run python manage.py migrate
For more information, see the Django migrations documentation.
Docker#
Rebuild the image#
Pass --build to force Docker to rebuild the web image:
docker compose -f docker-compose.dev.yml up -d --build
Do this after:
Adding or removing a Python dependency (
pyproject.toml/uv.lockchanged)Modifying
Dockerfile(e.g. adding a system package)Switching base image or Python version
You do not need to rebuild when changing Python source files under src/ — those are volume-mounted and Django’s dev server reloads them automatically.
Stop e-Babylab#
docker compose -f docker-compose.dev.yml down
To stop without destroying the containers:
docker compose -f docker-compose.dev.yml stop
For more information, see docker compose down and docker compose stop.
View container logs#
docker compose -f docker-compose.dev.yml logs -f web # follow live output
docker compose -f docker-compose.dev.yml logs --tail=100 web # last 100 lines
Open a shell inside the container#
docker compose -f docker-compose.dev.yml exec web bash
Restart a service#
For example, restart the web (Django) service after .env changes:
docker compose -f docker-compose.dev.yml restart web
Wipe and reset the database#
Danger
This permanently deletes all data in the dev database.
docker compose -f docker-compose.dev.yml down -v # removes containers + named volumes
# optional: remove bind-mounted data
rm -rf media/ webcam/ reports/
docker compose -f docker-compose.dev.yml up -d --build
docker compose -f docker-compose.dev.yml exec web uv run python manage.py migrate
docker compose -f docker-compose.dev.yml exec web uv run python manage.py createsuperuser
down -v removes Docker named volumes (postgres_data_dev, venv_cache) but not bind-mounted directories. The rm -rf step is optional — include it to also clear uploaded media, webcam recordings, and reports for a fully clean slate. These directories are recreated automatically when Django needs them.