Caching System
How Donobu caches AI-generated action sequences for fast test reruns, and strategies for managing the cache in CI.
Donobu caches the action sequences produced by autonomous page.ai() runs so that subsequent test runs are fast — no AI call required.
How it works
First run (AI) Subsequent runs (manual)
───────────────────── ────────────────────────
page.ai('instruction') page.ai('instruction')
│ │
▼ ▼
AI reasons about page Cache lookup (instant)
│ │
▼ ▼
Executes tool calls Replays same tool calls
│ │
▼ ▼
Writes to cache file Returns result
On the first run, the AI executes the flow autonomously and the resulting sequence of tool calls is written to a cache file. On every subsequent run, Donobu reads the cache and replays those exact tool calls without calling the AI. This makes re-runs consistently fast — typically an order of magnitude faster than the first run.
Cache file location
Cache files live in a .cache-lock/ directory that sits alongside your test files:
tests/
.cache-lock/
checkout.test.ts.cache.js ← generated automatically
login.test.ts.cache.js
checkout.test.ts
login.test.ts
Cache files are plain JavaScript modules and are safe to commit to version control — committing them ensures every team member and every CI run starts with a warm cache.
Cache key
Each page.ai() call is identified by a cache key composed of:
- Page hostname (e.g.
app.example.com) — not the full URL, so the cache works across different paths on the same domain - Instruction text — the exact string passed to
page.ai() - Schema shape — the Zod schema, if provided
allowedTools— the tool allow-list, if setmaxToolCalls— the tool call cap, if setenvVars— the list of exposed environment variable names
If any of these change, Donobu treats it as a new instruction and runs the AI again.
Cache invalidation
There are several ways to bypass the cache. Use the one with the desired granularity.
Invalidate all cache entries for a run
There are two equivalent ways to invalidate the entire cache for a run:
CLI flag (recommended for one-off runs):
npx donobu test --clear-ai-cache
Environment variable (useful in scripts or CI where you need more control):
DONOBU_PAGE_AI_CLEAR_CACHE=1 npx donobu test
Invalidate a single call
Pass cache: false to a specific page.ai() call to disable the cache every time that call is made:
await page.ai('Find the current pricing and click the Pro plan', {
cache: false,
});
Invalidate manually
Delete the .cache-lock/ directory (or a specific .cache.js file) to force a full re-learn for the corresponding test:
rm tests/.cache-lock/checkout.test.ts.cache.js
Using the cache in CI
The recommended strategy depends on your team's workflow:
| Strategy | How | Trade-off |
|---|---|---|
| Commit the cache | Add .cache-lock/ files to git | Zero AI cost on every CI run; cache must be updated when the UI changes |
| Cache as CI artifact | Upload .cache-lock/ as an artifact; restore on the next run | Cache persists across runs without being in git; first run after a cache miss costs AI tokens |
| Always re-run the AI | Pass --clear-ai-cache to npx donobu test (or set DONOBU_PAGE_AI_CLEAR_CACHE=1) | Slowest, highest cost, but always exercises the latest page state |
For most teams, committing the cache is the best starting point. Update the cache files whenever page.ai() calls break due to UI changes (or let self-healing update them automatically).
Tip: Do not use JavaScript string interpolation
There may be cases where you want a separate cache entry per value — for example, if you are testing multiple distinct user personas whose flows genuinely differ. However, if the value only changes between environments (staging vs. production credentials, for instance) or on every run (a timestamp), baking it directly into the instruction creates a unique cache key per value. This can result in an exceedingly large cache, or cache keys that will never be reused.
// ❌ Creates a new cache entry per user — fine for a small fixed set,
// but problematic for credentials or timestamps that vary widely
await page.ai(`Log in as ${process.env.USERNAME}`);
// ✅ Keeps the cache key stable regardless of the username value
await page.ai('Log in as {{$.env.USERNAME}}', { envVars: ['USERNAME'] });
See Environment Variable Interpolation for the full syntax.