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

The Picker

For the complete documentation index, see llms.txt

An intent locator names what an element means rather than where it sits: “the checkout button”, not .btn.btn-primary. Resolving one takes a model that reads the page — that is the Picker.

Without it your tests still run. Locators that name what they click — getByRole, getByTestId, getByLabel — need no model at all, and they are the ones you should be writing anyway (see Stable selectors). The Picker is for the part before that: your agent exploring a page it has never seen, and turning what it finds into those locators.

The grounder a box gives you is the one your MCP server uses while your agent writes tests. A suite running on the box is handed the same address and token, but it resolves no intents of its own: a scenario carries the locator it was written with, and grounding belongs to the side that writes the test.

Most of this page is the Picker on a box. An account can also have one of its own, with no box rented at all — the same service, a different switch and a different way of being charged: Without a box.

Which model reads your page

Grounding takes two steps, and only the second one is what the mode is about. First the page is turned into a structured capture — the visible text, the roles and the accessible names of its elements — and indexed on hardware we operate; nothing from that step leaves us, in either mode. Then a shortlist of candidate elements is put to a model together with your intent phrase, and the model picks between them.

That split is how the boxes we run are configured, not a property of the software: the indexing model is ours in both modes and only the picker changes. A grounder you run yourself can be pointed at a provider for both steps, and then this promise is not the one that applies — see Your own endpoint.

There are two modes, and they are not the same deal:

ModeWhat it means
Our GPUThe model that reads the shortlist runs on hardware we operate as well. The shortlist reaches no third party.
External provider (Gemini)The shortlist and your intent phrase go to Google’s Gemini API — never the whole capture. We hold the key and pay the bill: you never bring one, and there is no on-prem grounder to install.

The mode is how the service is configured, not a per-account setting and not a switch in the dashboard.

The straight answer

If your app draws secrets on the page itself — one-time codes, tokens, card numbers — Our GPU is the mode to ask for.

Masking works by value: it hides a secret the run was told about, written the same way. A value your application renders on its own, that the run never knew, is text on the page like any other — and it reaches the model inside the shortlist like any other. That is how grounding works, not a defect about to be fixed, and it is the real reason the perimeter is worth paying for.

The privacy notice is the long version: what is sent in the provider mode, what is masked before it goes, and what we keep.

Your own endpoint, your own key

Everything above is the Picker on a box, where we run the grounder and hold the key. @unotest/web also grounds locally, and there the backend is yours:

  • UNOTEST_GROUNDER_MODE=local grounds through a model server you point it at. The default is ollama on http://localhost:11434 — nothing leaves your machine.
  • UNOTEST_GROUNDER_TRANSPORT=openai, with UNOTEST_GROUNDER_BASE_URL and UNOTEST_GROUNDER_API_KEY, sends the same requests to any OpenAI-compatible endpoint using your key.

The second one is outside everything this page promises. We do not hold that key, we never see those requests, and no ceiling of ours applies to them. Where the page content goes, under whose terms it is processed, and who pays for it are decided by the endpoint you named — read its terms before you point a test suite at it.

Turning it on and off

Terminal window
cd unotest && npx @unotest/web box picker on
cd unotest && npx @unotest/web box picker off

on asks the cloud to switch the Picker on for the box, mints the box’s tokens the way box access does, and writes the Picker’s three keys — UNOTEST_GROUNDER_MODE=remote and UNOTEST_GROUNDER_REMOTE_URL into unotest/.env, UNOTEST_GROUNDER_REMOTE_TOKEN into unotest/.secrets.

The server reads the token back from the same places, in this order: the environment first (an export, a CI secret), then unotest/.secrets, then unotest/.env. Nothing needs to be pasted into your editor’s MCP configuration — and nothing should be, since that file is committed.

The keys are read when the MCP server starts, so a Picker switched on under a running editor changes nothing until that editor reconnects. When grounding refuses, the message names every place the token was looked for: an editor that was not restarted is the first thing to rule out.

off switches it back and clears those three again — but only when they name this box’s grounder. A project pointed at a grounder of its own, or at another box’s, keeps its settings and is told so.

Neither command re-creates the box or re-pushes the suite, and asking for the state a box is already in is not an error.

box create asks the same question once, on a terminal, quoting what the Picker costs for the size you are creating. --picker answers yes up front; --yes, --json and a run with no terminal take the safe answer, which is no. You can connect it later with box picker on.

Without a box

The Picker is also an account’s own, and that one needs no box. It is switched on in the dashboard, on the Picker tab — the spend decision lives beside the price, not in a terminal — and each project is connected with one command:

Terminal window
cd unotest && npx @unotest/web picker on
cd unotest && npx @unotest/web picker off

picker on fetches this project’s own access and writes the same three keys box picker on writes: UNOTEST_GROUNDER_MODE=remote and UNOTEST_GROUNDER_REMOTE_URL into unotest/.env, UNOTEST_GROUNDER_REMOTE_TOKEN into unotest/.secrets, kept out of the repository. The editor has to be restarted afterwards for the same reason as above. Run it in a second project — a laptop, a CI job, another suite — and that project gets access of its own; the dashboard lists them and can disconnect any one of them alone.

off disconnects this project and clears those three again, and only when they name the account’s service: a project pointed at a model server of its own keeps it and is told so. The Picker stays on for the account and the other projects go on resolving intents. Access ends within the time it takes the withdrawn grant to reach the service, not at once — the command says so. What switches it off for everything, and stops it counting against the monthly ceiling, is the switch in the dashboard.

There is no hourly charge for a Picker without a box. What bounds the spend is the account’s monthly ceiling, and picker on says what it is before anything is spent.

So the two commands answer two different questions:

CommandWhat it switchesWhat it costs
box picker on|offthe Picker on one boxan hourly surcharge on that box
picker on|offnothing — it connects or disconnects one projectnothing per hour; the monthly ceiling is the limit

The account’s Picker is switched on and off in the dashboard, never by picker on|off.

What it costs

The Picker on a box has a list price: an hourly amount on top of the box rate, one per box size, held in the cloud’s own price list.

Right now it is included in the box price. Nothing is added to your hourly rate, and the dashboard shows the list price struck through beside it. When that ends, the list price is what starts being charged — and it will have been on the page all along rather than appearing on the day.

This page does not repeat the numbers. A price copied into prose is a price that goes stale: box create quotes what applies to the size you are creating, and the dashboard shows it beside each size and on the box itself.

Whatever applies, it starts and stops at the next hourly tick and not at the command: a started hour is charged at the rate it started with.

The monthly ceiling

On External provider (Gemini), the estimated spend on your account has a monthly ceiling, so a runaway suite cannot turn into somebody else’s bill.

  • At 80 % you get an email, and the dashboard shows how much of the ceiling the month’s estimate has reached.
  • At 100 % the grounder stops resolving intents.

What stops is writing tests, not running them — only the MCP server asks the grounder:

  • the ground_element tool;
  • an intent locator inside explore_step or find_element.

They fail with grounder-budget-exhausted — HTTP 402, never 401. The token is perfectly good and re-minting it changes nothing: this is money, not permission. The refusal quotes no amount and says nothing about any other account.

What keeps working: your scenarios, their runs on the box, and the viewer. An intent locator in a test file was resolved when the test was written and is a plain locator from then on — a suite that is already written does not touch the grounder at all.

There are two ways out:

  • top up the balance to raise the ceiling, or
  • stop the Picker — cd unotest && npx @unotest/web box picker off for a box’s, the switch in the dashboard for an account’s own. Either way the surcharge or the counting stops and intents ground locally again.

The ceiling turns over with the month either way.

The figure you see is an estimate, not an invoice: it is what we expect the provider to charge for what your account used, rounded up. We show it as a percentage of the ceiling rather than in euros, because it is not a number we own — you would rightly check ours against theirs.