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

Environments & values

For the complete documentation index, see llms.txt

A suite’s externals — the URL it points at, non-secret settings, credentials — live with the environment on the box, never in a bundle. The same bundle runs in test and in prod, and a new bundle cannot reset an environment’s configuration.

An environment

Each environment is declared in the box’s configuration, which unotest maintains with you:

SettingMeaning
nameStable id (test, staging, prod). It names the run history, the queue and the viewer, so renaming one is renaming its history.
targetThe URL the tests run against, injected into every run as APP_BASE_URL — which unotest.config auto-wires baseUrl from.
defaultBranchWhich branch’s newest bundle the environment follows for schedules and after a restart (main by default).
allowSwitchWhether anyone may point the environment at another bundle from the viewer. Off for environments where “what is deployed” is the point.
concurrencyRuns at a time in this environment (1 by default).
variablesNon-secret values every run gets.

Three layers of values

LayerWhatWho sees the value
targetThe URL, as APP_BASE_URLeveryone
variablesNon-secret values every run getsadministrators; the viewer’s Variables panel
secretsCredentialsnobody — only a run’s process

The configuration gives each environment its initial target and variables. Everything set through the page or the commands below layers over that: a value set on the box wins over the configuration, and a secret wins over a variable of the same name. No restart is needed — a run reads the current values when it starts.

What a run sees

For every run the box builds the process environment in this order, later entries winning:

  1. box policy (UNOTEST_BROWSER_CHANNEL=bundled — the box uses the bundled browser);
  2. APP_BASE_URL = the target (an override if set, else the configured one);
  3. variables from the configuration;
  4. variables set on the box;
  5. secrets.

The viewer’s Variables panel lists the same names as read-only rows with a box badge; a secret’s value is never sent to the browser, only its name. Changing values restarts that environment’s viewer: the page reloads, and a debug session open in it ends.

The values page

Administration → Environments → the environment. One table, one row per key:

RowWhat it shows
APP_BASE_URL (target)The URL, tagged config or override.
a variableIts value, tagged config (from the configuration) or box (set here or by env push / env set).
a secret (secret tag)Dots. The value is not on the page.
  • Edit (pencil) opens the value in place; Enter or the check mark saves, Esc or the cross cancels. Overriding the target and reverting it (back to configured) work the same way.
  • Remove (bin) is offered for what the box itself holds — a box variable, a secret, a target override — and asks first. A config variable comes from the configuration and is not removable here.
  • The add row at the bottom: NAME, value, a secret checkbox, Add. Ticked, the value goes into the secret store.

Each change is one audit event: env.value.set or env.value.removed, carrying the name and never the value.

Revealing a secret

The eye asks the box for a secret’s value and shows it in the row (the eye becomes hide); the copy button puts it on the clipboard without showing it.

Every look is audited. Each reveal or copy is one secret.revealed event with who, which secret(s), and how: show | copy. The answer is served with Cache-Control: no-store, so a revealed value does not survive in a browser cache. If you see reveals you did not expect in Recent audit, that is the trail doing its job.

JSON view

The JSON switch shows the same rows as one document:

{
"APP_BASE_URL": "https://staging.acme.io",
"REGION": "eu",
"GEMINI_API_KEY": "••••"
}

Reveal all fills in the secrets (one audited look, listing every name). Edit makes the document editable; Save sends the whole environment at once; Cancel discards. On save:

  • APP_BASE_URL equal to the configured target, or absent, clears the override; any other URL sets one.
  • A secret left as "••••" — or as the value Reveal all showed — is kept as it is. A different string replaces it. A removed key removes the secret. To add a secret, use the table’s add row.
  • Every other key is a variable: a new key becomes a variable, a removed one is removed.
  • Anything that is not an object, a non-string value, or a name that could not be a variable is refused on the page; nothing is sent.

The batch is applied or refused as a whole (env.values.replaced in the trail). A refusal from the box is shown as the box phrased it — for instance cannot keep what the environment does not hold when the page and the box disagree about which secrets exist.

Without JavaScript

Every row is an ordinary form: set / remove post it. Reveal, copy and the JSON view need the script.

From the command line

The same values can be set from a laptop or a deploy job, with a project token minted for values (see Tokens).

env push — send your files

Terminal window
UNOTEST_BOX_URL=https://tests.example.com \
UNOTEST_BOX_TOKEN=unob_… \
npx @unotest/web env push dev

dev is the local environment — the one --env dev uses — and the command reads exactly what a local run reads: unotest/.env, .env.dev, .secrets, .secrets.dev, later files winning. Then:

  • .env* → the environment’s variables
  • .secrets* → its secrets
  • APP_BASE_URL, from either → its target
  • UNOTEST_*not sent, listed as skipped: they configure the runner on your machine
  • empty values → not sent, listed as skipped

The box environment is assumed to have the same name; --box-env <name> says otherwise. --env-file / --secrets-file read named files instead (repeat for layers; later files win). --dry-run reads and lists without sending; --json prints the box’s answer.

env push replaces the three layers: a name your files no longer carry is removed from the box, and the command says which. Values set by hand (env set, the values page) are part of what gets replaced.

A value is never printed — not on success, not with --json, not in --dry-run. Names are.

env set, env rm — one value

Terminal window
printf '%s' "$GEMINI_API_KEY" | npx @unotest/web env set dev GEMINI_API_KEY --secret
printf '%s' 'eu-west' | npx @unotest/web env set dev REGION
printf '%s' 'https://pr-42.preview.example' | npx @unotest/web env set dev APP_BASE_URL
npx @unotest/web env rm dev REGION
npx @unotest/web env rm dev APP_BASE_URL # back to the configured target

The value comes from stdin, never from the command line, so it does not land in shell history or ps. --secret makes it a secret; APP_BASE_URL is always the target.

env set has no name filter: unlike env push, it accepts UNOTEST_* too. The asymmetry is deliberate — env push reads the files a local run reads, and those UNOTEST_* configure the runner on your machine, so sending them would be an accident. Naming one by hand is a decision, so it is allowed. That is how a box gets the runner settings it does need, such as UNOTEST_JUDGE_MODE and the judge’s key:

Terminal window
printf '%s' local | npx @unotest/web env set dev UNOTEST_JUDGE_MODE
npx @unotest/web env set dev UNOTEST_JUDGE_PREAMBLE < preamble.txt

The only name the box refuses is APP_BASE_URL as a plain variable — it is the environment’s target, set as shown above. A value cannot be empty: the box has no way to declare one, so use a non-empty sentinel and treat it as the default in your scenario.

Tokens

The CLI authenticates with a project token (unob_…). Tokens are issued per project by unotest and come in two scopes:

ScopeMay
bundlespush bundles and order runs — what a CI job needs
bundles+valuesalso write the project’s environment values

A bundles token used with env push or env set is refused with forbidden (this token pushes bundles only). That is deliberate: the token in your CI secret store that pushes suites on every commit should not also be able to rewrite an environment’s credentials.

The token decides the project; a command names only the environment. Nothing in a request can point it at another project’s values.

Where values go, and where they never go

  • Into a run’s process — target, variables and secrets, in the order above. The runner masks secret values in logs and artifacts as it does locally.
  • Into the viewer — names only, with a box badge.
  • Over the wire from the CLI — values go in; no answer ever carries one back. Secrets are write-only on that door.
  • Into the audit trail — names only, never a value.
  • Into a bundle — never. unotest/.env* and .secrets* are excluded from bundle push even when committed.

The one place a secret’s value is ever shown again is the values page, to an administrator, with a secret.revealed event for each look.