此问题基于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
这些都是信息。我无法完全说明正在发生的事情。您能详细解释一下发生了什么吗?
答案 0 :(得分:1)
waitFor
个问题。 waitForSelector
使用raf
选项进行轮询:
raf:在requestAnimationFrame回调中不断执行pageFunction。这是最严格的轮询模式,适合观察样式更改。
所以,基本上。 waitForSelector
将发送将在每个requestAnimationFrame
上运行的函数。当选择器可见(或隐藏,取决于您的选项)或超时时,该函数将解决承诺。
当该函数被序列化并发送到Chromium时,将发生以下情况:
waitForPredicatePageFunction
将被执行。polling
方法将是raf
,因此它将等待pollRaf
。pollRaf
将执行作为参数获得的功能,在本例中为selector
检查。requestAnimationFrame
。