Halmurat T.
Halmurat T.

Senior SDET

Home Blog Books ask About

The Dispatch

Weekly QA notes from the trenches.

Welcome aboard!

You're on the list. Expect real-world QA insights — no fluff, no spam.

© 2026 Halmurat T.

Automation 28
  • Selenium
  • Playwright
  • Appium
  • Cypress
AI Testing 67
CI/CD 12
  • GitHub Actions
  • Slack Reporting
QA Strategy 10
Case Studies 8
Blog/AI Testing
AI TestingHalmurat T./July 23, 2026/11 min

Your AI Test Should Be Allowed to Fail — By Design

Filed underai-testing/playwright/llm/cicd
Your AI Test Should Be Allowed to Fail — By Design

Table of Contents
  • Why does a correct AI test fail half the time?
  • Why is retries the wrong fix for AI flake?
  • How many runs before an AI test is actually “green”?
  • Why 18/20 isn’t a 90% feature
  • The rule of three for zero-failure runs
  • Where should the reliability gate run?
  • Isolate the gate in its own Playwright project
  • What number do you actually gate on?
  • The one thing to do today

On this page

  • Why does a correct AI test fail half the time?
  • Why is retries the wrong fix for AI flake?
  • How many runs before an AI test is actually “green”?
  • Why 18/20 isn’t a 90% feature
  • The rule of three for zero-failure runs
  • Where should the reliability gate run?
  • Isolate the gate in its own Playwright project
  • What number do you actually gate on?
  • The one thing to do today

Your AI-feature test asserts the chatbot answered the refund question correctly, and it went green. Run it again — same input, same build — and it’s red. Nothing broke. You’re asking a probabilistic system a yes/no question and treating one coin flip as a verdict. The honest test isn’t “did it pass?” It’s “does it pass often enough?” — and that’s a number with a confidence interval, not a boolean.

This is the part most teams get backwards. They feel the flake, reach for Playwright’s retries, watch the suite go green, and ship. What they’ve actually done is convert a reliability regression into a silent pass, which is far more dangerous than a flake you can see. The senior move is to stop pretending an AI assertion is deterministic and start gating on pass@k with an explicit reliability budget — the same discipline you’d bring to an SLO. Below is the math, the Playwright wiring, and the one config change that keeps retries from lying to you.

Why does a correct AI test fail half the time?

Because the feature under test has a true success rate below 100%, and a single assertion samples it exactly once. If your checkout assistant answers correctly 90% of the time, a one-shot expect is a Bernoulli trial that fails 1 run in 10 — indistinguishable from a real bug at the moment it goes red. The same model drift that makes LLM features non-deterministic between runs means there is no single “right answer” to snapshot against.

The evaluation literature already solved the measurement problem. pass@k — introduced with OpenAI’s Codex/HumanEval work — is the probability that at least one of k independent samples succeeds. It exists precisely because LLM-based systems exhibit run-to-run variability that a single sample can’t characterize. The mistake is importing the metric without importing the statistics behind it.

§ One assertion, one sample

90%

True success rate

Feature works most of the time

1 in 10

Runs go red

Pure sampling, no bug

0

Bugs found

It's a false alarm

Why is retries the wrong fix for AI flake?

Because retries answer “did any attempt pass?” when the question you actually care about is “how often does it pass?” — and those diverge exactly when quality regresses. Playwright’s retry mechanism reruns a failed test up to N times and marks it passed if any attempt succeeds. That’s pass@(retries+1), and it’s great for genuine infrastructure flake: a slow network, a cold container, a race you haven’t fixed yet.

It’s poison for model quality. Watch what retries do to a regression:

§ retries: 2 masks a 30-point drop

90% → 60%

Real quality drop

Model regressed hard

93.6%

pass@3 still green

1 − 0.4³

0

Alarms raised

Nightly stays green

If your feature’s true success rate silently falls from 90% to 60%, retries: 2 computes 1 − (1 − 0.6)³ = 93.6% — your suite still passes almost every time. You’ve built a green check that hides a real bug, and you’ll find out from a customer. Retries suppress the signal you most need to see.

[ WARNING ]

Retries are for infrastructure flake, never for model quality. A retry that turns a 60%-reliable feature green isn’t stabilizing your suite — it’s laundering a regression into a passing build.

How many runs before an AI test is actually “green”?

Enough runs that the lower bound of your estimated success rate clears your target — not enough that one lucky pass squeaks through. You’re not asserting a boolean anymore; you’re estimating a proportion and gating on its confidence interval. Sample the feature N times, count the successes, and compute a Wilson lower bound on the true success rate.

tests/ai/reliability.ts
// Wilson score lower bound — stable at small N, unlike the naive normal interval.
export function wilsonLower(successes: number, n: number, z = 1.96): number {
if (n === 0) return 0;
const p = successes / n;
const denom = 1 + (z * z) / n;
const centre = p + (z * z) / (2 * n);
const margin = z * Math.sqrt((p * (1 - p)) / n + (z * z) / (4 * n * n));
return (centre - margin) / denom;
}

Why 18/20 isn’t a 90% feature

Here’s the number that makes the point concrete. Observe 18 passes out of 20 — a healthy-looking 90% — and the 95% Wilson lower bound is only ≈0.70. You cannot honestly claim a 95%-reliable feature from 18/20. The interval is telling you that you don’t have the evidence yet, which is the entire job of the gate.

The rule of three for zero-failure runs

For the clean case — zero failures — lean on the rule of three: if you see 0 failures in N runs, you can be ~95% confident the true failure rate is below 3/N. So a gate of “0 failures in 60 samples” buys you ~95% confidence the feature fails less than 5% of the time. That single heuristic is worth more than any retry count you’ll ever tune.

tests/ai/reliability.spec.ts
import { test, expect } from '@playwright/test';
import { wilsonLower } from './reliability';
test('checkout assistant clears the reliability budget', async ({ request }) => {
const N = 60, target = 0.95;
let passes = 0;
for (let i = 0; i < N; i++) passes += (await answersRefundQ(request)) ? 1 : 0;
const lower = wilsonLower(passes, N);
// Gate on the LOWER bound, not the point estimate — that's what kills false greens.
expect(lower, `pass=${passes}/${N}, 95% lower=${lower.toFixed(3)}`)
.toBeGreaterThanOrEqual(target);
});

Where should the reliability gate run?

Not on every pull request — it’s too slow and too expensive for the merge path. Sixty live model calls per feature, times a dozen AI features, is real latency and a real bill; recall that naive AI-in-CI integrations quietly cost six figures a year. Split the suite by determinism and put each half where it belongs.

PRs: deterministic checks, mocked model

Structure, schema, no-hallucinated-links, refusal handling — all deterministic. Mock the model so these are byte-stable and fast. These gate every merge.

Nightly: the reliability gate, live model

Run the pass@k budget against the real provider on a schedule. This is a graded job with its own dashboard, not a merge blocker — it tells you the trend, and it pages you when the lower bound breaches the SLO.

Never retry the reliability gate

Retries corrupt your sample. A reliability project must run with retries: 0, because every run is a data point you’re counting.

Isolate the gate in its own Playwright project

That last point deserves its own config, because it’s the one line teams forget. Give the reliability gate a dedicated project so the default retry policy can’t contaminate its statistics:

playwright.config.ts
export default defineConfig({
retries: 2, // fine for the deterministic UI suite — infra flake only
projects: [
{ name: 'ui', testMatch: /.*\.spec\.ts/ },
{
name: 'ai-reliability',
testMatch: /reliability\.spec\.ts/,
retries: 0, // every run is a sample — retrying would inflate the pass rate
},
],
});
[ TIP ]

Playwright 1.61 added video retention modes that keep recordings only for flaky runs. Point them at the deterministic suite, not the reliability gate — in the reliability gate, “flaky” is the measurement, so there’s nothing to retain.

What number do you actually gate on?

Write it down as a service level objective before you argue about the threshold, because the threshold is a product decision, not a testing one. “The checkout assistant answers correctly on ≥95% of runs, measured over a rolling 200 samples” is a sentence a PM can sign off on and an on-call engineer can be paged against. Once it’s a documented budget, pass@k stops being a clever trick and becomes an accountability line.

Be honest that pass@k is a blunt instrument — it collapses a distribution into one number, and a 2025 Bayesian critique argues you should model per-sample success probability directly for tighter estimates. For a merge gate, the Wilson bound is the right amount of rigor; save beta-binomial modeling for when you’re comparing two model versions and need to detect a 2-point difference. And whatever grades “correct” — a schema check or a reproducible LLM judge — has to be pinned and calibrated first, or you’re computing a confidence interval on noise.

§ Delta · AI feature CI gate Regressions surface

Before

One assertion + retries: 2 — silent pass at 60% reliability

→ ↓

After

pass@k over N samples, gate on Wilson lower bound ≥ SLO

The one thing to do today

Take your single flakiest AI-feature test and wrap its assertion in a 30-run loop that counts passes and prints the ratio. Don’t gate on it yet — just look at the number. If it reads 22/30, you’ve been shipping a 73%-reliable feature and calling it green, and you now have the evidence to set a real budget. Move the deterministic checks to your PR gate with a mocked model, stand up the pass@k loop as a nightly graded job with retries: 0, and give it an SLO a human agreed to. An AI test that’s allowed to fail — within a measured budget — tells you the truth. One that always passes is just lying more confidently.

§ Frequently Asked FAQ
+ Isn't pass@k the same as just setting Playwright retries?

No — conflating them is the core mistake. Retries mark a test green if any attempt passes, which hides quality regressions: a feature dropping from 90% to 60% reliability still passes pass@3 about 94% of the time. pass@k as a reliability gate counts every run as a sample and asserts on the lower confidence bound of the success rate, so the drop surfaces. Run the reliability gate with retries: 0.

+ How many samples do I need to gate an AI feature?

Enough that your confidence interval clears the target. For a clean pass, the rule of three says 0 failures in N runs gives ~95% confidence the failure rate is under 3/N — so 60 samples with zero failures backs a 95% reliability claim. If you have failures, compute a Wilson lower bound and gate on that, not the point estimate (18/20 looks like 90% but its 95% lower bound is only ≈0.70).

+ Won't running 60 model calls per test make CI too slow and expensive?

Yes — which is why the reliability gate does not belong on the pull-request path. Run deterministic structure checks with a mocked model on every PR, and run the pass@k reliability gate as a scheduled nightly job with its own dashboard. It’s a graded metric, not a merge blocker.

+ What counts as a 'pass' when the output is non-deterministic?

You need a grader that is itself reproducible — a deterministic schema or invariant check where possible, or a version-pinned, calibrated LLM judge for meaning-level correctness. If the grader is noisy, your pass@k estimate is noise too. Pin and calibrate the judge before you trust any reliability number it produces.

§ Further Reading 03 of 03
01AI Testing

Your AI Agent Is Looping, and No Test Caught It

A max-iterations cap hides livelock instead of catching it. Here's the Playwright test a senior SDET writes to detect cyclic tool calls before they hit prod.

Read →
02AI Testing

Your AI Agent Retries and Charges the Card Twice

A tool call commits but the response is lost, so your AI agent retries and double-charges. Here's the Playwright test a senior SDET writes for exactly-once.

Read →
03AI Testing

Your 30-Case AI Eval Can't Tell If the Model Got Worse

You upgraded the model, reran your 30-case AI eval, and it passed. That comparison is statistically meaningless. Here's the paired-test math a senior SDET uses.

Read →

Don't miss a thing

Subscribe to get updates straight to your inbox.

HT

No spam · Unsubscribe anytime

Welcome aboard!

You're on the list. Expect real-world QA insights — no fluff, no spam.

§ Colophon

Halmurat T. — Senior SDET writing about test automation, CI/CD, and QA strategy from 10+ years in the enterprise trenches.

Set in
IBM Plex Sans, Lora, and IBM Plex Mono.
Built with
Astro, MDX, Tailwind CSS & Expressive Code. Served by Vercel.
Privacy
No cookies. No tracking scripts on the main thread — analytics run sandboxed via Partytown.
Source
github.com/Halmurat-Uyghur
Terminal
Try /ask to query Halmurat's notes in a shell prompt.

© 2026 Halmurat T. · Written in plain text, shipped in plain time.

Search
Esc

Search is not available in dev mode.

Run npm run build then npm run preview:local to test search locally.