Recipes
Battle-tested patterns for getting real value out of monkeytester. Copy, paste, adjust.
Test a deploy preview, fail the build on bugs
import { Monkey, autoLLMConfig } from '@monkeytestai/monkeytester';
const monkey = new Monkey({ llm: autoLLMConfig({ quality: 'balanced' }) });
const { run } = await monkey.executeAndTriage(process.env.PREVIEW_URL!, {
explore: { maxPages: 6, screenshotsDir: '.monkeytest/shots' },
plan: { maxFlows: 4 },
run: { screenshotsDir: '.monkeytest/shots', headless: true },
});
const blocking = run.bugs.filter(b => b.severity !== 'minor');
if (blocking.length){
console.error(`Found ${blocking.length} blocking bugs:`);
for (const b of blocking) console.error(`- [${b.severity}] ${b.title}`);
process.exit(1);
}Persist a run to disk and diff it later
import { writeFileSync } from 'node:fs';
const result = await monkey.executeAndTriage(url, { explore: ..., plan: ..., run: ... });
writeFileSync(
`runs/${new Date().toISOString()}.json`,
JSON.stringify({ siteMap: result.siteMap, plan: result.plan, run: result.run }, null, 2),
);Bug fingerprints are stable across runs, so you can diff bugs[].fingerprint between two runs to know what's new, fixed, or regressed.
Live progress in your CLI
import { Monkey, EventBus, autoLLMConfig } from '@monkeytestai/monkeytester';
const events = new EventBus();
events.on(e => {
const tag = `[${e.phase}/${e.type}]`.padEnd(28);
console.log(`${new Date(e.ts).toLocaleTimeString()} ${tag} ${e.message}`);
});
await new Monkey({ llm: autoLLMConfig() }).executeAndTriage(url, {
events,
explore: { maxPages: 8, screenshotsDir: '.monkeytest/shots' },
plan: { maxFlows: 4 },
run: { screenshotsDir: '.monkeytest/shots' },
});Stream live progress over SSE
import express from 'express';
import { EventBus } from '@monkeytestai/monkeytester';
const app = express();
const buses = new Map<string, EventBus>();
app.get('/runs/:id/stream', (req, res) => {
res.set({ 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache' });
const bus = buses.get(req.params.id);
if (!bus) return res.end();
const off = bus.on(evt => res.write(`data: ${JSON.stringify(evt)}\n\n`));
req.on('close', off);
});Headed mode (watch the monkey work)
Helpful when debugging selectors or auth flows.
await monkey.executeAndTriage(url, {
explore: { maxPages: 4, headless: false, screenshotsDir },
plan: { maxFlows: 2 },
run: { screenshotsDir, headless: false },
});Cheap local dev with Ollama
import { Monkey } from '@monkeytestai/monkeytester';
const monkey = new Monkey({
llm: {
explorer: { provider: 'openai_compatible', baseURL: 'http://localhost:11434/v1', apiKey: 'ollama', model: 'llama3.1:8b' },
planner: { provider: 'openai_compatible', baseURL: 'http://localhost:11434/v1', apiKey: 'ollama', model: 'llama3.1:70b' },
runner: { provider: 'openai_compatible', baseURL: 'http://localhost:11434/v1', apiKey: 'ollama', model: 'llama3.1:8b' },
},
});Polished bug reports for production runs
Add a cheap triager LLM and the engine rewrites every bug's copy after the deterministic pass. Per-bug try/catch — failures keep the rule-based copy.
new Monkey({
llm: {
explorer: { provider: 'gemini', model: 'gemini-flash-lite-latest' },
planner: { provider: 'anthropic', model: 'claude-sonnet-4-5' },
runner: { provider: 'groq', model: 'llama-3.3-70b-versatile' },
triager: { provider: 'anthropic', model: 'claude-haiku-4-5' },
},
});See Triager polish for what changes and how to control cost.
Use Anthropic for the planner only
The single best price/quality lever:
new Monkey({
llm: {
explorer: { provider: 'gemini', model: 'gemini-flash-lite-latest' },
planner: { provider: 'anthropic', model: 'claude-sonnet-4-5' },
runner: { provider: 'gemini', model: 'gemini-flash-lite-latest' },
},
});Test only specific user journeys
The library doesn't (yet) accept a hand-written plan, but you can pass a deeper start URL so the planner focuses on a specific area:
await monkey.executeAndTriage('https://app.example.com/checkout', {
explore: { maxPages: 4, screenshotsDir },
plan: { maxFlows: 3, includeAuthFlows: false },
run: { screenshotsDir, headless: true },
});Embed in your own product
The package is ESM, fully typed, and has zero opinionated dependencies on a host framework. Wrap it in a worker, an API route, a Tauri app, a VS Code extension — wherever.
// worker/runMonkey.ts
import { Monkey, autoLLMConfig, EventBus } from '@monkeytestai/monkeytester';
export async function runMonkeyTest({ url, screenshotsDir, onEvent }) {
const events = new EventBus();
events.on(onEvent);
const monkey = new Monkey({ llm: autoLLMConfig({ quality: 'balanced' }) });
return monkey.executeAndTriage(url, {
events,
explore: { maxPages: 8, screenshotsDir },
plan: { maxFlows: 4 },
run: { screenshotsDir, headless: true },
});
}Next: Use in CI →