# End-to-end tests

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:

```bash
uv run pytest e2e -q
```

## What one run does

```
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:

1. Create a throwaway PostgreSQL database `navidb_e2e_<pid>_<hex>` from the
   DSN in `.env` (or `E2E_DATABASE_URL`) and drop it afterwards.
2. Start `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.
3. 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:

   ```bash
   cd webclient && npm run build
   ```

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

## Scenarios

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

## Requirements

- Local PostgreSQL reachable through `DATABASE_URL`, and a role allowed to
  `CREATE DATABASE` (the local dev role is).
- A working real LLM route: `llm.json` plus the credentials in `.env`.
- `psql` on `PATH`.
- Playwright's Python package (already a dependency) and Chromium at
  `/usr/bin/chromium`.
- A fresh `webclient/dist`.

## Tuning and diagnostics

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

## Why the tests can fail without a code change

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.
