Managing Multiple Browser Windows with Puppeteer

I’m working on automating a registration process that involves two browser windows. Here’s the workflow:

In the first window, I enter details in a registration form and click a button that opens a second window with a generated token.

The second window shows the token I need to copy. After this, I should close the second window, return to the first, paste the token into a designated field, and then submit the form.

I can manage basic tasks like filling out the initial form and clicking the button to open the new window. My challenge is figuring out how to interact with the newly opened window.

const puppeteer = require('puppeteer');

(async() => {
    const browser = await puppeteer.launch({headless: false});
    const mainPage = await browser.newPage();

    await mainPage.goto('https://signup.testsite.com/register');

    const registrationForm = await mainPage.$('registration-form');

    await mainPage.focus('#name-field');
    await mainPage.type('My Application');

    await mainPage.focus('#description-field');
    await mainPage.type('Application description goes here');

    await mainPage.click('.generate-token'); // this creates a new window

    // Need help here - how to work with the new window?
    // Extract token from new window
    // Close the new window

    // Return to original window
    await mainPage.focus('#token-input');
    await mainPage.type(extractedToken);

    await registrationForm.evaluate(form => form.submit());

    browser.close();
})();

How do I get access to the second window when it opens? I need a practical example of handling this multi-window scenario.

I ran into this exact scenario last month and found that using browser.waitForTarget() gives you more control than other approaches. After clicking your generate-token button, you can wait specifically for the new target to be created with a predicate function. Here’s what worked reliably for me:

const newWindowTarget = await browser.waitForTarget(target => target.url().includes('token') && target.type() === 'page');
const tokenPage = await newWindowTarget.page();

This method ensures you’re grabbing the correct window even if multiple popups might open. Once you have the tokenPage reference, extract your token with standard querySelector methods, then call tokenPage.close() before returning focus to your main page. The advantage here is that waitForTarget blocks until the specific window you need is actually ready, avoiding timing issues that can occur with delays or polling browser.pages().

The key is using browser.pages() to get all open pages after the new window opens. When you click the generate token button, wait a moment for the popup to fully load, then fetch all pages. The original page will be at index 0, and your new window will be at index 1. I handle this by adding a small delay after the button click, then accessing the new page directly. Once you have the new page reference, you can extract the token using standard selectors, close that page with newPage.close(), and continue with your original page. This approach is more reliable than event listeners in my experience, especially when dealing with slower loading popups.

you gotta listen for the popup event first. use browser.on('targetcreated') to catch when the new window opens, then get the page with target.page(). something like this worked for me - set up the listener before clicking the button, then u can interact with both windows normally.