Puppeteer waitForSelector on multiple selectors

故事扮演 提交于 2020-05-13 04:12:10

问题


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);
}

回答1:


Making any of the elements exists

You can use querySelectorAll and waitFor together to solve this problem. Using all selectors with comma will return all nodes that matches any of the selector.

await page.waitFor(() => 
  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.




回答2:


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



回答3:


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
}



回答4:


Puppeteer methods might throw errors if they are unable to fufill a request. For example, page.waitForSelector(selector[, options]) might fail if the selector doesn't match any nodes during the given timeframe.

For certain types of errors Puppeteer uses specific error classes. These classes are available via require('puppeteer/Errors').

List of supported classes:

TimeoutError

An example of handling a timeout error:

const {TimeoutError} = require('puppeteer/Errors');

// ...

try {
  await page.waitForSelector('.foo');
} catch (e) {
  if (e instanceof TimeoutError) {
    // Do something if this is a timeout.
  }
}



回答5:


Combining some elements from above into a helper method, I've built a command that allows me to create multiple possible selector outcomes and have the first to resolve be handled.

/**
 * @typedef {import('puppeteer').ElementHandle} PuppeteerElementHandle
 * @typedef {import('puppeteer').Page} PuppeteerPage
 */

/** Description of the function
  @callback OutcomeHandler
  @async
  @param {PuppeteerElementHandle} element matched element
  @returns {Promise<*>} can return anything, will be sent to handlePossibleOutcomes
*/

/**
 * @typedef {Object} PossibleOutcome
 * @property {string} selector The selector to trigger this outcome
 * @property {OutcomeHandler} handler handler will be called if selector is present
 */

/**
 * Waits for a number of selectors (Outcomes) on a Puppeteer page, and calls the handler on first to appear,
 * Outcome Handlers should be ordered by preference, as if multiple are present, only the first occuring handler
 * will be called.
 * @param {PuppeteerPage} page Puppeteer page object
 * @param {[PossibleOutcome]} outcomes each possible selector, and the handler you'd like called.
 * @returns {Promise<*>} returns the result from outcome handler
 */
async function handlePossibleOutcomes(page, outcomes)
{
  var outcomeSelectors = outcomes.map(outcome => {
    return outcome.selector;
  }).join(', ');
  return page.waitFor(outcomeSelectors)
  .then(_ => {
    let awaitables = [];
    outcomes.forEach(outcome => {
      let await = page.$(outcome.selector)
      .then(element => {
        if (element) {
          return [outcome, element];
        }
        return null;
      });
      awaitables.push(await);
    });
    return Promise.all(awaitables);
  })
  .then(checked => {
    let found = null;
    checked.forEach(check => {
      if(!check) return;
      if(found) return;
      let outcome = check[0];
      let element = check[1];
      let p = outcome.handler(element);
      found = p;
    });
    return found;
  });
}

To use it, you just have to call and provide an array of Possible Outcomes and their selectors / handlers:

 await handlePossibleOutcomes(page, [
    {
      selector: '#headerNavUserButton',
      handler: element => {
        console.log('Logged in',element);
        loggedIn = true;
        return true;
      }
    },
    {
      selector: '#email-login-password_error',
      handler: element => {
        console.log('password error',element);
        return false;
      }
    }
  ]).then(result => {
    if (result) {
      console.log('Logged in!',result);
    } else {
      console.log('Failed :(');
    }
  })


来源:https://stackoverflow.com/questions/49946728/puppeteer-waitforselector-on-multiple-selectors

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!