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.
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?