mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
fix(page): execute frame.waitFor{Selector,XPath} in secondary world (#3856)
This patch starts executing frame.waitForSelector and frame.waitForXPath in secondary world. As a result, websites that mutate page global context (e.g. removing global MutationObserver) don't break Puppeteer's behavior. Fixes #609
This commit is contained in:
parent
2061dd4718
commit
55432f88e9
@ -173,6 +173,22 @@ class ExecutionContext {
|
|||||||
});
|
});
|
||||||
return createJSHandle(this, response.objects);
|
return createJSHandle(this, response.objects);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Puppeteer.ElementHandle} elementHandle
|
||||||
|
* @return {Promise<Puppeteer.ElementHandle>}
|
||||||
|
*/
|
||||||
|
async _adoptElementHandle(elementHandle) {
|
||||||
|
assert(elementHandle.executionContext() !== this, 'Cannot adopt handle that already belongs to this execution context');
|
||||||
|
assert(this._world, 'Cannot adopt handle without DOMWorld');
|
||||||
|
const nodeInfo = await this._client.send('DOM.describeNode', {
|
||||||
|
objectId: elementHandle._remoteObject.objectId,
|
||||||
|
});
|
||||||
|
const {object} = await this._client.send('DOM.resolveNode', {
|
||||||
|
backendNodeId: nodeInfo.node.backendNodeId,
|
||||||
|
});
|
||||||
|
return /** @type {Puppeteer.ElementHandle}*/(createJSHandle(this, object));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {ExecutionContext, EVALUATION_SCRIPT_URL};
|
module.exports = {ExecutionContext, EVALUATION_SCRIPT_URL};
|
||||||
|
@ -608,8 +608,14 @@ class Frame {
|
|||||||
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
|
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
|
||||||
* @return {!Promise<?Puppeteer.ElementHandle>}
|
* @return {!Promise<?Puppeteer.ElementHandle>}
|
||||||
*/
|
*/
|
||||||
waitForSelector(selector, options) {
|
async waitForSelector(selector, options) {
|
||||||
return this._mainWorld.waitForSelector(selector, options);
|
const handle = await this._secondaryWorld.waitForSelector(selector, options);
|
||||||
|
if (!handle)
|
||||||
|
return null;
|
||||||
|
const mainExecutionContext = await this._mainWorld.executionContext();
|
||||||
|
const result = await mainExecutionContext._adoptElementHandle(handle);
|
||||||
|
await handle.dispose();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -617,8 +623,14 @@ class Frame {
|
|||||||
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
|
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
|
||||||
* @return {!Promise<?Puppeteer.ElementHandle>}
|
* @return {!Promise<?Puppeteer.ElementHandle>}
|
||||||
*/
|
*/
|
||||||
waitForXPath(xpath, options) {
|
async waitForXPath(xpath, options) {
|
||||||
return this._mainWorld.waitForXPath(xpath, options);
|
const handle = await this._secondaryWorld.waitForXPath(xpath, options);
|
||||||
|
if (!handle)
|
||||||
|
return null;
|
||||||
|
const mainExecutionContext = await this._mainWorld.executionContext();
|
||||||
|
const result = await mainExecutionContext._adoptElementHandle(handle);
|
||||||
|
await handle.dispose();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -205,6 +205,13 @@ module.exports.addTests = function({testRunner, expect, product}) {
|
|||||||
await frame.waitForSelector('div');
|
await frame.waitForSelector('div');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should work with removed MutationObserver', async({page, server}) => {
|
||||||
|
await page.evaluate(() => delete window.MutationObserver);
|
||||||
|
const waitForSelector = page.waitForSelector('.zombo');
|
||||||
|
await page.setContent(`<div class='zombo'>anything</div>`);
|
||||||
|
expect(await page.evaluate(x => x.textContent, await waitForSelector)).toBe('anything');
|
||||||
|
});
|
||||||
|
|
||||||
it('should resolve promise when node is added', async({page, server}) => {
|
it('should resolve promise when node is added', async({page, server}) => {
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
const frame = page.mainFrame();
|
const frame = page.mainFrame();
|
||||||
@ -247,15 +254,6 @@ module.exports.addTests = function({testRunner, expect, product}) {
|
|||||||
expect(eHandle.executionContext().frame()).toBe(frame2);
|
expect(eHandle.executionContext().frame()).toBe(frame2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw if evaluation failed', async({page, server}) => {
|
|
||||||
await page.evaluateOnNewDocument(function() {
|
|
||||||
document.querySelector = null;
|
|
||||||
});
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
|
||||||
let error = null;
|
|
||||||
await page.waitForSelector('*').catch(e => error = e);
|
|
||||||
expect(error.message).toContain('document.querySelector is not a function');
|
|
||||||
});
|
|
||||||
it('should throw when frame is detached', async({page, server}) => {
|
it('should throw when frame is detached', async({page, server}) => {
|
||||||
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
|
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
|
||||||
const frame = page.frames()[1];
|
const frame = page.frames()[1];
|
||||||
@ -394,15 +392,6 @@ module.exports.addTests = function({testRunner, expect, product}) {
|
|||||||
const eHandle = await waitForXPathPromise;
|
const eHandle = await waitForXPathPromise;
|
||||||
expect(eHandle.executionContext().frame()).toBe(frame2);
|
expect(eHandle.executionContext().frame()).toBe(frame2);
|
||||||
});
|
});
|
||||||
it('should throw if evaluation failed', async({page, server}) => {
|
|
||||||
await page.evaluateOnNewDocument(function() {
|
|
||||||
document.evaluate = null;
|
|
||||||
});
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
|
||||||
let error = null;
|
|
||||||
await page.waitForXPath('*').catch(e => error = e);
|
|
||||||
expect(error.message).toContain('document.evaluate is not a function');
|
|
||||||
});
|
|
||||||
it('should throw when frame is detached', async({page, server}) => {
|
it('should throw when frame is detached', async({page, server}) => {
|
||||||
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
|
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
|
||||||
const frame = page.frames()[1];
|
const frame = page.frames()[1];
|
||||||
|
Loading…
Reference in New Issue
Block a user