当我们调用puppeteer waitForSelector API时会发生什么

时间:2019-08-07 11:23:56

标签: google-chrome-devtools puppeteer devtools

此问题基于Puppeteer和无头Chrome交互(基于chrome devtools协议)。

Puppeteer将JSON格式的消息发送到Chrome devtools,以控制Chrome操作,例如访问页面,在文本字段中键入或单击按钮等。

当我们执行以下代码时(这有助于等到#username可见)

await page.waitForSelector('#username', { visible: true });

Puppeteer向Chrome发送以下5条消息。

{"sessionId":"EB950D87CE0E2EED6D432F080811B87D","method":"Runtime.callFunctionOn","params":{"functionDeclaration":"async function waitForPredicatePageFunction(predicateBody, polling, timeout, ...args) {\n  const predicate = new Function('...args', predicateBody);\n  let timedOut = false;\n  if (timeout)\n    setTimeout(() => timedOut = true, timeout);\n  if (polling === 'raf')\n    return await pollRaf();\n  if (polling === 'mutation')\n    return await pollMutation();\n  if (typeof polling === 'number')\n    return await pollInterval(polling);\n\n  /**\n   * @return {!Promise<*>}\n   */\n  function pollMutation() {\n    const success = predicate.apply(null, args);\n    if (success)\n      return Promise.resolve(success);\n\n    let fulfill;\n    const result = new Promise(x => fulfill = x);\n    const observer = new MutationObserver(mutations => {\n      if (timedOut) {\n        observer.disconnect();\n        fulfill();\n      }\n      const success = predicate.apply(null, args);\n      if (success) {\n        observer.disconnect();\n        fulfill(success);\n      }\n    });\n    observer.observe(document, {\n      childList: true,\n      subtree: true,\n      attributes: true\n    });\n    return result;\n  }\n\n  /**\n   * @return {!Promise<*>}\n   */\n  function pollRaf() {\n    let fulfill;\n    const result = new Promise(x => fulfill = x);\n    onRaf();\n    return result;\n\n    function onRaf() {\n      if (timedOut) {\n        fulfill();\n        return;\n      }\n      const success = predicate.apply(null, args);\n      if (success)\n        fulfill(success);\n      else\n        requestAnimationFrame(onRaf);\n    }\n  }\n\n  /**\n   * @param {number} pollInterval\n   * @return {!Promise<*>}\n   */\n  function pollInterval(pollInterval) {\n    let fulfill;\n    const result = new Promise(x => fulfill = x);\n    onTimeout();\n    return result;\n\n    function onTimeout() {\n      if (timedOut) {\n        fulfill();\n        return;\n      }\n      const success = predicate.apply(null, args);\n      if (success)\n        fulfill(success);\n      else\n        setTimeout(onTimeout, pollInterval);\n    }\n  }\n}\n//# sourceURL=__puppeteer_evaluation_script__\n","executionContextId":4,"arguments":[{"value":"return (function predicate(selectorOrXPath, isXPath, waitForVisible, waitForHidden) {\n      const node = isXPath\n        ? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue\n        : document.querySelector(selectorOrXPath);\n      if (!node)\n        return waitForHidden;\n      if (!waitForVisible && !waitForHidden)\n        return node;\n      const element = /** @type {Element} */ (node.nodeType === Node.TEXT_NODE ? node.parentElement : node);\n\n      const style = window.getComputedStyle(element);\n      const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox();\n      const success = (waitForVisible === isVisible || waitForHidden === !isVisible);\n      return success ? node : null;\n\n      /**\n       * @return {boolean}\n       */\n      function hasVisibleBoundingBox() {\n        const rect = element.getBoundingClientRect();\n        return !!(rect.top || rect.bottom || rect.width || rect.height);\n      }\n    })(...args)"},{"value":"raf"},{"value":30000},{"value":"#username"},{"value":false},{"value":true},{"value":false}],"returnByValue":false,"awaitPromise":true,"userGesture":true},"id":28}

---------------

{"sessionId":"EB950D87CE0E2EED6D432F080811B87D","method":"Runtime.callFunctionOn","params":{"functionDeclaration":"s => !s\n//# sourceURL=__puppeteer_evaluation_script__\n","executionContextId":4,"arguments":[{"objectId":"{\"injectedScriptId\":4,\"id\":3}"}],"returnByValue":true,"awaitPromise":true,"userGesture":true},"id":29}

-------------

{"sessionId":"EB950D87CE0E2EED6D432F080811B87D","method":"DOM.describeNode","params":{"objectId":"{\"injectedScriptId\":4,\"id\":3}"},"id":30}

-------------

{"sessionId":"EB950D87CE0E2EED6D432F080811B87D","method":"DOM.resolveNode","params":{"backendNodeId":11,"executionContextId":3},"id":31}

-------------

{"sessionId":"EB950D87CE0E2EED6D432F080811B87D","method":"Runtime.releaseObject","params":{"objectId":"{\"injectedScriptId\":4,\"id\":3}"},"id":32}

我试图了解这里发生的事情。第一条消息看起来是javascript函数。此Javascript函数是否以毫不费力的Chrome执行。

基本上,我需要清楚地了解执行 waitForSelector 时发生的情况。

编辑 如果我是从第一条JSON消息中提取的,则javascript函数如下所示。

async function waitForPredicatePageFunction(predicateBody, polling, timeout, ...args) {\
  const predicate = new Function('...args', predicateBody);\
  let timedOut = false;\
  if (timeout)\
    setTimeout(() => timedOut = true, timeout);\
  if (polling === 'raf')\
    return await pollRaf();\
  if (polling === 'mutation')\
    return await pollMutation();\
  if (typeof polling === 'number')\
    return await pollInterval(polling);\
\
  /**\
   * @return {!Promise<*>}\
   */\
  function pollMutation() {\
    const success = predicate.apply(null, args);\
    if (success)\
      return Promise.resolve(success);\
\
    let fulfill;\
    const result = new Promise(x => fulfill = x);\
    const observer = new MutationObserver(mutations => {\
      if (timedOut) {\
        observer.disconnect();\
        fulfill();\
      }\
      const success = predicate.apply(null, args);\
      if (success) {\
        observer.disconnect();\
        fulfill(success);\
      }\
    });\
    observer.observe(document, {\
      childList: true,\
      subtree: true,\
      attributes: true\
    });\
    return result;\
  }\
\
  /**\
   * @return {!Promise<*>}\
   */\
  function pollRaf() {\
    let fulfill;\
    const result = new Promise(x => fulfill = x);\
    onRaf();\
    return result;\
\
    function onRaf() {\
      if (timedOut) {\
        fulfill();\
        return;\
      }\
      const success = predicate.apply(null, args);\
      if (success)\
        fulfill(success);\
      else\
        requestAnimationFrame(onRaf);\
    }\
  }\
\
  /**\
   * @param {number} pollInterval\
   * @return {!Promise<*>}\
   */\
  function pollInterval(pollInterval) {\
    let fulfill;\
    const result = new Promise(x => fulfill = x);\
    onTimeout();\
    return result;\
\
    function onTimeout() {\
      if (timedOut) {\
        fulfill();\
        return;\
      }\
      const success = predicate.apply(null, args);\
      if (success)\
        fulfill(success);\
      else\
        setTimeout(onTimeout, pollInterval);\
    }\
  }\
}\
//# sourceURL=__puppeteer_evaluation_script__\

在参数列表中,我看到以下参数

return (function predicate(selectorOrXPath, isXPath, waitForVisible, waitForHidden) {\
      const node = isXPath\
        ? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue\
        : document.querySelector(selectorOrXPath);\
      if (!node)\
        return waitForHidden;\
      if (!waitForVisible && !waitForHidden)\
        return node;\
      const element = /** @type {Element} */ (node.nodeType === Node.TEXT_NODE ? node.parentElement : node);\
\
      const style = window.getComputedStyle(element);\
      const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox();\
      const success = (waitForVisible === isVisible || waitForHidden === !isVisible);\
      return success ? node : null;\
\
      /**\
       * @return {boolean}\
       */\
      function hasVisibleBoundingBox() {\
        const rect = element.getBoundingClientRect();\
        return !!(rect.top || rect.bottom || rect.width || rect.height);\
      }\
    })(...args)

然后我看到其他论点

raf

3000

用户名

false

true

false

这些都是信息。我无法完全说明正在发生的事情。您能详细解释一下发生了什么吗?

1 个答案:

答案 0 :(得分:1)

正在使用轮询解决Puppeteer中的

waitFor个问题。 waitForSelector使用raf选项进行轮询:

  

raf:在requestAnimationFrame回调中不断执行pageFunction。这是最严格的轮询模式,适合观察样式更改。

所以,基本上。 waitForSelector将发送将在每个requestAnimationFrame上运行的函数。当选择器可见(或隐藏,取决于您的选项)或超时时,该函数将解决承诺。

当该函数被序列化并发送到Chromium时,将发生以下情况:

  • waitForPredicatePageFunction将被执行。
  • 由于polling方法将是raf,因此它将等待pollRaf
  • pollRaf将执行作为参数获得的功能,在本例中为selector检查。
  • 如果为false,则返回承诺。
  • 它将自行调用requestAnimationFrame
  • 它将循环运行直到谓词返回true或超时。