# UniDesk E2E Reference

UniDesk delivery is not complete until the public production frontend, public dev frontend proxy when deployed, public provider ingress, internal core API, PostgreSQL database, local provider-gateway self-connection, and frontend Playwright flow pass the relevant end-to-end checks. The canonical automated command is `bun scripts/cli.ts e2e run`; set `UNIDESK_E2E_FRONTEND=dev` to target the public dev frontend proxy and `UNIDESK_E2E_AUTH_USERNAME` / `UNIDESK_E2E_AUTH_PASSWORD` when that environment uses credentials different from root `config.json`.

## Required Preconditions

- `config.json` `network.publicHost` must be the externally reachable host name or IP of the main server, not `127.0.0.1`, when validating browser access from outside the server.
- The Docker stack must be running through `bun scripts/cli.ts server start`, and `bun scripts/cli.ts server status` must report healthy frontend, provider ingress, internal core, database, and provider-gateway containers.
- Browser runner dependencies, Playwright execution shape, screenshots, focused frontend checks and layout assertions are owned by `$unidesk-webdev`; this file only records delivery gate scope.

## Automated E2E Scope

`bun scripts/cli.ts e2e run` validates the public production frontend, optional public dev frontend proxy, public provider ingress, internal core API, PostgreSQL database, provider-gateway self-connection and registered user service paths derived from `config.json`. The CLI response is intentionally bounded: it prints check names/statuses, screenshot path, counts, failed checks and `resultPath`; full diagnostics stay under `.state/e2e/`.

## Selective Execution Rule

Run E2E in two stages. First run the smallest real selection that covers the change, then run the full public `bun scripts/cli.ts e2e run` before claiming delivery. Selection uses `--only <pattern[,pattern...]>` and `--skip <pattern[,pattern...]>`; patterns may be full check names, prefixes or wildcards. Frontend selection and layout acceptance details are in `$unidesk-webdev`.

Typical targeted commands:

- `bun scripts/cli.ts e2e run --only frontend:pipeline-step-timeline-visible`
- `bun scripts/cli.ts e2e run --only frontend:pipeline`
- `bun scripts/cli.ts e2e run --only frontend --skip frontend:todo-note-integrated-visible,frontend:findjob-integrated-visible`
- `bun scripts/cli.ts e2e run --only network,provider-ingress`

Stable non-frontend E2E groups:

- Public exposure: only production frontend, dev frontend proxy and provider ingress are unrestricted public entries; private service ports must remain private or source-restricted.
- Core API and database: internal overview/performance endpoints, PostgreSQL readiness and named volume persistence must pass.
- Provider self-connection and remote control: main-server provider-gateway must be online, versioned, always-enabled, host SSH capable and able to plan `provider.upgrade` without recreating itself.
- User services: registered services must be visible through the real UniDesk proxy path, with health and representative API checks for Todo Note, OA Event Flow, k3s Control, Code Queue, File Browser, FindJob, Pipeline, MET Nonlinear and ClaudeQQ.
- Pipeline/OA event flow and Code Queue liveness selections remain first-class E2E checks; their current exact check names live in the CLI output and source tests, not in this long-term reference.
- Frontend Playwright and dense layout assertions are governed by `$unidesk-webdev`.

## Frontend JSON Rule

The frontend must render JSON data into React controls by default. Raw JSON is allowed only after an explicit `查看原始JSON` user action, and E2E must fail if the initial page exposes raw JSON text or a raw JSON block.

Remote update records in the frontend are covered by the same rule: `provider.upgrade` task history must be rendered as rows/cards with status, mode, task id, source, duration, policy, outcome summary, and updated time. The page must not expose upgrade plan/result JSON as a log block unless the operator clicks `查看原始JSON`.

Provider operation availability is also covered by the structured rendering rule. `host.ssh` availability must be displayed as badges or equivalent controls derived from capabilities and `hostSsh*` labels, and remote update availability must be displayed from `provider.upgrade` capability plus the `always-enabled` policy; these fields must not require opening raw Provider JSON.

User service pages are covered by the same rule. `Todo Note` must show lists, task tree, filters, reminder input, movement controls, undo/redo and metrics as controls; `OA Event Flow` must show health, live tag stream state, event table, tag filter presets and Trace stats table as controls; `Code Queue` must show queue cards, execution liveness diagnostics, live transcript, model/cwd/max attempt inputs, judge decision, attempt table, append prompt, interrupt and retry controls; `File Browser` must show D518 as the default target, D601 as an alternate target, a screenshot export action, and an embedded upstream WebUI frame served through `/api/microservices/<id>/proxy/` with compact file rows that do not let material-icon fallback text cover file metadata; `FindJob` must show metrics, jobs and drafts as cards/tables; `Pipeline` must show component classes, React Flow graph nodes/edges, run cards, Gantt execution lines and OpenCode Trace timelines as controls; `MET Nonlinear` must show queue rows, GPU/image cards, a real path tree for the project library, structured project/job detail panels, project config preview, `data/` training state, model parameter count, metrics, progress bars, ETA, `epoch/h` speed and history diagnostics as controls; `ClaudeQQ` must show NapCat HTTP/WS/login badges, QR/login panel, event cache, subscriptions and message push controls; the full user-service config, summary, snapshot, jobs preview, drafts, OA events and run JSON can only appear after an explicit `查看原始JSON` click.

## Public Boundary Rule

The production frontend URL, dev frontend proxy URL and provider ingress URL are the only unrestricted public network interfaces. backend-core REST API remains Docker-internal only; PostgreSQL and OA Event Flow may expose restricted host mappings solely for controlled Code Queue nodes, and E2E must prove those mappings are unreachable to generic clients or protected by explicit source rules.

## Database Persistence Rule

The PostgreSQL data volume is the named Docker volume `unidesk_pgdata_10gb`. CLI server control commands must never use `docker compose down -v`, `docker volume rm`, or any equivalent data-volume removal. To validate persistence, insert a marker row into `unidesk_e2e_markers`, run `bun scripts/cli.ts server start` or a full stop/start cycle, and verify the marker row still exists.

## User Service Restart-Recovery Rule

Any new user service, service migration, or change to a service's Compose/docker run/k8s configuration must prove it can recover after container restart and Docker daemon restart. The delivery evidence must include the service's `config.json` id/provider/container or Kubernetes Service mapping, restart policy or Deployment replica policy, private port or ClusterIP Service, persistent mounts or PostgreSQL tables, health readiness fields, and at least one post-restart `bun scripts/cli.ts microservice health <id>` plus a representative `microservice proxy` check through the real UniDesk path. `k3sctl-managed` services must prove the proxy path through `k3sctl-adapter` and Kubernetes API service proxy, not the provider-gateway direct business path.

D601 services have an extra gate because Windows, WSL, Docker Desktop and native k3s are separate supervisors: record the Windows scheduled task or equivalent keepalive, run `docker inspect` to confirm Docker-managed services such as `met-nonlinear-ts` have non-empty restart policies and host bind mounts for durable state, and run `kubectl -n unidesk get deploy,pod,svc,endpoints claudeqq -o wide` for k3s-managed ClaudeQQ. Then verify MET Nonlinear queue/image health and ClaudeQQ logged-in NapCat HTTP/WebSocket state through the real UniDesk proxy after the restart. A service that only becomes `running` but loses login, queue, token, subscription, data directory or pending work is not restart-recovery complete.

## Delivery Gate

Before claiming delivery, run these checks and keep their JSON output or screenshot path available for review:

1. `bun scripts/cli.ts check`
2. `bun scripts/cli.ts server start`, then `bun scripts/cli.ts job status latest` until `succeeded`
3. `bun scripts/cli.ts server status`
4. `bun scripts/cli.ts e2e run` and inspect `failedChecks` or the emitted `resultPath` if any check fails
5. a database persistence marker check across at least one CLI-controlled restart

## Provider Upgrade Gate

When delivery explicitly includes upgrading or rebuilding a compute-node `provider-gateway` such as D601 or D518, the automated E2E plan check is not sufficient. The operator must first bootstrap any legacy provider only from a node-local terminal, node-owned web terminal, systemd, scheduled task, or detached shell if it cannot yet schedule upgrades; SSH passthrough carried by the same provider-gateway must not be used for synchronous self-rebuilds. Then run `provider.upgrade` with `mode: "schedule"` against that Provider ID, confirm the task succeeds, confirm the sleep-and-validate candidate gateway reconnects in the public frontend, confirm `docker inspect` reports final restart policy `always` and PID mode `host`, record whether systemd/PM2/Windows scheduled task/Docker Desktop autostart is the daemon-level supervisor, and finally verify any required `host.ssh` capability with `trans <PROVIDER_ID> hostname`. This schedule check is a node-upgrade gate, not a replacement for the standard public frontend Playwright E2E gate.

External compute nodes should run that schedule check through the remote main-server passthrough form: `bun scripts/cli.ts --main-server-ip 74.48.78.17 debug dispatch <PROVIDER_ID> provider.upgrade --mode schedule --wait-ms 15000`. The default remote transport logs in to the public frontend and does not require a main server SSH key; this proves the node can validate itself without direct access to backend-core REST or PostgreSQL.
