web-dev-qa-db-ja.com

複数のセレクターの操り人形師waitForSelector

結果または「レコードが見つかりません」というメッセージを返すことができるルックアップフォームを使用して、PuppeteerがWebサイトを制御しています。どちらが返されたかはどうすればわかりますか? waitForSelectorは一度に1つしか待機していないようですが、waitForNavigationはAjaxを使用して返されるため、機能していないようです。私はtrycatchを使用していますが、正しく実行するのは難しいため、すべてが遅くなります。

try {
    await page.waitForSelector(SELECTOR1,{timeout:1000}); 
}
catch(err) { 
    await page.waitForSelector(SELECTOR2);
}
5
Jon Wilson

Md。AbuTaherの提案を使用して、私はこれに行き着きました:

// 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
Jon Wilson

要素のいずれかを作成する

querySelectorAllwaitForを一緒に使用して、この問題を解決できます。すべてのセレクターをコンマで使用すると、いずれかのセレクターに一致するすべてのノードが返されます。

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

これで、要素がある場合にのみtrueが返され、どのセレクターがどの要素に一致したかは返されません。

9
Md. Abu Taher

私は同様の問題を抱えていて、この単純な解決策を選びました:

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
}
1
Gabriel Morin

Puppeteerメソッドは、リクエストを実行できない場合、エラーをスローする可能性があります。たとえば、指定された時間枠内にセレクターがどのノードとも一致しない場合、page.waitForSelector(selector [、options])が失敗する可能性があります。

特定のタイプのエラーの場合、Puppeteerは特定のエラークラスを使用します。これらのクラスはrequire( 'puppeteer/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.
  }
}
0

上記のいくつかの要素をヘルパーメソッドに組み合わせて、複数の可能なセレクター結果を作成し、最初に解決するものを処理できるようにするコマンドを作成しました。

/**
 * @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 :(');
    }
  })
0
BadPirate