当我们调用 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 中的 waitFor
s 正在使用轮询解决。 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 为真或超时。
这个问题是基于 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
用户名
错误
正确
错误
这些都是信息。我无法将正在发生的事情完全联系起来。你能详细解释一下这里发生了什么吗?
waitFor
s 正在使用轮询解决。 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 为真或超时。