Skip to content

Agent Hooks

Hooks run custom code before or after a specific agent. Use them to prepare and clean up the page per agent.

Use CaseSolution
Wait for element on all page visitsKnowledge: waitForElement
Dismiss cookie banner on page loadKnowledge: code
Wait for network idle only during researchHook: researcher.beforeHook
Clean up test data after each testHook: tester.afterHook
Different waits for navigation vs testingHooks for each agent

Hooks are configured per-agent in explorbot.config.js:

export default {
ai: {
model: myProvider('gpt-4o'),
agents: {
navigator: {
beforeHook: {
type: 'playwright',
hook: async ({ page, url }) => {
await page.waitForLoadState('networkidle');
}
}
},
tester: {
afterHook: {
type: 'codeceptjs',
hook: async ({ I, url }) => {
await I.executeScript(() => localStorage.clear());
}
}
}
}
}
}

Get direct access to the Playwright page object:

beforeHook: {
type: 'playwright',
hook: async ({ page, url }) => {
await page.waitForLoadState('networkidle');
await page.locator('.loading').waitFor({ state: 'hidden' });
}
}

Use the CodeceptJS I actor:

beforeHook: {
type: 'codeceptjs',
hook: async ({ I, url }) => {
await I.waitForElement('.page-ready');
await I.wait(1);
}
}

Run different hooks for different URL patterns:

researcher: {
beforeHook: {
'/login': {
type: 'codeceptjs',
hook: async ({ I }) => await I.waitForElement('#login-form')
},
'/admin/*': {
type: 'playwright',
hook: async ({ page }) => await page.waitForLoadState('networkidle')
},
'/api/*': {
type: 'codeceptjs',
hook: async ({ I }) => await I.wait(2)
}
}
}
PatternMatches
/loginExact path /login
/admin/*/admin and any path starting with /admin/
*All URLs (fallback)
^/users/\d+$Regex: /users/ followed by digits
**/*.htmlGlob: any .html file
AgentbeforeHookafterHookDescription
navigatorAfter navigationAfter page captureBrowser navigation
researcherAfter navigationAfter research completePage analysis
testerBefore test loopAfter test loopTest execution
drillerBefore drilling startsAfter drilling completesComponent drilling
captainBefore handling commandAfter command completeUser commands
navigator: {
beforeHook: {
type: 'playwright',
hook: async ({ page }) => {
await page.waitForFunction(() => {
return window.__APP_READY__ === true;
});
}
}
}
researcher: {
beforeHook: {
type: 'codeceptjs',
hook: async ({ I }) => {
const modalVisible = await I.grabNumberOfVisibleElements('.modal-overlay');
if (modalVisible > 0) {
await I.click('.modal-close');
await I.wait(0.5);
}
}
}
}
tester: {
afterHook: {
type: 'playwright',
hook: async ({ page }) => {
await page.evaluate(() => {
localStorage.clear();
sessionStorage.clear();
});
}
}
}
tester: {
beforeHook: {
'/checkout': {
type: 'codeceptjs',
hook: async ({ I }) => {
// Ensure cart has items before checkout tests
await I.executeScript(() => {
if (!localStorage.getItem('cart')) {
localStorage.setItem('cart', JSON.stringify([{ id: 1, qty: 1 }]));
}
});
}
},
'/admin/*': {
type: 'codeceptjs',
hook: async ({ I }) => {
// Ensure admin session
await I.waitForElement('.admin-header', 5);
}
}
}
}

A hook error is logged but does not stop the agent:

beforeHook: {
type: 'codeceptjs',
hook: async ({ I }) => {
try {
await I.waitForElement('.optional-banner', 2);
await I.click('.dismiss');
} catch {
// Banner not present, continue
}
}
}
┌─────────────────────────────────────────────────────────┐
│ Agent Execution │
├─────────────────────────────────────────────────────────┤
│ 1. Agent starts │
│ 2. Navigate to URL (if applicable) │
│ 3. ▶ beforeHook executes │
│ 4. Agent performs main work │
│ 5. ▶ afterHook executes │
│ 6. Agent completes │
└─────────────────────────────────────────────────────────┘