How to correctly retrieve HTTP response data using Puppeteer?

I need help getting the HTTP response status code when testing a user registration form with Puppeteer. I’m having trouble with the proper way to set up response listeners.

Here’s what I’m working with:

describe('User registration test', () => {
  it('should return 400 when username already exists', async () => {
    await browser.goto(`${config.BASE_URL}/register`)
    await browser.waitForSelector('input[name="fullname"]')

    await browser.type('input[name="fullname"]', 'John Doe')
    await browser.type('input[name="email"]', '[email protected]')
    await browser.type('input[name="password"]', 'password123', {delay: 50})
    await browser.click('button[type="submit"]', {delay: 500})

    const responseData = await browser.on('response', res => res)

    console.log('status code:', responseData.status())
    // expect(responseData.status()).toBe(400)
  })
})

I also tried using request interception but that doesn’t seem right for my use case:

await browser.setRequestInterception(true);
browser.on('request', req => {
  req.respond({
    status: 404,
    contentType: 'text/plain',
    body: 'Page not found'
  });
});

The main issue is that I keep getting the browser object back instead of the actual response. How do I properly capture HTTP responses in Puppeteer?

UPDATE - SOLUTION FOUND:

Thanks to the community help, I figured out the correct approach. The key was setting up the listener before making the request and running assertions inside the callback:

it('should return 400 when username already exists', async () => {
    await browser.goto(`${config.BASE_URL}/register`)
    await browser.waitForSelector('input[name="fullname"]')

    await browser.type('input[name="fullname"]', 'John Doe')
    await browser.type('input[name="email"]', '[email protected]')
    await browser.type('input[name="password"]', 'password123', {delay: 50})

    await browser.on('response', responseData => {
      if (responseData.request().method() === 'POST' && responseData.url().includes('/api/users')) {
        expect(responseData.status()).toBe(400)
      }
    })

    await browser.click('button[type="submit"]', {delay: 500})
  })

  afterAll(async () => {
    await puppeteerBrowser.close()
  })

Hope this helps others facing the same issue!

I’ve had good luck using Promise-based response handling with page.evaluateOnNewDocument() for complex scenarios. But for what you’re doing, just wrap the response capture in a try-catch since network requests can fail:

it('should return 400 when username already exists', async () => {
    await browser.goto(`${config.BASE_URL}/register`)
    await browser.waitForSelector('input[name="fullname"]')

    await browser.type('input[name="fullname"]', 'John Doe')
    await browser.type('input[name="email"]', '[email protected]') 
    await browser.type('input[name="password"]', 'password123')

    try {
        const [response] = await Promise.all([
            browser.waitForResponse(res => res.url().includes('/api/users')),
            browser.click('button[type="submit"]')
        ])
        
        expect(response.status()).toBe(400)
    } catch (error) {
        console.error('Response capture failed:', error)
        throw error
    }
})

This way the click and response waiting happen at the same time, so you won’t hit race conditions from separate async calls.

Your solution works, but there’s a cleaner way using page.waitForResponse() that dodges timing issues. Skip the event listeners and wait for specific responses directly:

it('should return 400 when username already exists', async () => {
    await browser.goto(`${config.BASE_URL}/register`)
    await browser.waitForSelector('input[name="fullname"]')

    await browser.type('input[name="fullname"]', 'John Doe')
    await browser.type('input[name="email"]', '[email protected]')
    await browser.type('input[name="password"]', 'password123', {delay: 50})

    const responsePromise = browser.waitForResponse(response => 
        response.url().includes('/api/users') && response.request().method() === 'POST'
    )

    await browser.click('button[type="submit"]', {delay: 500})
    const response = await responsePromise

    expect(response.status()).toBe(400)
})

This approach is way more reliable. It waits for the response to finish before checking anything, while event listeners can miss responses if timing gets wonky.

you could also use page.on('response') with a promise wrapper. i do this constantly for API testing - wrap the listener in a promise and resolve when you hit the right response. sometimes it’s better than waitForResponse when you need finer control over filtering.