Skip to content

Live events ​

Every phase of the engine emits typed events through an optional EventBus. This is the same channel that powers MonkeyTest Cloud's live-activity panel — you can plug it into your own UI in five lines.

ts
import { Monkey, EventBus, autoLLMConfig } from '@monkeytestai/monkeytester';

const bus = new EventBus();
const off = bus.on(evt => {
  console.log(`[${evt.phase}] ${evt.type}: ${evt.message}`);
});

await new Monkey({ llm: autoLLMConfig() }).executeAndTriage(url, {
  events: bus,
  explore: { maxPages: 8, screenshotsDir: '.monkeytest/shots' },
  plan:    { maxFlows: 4 },
  run:     { screenshotsDir: '.monkeytest/shots' },
});

off();

Event shape ​

ts
interface LiveEvent {
  ts: string;             // ISO timestamp
  phase: 'explorer' | 'planner' | 'runner' | 'triager';
  type:  LiveEventType;   // see below
  message: string;        // one-line, human readable
  data?: Record<string, unknown>;  // structured payload
}

Phases ​

PhaseWhat's happening
explorerCrawling and summarising pages
plannerGenerating flows from the site map
runnerExecuting Playwright steps
triagerCollapsing failures into bug reports

Common event types ​

TypePhaseNotes
explore.startexplorerFirst event of the run. data.url is the start URL.
explore.page_visitedexplorerPer-page success. data.url, data.title.
explore.page_failedexplorerPer-page failure. data.url, data.reason.
explore.screenshotexplorerA page screenshot was written. data.path.
explore.warningexplorerA model summary was incomplete and the crawl continued. message is safe to show. data.detail is the parser error for logs, not for end users.
explore.completeexplorerSite map ready. data.pages, data.failed. data.warnings repeats any explore.warning messages.
plan.startplannerAsking the LLM for a plan.
plan.completeplannerPlan ready. data.flows.
plan.skippedplannerNo flows produced. data.reason.
run.startrunnerExecuting the plan. data.flows.
run.flow_startrunnerA flow began. data.flowId, data.name, data.flowIndex, data.flowsTotal.
run.step_startrunnerA step began. data.flowId, data.index, data.step.
run.step_completerunnerA step finished. data.status, data.observation, data.screenshotPath.
run.screenshotrunnerA step screenshot was written. data.path, data.flowId, data.index.
run.flow_completerunnerA flow finished. data.status, data.bugs.
run.completerunnerAll flows finished. data.stats.
triage.start / triage.completetriagerCollapsing failures into bugs.
engine.erroranyNon-fatal failure in a phase. data.error. Partial results may still exist.

The exhaustive type-level list lives at Reference: EventBus.

Patterns ​

Forward to SSE ​

ts
import express from 'express';
const app = express();
app.get('/runs/:id/stream', (req, res) => {
  res.set({ 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache' });
  const off = bus.on(evt => res.write(`data: ${JSON.stringify(evt)}\n\n`));
  req.on('close', off);
});

Buffer + persist (poll-friendly) ​

A common server-side pattern: keep a rolling 200-event buffer in memory, throttle DB writes to every 750ms, and serve the latest snapshot via a regular HTTP GET. This is exactly what MonkeyTest Cloud does — the engine doesn't ship a buffer/storage layer because every host wants something slightly different.

Filter by phase ​

ts
bus.on(evt => {
  if (evt.phase === 'runner' && evt.type === 'run.screenshot') {
    refreshLiveShot(evt.data.path);
  }
});

Performance notes ​

  • The EventBus is in-process and synchronous; listeners run on the same event loop as the engine.
  • Keep listeners cheap — a slow listener will slow down the run.
  • For UI use, prefer buffering in your host process and shipping batches to the browser instead of one event per render.

AGPL-3.0 · Made for the testing community