Skip to main content
Monitor page events including HTTP requests, navigation, DOM changes, and custom events emitted by the page.

Methods

page.on()

Listens for page events. Returns an AbortController for cleanup.
page.on(event: string, handler: EventListener): Promise<AbortController>
Parameters:
  • event: Event type to listen for
  • handler: Function to handle the event
Returns: Promise<AbortController> - Controller for stopping the listener Example:
const controller = await page.on('xhr', async (event) => {
  console.log('HTTP Request:', event.data.url)
})

// Later, stop listening
await page.off(controller)

page.once()

Listens for a page event once, then automatically removes the listener.
page.once(event: string, handler: EventListener): Promise<AbortController>
Parameters:
  • event: Event type to listen for
  • handler: Function to handle the event
Returns: Promise<AbortController> - Controller (listener auto-removed after first event) Example:
await page.once('finished', async (event) => {
  console.log('Page finished loading')
})

page.off()

Stops listening to events by providing the AbortController.
page.off(controller: AbortController): void
Parameters:
  • controller: AbortController returned from on() or once()
Example:
const controller = await page.on('domchange', handler)
await page.off(controller)

Available events

xhr

Emitted when an HTTP request (fetch/XHR) is made. Event data:
{
  type: string                              // e.g., "fetch", "xhr"
  url: string                               // Request URL
  method: string                            // HTTP method
  headers: Record<string, string>           // Request headers
  data: Record<string, unknown>             // Request body
  response?: {
    status: string                          // Response status code
    data: Record<string, unknown>           // Response body
    headers: Record<string, string>         // Response headers
  }
}
Example:
await page.on('xhr', async (event) => {
  const request = event.data

  console.log(`${request.method} ${request.url}`)

  if (request.response) {
    console.log(`Response status: ${request.response.status}`)
  }
})

started

Emitted when page load starts. Example:
await page.on('started', async (event) => {
  console.log('Page load started')
})

finished

Emitted when page load finishes. Example:
await page.on('finished', async (event) => {
  console.log('Page load finished')
})

locationchange

Emitted when the page URL changes. Example:
await page.on('locationchange', async (event) => {
  const newUrl = await page.url()
  console.log('URL changed to:', newUrl)
})

domchange

Emitted when the DOM is mutated. Example:
await page.on('domchange', async (event) => {
  console.log('DOM changed')
})

dispatch

Emitted for custom events dispatched by the page. Example:
await page.on('dispatch', async (event) => {
  console.log('Custom event:', event.data)
})

close

Emitted when the browser requests to close (e.g., user clicks close). Example:
await page.on('close', async (event) => {
  console.log('Browser close requested')
  // Cleanup or save state
})

closed

Emitted when the page is actually closed. Example:
await page.on('closed', async (event) => {
  console.log('Page closed')
})

hostblocked

Emitted when a host is blocked by an allowlist. Example:
await page.on('hostblocked', async (event) => {
  console.log('Blocked host:', event.data)
})

Complete examples

Monitor all HTTP requests

const controller = await page.on('xhr', async (event) => {
  const req = event.data

  console.log(`[${req.method}] ${req.url}`)

  if (req.response) {
    console.log(`  Status: ${req.response.status}`)
    console.log(`  Headers:`, req.response.headers)
  }
})

await page.goto('https://example.com')

// Stop monitoring after some time
await new Promise(resolve => setTimeout(resolve, 10000))
await page.off(controller)

Filter API requests

await page.on('xhr', async (event) => {
  const req = event.data

  // Only log API calls
  if (req.url.includes('/api/')) {
    console.log('API Request:', req.method, req.url)

    if (req.response) {
      console.log('API Response:', req.response.status)

      // Log response data for specific endpoints
      if (req.url.includes('/api/users')) {
        console.log('Users data:', req.response.data)
      }
    }
  }
})

await page.goto('https://example.com/app')

Track page lifecycle

await page.on('started', async () => {
  console.log('[Lifecycle] Page load started')
})

await page.on('finished', async () => {
  console.log('[Lifecycle] Page load finished')
})

await page.on('locationchange', async () => {
  const url = await page.url()
  console.log('[Lifecycle] URL changed:', url)
})

await page.goto('https://example.com')
await page.click('#navigate-link')

Count DOM changes

let domChangeCount = 0

const controller = await page.on('domchange', async () => {
  domChangeCount++
})

await page.goto('https://example.com/dynamic-page')

// Wait for page to settle
await new Promise(resolve => setTimeout(resolve, 5000))

await page.off(controller)
console.log(`Total DOM changes: ${domChangeCount}`)

Wait for specific request

async function waitForAPICall(page, apiPath) {
  return new Promise((resolve) => {
    page.on('xhr', async (event) => {
      const req = event.data

      if (req.url.includes(apiPath) && req.response) {
        resolve(req.response)
      }
    })
  })
}

// Click button that triggers API call
await page.click('#load-data')

// Wait for the API call to complete
const response = await waitForAPICall(page, '/api/data')
console.log('API call completed:', response.status)

Detect failed requests

const failedRequests = []

await page.on('xhr', async (event) => {
  const req = event.data

  if (req.response && parseInt(req.response.status) >= 400) {
    failedRequests.push({
      url: req.url,
      method: req.method,
      status: req.response.status
    })

    console.error(`Failed request: ${req.method} ${req.url} - ${req.response.status}`)
  }
})

await page.goto('https://example.com')

console.log(`Total failed requests: ${failedRequests.length}`)

Monitor page navigation

const navigationHistory = []

await page.on('locationchange', async () => {
  const url = await page.url()
  navigationHistory.push({
    url,
    timestamp: Date.now()
  })

  console.log('Navigated to:', url)
})

await page.goto('https://example.com')
await page.click('#link1')
await page.click('#link2')
await page.click('#back-button')

console.log('Navigation history:')
navigationHistory.forEach((nav, i) => {
  console.log(`${i + 1}. ${nav.url} at ${new Date(nav.timestamp).toISOString()}`)
})

Measure request performance

const requestTimes = new Map()

await page.on('xhr', async (event) => {
  const req = event.data

  if (!requestTimes.has(req.url)) {
    requestTimes.set(req.url, {
      startTime: Date.now(),
      url: req.url
    })
  }

  if (req.response) {
    const startTime = requestTimes.get(req.url)?.startTime
    if (startTime) {
      const duration = Date.now() - startTime
      console.log(`${req.url} took ${duration}ms`)
    }
  }
})

await page.goto('https://example.com')

Handle custom events

// Add user script to dispatch custom events
await page.addUserScript(`
  window.addEventListener('load', () => {
    // Dispatch custom event
    window.dispatchEvent(new CustomEvent('app-ready', {
      detail: { version: '1.0' }
    }))
  })
`)

// Listen for custom events
await page.on('dispatch', async (event) => {
  console.log('Custom event received:', event.type, event.data)
})

await page.goto('https://example.com')

Cleanup on page close

let eventListeners = []

async function setupListeners(page) {
  const xhrController = await page.on('xhr', xhrHandler)
  const domController = await page.on('domchange', domHandler)

  eventListeners.push(xhrController, domController)

  // Cleanup when page closes
  await page.on('closed', async () => {
    console.log('Page closed, cleaning up listeners')

    for (const controller of eventListeners) {
      await page.off(controller)
    }

    eventListeners = []
  })
}

await setupListeners(page)

Debug mode logging

async function enableDebugMode(page) {
  await page.on('xhr', async (event) => {
    const req = event.data
    console.log('[XHR]', req.method, req.url)
  })

  await page.on('started', async () => {
    console.log('[NAV] Page load started')
  })

  await page.on('finished', async () => {
    console.log('[NAV] Page load finished')
  })

  await page.on('locationchange', async () => {
    const url = await page.url()
    console.log('[NAV] Location changed:', url)
  })

  await page.on('domchange', async () => {
    console.log('[DOM] DOM mutated')
  })

  console.log('Debug mode enabled')
}

await enableDebugMode(page)
await page.goto('https://example.com')

Best practices

Always remove event listeners when done:
const controller = await page.on('xhr', handler)

// Do work

await page.off(controller)
Use once() for events you only need to handle once:
await page.once('finished', async () => {
  console.log('Page loaded')
})
Filter events in your handler to avoid processing irrelevant data:
await page.on('xhr', async (event) => {
  const req = event.data

  if (!req.url.includes('/api/')) return  // Skip non-API requests

  // Process API request
})
Event handlers can be async, but they don’t block the page:
await page.on('xhr', async (event) => {
  // This is async but doesn't block page execution
  await processRequest(event.data)
})