Close a modal window with Puppeteer/NodeJS

I’m trying to close a modal window that appears when a record is invalid.
I’ve already analyzed the HTML a lot, but still can’t figure out why it isn’t closing.

I can continue the processing even if the modal remains open ; but when it does, i have a timeout on another part of my code (it waits a timeout because it doesn’t go to the next entry, just waits for the modal to be closed).

And i can’t remove this timeout, because its a timeout that i wait until an important element of the page shows up. If i leave the code without this timeout, its way prone to errors (yeah i tried all sorts of wait, this timeout was the best solution i came up).

This is the HTML code of the modal:

<div _ngcontent-cdm-c21="" style="width: 400px; max-height: 530px; min-height: 250px; margin-left: -200px; margin-top: -125px; display: block;"><h2 _ngcontent-cdm-c7="" class="title_modal title_bottom" style="padding-bottom: 1em">Erro na validação</h2><div _ngcontent-cdm-c7="" class="col-xs-12" style="border-radius: 20px; margin-left: 1px;margin-right: 1px;padding-top:1em;margin-top:-1em;padding-bottom:3em"><h2 _ngcontent-cdm-c7="" class="title_modal OpenSansLight font14">CPF Inválido</h2></div><div _ngcontent-cdm-c7="" class="col-xs-4 col-xs-offset-4" style="padding-top:0.5em"><button _ngcontent-cdm-c7="" buttontype="primary" spaui-button-custom="" class="spaui-button primary" type="submit"><!----><!----><!----><!---->OK<!----></button></div><!----><span _ngcontent-cdm-c21="" class="closeButton ng-star-inserted" id="modalCPFInvalido__dialog_close_button"><i _ngcontent-cdm-c21="" class="icon-fechar"></i></span></div>

Out of curiosity, the entire HTML code is on this link (can’t paste it here, too big because of CSS): https://pastecode.io/s/nmekts33

The method i’ve created for trying to close the modal is this one:

const handleValidationErrorModal = async (page) => {
    const errorDetected = await page.evaluate(() => {
        const errorModal = document.querySelector('h2.title_modal.title_bottom');
        if (errorModal?.textContent.includes('Erro na validação')) {
            const closeButton = document.querySelector('.closeButton .icon-fechar');
            if (closeButton) {
                closeButton.click();
                return true;
            }
        }
        return false;
    });

    if (errorDetected) {
        console.log('Validation error detected - CPF Inválido');
        await delay(100);
    }

    return errorDetected;
};

The call for the method is being made here:]

const runPuppeteer = async (filePath, login, password) => {
    const browser = await puppeteer.launch({ headless: false });
    const page = await browser.newPage();
    let processedCount = 0;

    try {
        await handleLogin(page, login, password);
        const entries = await processXlsxFile(filePath);

        for (const group of entries) {
            for (const entry of group) {
                console.log(`Processing: ${entry.numCpfCnpj}`);
                
                await searchAndNavigate(page, entry.numCpfCnpj);
                await delay(200);

                // Handle the validation error modal if it appears
               //// const errorOccurred = await handleValidationErrorModal(page);
                //if (errorOccurred) continue;
                if (await handleCustomerSearchErrorModal(page)) continue;

I’ve also tried calling this handleErrorModal as the first method, even before the searchAndNavigate. But nothing happens also.

The page stays like this:
problem

And then i receive this error:

Error extracting table data: TimeoutError: Waiting for selector
spaui-datatable table tbody tr failed: Waiting failed: 15000ms
exceeded
at new WaitTask (C:Usersusuarionodeprojectsnode_modulespuppeteer-corelibcjspuppeteercommonWaitTask.js:50:34)
at IsolatedWorld.waitForFunction (C:Usersusuarionodeprojectsnode_modulespuppeteer-corelibcjspuppeteerapiRealm.js:25:26)
at CSSQueryHandler.waitFor (C:Usersusuarionodeprojectsnode_modulespuppeteer-corelibcjspuppeteercommonQueryHandler.js:172:95)
at async CdpFrame.waitForSelector (C:Usersusuarionodeprojectsnode_modulespuppeteer-corelibcjspuppeteerapiFrame.js:523:21)
at async CdpPage.waitForSelector (C:Usersusuarionodeprojectsnode_modulespuppeteer-corelibcjspuppeteerapiPage.js:1368:20)
at async runPuppeteer (C:UsersusuarionodeprojectspuppeteerRunner.js:173:21)
at async C:Usersusuarionodeprojectsserver.js:25:25

If i could just close the modal and retry the current numCpfCnpj, would be fine for me. But i can’t figure out the logic for doing so. I’ve just started with NodeJS after failing to accomplish this same task using C# (and it was painfully more slow also).

Out of curiosity, this is my entire puppeteer.js code:

const puppeteer = require('puppeteer');
const XLSX = require('xlsx');

// Helper function to delay execution
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

// Function to handle the login process
const handleLogin = async (page, login, password) => {
    await page.goto('https://www.santandernegocios.com.br/portaldenegocios/#/externo');
    await page.waitForSelector('#userLogin__input');
    await page.type('#userLogin__input', login);
    await page.waitForSelector('#userPassword__input');
    await page.type('#userPassword__input', password);
    await page.keyboard.press('Enter');
    await page.waitForNavigation();
};

// Function to search for a CPF/CNPJ and navigate to the results
const searchAndNavigate = async (page, cpfCnpj) => {
    let retryCount = 0;
    const maxRetries = 2;

    while (retryCount <= maxRetries) {
        await page.waitForSelector('input[name="inputSearch"]');
        await page.evaluate(() => document.querySelector('input[name="inputSearch"]').value = ''); // Clear the input
        await delay(100); // Small delay before typing
        await page.type('input[name="inputSearch"]', cpfCnpj, { delay: 10 }); // Typing with a delay

        await page.waitForFunction(
            (selector, text) => document.querySelector(selector)?.value === text,
            {},
            'input[name="inputSearch"]',
            cpfCnpj
        );
        await page.keyboard.press('Enter');
        await page.waitForNavigation({ waitUntil: 'networkidle2' });

        if (!(await handleValidationErrorModal(page))) {
            break; // Exit the loop if no validation error
        }

        retryCount++;
        if (retryCount <= maxRetries) {
            console.log(`Retrying typing CPF/CNPJ (${retryCount}/${maxRetries})...`);
        } else {
            console.log('Max retries reached, proceeding to the next entry...');
        }
    }
};

// Function to process the XLSX file and group entries
const processXlsxFile = (filePath) => {
    const workbook = XLSX.readFile(filePath);
    const sheet = workbook.Sheets[workbook.SheetNames[0]];
    const data = XLSX.utils.sheet_to_json(sheet, { header: 1 }).slice(1); // Skip header row

    return data.reduce((acc, row, index) => {
        const groupIndex = Math.floor(index / 5);
        if (!acc[groupIndex]) acc[groupIndex] = [];
        acc[groupIndex].push({
            numCpfCnpj: String(row[0]),
            numAcordo: String(row[1])
        });
        return acc;
    }, []);
};

// Function to handle the error modal if it appears
const handleValidationErrorModal = async (page) => {
    const errorDetected = await page.evaluate(() => {
        const errorModal = document.querySelector('h2.title_modal.title_bottom');
        if (errorModal?.textContent.includes('Erro na validação')) {
            const closeButton = document.querySelector('.closeButton .icon-fechar');
            if (closeButton) {
                closeButton.click();
                return true;
            }
        }
        return false;
    });

    if (errorDetected) {
        console.log('Validation error detected - CPF Inválido');
        await delay(100);
    }

    return errorDetected;
};

const handleCustomerSearchErrorModal = async (page) => {
    const errorDetected = await page.evaluate(() => {
        const errorModal = document.querySelector('h2.title_modal');
        if (errorModal?.textContent.includes('Erro na busca do cliente')) {
            document.getElementById('modalNaoCorrentistaPJ__dialog_close_button').click();
            return true;
        }
        return false;
    });

    if (errorDetected) {
        console.log('Customer search error detected');
        await delay(100);
    }

    return errorDetected;
};

// Function to click the menu link
const clickMenuLink = async (page) => {
    await page.waitForSelector('div.session-menu');
    await page.evaluate(() => {
        const sessionMenu = document.querySelector('div.session-menu ul');
        const link = Array.from(sessionMenu.querySelectorAll('li a')).find(anchor => 
            ['Acordo Pj', 'Acordo Pf'].includes(anchor.textContent.trim())
        );
        if (link) link.click();
    });
    await page.waitForNavigation();
};

// Function to click the radio button in the matching table row




// Main function to run the Puppeteer automation
const runPuppeteer = async (filePath, login, password) => {
    const browser = await puppeteer.launch({ headless: false });
    const page = await browser.newPage();
    let processedCount = 0;

    try {
        await handleLogin(page, login, password);
        const entries = await processXlsxFile(filePath);

        for (const group of entries) {
            for (const entry of group) {
                console.log(`Processing: ${entry.numCpfCnpj}`);
                if (await handleCustomerSearchErrorModal(page)) continue;
                await searchAndNavigate(page, entry.numCpfCnpj);
                await delay(200);

                // Handle the validation error modal if it appears
               //// const errorOccurred = await handleValidationErrorModal(page);
                //if (errorOccurred) continue;
               

                await page.evaluate(() => {
                    document.querySelector('div.avatar-letters').click();
                });

                await clickMenuLink(page);
                await delay(400);
                await page.evaluate(() => {
                    const sessionMenu = document.querySelector('div.session-menu');
                    if (sessionMenu) {
                        const ul = sessionMenu.querySelector('ul');
                        if (ul) {
                            const link = Array.from(ul.querySelectorAll('li a')).find(anchor => anchor.textContent.trim() === 'Acordo Pj' || anchor.textContent.trim() === 'Acordo Pf');
                            if (link) link.click();
                        }
                    }
                });
                // Wait for the specific table to be fully loaded
               
                //await page.waitForNavigation();
                await page.evaluate(() => {
                    document.querySelector('#accordioAcordosPendentes > div.accordion-tab > div > div > h4 > span.accordion-tab-button').click();
                });
                //Going to extract data from the acordos table
                try
                {
                    await page.waitForSelector('spaui-datatable table tbody tr', { timeout: 15000 });
                    rows = await page.$$eval('spaui-datatable table tbody tr', rows => {
                        return rows.map(row => {
                            const cells = row.querySelectorAll('td');
                            
                            return {
                                product: cells[1] ? cells[1].innerText.trim() : 'N/A',
                                contractDate: cells[2] ? cells[2].innerText.trim() : 'N/A',
                                contractNumber: cells[3] ? cells[3].innerText.trim() : 'N/A',
                                contractedValue: cells[4] ? cells[4].innerText.trim() : 'N/A',
                                dueDate: cells[5] ? cells[5].innerText.trim() : 'N/A',
                                status: cells[6] ? cells[6].innerText.trim() : 'N/A',
                            };
                        });
                    });
                    console.log(rows);
                }
                catch(error)
                {
                    console.error('Error extracting table data:', error);
                    rows = []; // Ensure rows is an array even if empty
                }
                // Find the row where the contractNumber equals numAcordo and try to click the radio button
                const matchingRowIndex = rows.findIndex(row => row.contractNumber === entry.numAcordo);
                if (matchingRowIndex !== -1)
                {
                    console.log(matchingRowIndex)
                }
                
                
                console.log(`Processed: ${entry.numCpfCnpj} - ${entry.numAcordo}`);
                processedCount++;
            }
        }
    } catch (error) {
        console.error('Error occurred:', error);
    } finally {
        console.log(`Total processed entries: ${processedCount}`);
        await browser.close();
    }
};

module.exports = { runPuppeteer };

Thanks in advance for any input, and sorry for the long posting.
BTW: Should i try to make these questions smaller, or provide the maximum details as possible?