From 4afd39117a6cff704f91095d86768825a8e1c98a Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Wed, 29 Apr 2020 20:46:42 -0700 Subject: [PATCH] fix(waitForSelector): use raf polling instead of mutation (#2047) MutationObserver does not work with mutations in the shadow, so we cannot use it for selectors that pierce shadows. --- src/selectors.ts | 5 ++--- test/dispatchevent.spec.js | 18 ++++++++++++++++++ test/waittask.spec.js | 17 +++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/selectors.ts b/src/selectors.ts index 218f810aba4d2..0bef8dcb71405 100644 --- a/src/selectors.ts +++ b/src/selectors.ts @@ -145,8 +145,7 @@ export class Selectors { _waitForSelectorTask(selector: string, waitFor: 'attached' | 'detached' | 'visible' | 'hidden', deadline: number): { world: 'main' | 'utility', task: (context: dom.FrameExecutionContext) => Promise } { const parsed = this._parseSelector(selector); const task = async (context: dom.FrameExecutionContext) => context.evaluateHandleInternal(({ evaluator, parsed, waitFor, timeout }) => { - const polling = (waitFor === 'attached' || waitFor === 'detached') ? 'mutation' : 'raf'; - return evaluator.injected.poll(polling, timeout, () => { + return evaluator.injected.poll('raf', timeout, () => { const element = evaluator.querySelector(parsed, document); switch (waitFor) { case 'attached': @@ -166,7 +165,7 @@ export class Selectors { _dispatchEventTask(selector: string, type: string, eventInit: Object, deadline: number): (context: dom.FrameExecutionContext) => Promise { const parsed = this._parseSelector(selector); const task = async (context: dom.FrameExecutionContext) => context.evaluateHandleInternal(({ evaluator, parsed, type, eventInit, timeout }) => { - return evaluator.injected.poll('mutation', timeout, () => { + return evaluator.injected.poll('raf', timeout, () => { const element = evaluator.querySelector(parsed, document); if (element) evaluator.injected.dispatchEvent(element, type, eventInit); diff --git a/test/dispatchevent.spec.js b/test/dispatchevent.spec.js index 991cdb665ed58..ccd5663576f20 100644 --- a/test/dispatchevent.spec.js +++ b/test/dispatchevent.spec.js @@ -79,6 +79,24 @@ describe('Page.dispatchEvent(click)', function() { await page.dispatchEvent('button', 'click'); expect(await page.evaluate(() => window.clicked)).toBeTruthy(); }); + it('should dispatch click when node is added in shadow dom', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const watchdog = page.dispatchEvent('span', 'click'); + await page.evaluate(() => { + const div = document.createElement('div'); + div.attachShadow({mode: 'open'}); + document.body.appendChild(div); + }); + await page.evaluate(() => new Promise(f => setTimeout(f, 100))); + await page.evaluate(() => { + const span = document.createElement('span'); + span.textContent = 'Hello from shadow'; + span.addEventListener('click', () => window.clicked = true); + document.querySelector('div').shadowRoot.appendChild(span); + }); + await watchdog; + expect(await page.evaluate(() => window.clicked)).toBe(true); + }); }); describe('Page.dispatchEvent(drag)', function() { diff --git a/test/waittask.spec.js b/test/waittask.spec.js index 2b3a2a18db287..7130c0d8aede9 100644 --- a/test/waittask.spec.js +++ b/test/waittask.spec.js @@ -192,6 +192,23 @@ describe('Frame.waitForSelector', function() { const tagName = await eHandle.getProperty('tagName').then(e => e.jsonValue()); expect(tagName).toBe('DIV'); }); + it('should resolve promise when node is added in shadow dom', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const watchdog = page.waitForSelector('span'); + await page.evaluate(() => { + const div = document.createElement('div'); + div.attachShadow({mode: 'open'}); + document.body.appendChild(div); + }); + await page.evaluate(() => new Promise(f => setTimeout(f, 100))); + await page.evaluate(() => { + const span = document.createElement('span'); + span.textContent = 'Hello from shadow'; + document.querySelector('div').shadowRoot.appendChild(span); + }); + const handle = await watchdog; + expect(await handle.evaluate(e => e.textContent)).toBe('Hello from shadow'); + }); it('should work when node is added through innerHTML', async({page, server}) => { await page.goto(server.EMPTY_PAGE); const watchdog = page.waitForSelector('h3 div');