The e2e suite drives the real stack in a browser: a real Navi backend, the built webclient, a real model from llm.json, and the actual file tools. A test sends a message, the agent calls filesystem_read / filesystem_write / file_ai, and the assertions check both the rendered tool cards and the file on disk.
It lives in e2e/, outside testpaths = ["tests"], so the normal suite never collects it. Run it explicitly:
uv run pytest e2e -q
e2e/conftest.py # database, server process, browser, per-test screenshots e2e/support.py # session/HTTP/UI helpers e2e/test_agent_file_tools.py
Per run the fixtures:
navidb_e2e_<pid>_<hex> from the DSN in .env (or E2E_DATABASE_URL) and drop it afterwards.navi-server on a free port with a temporary working directory, its own temp workspace/ and session_files/, and NAVI_AUTH_ENABLED=false (the local anonymous admin). Because the working directory is temporary, mcp_servers.d/ is not found, so no MCP server is started and the run does not depend on their credentials.Serve the built SPA from webclient/dist — the same assets production serves. The suite refuses to run when webclient/src is newer than dist/index.html; rebuild first:
cd webclient && npm run build
Launch Chromium through Playwright with /usr/bin/chromium (E2E_CHROMIUM overrides the path) and a fresh context per test. Service workers are blocked so a cached build cannot answer.
| Test | What it proves |
|---|---|
test_agent_writes_then_reads_file |
filesystem_write write creates the file, filesystem_read read renders its numbered content, the file matches on disk |
test_agent_edits_file_and_reads_diff |
filesystem_write edit changes the file, filesystem_read diff renders the unified diff |
test_agent_uses_file_ai_to_edit |
file_ai edit applies an instruction through the AI helper and renders its diff |
DATABASE_URL, and a role allowed to CREATE DATABASE (the local dev role is).llm.json plus the credentials in .env.psql on PATH./usr/bin/chromium.webclient/dist.| Variable | Default | Meaning |
|---|---|---|
E2E_DATABASE_URL |
.env's DATABASE_URL |
DSN used to create/drop the throwaway DB |
E2E_CHROMIUM |
/usr/bin/chromium |
Browser binary |
E2E_SERVER_TIMEOUT |
120 |
Seconds to wait for /health |
E2E_TURN_TIMEOUT_MS |
300000 |
Milliseconds one agent turn may take |
E2E_SHOTS_DIR |
/tmp/navi-e2e-shots |
Screenshots and browser logs on failure |
E2E_LOG_LEVEL |
INFO |
Backend log level; the log lives in the run's temp dir |
The model is the production route, not a stub. It can pick terminal instead of filesystem_write, refuse, or ask a question. The prompts are explicit ("use the tools yourself", exact action/arguments), but this is the price of an honest end-to-end check. On failure the suite saves a screenshot and the browser console log under E2E_SHOTS_DIR, and the backend log is in the run's temp directory (/tmp/pytest-*/navi-e2e*/server.log); both make the model's actual choice visible.