Last active
July 29, 2022 21:45
-
-
Save rinogo/7370cfd10f0290a01c773221b26994ad to your computer and use it in GitHub Desktop.
Wait for an element to remain unchanged for a period of time. Useful for waiting for asynchronous (AJAX) updates. Playwright, ES6, Promise, Mutation, MutationObserver
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/////// | |
//To test this code, execute it locally or copy/paste it at https://try.playwright.tech/ | |
//Usage example: `await waitForMutationToStop(await page.$("#container"));` | |
/////// | |
// @ts-check | |
const playwright = require("playwright"); | |
//Wait for `elem` to have no mutations for a period of `noMutationDuration` ms. If `timeout` ms elapse without a "no mutation" period of sufficient length, throw an error. If `waitForFirstMutation` is true, wait until the first mutation before starting to wait for the `noMutationDuration` period. | |
const waitForMutationToStop = async (elem, noMutationDuration = 3000, timeout = 60000, waitForFirstMutation = true) => { | |
return elem.evaluate(async (elem, [noMutationDuration, timeout, waitForFirstMutation]) => { | |
//Resolve when a mutation occurs on elem. | |
const waitForMutation = async (elem) => { | |
let html = elem.innerHTML; | |
return new Promise((resolve, reject) => { | |
new MutationObserver((mutationRecords, observer) => { | |
if(elem.innerHTML != html) { | |
console.log("Mutation detected."); | |
resolve(); | |
observer.disconnect(); | |
} | |
}) | |
.observe(document.documentElement, { | |
childList: true, | |
attributes: true, | |
characterData: true, | |
subtree: true, | |
}); | |
}) | |
}; | |
let timeoutId, noMutationTimeoutId; | |
return Promise.race([ | |
//Reject when `timeout` ms have passed | |
new Promise((resolve, reject) => timeoutId = setTimeout(reject, timeout, `Reached timeout of ${timeout} ms while waiting for mutation to stop.`)), | |
//Resolve when `noMutationDuration` ms have passed since the last mutation. | |
new Promise(async (resolve, reject) => { | |
//If requested, wait for the first mutation to occur. This allows the function to wait longer than `noMutationDuration` ms for the first mutation to occur. Once the first mutation occurs, the function will resume "normal" behavior - that is, it will wait until no mutations occur for `noMutationDuration` ms before resolving. | |
if(waitForFirstMutation) { | |
console.log("Waiting for first mutation."); | |
await waitForMutation(elem); | |
} | |
while(true) { | |
noMutationTimeoutId = setTimeout(resolve, noMutationDuration) //We reset this timer every time a mutation occurs. So, when it finally "executes", we know that `noMutationDuration` has passed since the last mutation. | |
console.log(`Waiting ${noMutationDuration} ms for mutation.`); | |
await waitForMutation(elem); | |
if(!noMutationTimeoutId) { | |
break; | |
} | |
clearTimeout(noMutationTimeoutId); | |
} | |
}), | |
]) | |
.then( | |
(value) => { | |
console.log(`${noMutationDuration} ms have elapsed since this function was called or the last mutation was detected. Fulfilling.`); | |
}, (reason) => { | |
console.log(`${timeout} ms have elapsed without a ${noMutationDuration} ms period devoid of mutation. Rejecting.`); | |
throw new Error(reason); | |
} | |
) | |
//Clear timeouts - if we don't, Node will refuse to exit until active timeouts expire | |
.finally(() => { | |
clearTimeout(timeoutId); | |
clearTimeout(noMutationTimeoutId); | |
noMutationTimeoutId = null; | |
}) | |
}, | |
[noMutationDuration, timeout, waitForFirstMutation]); | |
} | |
//Test harness | |
(async () => { | |
const browser = await playwright.chromium.launch(); | |
const context = await browser.newContext(); | |
const page = await context.newPage(); | |
await page.goto("https://rinogo.github.io/playwright-mutation-test-site/static-delays.html"); | |
await waitForMutationToStop(await page.$("#container")); | |
await page.screenshot({ path: `example.png` }); | |
await browser.close(); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Released under the MIT License - please let me know if you appreciate this and share your improvements! 👍