api: remove ExecutionContext from api (#290)

In the current state, it is superseeded by Frame and JSHandle.
This commit is contained in:
Dmitry Gozman 2019-12-18 13:51:45 -08:00 committed by GitHub
parent 58cd8210b0
commit 7750db97fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 53 additions and 66 deletions

View File

@ -125,7 +125,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate {
if (error.message.includes('Object couldn\'t be returned by value')) if (error.message.includes('Object couldn\'t be returned by value'))
return {result: {type: 'undefined'}}; return {result: {type: 'undefined'}};
if (error.message.endsWith('Cannot find context with specified id') || error.message.endsWith('Inspected target navigated or closed')) if (error.message.endsWith('Cannot find context with specified id') || error.message.endsWith('Inspected target navigated or closed') || error.message.endsWith('Execution context was destroyed.'))
throw new Error('Execution context was destroyed, most likely because of a navigation.'); throw new Error('Execution context was destroyed, most likely because of a navigation.');
throw error; throw error;
} }
@ -140,7 +140,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate {
for (const property of response.result) { for (const property of response.result) {
if (!property.enumerable) if (!property.enumerable)
continue; continue;
result.set(property.name, handle.executionContext()._createHandle(property.value)); result.set(property.name, handle._context._createHandle(property.value));
} }
return result; return result;
} }

View File

@ -7,7 +7,7 @@ export { ElementHandle } from '../dom';
export { TimeoutError } from '../errors'; export { TimeoutError } from '../errors';
export { Frame } from '../frames'; export { Frame } from '../frames';
export { Keyboard, Mouse } from '../input'; export { Keyboard, Mouse } from '../input';
export { ExecutionContext, JSHandle } from '../javascript'; export { JSHandle } from '../javascript';
export { Request, Response } from '../network'; export { Request, Response } from '../network';
export { Browser } from './Browser'; export { Browser } from './Browser';
export { BrowserContext } from '../browserContext'; export { BrowserContext } from '../browserContext';

View File

@ -85,10 +85,6 @@ export class Worker extends EventEmitter {
return this._url; return this._url;
} }
async executionContext(): Promise<js.ExecutionContext> {
return this._executionContextPromise;
}
evaluate: types.Evaluate = async (pageFunction, ...args) => { evaluate: types.Evaluate = async (pageFunction, ...args) => {
return (await this._executionContextPromise).evaluate(pageFunction, ...args as any); return (await this._executionContextPromise).evaluate(pageFunction, ...args as any);
} }

View File

@ -28,7 +28,7 @@ export class ConsoleMessage {
text(): string { text(): string {
if (this._text === undefined) if (this._text === undefined)
this._text = this._args.map(arg => arg.executionContext()._delegate.handleToString(arg, false /* includeType */)).join(' '); this._text = this._args.map(arg => arg._context._delegate.handleToString(arg, false /* includeType */)).join(' ');
return this._text; return this._text;
} }

View File

@ -87,6 +87,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
this._page = context.frame()._page; this._page = context.frame()._page;
} }
frame(): frames.Frame {
return this._context.frame();
}
asElement(): ElementHandle<T> | null { asElement(): ElementHandle<T> | null {
return this; return this;
} }

View File

@ -106,7 +106,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate {
return context._createHandle(payload.result); return context._createHandle(payload.result);
function rewriteError(error) : never { function rewriteError(error) : never {
if (error.message.includes('Failed to find execution context with id')) if (error.message.includes('Failed to find execution context with id') || error.message.includes('Execution context was destroyed!'))
throw new Error('Execution context was destroyed, most likely because of a navigation.'); throw new Error('Execution context was destroyed, most likely because of a navigation.');
throw error; throw error;
} }
@ -119,7 +119,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate {
}); });
const result = new Map(); const result = new Map();
for (const property of response.properties) for (const property of response.properties)
result.set(property.name, handle.executionContext()._createHandle(property.value)); result.set(property.name, handle._context._createHandle(property.value));
return result; return result;
} }

View File

@ -7,7 +7,7 @@ export { Browser } from './Browser';
export { BrowserContext } from '../browserContext'; export { BrowserContext } from '../browserContext';
export { BrowserFetcher } from '../browserFetcher'; export { BrowserFetcher } from '../browserFetcher';
export { Dialog } from '../dialog'; export { Dialog } from '../dialog';
export { ExecutionContext, JSHandle } from '../javascript'; export { JSHandle } from '../javascript';
export { ElementHandle } from '../dom'; export { ElementHandle } from '../dom';
export { Accessibility } from './features/accessibility'; export { Accessibility } from './features/accessibility';
export { Interception } from './features/interception'; export { Interception } from './features/interception';

View File

@ -354,10 +354,6 @@ export class Frame {
return this._context('utility'); return this._context('utility');
} }
executionContext(): Promise<dom.FrameExecutionContext> {
return this._mainContext();
}
evaluateHandle: types.EvaluateHandle = async (pageFunction, ...args) => { evaluateHandle: types.EvaluateHandle = async (pageFunction, ...args) => {
const context = await this._mainContext(); const context = await this._mainContext();
return context.evaluateHandle(pageFunction, ...args as any); return context.evaluateHandle(pageFunction, ...args as any);
@ -632,7 +628,7 @@ export class Frame {
const values = value === undefined ? [] : Array.isArray(value) ? value : [value]; const values = value === undefined ? [] : Array.isArray(value) ? value : [value];
const context = await this._utilityContext(); const context = await this._utilityContext();
const adoptedValues = await Promise.all(values.map(async value => { const adoptedValues = await Promise.all(values.map(async value => {
if (value instanceof dom.ElementHandle && value.executionContext() !== context) { if (value instanceof dom.ElementHandle && value._context !== context) {
const adopted = this._page._delegate.adoptElementHandle(value, context); const adopted = this._page._delegate.adoptElementHandle(value, context);
toDispose.push(adopted); toDispose.push(adopted);
return adopted; return adopted;

View File

@ -47,10 +47,6 @@ export class JSHandle<T = any> {
this._remoteObject = remoteObject; this._remoteObject = remoteObject;
} }
executionContext(): ExecutionContext {
return this._context;
}
evaluate: types.EvaluateOn<T> = (pageFunction, ...args) => { evaluate: types.EvaluateOn<T> = (pageFunction, ...args) => {
return this._context.evaluate(pageFunction, this, ...args); return this._context.evaluate(pageFunction, this, ...args);
} }

View File

@ -174,8 +174,7 @@ export class Page extends EventEmitter {
} }
evaluateHandle: types.EvaluateHandle = async (pageFunction, ...args) => { evaluateHandle: types.EvaluateHandle = async (pageFunction, ...args) => {
const context = await this.mainFrame().executionContext(); return this.mainFrame().evaluateHandle(pageFunction, ...args as any);
return context.evaluateHandle(pageFunction, ...args as any);
} }
$eval: types.$Eval = (selector, pageFunction, ...args) => { $eval: types.$Eval = (selector, pageFunction, ...args) => {

View File

@ -261,7 +261,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate {
for (const property of response.properties) { for (const property of response.properties) {
if (!property.enumerable) if (!property.enumerable)
continue; continue;
result.set(property.name, handle.executionContext()._createHandle(property.value)); result.set(property.name, handle._context._createHandle(property.value));
} }
return result; return result;
} }

View File

@ -215,7 +215,7 @@ export class FrameManager implements PageDelegate {
else if (type === 'timing') else if (type === 'timing')
derivedType = 'timeEnd'; derivedType = 'timeEnd';
const mainFrameContext = await this._page.mainFrame().executionContext(); const mainFrameContext = await this._page.mainFrame()._mainContext();
const handles = (parameters || []).map(p => { const handles = (parameters || []).map(p => {
let context: dom.FrameExecutionContext | null = null; let context: dom.FrameExecutionContext | null = null;
if (p.objectId) { if (p.objectId) {

View File

@ -5,7 +5,7 @@ export { TimeoutError } from '../errors';
export { Browser } from './Browser'; export { Browser } from './Browser';
export { BrowserContext } from '../browserContext'; export { BrowserContext } from '../browserContext';
export { BrowserFetcher } from '../browserFetcher'; export { BrowserFetcher } from '../browserFetcher';
export { ExecutionContext, JSHandle } from '../javascript'; export { JSHandle } from '../javascript';
export { ElementHandle } from '../dom'; export { ElementHandle } from '../dom';
export { Frame } from '../frames'; export { Frame } from '../frames';
export { Mouse, Keyboard } from '../input'; export { Mouse, Keyboard } from '../input';

View File

@ -67,11 +67,11 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
expect(log.args().length).toBe(4); expect(log.args().length).toBe(4);
expect(await (await log.args()[3].getProperty('origin')).jsonValue()).toBe('null'); expect(await (await log.args()[3].getProperty('origin')).jsonValue()).toBe('null');
}); });
it('should have an execution context', async function({page}) { it('should evaluate', async function({page}) {
const workerCreatedPromise = new Promise(x => page.workers.once('workercreated', x)); const workerCreatedPromise = new Promise(x => page.workers.once('workercreated', x));
await page.evaluate(() => new Worker(`data:text/javascript,console.log(1)`)); await page.evaluate(() => new Worker(`data:text/javascript,console.log(1)`));
const worker = await workerCreatedPromise; const worker = await workerCreatedPromise;
expect(await (await worker.executionContext()).evaluate('1+1')).toBe(2); expect(await worker.evaluate('1+1')).toBe(2);
}); });
it('should report errors', async function({page}) { it('should report errors', async function({page}) {
const errorPromise = new Promise(x => page.on('pageerror', x)); const errorPromise = new Promise(x => page.on('pageerror', x));

View File

@ -83,7 +83,7 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
location.reload(); location.reload();
return new Promise(() => {}); return new Promise(() => {});
}).catch(e => error = e); }).catch(e => error = e);
expect(error.message).toContain('Protocol error'); expect(error.message).toContain('navigation');
}); });
it('should await promise', async({page, server}) => { it('should await promise', async({page, server}) => {
const result = await page.evaluate(() => Promise.resolve(8 * 7)); const result = await page.evaluate(() => Promise.resolve(8 * 7));
@ -227,13 +227,15 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
expect(result).toBe(true); expect(result).toBe(true);
}); });
it('should throw a nice error after a navigation', async({page, server}) => { it('should throw a nice error after a navigation', async({page, server}) => {
const executionContext = await page.mainFrame().executionContext(); const errorPromise = page.evaluate(() => new Promise(f => window.__resolve = f)).catch(e => e);
await Promise.all([ await Promise.all([
page.waitForNavigation(), page.waitForNavigation(),
executionContext.evaluate(() => window.location.reload()) page.evaluate(() => {
window.location.reload();
setTimeout(() => window.__resolve(42), 1000);
})
]); ]);
const error = await executionContext.evaluate(() => null).catch(e => e); const error = await errorPromise;
expect(error.message).toContain('navigation'); expect(error.message).toContain('navigation');
}); });
it.skip(FFOX)('should not throw an error when evaluation does a navigation', async({page, server}) => { it.skip(FFOX)('should not throw an error when evaluation does a navigation', async({page, server}) => {

View File

@ -22,33 +22,6 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
const {it, fit, xit} = testRunner; const {it, fit, xit} = testRunner;
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
describe('Frame.executionContext', function() {
it('should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
expect(page.frames().length).toBe(2);
const [frame1, frame2] = page.frames();
const context1 = await frame1.executionContext();
const context2 = await frame2.executionContext();
expect(context1).toBeTruthy();
expect(context2).toBeTruthy();
expect(context1 !== context2).toBeTruthy();
expect(context1.frame()).toBe(frame1);
expect(context2.frame()).toBe(frame2);
await Promise.all([
context1.evaluate(() => window.a = 1),
context2.evaluate(() => window.a = 2)
]);
const [a1, a2] = await Promise.all([
context1.evaluate(() => window.a),
context2.evaluate(() => window.a)
]);
expect(a1).toBe(1);
expect(a2).toBe(2);
});
});
describe('Frame.evaluateHandle', function() { describe('Frame.evaluateHandle', function() {
it('should work', async({page, server}) => { it('should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
@ -66,6 +39,24 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
await frame1.evaluate(() => 7 * 8).catch(e => error = e); await frame1.evaluate(() => 7 * 8).catch(e => error = e);
expect(error.message).toContain('Execution Context is not available in detached frame'); expect(error.message).toContain('Execution Context is not available in detached frame');
}); });
it('should be isolated between frames', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
expect(page.frames().length).toBe(2);
const [frame1, frame2] = page.frames();
expect(frame1 !== frame2).toBeTruthy();
await Promise.all([
frame1.evaluate(() => window.a = 1),
frame2.evaluate(() => window.a = 2)
]);
const [a1, a2] = await Promise.all([
frame1.evaluate(() => window.a),
frame2.evaluate(() => window.a)
]);
expect(a1).toBe(1);
expect(a2).toBe(2);
});
}); });
describe('Frame Management', function() { describe('Frame Management', function() {

View File

@ -146,14 +146,17 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF
expect(await page.evaluate(() => !!window.opener)).toBe(false); expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(true); expect(await popup.evaluate(() => !!window.opener)).toBe(true);
}); });
it('should work with fake-clicking target=_blank and rel=noopener', async({page, server}) => { it.skip(FFOX)('should work with fake-clicking target=_blank and rel=noopener', async({page, server}) => {
// TODO: FFOX sends events for "one-style.html" request to both pages.
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.setContent('<a target=_blank rel=noopener href="/one-style.html">yo</a>'); await page.setContent('<a target=_blank rel=noopener href="/one-style.html">yo</a>');
const [popup] = await Promise.all([ const [popup] = await Promise.all([
new Promise(x => page.once('popup', x)), page.waitForEvent('popup'),
page.$eval('a', a => a.click()), page.$eval('a', a => a.click()),
]); ]);
expect(await page.evaluate(() => !!window.opener)).toBe(false); expect(await page.evaluate(() => !!window.opener)).toBe(false);
// TODO: At this point popup might still have about:blank as the current document.
// FFOX is slow enough to trigger this. We should do something about popups api.
expect(await popup.evaluate(() => !!window.opener)).toBe(false); expect(await popup.evaluate(() => !!window.opener)).toBe(false);
}); });
it('should work with clicking target=_blank and rel=noopener', async({page, server}) => { it('should work with clicking target=_blank and rel=noopener', async({page, server}) => {
@ -381,7 +384,7 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF
it('should work with relaxed search params match', async({page, server}) => { it('should work with relaxed search params match', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const [request] = await Promise.all([ const [request] = await Promise.all([
page.waitForRequest({ searchParams: { 'foo': ['bar', /^baz$/], 'bar': 'foo' } }), page.waitForRequest({ searchParams: { 'foo': ['bar', /^baz$/], 'bar': 'foo' }, url: /\.png/ }),
page.evaluate(() => { page.evaluate(() => {
fetch('/digits/1.png?key=value&foo=something'); fetch('/digits/1.png?key=value&foo=something');
fetch('/digits/2.png?foo=baz'); fetch('/digits/2.png?foo=baz');
@ -402,7 +405,7 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF
it('should throw for incorrect searchParams match', async({page, server}) => { it('should throw for incorrect searchParams match', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const [error] = await Promise.all([ const [error] = await Promise.all([
page.waitForRequest({ searchParams: { 'foo': 123 } }).catch(e => e), page.waitForRequest({ searchParams: { 'foo': 123 }, url: /\.png/ }).catch(e => e),
page.evaluate(() => { page.evaluate(() => {
fetch('/digits/1.png?foo=bar'); fetch('/digits/1.png?foo=bar');
}) })

View File

@ -252,7 +252,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
await otherFrame.evaluate(addElement, 'div'); await otherFrame.evaluate(addElement, 'div');
await page.evaluate(addElement, 'div'); await page.evaluate(addElement, 'div');
const eHandle = await watchdog; const eHandle = await watchdog;
expect(eHandle.executionContext().frame()).toBe(page.mainFrame()); expect(eHandle.frame()).toBe(page.mainFrame());
}); });
it('should run in specified frame', async({page, server}) => { it('should run in specified frame', async({page, server}) => {
@ -264,7 +264,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
await frame1.evaluate(addElement, 'div'); await frame1.evaluate(addElement, 'div');
await frame2.evaluate(addElement, 'div'); await frame2.evaluate(addElement, 'div');
const eHandle = await waitForSelectorPromise; const eHandle = await waitForSelectorPromise;
expect(eHandle.executionContext().frame()).toBe(frame2); expect(eHandle.frame()).toBe(frame2);
}); });
it('should throw when frame is detached', async({page, server}) => { it('should throw when frame is detached', async({page, server}) => {
@ -415,7 +415,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
await frame1.evaluate(addElement, 'div'); await frame1.evaluate(addElement, 'div');
await frame2.evaluate(addElement, 'div'); await frame2.evaluate(addElement, 'div');
const eHandle = await waitForXPathPromise; const eHandle = await waitForXPathPromise;
expect(eHandle.executionContext().frame()).toBe(frame2); expect(eHandle.frame()).toBe(frame2);
}); });
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);