代码之家  ›  专栏  ›  技术社区  ›  Jon Wilson

多个选择器上的木偶等待选择器

  •  20
  • Jon Wilson  · 技术社区  · 6 年前

    我让木偶师用一个查找表单控制一个网站,该表单可以返回结果或“未找到记录”消息。我怎么知道哪个被退回了? waitForSelector似乎一次只等待一个,而waitForNavigation似乎不起作用,因为它是使用Ajax返回的。 我正在尝试接球,但很难做到正确,让一切都慢下来。

    try {
        await page.waitForSelector(SELECTOR1,{timeout:1000}); 
    }
    catch(err) { 
        await page.waitForSelector(SELECTOR2);
    }
    
    10 回复  |  直到 4 年前
        1
  •  24
  •   Md. Abu Taher    3 年前

    使任何元素存在

    您可以使用 querySelectorAll waitForFunction 共同解决这个问题。使用带逗号的所有选择器将返回与任何选择器匹配的所有节点。

    await page.waitForFunction(() => 
      document.querySelectorAll('Selector1, Selector2, Selector3').length
    );
    

    现在这只会回来 true 如果有某个元素,它不会返回与哪些元素匹配的选择器。

        2
  •  12
  •   Alferd Nobel    4 年前

    使用如何 Promise.race() 就像我在下面的代码片段中所做的一样,不要忘记 { visible: true } 中的选项 page.waitForSelector() 方法

    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);
    }
    
        3
  •  7
  •   Jon Wilson    6 年前

    根据Abu Taher博士的建议,我得出以下结论:

    // 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
    
        4
  •  6
  •   Gabriel Morin    5 年前

    我也遇到了类似的问题,于是选择了这个简单的解决方案:

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

    然后使用它:

    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
    }
    
        5
  •  6
  •   Jon    4 年前

    另一种简单的解决方案是从更CSS的角度来解决这个问题。 waitForSelector 似乎遵循 CSS selector list rules 因此,基本上您可以通过使用逗号来选择多个CSS元素。

    try {    
        await page.waitForSelector('.selector1, .selector2',{timeout:1000})
    } catch (error) {
        // handle error
    }
    
        6
  •  5
  •   Andyba    3 年前

    在Puppeter中,您可以简单地使用多个选择器,这些选择器由coma分隔,如下所示:

    const foundElement = await page.waitForSelector('.class_1, .class_2');
    

    返回的元素将是页面中找到的第一个元素的elementHandle。

    接下来,如果您想知道找到了哪个元素,可以获得如下类名:

    const className = await page.evaluate(el => el.className, foundElement);
    

    在您的情况下,应该可以使用类似的代码:

    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 }
    
        7
  •  1
  •   LeOn - Han Li    4 年前

    进一步使用 Promise.race() 通过包装它,只需检查索引的进一步逻辑:

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

    用法:

    const navOutcome = await racePromises([
      page.waitForSelector('SELECTOR1'),
      page.waitForSelector('SELECTOR2')
    ]);
    if (navigationOutcome === 0) {
      //logic for 'SELECTOR1'
    } else if (navigationOutcome === 1) {
      //logic for 'SELECTOR2'
    }
    
    
    
        8
  •  0
  •   BadPirate    5 年前

    将上面的一些元素组合到助手方法中,我构建了一个命令,允许我创建多个可能的选择器结果,并处理第一个要解决的问题。

    /**
     * @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;
      });
    }

    要使用它,您只需调用并提供一系列可能的结果及其选择器/处理程序:

     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 :(');
        }
      })
    
        9
  •  0
  •   ItsMsalati    2 年前

    我刚开始 木偶演员 ,并且遇到了相同的问题,因此我想创建一个满足相同用例的自定义函数。

    功能如下:

    async function waitForMySelectors(selectors, page){
        for (let i = 0; i < selectors.length; i++) {
            await page.waitForSelector(selectors[i]);
        }
    }
    

    函数中的第一个参数接收选择器数组,第二个参数是我们在其中执行等待过程的页面。

    调用函数,如下例所示:

    var SelectorsArray = ['#username', '#password'];
    await waitForMySelectors(SelectorsArray, page);
    

    虽然我还没有对它进行任何测试,但它似乎很实用。

        10
  •  -1
  •   C Alonso C Ortega    6 年前

    如果木偶程序方法无法完成请求,则可能会抛出错误。例如,第页。如果选择器在给定的时间段内不匹配任何节点,waitForSelector(选择器[,选项])可能会失败。

    对于某些类型的错误,Puppeter使用特定的错误类。这些类可以通过require('puppeter/Errors')获得。

    支持的类列表:

    TimeoutError

    处理超时错误的示例:

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