This documentation is available as Markdown. For the complete index, see llms.txt. Skip to content

Overview

For the complete documentation index, see llms.txt

A box is a hosted unotest installation for one organisation. It runs the same viewer you use locally, in front of your own environments, and adds what a team needs around it: sign-in with your identity provider, roles, seats, an audit trail, scheduled runs and a place for CI to push suites to.

You rent a box yourself — cd unotest && npx @unotest/web box create, or Create box in the dashboard — and pay for it by the hour from a prepaid balance (prices); the steps are in Quick start → Run it on a box. From the CLI, renting a box, deleting one and turning its Picker on wait for a person: the key from login cannot spend or delete by itself, so the command prints where to approve and waits until somebody presses Approve on the dashboard’s Approvals tab. A request nobody answers expires in ten minutes, with nothing created and nothing charged. unotest can also provision one for you under your own domain and sign-in — see What you receive.

A box has a lifetime. At its end the box is deleted by itself and charging stops, so a box nobody remembered does not burn the balance. A new box gets 24 hours unless you choose otherwise — from @unotest/web 0.55.0 on, box create --ttl 8h (hours or days, up to 30d), or --ttl none for a box that runs until it is deleted, which is what a CI box wants. box extend <name> --ttl 4h moves the end later and charges nothing at once. box status, box list and the dashboard show when a box ends, next to its price; in the last hour box status prints the command that extends it. The owner gets a letter when the lifetime ends the box. Boxes rented before lifetimes existed have none and keep running while they are paid for.

Your tests never leave your repository except by your own action: a box holds no credential to your code and never pulls anything. Suites reach it through bundle push. To run them on every push, npx @unotest/web init --ci github writes the CI workflow — see Run on every push.

What is inside

ComponentWhat it does
GuardSign-in, roles, seats, the licence check, the administration pages and the audit trail. Every request to the box passes through it.
ViewerOne viewer per environment, in a container of its own. The same UI as cd unotest && npx @unotest/web viewer, with the controls your role does not permit greyed out. It sees the environment’s bundle and run history and nothing else of the box.
Run sidecarThe only component that holds the docker socket. Every run — ordered from the viewer, from CI, or by a schedule — and every bundle’s npm ci executes in a throwaway container it starts for that one job.
DaemonStores pushed bundles, holds each environment’s values and secrets, runs the per-environment queue and the schedules, and answers CI. It executes none of your code: it asks the sidecar for a container.
UpdaterInstalls the version your box is set to, verifying every artifact against a signature before it is installed and rolling back if the stack does not come up healthy. It never picks a version of its own — see Updates.

Projects and environments

A box serves one or more projects, each with one or more environments (test, staging, prod, …). An environment has a target — the URL the tests run against, injected into every run as APP_BASE_URL — plus its own variables, secrets, run history, queue and viewer.

A box runs only your tests. It does not start your app: every target must be an address the box reaches over the internet. localhost, a private network (10.x, 172.16–31.x, 192.168.x), a link-local address or a name only your network resolves (*.local, a compose service name) cannot work there — see What a box cannot reach.

The box’s configuration lists the environments; installing a bundle fills them. The list is set when the box is made: box create takes your project’s own environments — the unotest/.env.<name> overlays you run locally, or default when there are none — the dashboard asks for an address and the environment names on it, and a box unotest provisions for you gets the list you agree on. A pushed suite cannot create or rename an environment.

An environment gets its viewer once a bundle is installed for it. bundle push uploads the suite; the newest bundle of the environment’s branch becomes its bundle, the box installs it, and the viewer opens on it (see Pushing suites). Until then the environment is listed with no viewer.

Pull requests can get environments of their own (pr-<n>), created and removed automatically when a project has preview deployments — see Pushing suites.

Updates

A box never updates itself. A new release is offered, not installed: your box keeps running the version it is on until somebody presses Update.

Your box’s card in the cabinet shows both versions — what it runs and what is on offer — with an Update button when they differ. The card is also where you see the result: the box picks the new version up, installs it, and reports back. How soon it picks it up depends on the box: one whose updater checks for a new target every five minutes takes minutes, one still on the hourly check takes up to an hour and a half. A box waits that long only until it has the five-minute check — the update that brings it is the last slow one. If the stack does not come up healthy the updater rolls the box back to the version that worked, and the card says so with the reason; the update is not retried until you press again.

An agent does the same over the REST API: GET /api/v1/boxes/<name> carries an update object with what is installed, what is available and where the box is between the two, and POST /api/v1/boxes/<name>/update with {"version": "<the version you were offered>"} presses the button. The version is named on purpose — if a newer one appeared while you were looking, the call is refused with 409 update-stale and the version that is on offer now, rather than installing something nobody chose. cd unotest && npx @unotest/web box status prints the same three facts.

Balance and deletion

A box is paid for from your prepaid balance, one started hour at a time. When the balance cannot cover a box’s next hour, that box is deleted. The moment that will happen, if nobody tops up, is computed from the balance and every box on the account, each at its own rate: fundedUntil on a box in GET /api/v1/boxes/<name>, and on the account in GET /api/v1/balance. The paidUntil beside it is only the end of the hour already paid for — with credit left, the next hour is simply bought.

Where your code runs

Nothing of your suite executes next to the box’s own state. A run gets a container of its own: the bundle’s files read-only, the environment’s run history read-write, the target, variables and secrets of that one environment — and no view of the daemon, of other environments, of tokens or of channel credentials. The container is gone when the run is over. A bundle’s npm ci runs the same way, so a dependency’s install script builds a native module without touching the box. The viewer of an environment is a long-lived container on a network shared only with the guard and the daemon, with no route out of the box.

Two consequences for the UI on a box: editing a suite’s files is not available — the sources are read-only there, and a push is what changes them (such edits never survived the next bundle push anyway) — and Debug cannot attach to a run, which lives in a container the viewer has no path into. Details in Security model. What the container holds — which paths a suite may write, the variables it is given, where a shell() step starts — is on What a run gets.

What you receive

A box you rent yourself gets its address and tokens written into your project by box create. When unotest provisions a box for you:

  • A domain — https://tests.example.com, or a name under your own domain. Sign-in redirects, the CI webhook and the viewer all live there.

  • A licence — signed, with your organisation’s name, a seat count and an expiry date. Seats are counted against people who have signed in recently; see Sign-in.

  • Project tokens — for bundle push and CI, one per project. Tokens that may also write environment values are minted separately.

  • A Picker, if you want one — the grounder that resolves intent locators while your agent writes tests, billed per hour on top of the box. It serves the MCP server on your own machine, not the runs on the box. box create --picker quotes what it costs before anything is charged, and what it reads is on The Picker.

You provide the identity-provider details (an OAuth client, and for Google Workspace a directory service account) and the mapping from your groups to roles. Nothing about your Workspace is stored anywhere but on your box.

Roles

Two roles exist:

  • admin — run tests, open the terminal, switch an environment’s bundle, administer users, see and set environment values.
  • readonly — browse scenarios, runs and results. Run and the terminal are unavailable, and the proxy refuses them regardless of what the UI shows.

Roles come from your directory groups, and an administrator can pin one on the box. Details in Sign-in and Administration.

Next