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

What happens when we call puppeteer waitForSelector API

这个问题是基于 Puppeteer 和 headless 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 函数是否在 hesdless 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)

然后我看到了其他论点

拉夫

3000

用户名

错误

正确

错误

这些都是信息。我无法将正在发生的事情完全联系起来。你能详细解释一下这里发生了什么吗?

Puppeteer 中的

waitFors 正在使用轮询解决。 waitForSelector 使用 raf 选项进行轮询:

raf: constantly execute pageFunction in requestAnimationFrame callback. This is the tightest polling mode which is suitable to observe styling changes.

所以,基本上。 waitForSelector 将在每个 requestAnimationFrame 上发送一个 运行 的函数。 That function will resolve a promise when the selector is visible (or hidden, depending on your options), or when it timeouts.

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

  • waitForPredicatePageFunction将被执行。
  • 由于 polling 方法将是 raf,它将等待 pollRaf
  • pollRaf 将执行它作为参数获得的函数,在本例中为 selector 检查。
  • 如果 false 将 return 一个承诺。
  • 它将调用自己调用requestAnimationFrame
  • 它将循环直到谓词 return 为真或超时。