How to close a modal dialog using Puppeteer in Node.js?

Description

I’m currently facing an issue where a modal window pops up when there’s a record validation error, and I need to close it programmatically using Puppeteer.

Problem

Despite my efforts to investigate the HTML structure extensively, I have not been able to resolve the issue of closing this modal. The continuation of my automated processing is possible even with the modal open, but this leads to a timeout on another process in my script that waits for the modal to close before it proceeds to the next record. This timeout is crucial because it ensures that an important page element is loaded. Without it, my code becomes error-prone, and after trying numerous wait strategies, I found this timeout to be the most effective.

Modal HTML

Here’s the HTML snippet of the modal:

<div style="width: 400px; max-height: 530px; min-height: 250px; display: block;">
  <h2 class="title_modal">Validation Error</h2>
  <div class="col-xs-12"><h2 class="title_modal">Invalid ID</h2></div>
  <div class="col-xs-4 col-xs-offset-4"><button class="primary">OK</button></div>
  <span class="closeButton" id="modal_close_button"><i class="icon-close"></i></span>
</div>

Current Attempt

My current implementation for closing the modal is as follows:

const closeModalIfPresent = async (page) => {
    const validationError = await page.evaluate(() => {
        const modalHeader = document.querySelector('.title_modal');
        if (modalHeader && modalHeader.textContent.includes('Validation Error')) {
            const closeBtn = document.querySelector('.closeButton');
            if (closeBtn) {
                closeBtn.click();
                return true;
            }
        }
        return false;
    });

    if (validationError) {
        console.log('Validation error modal closed.');
        await new Promise(resolve => setTimeout(resolve, 100));
    }

    return validationError;
};

Method Invocation

This method gets called here:

const executePuppeteerScript = async (path, user, pass) => {
    // Puppeteer setup...
    await closeNewSession(page, user, pass);
    const dataEntries = await readAndProcessFile(path);

    for (const group of dataEntries) {
        for (const entry of group) {
            console.log(`Now processing: ${entry.id}`);
            if (await checkForCustomerError(page)) continue;
            await searchAndProceed(page, entry.id);
            await new Promise(resolve => setTimeout(resolve, 200));

            // Modal handling logic here
            // if (await closeModalIfPresent(page)) continue;
        }
    }
};

Additional Notes

I’ve tried invoking the modal handling function before my search logic, but I still can’t get the modal to close.

Errors Encountered

When the modal stays open, I receive a timeout error like this:

Error getting data: TimeoutError: Waiting for selector failed: Waiting failed: 15000ms exceeded

If anyone knows how to successfully close the modal or can provide insights into adjusting my logic to re-attempt the entry processing, I would greatly appreciate it. I’ve only recently started using Node.js after struggling with the same task in C#, which was not only slower but also more complicated.

Thanks for taking the time to read this detailed query. Would it be better for future posts to keep them concise or is it more beneficial to provide comprehensive details?

You can simplify and enhance your current method by directly utilizing Puppeteer’s selectors and click functionality to ensure the modal closes promptly before continuing with the execution. Here’s a refined approach:

const closeModalIfPresent = async (page) => {
    try {
        await page.waitForSelector('.closeButton', { visible: true, timeout: 5000 });  // Adjust timeout as necessary
        await page.click('.closeButton');
        console.log('Validation error modal closed.');
        return true;
    } catch (error) {
        console.log('Modal not found or already closed.');
        return false;
    }
};

In your execution flow, make sure this function is invoked before proceeding further with any dependent processes to avoid timeout issues. Keeping your function simple and concise ensures it integrates smoothly without unnecessary delays or errors.