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.
This commit is contained in:
Dmitry Gozman 2020-04-29 20:46:42 -07:00 committed by GitHub
parent 9f62f29946
commit 4afd39117a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 37 additions and 3 deletions

View File

@ -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<js.JSHandle> } {
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<js.JSHandle> {
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);

View File

@ -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() {

View File

@ -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');