diff --git a/test/integration/copy_paste_spec.mjs b/test/integration/copy_paste_spec.mjs index 94d6be9a76fd75..30b32978860255 100644 --- a/test/integration/copy_paste_spec.mjs +++ b/test/integration/copy_paste_spec.mjs @@ -23,9 +23,10 @@ import { } from "./test_utils.mjs"; const selectAll = async page => { - const promise = waitForEvent(page, "selectionchange"); + const generator = waitForEvent(page, "selectionchange"); + await generator.next(); // Wait until the event listener is registered. await kbSelectAll(page); - await promise; + await generator.next(); // Wait for the event or a timeout. await page.waitForFunction(() => { const selection = document.getSelection(); diff --git a/test/integration/test_utils.mjs b/test/integration/test_utils.mjs index 4fd4e9e35db844..fc365d387a27b8 100644 --- a/test/integration/test_utils.mjs +++ b/test/integration/test_utils.mjs @@ -186,13 +186,14 @@ async function getSpanRectFromText(page, pageNumber, text) { ); } -async function waitForEvent( +async function* waitForEvent( page, eventName, selector = null, validator = null, timeout = 5000 ) { + // Register the event listener in the page context. const handle = await page.evaluateHandle( (name, sel, validate, timeOut) => { let callback = null, @@ -227,13 +228,22 @@ async function waitForEvent( validator ? validator.toString() : null, timeout ); + + // Give back control to the caller to trigger the event. Note that the event + // listener registration above *must* be fully completed (and is guaranteed + // to be at this point) before it's safe to hand back control to the caller, + // otherwise the caller could trigger the event before the event listener + // is in place and therefore miss the event here and get in the timeout. + yield; + + // The caller indicated that it's done triggering the event by calling + // `next()`, so we can now safely wait for the result of `Promise.race` above. const success = await awaitPromise(handle); if (success === null) { console.log(`waitForEvent: ${eventName} didn't trigger within the timeout`); } else if (!success) { console.log(`waitForEvent: ${eventName} triggered, but validation failed`); } - return success; } async function waitForStorageEntries(page, nEntries) { @@ -293,9 +303,10 @@ async function mockClipboard(pages) { } async function copy(page) { - const promise = waitForEvent(page, "copy"); + const generator = waitForEvent(page, "copy"); + await generator.next(); // Wait until the event listener is registered. await kbCopy(page); - await promise; + await generator.next(); // Wait for the event or a timeout. } async function copyData(page, data) { @@ -314,20 +325,18 @@ async function copyData(page, data) { } async function paste(page) { - const promise = waitForEvent(page, "paste"); + const generator = waitForEvent(page, "paste"); + await generator.next(); // Wait until the event listener is registered. await kbPaste(page); - await promise; + await generator.next(); // Wait for the event or a timeout. } async function pasteData(page, selector = null) { const validator = e => e.clipboardData.items.length !== 0; - let hasPasteEvent = false; - while (!hasPasteEvent) { - // We retry to paste if nothing has been pasted before the timeout. - const promise = waitForEvent(page, "paste", selector, validator); - await kbPaste(page); - hasPasteEvent = await promise; - } + const generator = waitForEvent(page, "paste", selector, validator); + await generator.next(); // Wait until the event listener is registered. + await kbPaste(page); + await generator.next(); // Wait for the event or a timeout. } async function getSerialized(page, filter = undefined) {