Handling multiple tabs or dialogs in Puppeteer

With the latest JavaScript libraries and available AI tools, auditing your page for performance and accessibility became easier to do, both by developers and QA engineers. One of these tools, which is built into Google Chrome, is called Lighthouse.


Author: Cristina Zavaliche, Software Tester

You provide Lighthouse with a URL to audit, and it runs a series of checks against the page. Then, it generates a report on how well the page did alongside improvement suggestions.

Integrating Lighthouse with Puppeteer enables a more comprehensive approach to automating form interactions, end to end UI scenarios and capturing a performance timeline trace of your website.

Puppeteer not only integrates with other performance tools, but being a JavaScript library, you can make use of any JS concept to launch and connect to a browser, create pages, open multiple tabs and manipulate them with Puppeteer’s API.

import puppeteer from 'puppeteer';

(async () => {
  // Launch the browser and open a new blank page
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // Navigate the page to a URL
  await page.goto('https://google.com');

  // Set screen size
  await page.setViewport({ width: 1080, height: 1024 });

  // Type into search box
  await page.type('.devsite-search-field', 'automate beyond recorder');

  await browser.close();
})();

Puppeteer is a powerful tool for automating browser actions, but did you know it can also run within a browser itself?

This feature allows you to harness Puppeteer's capabilities for tasks that do not require Node.js-specific features such as opening multiple tabs in one single browser session and shifting Puppeteer’s focus to a second tab using JavaScript. More specifically, making use of new Promise().

async function setup() {
    const browser = await puppeteer.launch({ headless:false, 
args: ['--start-maximized']});
    const page = await browser.newPage();

    await doLogin(page);
    await clickOnTileAndOpenTab(page);

    const [newPage] = await Promise.all([
        new Promise(resolve => browser.once('targetcreated', async target => {
            const page = await target.page();
            await page.waitForNavigation({ waitUntil: 'networkidle2' }); 
            resolve(page);
        })),
        page.click('#ContentPage_ContentPage_applicaties_linkButtonFinanceForms')
    ]);

    // Wait for the final version of the input field
    await newPage.waitForSelector('windmill-input input', { visible: true });

    await new Promise(resolve => setTimeout(resolve, 5000));

    await searchByWord(newPage);
    await browser.close();
}
 
module.exports = setup;

Here’s a breakdown of what this code does and why it’s working:

  • New Promise: this creates a new promise that resolves when a new target, such as a new tab, is created in the browser.
  • Promise.all: ensures both actions (waiting for the target and clicking the element) happen concurrently, which can improve efficiency.
  • targetCreated event: this event will be fired when a new browser context is created. The page object is retrieved from that target and then the navigation is awaited to ensure that the new page is fully loaded before continuing.

Promise.all ensures both operations run concurrently and await makes sure the code waits until both operations are completed before continuing.

  • The page.click happens immediately, but the new page creation might take some time.
  • When both operations finish, the result (new page) is returned.

Along with other tested solutions that were anticipated to work for switching focus between tabs, the aforementioned code did the trick and achieved the desired result.

Ultimately, by passing the newPage variable to the locators and methods, you instruct Puppeteer to shift focus to the new tab or page object, ensuring smooth interaction with the newly opened browser context.

References:

Getting started | Puppeteer

API Reference | Puppeteer

Introduction to Lighthouse | Chrome for Developers

How to Manage Multiple promises concurrently with Promise.all() - DEV Community

Understanding `new Promise` in JavaScript - Mastering JS

Why does page.bringToFront act differently between headless: 'new' and headless: true and is this intended? · Issue #11266 · puppeteer/puppeteer