I have Puppeteer controlling a website with a lookup form that can either return a result or a "No records found" message. How can I tell which was returned? waitForSelector seems to wait for only one at a time, while waitForNavigation doesn't seem to work because it is returned using Ajax. I am using a try catch, but it is tricky to get right and slows everything way down.
try {
await page.waitForSelector(SELECTOR1,{timeout:1000});
}
catch(err) {
await page.waitForSelector(SELECTOR2);
}
You can use querySelectorAll
and waitForFunction
together to solve this problem. Using all selectors with comma will return all nodes that matches any of the selector.
await page.waitForFunction(() =>
document.querySelectorAll('Selector1, Selector2, Selector3').length
);
Now this will only return true
if there is some element, it won't return which selector matched which elements.
how about using Promise.race()
like something I did in the below code snippet, and don't forget the { visible: true }
option in page.waitForSelector()
method.
public async enterUsername(username:string) : Promise<void> {
const un = await Promise.race([
this.page.waitForSelector(selector_1, { timeout: 4000, visible: true })
.catch(),
this.page.waitForSelector(selector_2, { timeout: 4000, visible: true })
.catch(),
]);
await un.focus();
await un.type(username);
}
An alternative and simple solution would be to approach this from a more CSS perspective. waitForSelector
seems to follow the CSS selector list rules. So essentially you can select multiple CSS elements by just using a comma.
try {
await page.waitForSelector('.selector1, .selector2',{timeout:1000})
} catch (error) {
// handle error
}
Using Md. Abu Taher's suggestion, I ended up with this:
// One of these SELECTORs should appear, we don't know which
await page.waitForFunction((sel) => {
return document.querySelectorAll(sel).length;
},{timeout:10000},SELECTOR1 + ", " + SELECTOR2);
// Now see which one appeared:
try {
await page.waitForSelector(SELECTOR1,{timeout:10});
}
catch(err) {
//check for "not found"
let ErrMsg = await page.evaluate((sel) => {
let element = document.querySelector(sel);
return element? element.innerHTML: null;
},SELECTOR2);
if(ErrMsg){
//SELECTOR2 found
}else{
//Neither found, try adjusting timeouts until you never get this...
}
};
//SELECTOR1 found
I had a similar issue and went for this simple solution:
helpers.waitForAnySelector = (page, selectors) => new Promise((resolve, reject) => {
let hasFound = false
selectors.forEach(selector => {
page.waitFor(selector)
.then(() => {
if (!hasFound) {
hasFound = true
resolve(selector)
}
})
.catch((error) => {
// console.log('Error while looking up selector ' + selector, error.message)
})
})
})
And then to use it:
const selector = await helpers.waitForAnySelector(page, [
'#inputSmsCode',
'#buttonLogOut'
])
if (selector === '#inputSmsCode') {
// We need to enter the 2FA sms code.
} else if (selector === '#buttonLogOut') {
// We successfully logged in
}
In puppeteer you can simply use multiple selectors separated by coma like this:
const foundElement = await page.waitForSelector('.class_1, .class_2');
The returned element will be an elementHandle of the first element found in the page.
Next if you want to know which element was found you can get the class name like so:
const className = await page.evaluate(el => el.className, foundElement);
in your case a code similar to this should work:
const foundElement = await page.waitForSelector([SELECTOR1,SELECTOR2].join(','));
const responseMsg = await page.evaluate(el => el.innerText, foundElement);
if (responseMsg == "No records found"){ // Your code here }
One step further using Promise.race()
by wrapping it and just check index for further logic:
// Typescript
export async function racePromises(promises: Promise<any>[]): Promise<number> {
const indexedPromises: Array<Promise<number>> = promises.map((promise, index) => new Promise<number>((resolve) => promise.then(() => resolve(index))));
return Promise.race(indexedPromises);
}
// Javascript
export async function racePromises(promises) {
const indexedPromises = promises.map((promise, index) => new Promise((resolve) => promise.then(() => resolve(index))));
return Promise.race(indexedPromises);
}
Usage:
const navOutcome = await racePromises([
page.waitForSelector('SELECTOR1'),
page.waitForSelector('SELECTOR2')
]);
if (navigationOutcome === 0) {
//logic for 'SELECTOR1'
} else if (navigationOutcome === 1) {
//logic for 'SELECTOR2'
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With