Skip to content

Trials, thresholds and cost

AI systems, simulated users and attackers don’t behave identically twice. This page covers the three controls that make their results trustworthy and affordable: trials (run the same test several times and report a pass rate), thresholds (the score that counts as passing), and cost (what calls a test makes, which judge to use, and how to estimate the bill).

A test with trials greater than 1 runs its whole check that many times, one after another, inside one run, and reports the combined result. Use it when a single run can pass or fail by chance: a conversation where the simulated user takes a different path each time, a red-team attack that sometimes gets through, an endpoint that answers slightly differently on each call.

Set it under Options → Trials when you create a test (or on the test’s Settings tab), from Once up to 10 times — report a pass rate. Over the API, send "trials": 5 with the test.

Kind With several trials, the run’s score is It passes when
conversation, mcp, a2a (and ui, api) Trials passed ÷ trials (the pass rate) The pass rate reaches the pass mark
eval, rag, redteam (and quality) The mean of the trial scores The mean reaches the pass mark

If every trial errors, the run is an error. With a single trial, the run simply takes that trial’s result.

A conversation run with 3 trials: the Overview's Pass rate shows 2 of 3 trials passed at 67 %, with a chip per trial and duration, turns and tokens

On the run page, Overview shows the pass rate and one chip per trial; each trial keeps its own status, score, summary and transcript (in the run’s trials list over the API). For evals, every trial scores the same rows again, and the Results tab marks each row with its trial.

Limit Value
Trials per test 1–10, and never more than MAX_TRIALS (default 10)
Time per run The worker allows 300 seconds per trial
Trials Retries
When they run Always, trials times Only after a failure
What you get A pass rate or mean score A pass on retry, marked flaky
For AI-product tests Recommended Off unless the test sets retries itself; the project’s default retries apply to UI and API tests only

Use trials to measure non-determinism, and retries to absorb the occasional infrastructure hiccup in browser tests.

The threshold, or pass mark, is the score a run must reach to pass. A run uses the first of these that is set:

  1. The test’s own threshold in its config (the Threshold field of an eval, the Pass mark of a red-team test).

  2. The kind’s default: 1.0 for red-team tests, so every attack must be resisted.

  3. The project’s Pass mark (Project → Settings → General, score_threshold over the API), which defaults to 80 %.

Write it as a fraction (0.85) or a percentage (85); any value above 1 and up to 100 is read as a percentage. 0 is honoured (the test always passes on score) and 1 means 100 %.

Kind What the threshold applies to
eval, rag The mean score over the rows, and each row’s own passed flag.
redteam The share of attacks resisted. The project pass mark doesn’t apply; only the test’s threshold lowers the default 100 %.
conversation, mcp, a2a The pass rate, when trials is more than 1. With one trial, the run passes or fails on its own checks.

Every scored run records the threshold it was judged against, and the CI quality gate compares the score with that value. Conversation, MCP and A2A tests have no threshold field in the app: set threshold in their config over the API, or rely on the project pass mark.

Set the project's pass mark and judge model
curl -s -X PUT http://localhost:8080/api/projects/$PROJECT_ID \
-H "Authorization: Bearer $PLAINWRIGHT_TOKEN" -H "Content-Type: application/json" \
-d '{"score_threshold": 0.85, "judge_ai_config_id": "<ai-config-id>"}'

Send "judge_ai_config_id": "" to go back to the default judge (the cheap model of the run model’s provider).

AI-product tests call two models: the run model (simulated users, attackers, agents, and the explanation of a failure) and the judge model. Your own system’s model costs are outside Plainwright.

Test Run-model calls per trial Judge calls per trial
Conversation over HTTP or A2A Up to max_turns short simulated-user turns (one fewer with opening_message) 1
Conversation in a website chat One browser-agent session of up to 12 + 5 × max_turns steps (60 at most); each step is a call that includes a page snapshot 1
Red team over HTTP or A2A Up to max_turns − 1 attacker turns per attack (the opening probe is free) 1 per attack (none when a secret leaks)
Red team in a website chat One browser-agent session of up to 12 + 4 × max_turns × attacks steps (90 at most) 1 per attack
LLM or RAG eval None 1 per row; none for a latency-only eval
MCP server Up to max_steps agent steps (default 20); none with contract_only None
A2A agent None for a smoke check; as a conversation otherwise 1 for a conversation

On top of that, every trial repeats all of the above, a failed run makes one more run-model call for its plain-English explanation, and any reply that isn’t valid JSON costs one extra repair call.

The judge is the project’s Judge model (Project → Settings → General). Left at its default, the judge uses the run model’s provider, key and endpoint with that provider’s cheap model: claude-haiku-5-5 for Anthropic, gpt-6-luna for OpenAI, gemini-3.5-flash-lite for Gemini, openai/gpt-oss-20b for Groq and anthropic/claude-haiku-5.5 for OpenRouter. Ollama and custom OpenAI-compatible endpoints judge with the run model itself. With no AI configuration at all, both fall back to the server default, claude-haiku-5-5 with the server’s ANTHROPIC_API_KEY.

  • The default is already inexpensive. Grading one answer against clear criteria is a much easier job than driving a browser, so the cheap model is usually enough. The model lists in AI providers mark these models fast & cheap, and the app reads them from GET /api/ai-configs/models, where the default judge carries "judge_default": true.
  • Check that the key reaches the cheap model. If the run model’s key can’t use it (an account limited to certain models, say), judging fails; choose a Judge model you know works.
  • Upgrade only where it matters. Read the judge’s reasoning on a few runs. Move a project to a stronger judge when its verdicts disagree with yours, not by default.
  • Use a different model family from your system’s so the judge doesn’t favour its own style.
  • Local models cost nothing per call. An Ollama model on your own machine works as a judge, provided it reliably returns JSON.
  • Reasoning models think before answering. If their replies come back cut off or unparseable, raise JUDGE_MAX_TOKENS.

Operators can bound spend for the whole server with these environment variables (set them for the backend and the worker). All of them are in Configuration.

Variable Default Caps
MAX_TRIALS 10 Trials per test (never more than 10).
MAX_EVAL_ROWS_PER_RUN 500 Dataset rows scored per eval or RAG run.
MAX_CONVERSATION_TURNS 12 Turns per conversation and per red-team attack.
JUDGE_MAX_TOKENS 800 The minimum output-token budget for a judge reply (it grows with the number of metrics).
MCP_TARGET_TIMEOUT_S 60 Seconds per request to an MCP server under test.

When billing is enabled, a run whose project owner has used up the plan’s monthly run or token allowance stops with an error before any model is called.

Every run records its tokens and an estimated cost in US dollars (cost_usd). Each model call in the run (agent, simulated user, attacker, judge and failure explanation) is priced at the list price of the model that made it, from a built-in price table checked against the vendors’ pricing pages. The run page shows the estimate next to Tokens (for example ≈ $0.07), and Analytics adds it up by model.

  • Ollama models cost $0: hardware and electricity aren’t counted.
  • A model without a known price (a custom endpoint, a contact-sales model, an id the table doesn’t know) leaves cost_usd empty for that run instead of guessing. Tokens are always recorded.
  • Discounts aren’t modelled. Prompt-caching and batch discounts, and anything your contract changes, make the real bill lower than the estimate. Long-prompt price tiers are applied per call.

To plan a budget before running anything:

  1. Run the test once with one trial.

  2. Read its cost and tokens. The run page shows both; the API returns cost_usd, input_tokens, output_tokens and total_tokens on GET /api/runs/{run_id}. The totals include every simulated-user, agent, judge and explanation call.

  3. Multiply by trials and by how often the test runs (schedules, CI pushes, manual runs).

For example, a 40-row eval that reports 46,000 input and 4,800 output tokens, judged by claude-haiku-5-5 ($0.10 per million input and $0.50 per million output tokens on 21 October 2026), costs about 0.046 × $0.10 + 0.0048 × $0.50 ≈ $0.007. Run on 20 pushes a day, that is about $0.14 a day. Prices change, so check your provider’s own pricing page before relying on the numbers.

Analytics shows token usage and estimated cost by model and by run. Tokens are grouped under the run’s model, so judge tokens appear there too.

  • Prefer an HTTP or A2A target to a website chat: a browser-agent session costs far more than a few short simulated-user turns.
  • Give conversations an opening_message, which saves one call each time.
  • Run MCP contract checks (contract_only) on every push, and full tool tests on a schedule.
  • Score a fixed sample_size in CI and the full dataset nightly. A latency-only eval is free.
  • Run the critical red-team packs in CI (pii_leak, harmful_content) and the full set before a release.
  • Keep trials for tests that need them, and don’t add retries to AI tests: each retry is a full run.