This question is based on Puppeteer and headless Chrome interaction (based on chrome devtools protocol).
Puppeteer sends JSON formatted messages to Chrome devtools to control chrome operations like accessing a page, typing in a textfield or click a button etc.
When we execute below line (this helps to wait till #username is visible)
await page.waitForSelector('#username', { visible: true });
Puppeteer sends below 5 messages to Chrome.
{"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}
I am trying to understand whats happening here. The 1st message looks a javascript function. Is this Javascript function gets executed at hesdless chrome.
Basically I need to understand clearly whats happening when waitForSelector is executed.
EDIT The javascript function if I extract from first JSON messages looks as below.
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__\
And in argument list I see below arguments
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)
Then other arguments I see
raf
3000
username
false
true
false
These are all information. I am not able to relate altogether what is happening. Can you please explain in detail whats going on here.
waitFor
s in Puppeteer are being solved using polling. waitForSelector
uses the raf
option for polling:
raf: constantly execute pageFunction in requestAnimationFrame callback. This is the tightest polling mode which is suitable to observe styling changes.
So, basically. waitForSelector
will send a function that will run on each requestAnimationFrame
. That function will resolve a promise when the selector is visible (or hidden, depending on your options), or when it timeouts.
When that function is serialized and sent to Chromium this is what would happen:
waitForPredicatePageFunction
will be executed.polling
method will be raf
, it will await pollRaf
.pollRaf
will execute the function it got as an argument, in this case, a selector
check.requestAnimationFrame
.