refactor: use generic implementations (#10833)

This commit is contained in:
jrandolf 2023-09-05 09:10:16 +02:00 committed by GitHub
parent 06c1588016
commit d4bf52985e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 89 additions and 313 deletions

View File

@ -36,9 +36,11 @@ import {
NodeFor,
} from '../common/types.js';
import {
getPageContent,
importFSPromises,
withSourcePuppeteerURLIfNone,
} from '../common/util.js';
import {assert} from '../util/assert.js';
import {throwIfDisposed} from '../util/decorators.js';
import {KeyboardTypeOptions} from './Input.js';
@ -320,6 +322,16 @@ export abstract class Frame extends EventEmitter {
*/
abstract isolatedRealm(): Realm;
/**
* @internal
*/
async document(): Promise<ElementHandle<Document>> {
// TODO(#10813): Implement document caching.
return await this.evaluateHandle(() => {
return document;
});
}
/**
* @internal
*/
@ -429,7 +441,8 @@ export abstract class Frame extends EventEmitter {
async $<Selector extends string>(
selector: Selector
): Promise<ElementHandle<NodeFor<Selector>> | null> {
return await this.mainRealm().$(selector);
using document = await this.document();
return await document.$(selector);
}
/**
@ -443,7 +456,8 @@ export abstract class Frame extends EventEmitter {
async $$<Selector extends string>(
selector: Selector
): Promise<Array<ElementHandle<NodeFor<Selector>>>> {
return await this.mainRealm().$$(selector);
using document = await this.document();
return await document.$$(selector);
}
/**
@ -480,7 +494,8 @@ export abstract class Frame extends EventEmitter {
...args: Params
): Promise<Awaited<ReturnType<Func>>> {
pageFunction = withSourcePuppeteerURLIfNone(this.$eval.name, pageFunction);
return await this.mainRealm().$eval(selector, pageFunction, ...args);
using document = await this.document();
return await document.$eval(selector, pageFunction, ...args);
}
/**
@ -517,7 +532,8 @@ export abstract class Frame extends EventEmitter {
...args: Params
): Promise<Awaited<ReturnType<Func>>> {
pageFunction = withSourcePuppeteerURLIfNone(this.$$eval.name, pageFunction);
return await this.mainRealm().$$eval(selector, pageFunction, ...args);
using document = await this.document();
return await document.$$eval(selector, pageFunction, ...args);
}
/**
@ -532,7 +548,8 @@ export abstract class Frame extends EventEmitter {
*/
@throwIfDetached
async $x(expression: string): Promise<Array<ElementHandle<Node>>> {
return await this.mainRealm().$x(expression);
using document = await this.document();
return await document.$x(expression);
}
/**
@ -670,7 +687,7 @@ export abstract class Frame extends EventEmitter {
*/
@throwIfDetached
async content(): Promise<string> {
return await this.isolatedRealm().content();
return await this.evaluate(getPageContent);
}
/**
@ -918,7 +935,10 @@ export abstract class Frame extends EventEmitter {
selector: string,
options: Readonly<ClickOptions> = {}
): Promise<void> {
return await this.isolatedRealm().click(selector, options);
using handle = await this.$(selector);
assert(handle, `No element found for selector: ${selector}`);
await handle.click(options);
await handle.dispose();
}
/**
@ -929,7 +949,9 @@ export abstract class Frame extends EventEmitter {
*/
@throwIfDetached
async focus(selector: string): Promise<void> {
return await this.isolatedRealm().focus(selector);
using handle = await this.$(selector);
assert(handle, `No element found for selector: ${selector}`);
await handle.focus();
}
/**
@ -941,7 +963,9 @@ export abstract class Frame extends EventEmitter {
*/
@throwIfDetached
async hover(selector: string): Promise<void> {
return await this.isolatedRealm().hover(selector);
using handle = await this.$(selector);
assert(handle, `No element found for selector: ${selector}`);
await handle.hover();
}
/**
@ -964,7 +988,9 @@ export abstract class Frame extends EventEmitter {
*/
@throwIfDetached
async select(selector: string, ...values: string[]): Promise<string[]> {
return await this.isolatedRealm().select(selector, ...values);
using handle = await this.$(selector);
assert(handle, `No element found for selector: ${selector}`);
return await handle.select(...values);
}
/**
@ -975,7 +1001,9 @@ export abstract class Frame extends EventEmitter {
*/
@throwIfDetached
async tap(selector: string): Promise<void> {
return await this.isolatedRealm().tap(selector);
using handle = await this.$(selector);
assert(handle, `No element found for selector: ${selector}`);
await handle.tap();
}
/**
@ -1005,7 +1033,9 @@ export abstract class Frame extends EventEmitter {
text: string,
options?: Readonly<KeyboardTypeOptions>
): Promise<void> {
return await this.isolatedRealm().type(selector, text, options);
using handle = await this.$(selector);
assert(handle, `No element found for selector: ${selector}`);
await handle.type(text, options);
}
/**
@ -1039,7 +1069,9 @@ export abstract class Frame extends EventEmitter {
*/
@throwIfDetached
async title(): Promise<string> {
return await this.isolatedRealm().title();
return await this.isolatedRealm().evaluate(() => {
return document.title;
});
}
/**

View File

@ -982,12 +982,12 @@ export class Page extends EventEmitter implements AsyncDisposable, Disposable {
>(
pageFunction: Func | string,
...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>>>;
async evaluateHandle<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
>(): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
throw new Error('Not implemented');
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
pageFunction = withSourcePuppeteerURLIfNone(
this.evaluateHandle.name,
pageFunction
);
return await this.mainFrame().evaluateHandle(pageFunction, ...args);
}
/**
@ -1440,14 +1440,14 @@ export class Page extends EventEmitter implements AsyncDisposable, Disposable {
* {@link Frame.url | page.mainFrame().url()}.
*/
url(): string {
throw new Error('Not implemented');
return this.mainFrame().url();
}
/**
* The full HTML contents of the page, including the DOCTYPE.
*/
async content(): Promise<string> {
throw new Error('Not implemented');
return await this.mainFrame().content();
}
/**
@ -1476,9 +1476,8 @@ export class Page extends EventEmitter implements AsyncDisposable, Disposable {
* - `networkidle2` : consider setting content to be finished when there are
* no more than 2 network connections for at least `500` ms.
*/
async setContent(html: string, options?: WaitForOptions): Promise<void>;
async setContent(): Promise<void> {
throw new Error('Not implemented');
async setContent(html: string, options?: WaitForOptions): Promise<void> {
await this.mainFrame().setContent(html, options);
}
/**
@ -1541,9 +1540,8 @@ export class Page extends EventEmitter implements AsyncDisposable, Disposable {
async goto(
url: string,
options?: WaitForOptions & {referer?: string; referrerPolicy?: string}
): Promise<HTTPResponse | null>;
async goto(): Promise<HTTPResponse | null> {
throw new Error('Not implemented');
): Promise<HTTPResponse | null> {
return await this.mainFrame().goto(url, options);
}
/**
@ -2235,12 +2233,12 @@ export class Page extends EventEmitter implements AsyncDisposable, Disposable {
>(
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>>;
async evaluate<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
>(): Promise<Awaited<ReturnType<Func>>> {
throw new Error('Not implemented');
): Promise<Awaited<ReturnType<Func>>> {
pageFunction = withSourcePuppeteerURLIfNone(
this.evaluate.name,
pageFunction
);
return await this.mainFrame().evaluate(pageFunction, ...args);
}
/**
@ -2473,7 +2471,7 @@ export class Page extends EventEmitter implements AsyncDisposable, Disposable {
* Shortcut for {@link Frame.title | page.mainFrame().title()}.
*/
async title(): Promise<string> {
throw new Error('Not implemented');
return await this.mainFrame().title();
}
async close(options?: {runBeforeUnload?: boolean}): Promise<void>;

View File

@ -15,20 +15,11 @@
*/
import {TimeoutSettings} from '../common/TimeoutSettings.js';
import {
EvaluateFunc,
EvaluateFuncWith,
HandleFor,
InnerLazyParams,
NodeFor,
} from '../common/types.js';
import {getPageContent, withSourcePuppeteerURLIfNone} from '../common/util.js';
import {EvaluateFunc, HandleFor, InnerLazyParams} from '../common/types.js';
import {TaskManager, WaitTask} from '../common/WaitTask.js';
import {assert} from '../util/assert.js';
import {ClickOptions, ElementHandle} from './ElementHandle.js';
import {ElementHandle} from './ElementHandle.js';
import {Environment} from './Environment.js';
import {KeyboardTypeOptions} from './Input.js';
import {JSHandle} from './JSHandle.js';
/**
@ -61,76 +52,6 @@ export abstract class Realm implements Disposable {
...args: Params
): Promise<Awaited<ReturnType<Func>>>;
async document(): Promise<ElementHandle<Document>> {
// TODO(#10813): Implement document caching.
return await this.evaluateHandle(() => {
return document;
});
}
async $<Selector extends string>(
selector: Selector
): Promise<ElementHandle<NodeFor<Selector>> | null> {
using document = await this.document();
return await document.$(selector);
}
async $$<Selector extends string>(
selector: Selector
): Promise<Array<ElementHandle<NodeFor<Selector>>>> {
using document = await this.document();
return await document.$$(selector);
}
async $x(expression: string): Promise<Array<ElementHandle<Node>>> {
using document = await this.document();
return await document.$x(expression);
}
async $eval<
Selector extends string,
Params extends unknown[],
Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
NodeFor<Selector>,
Params
>,
>(
selector: Selector,
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>> {
pageFunction = withSourcePuppeteerURLIfNone(this.$eval.name, pageFunction);
using document = await this.document();
return await document.$eval(selector, pageFunction, ...args);
}
async $$eval<
Selector extends string,
Params extends unknown[],
Func extends EvaluateFuncWith<
Array<NodeFor<Selector>>,
Params
> = EvaluateFuncWith<Array<NodeFor<Selector>>, Params>,
>(
selector: Selector,
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>> {
pageFunction = withSourcePuppeteerURLIfNone(this.$$eval.name, pageFunction);
using document = await this.document();
return await document.$$eval(selector, pageFunction, ...args);
}
async content(): Promise<string> {
return await this.evaluate(getPageContent);
}
async title(): Promise<string> {
return await this.evaluate(() => {
return document.title;
});
}
async waitForFunction<
Params extends unknown[],
Func extends EvaluateFunc<InnerLazyParams<Params>> = EvaluateFunc<
@ -171,50 +92,6 @@ export abstract class Realm implements Disposable {
return await waitTask.result;
}
async click(
selector: string,
options?: Readonly<ClickOptions>
): Promise<void> {
using handle = await this.$(selector);
assert(handle, `No element found for selector: ${selector}`);
await handle.click(options);
await handle.dispose();
}
async focus(selector: string): Promise<void> {
using handle = await this.$(selector);
assert(handle, `No element found for selector: ${selector}`);
await handle.focus();
}
async hover(selector: string): Promise<void> {
using handle = await this.$(selector);
assert(handle, `No element found for selector: ${selector}`);
await handle.hover();
}
async select(selector: string, ...values: string[]): Promise<string[]> {
using handle = await this.$(selector);
assert(handle, `No element found for selector: ${selector}`);
return await handle.select(...values);
}
async tap(selector: string): Promise<void> {
using handle = await this.$(selector);
assert(handle, `No element found for selector: ${selector}`);
await handle.tap();
}
async type(
selector: string,
text: string,
options?: Readonly<KeyboardTypeOptions>
): Promise<void> {
using handle = await this.$(selector);
assert(handle, `No element found for selector: ${selector}`);
await handle.type(text, options);
}
get disposed(): boolean {
return this.#disposed;
}

View File

@ -32,6 +32,7 @@ import {FrameManager} from './FrameManager.js';
import {IsolatedWorld} from './IsolatedWorld.js';
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
import {setPageContent} from './util.js';
/**
* We use symbols to prevent external parties listening to these events.
@ -257,7 +258,27 @@ export class CDPFrame extends Frame {
waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[];
} = {}
): Promise<void> {
return await this.isolatedRealm().setContent(html, options);
const {
waitUntil = ['load'],
timeout = this._frameManager.timeoutSettings.navigationTimeout(),
} = options;
await setPageContent(this.isolatedRealm(), html);
const watcher = new LifecycleWatcher(
this._frameManager.networkManager,
this,
waitUntil,
timeout
);
const error = await Deferred.race<void | Error | undefined>([
watcher.terminationPromise(),
watcher.lifecyclePromise(),
]);
watcher.dispose();
if (error) {
throw error;
}
}
override url(): string {

View File

@ -18,7 +18,6 @@ import {Protocol} from 'devtools-protocol';
import {JSHandle} from '../api/JSHandle.js';
import {Realm} from '../api/Realm.js';
import {assert} from '../util/assert.js';
import {Deferred} from '../util/Deferred.js';
import {Binding} from './Binding.js';
@ -26,7 +25,6 @@ import {CDPSession} from './Connection.js';
import {ExecutionContext} from './ExecutionContext.js';
import {CDPFrame} from './Frame.js';
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
import {TimeoutSettings} from './TimeoutSettings.js';
import {BindingPayload, EvaluateFunc, HandleFor} from './types.js';
import {
@ -34,7 +32,6 @@ import {
createCdpHandle,
debugError,
Mutex,
setPageContent,
withSourcePuppeteerURLIfNone,
} from './util.js';
import {WebWorker} from './WebWorker.js';
@ -127,11 +124,6 @@ export class IsolatedWorld extends Realm {
return this.#frameOrWorker.client;
}
get #frame(): CDPFrame {
assert(this.#frameOrWorker instanceof CDPFrame);
return this.#frameOrWorker;
}
clearContext(): void {
this.#context = Deferred.create();
}
@ -188,36 +180,6 @@ export class IsolatedWorld extends Realm {
return await context.evaluate(pageFunction, ...args);
}
async setContent(
html: string,
options: {
timeout?: number;
waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[];
} = {}
): Promise<void> {
const {
waitUntil = ['load'],
timeout = this.timeoutSettings.navigationTimeout(),
} = options;
await setPageContent(this, html);
const watcher = new LifecycleWatcher(
this.#frame._frameManager.networkManager,
this.#frame,
waitUntil,
timeout
);
const error = await Deferred.race<void | Error | undefined>([
watcher.terminationPromise(),
watcher.lifecyclePromise(),
]);
watcher.dispose();
if (error) {
throw error;
}
}
// If multiple waitFor are set up asynchronously, we need to wait for the
// first one to set up the binding in the page before running the others.
#mutex = new Mutex();

View File

@ -73,7 +73,7 @@ import {TargetManagerEmittedEvents} from './TargetManager.js';
import {TaskQueue} from './TaskQueue.js';
import {TimeoutSettings} from './TimeoutSettings.js';
import {Tracing} from './Tracing.js';
import {BindingPayload, EvaluateFunc, HandleFor} from './types.js';
import {BindingPayload, HandleFor} from './types.js';
import {
createCdpHandle,
createClientError,
@ -88,7 +88,6 @@ import {
valueFromRemoteObject,
waitForEvent,
waitWithTimeout,
withSourcePuppeteerURLIfNone,
} from './util.js';
import {WebWorker} from './WebWorker.js';
@ -525,20 +524,6 @@ export class CDPPage extends Page {
return this.#timeoutSettings.timeout();
}
override async evaluateHandle<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
>(
pageFunction: Func | string,
...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
pageFunction = withSourcePuppeteerURLIfNone(
this.evaluateHandle.name,
pageFunction
);
return await this.mainFrame().evaluateHandle(pageFunction, ...args);
}
override async queryObjects<Prototype>(
prototypeHandle: JSHandle<Prototype>
): Promise<JSHandle<Prototype[]>> {
@ -863,28 +848,6 @@ export class CDPPage extends Page {
this.emit(PageEmittedEvents.Dialog, dialog);
}
override url(): string {
return this.mainFrame().url();
}
override async content(): Promise<string> {
return await this.mainFrame().content();
}
override async setContent(
html: string,
options: WaitForOptions = {}
): Promise<void> {
await this.mainFrame().setContent(html, options);
}
override async goto(
url: string,
options: WaitForOptions & {referer?: string; referrerPolicy?: string} = {}
): Promise<HTTPResponse | null> {
return await this.mainFrame().goto(url, options);
}
override async reload(
options?: WaitForOptions
): Promise<HTTPResponse | null> {
@ -1042,20 +1005,6 @@ export class CDPPage extends Page {
return this.#viewport;
}
override async evaluate<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
>(
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>> {
pageFunction = withSourcePuppeteerURLIfNone(
this.evaluate.name,
pageFunction
);
return await this.mainFrame().evaluate(pageFunction, ...args);
}
override async evaluateOnNewDocument<
Params extends unknown[],
Func extends (...args: Params) => unknown = (...args: Params) => unknown,
@ -1331,10 +1280,6 @@ export class CDPPage extends Page {
return buffer;
}
override async title(): Promise<string> {
return await this.mainFrame().title();
}
override async close(
options: {runBeforeUnload?: boolean} = {runBeforeUnload: undefined}
): Promise<void> {

View File

@ -43,7 +43,6 @@ import {PDFOptions} from '../PDFOptions.js';
import {Viewport} from '../PuppeteerViewport.js';
import {TimeoutSettings} from '../TimeoutSettings.js';
import {Tracing} from '../Tracing.js';
import {EvaluateFunc, HandleFor} from '../types.js';
import {
debugError,
evaluationString,
@ -51,7 +50,6 @@ import {
validateDialogType,
waitForEvent,
waitWithTimeout,
withSourcePuppeteerURLIfNone,
} from '../util.js';
import {BidiBrowser} from './Browser.js';
@ -426,44 +424,6 @@ export class BidiPage extends Page {
this.removeAllListeners();
}
override async evaluateHandle<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
>(
pageFunction: Func | string,
...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
pageFunction = withSourcePuppeteerURLIfNone(
this.evaluateHandle.name,
pageFunction
);
return await this.mainFrame().evaluateHandle(pageFunction, ...args);
}
override async evaluate<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
>(
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>> {
pageFunction = withSourcePuppeteerURLIfNone(
this.evaluate.name,
pageFunction
);
return await this.mainFrame().evaluate(pageFunction, ...args);
}
override async goto(
url: string,
options?: WaitForOptions & {
referer?: string | undefined;
referrerPolicy?: string | undefined;
}
): Promise<HTTPResponse | null> {
return await this.mainFrame().goto(url, options);
}
override async reload(
options?: WaitForOptions
): Promise<HTTPResponse | null> {
@ -486,10 +446,6 @@ export class BidiPage extends Page {
return response;
}
override url(): string {
return this.mainFrame().url();
}
override setDefaultNavigationTimeout(timeout: number): void {
this.#timeoutSettings.setDefaultNavigationTimeout(timeout);
}
@ -502,17 +458,6 @@ export class BidiPage extends Page {
return this.#timeoutSettings.timeout();
}
override async setContent(
html: string,
options: WaitForOptions = {}
): Promise<void> {
await this.mainFrame().setContent(html, options);
}
override async content(): Promise<string> {
return await this.mainFrame().content();
}
override isJavaScriptEnabled(): boolean {
return this.#cdpEmulationManager.javascriptEnabled;
}
@ -721,10 +666,6 @@ export class BidiPage extends Page {
);
}
override title(): Promise<string> {
return this.mainFrame().title();
}
override async createCDPSession(): Promise<CDPSession> {
const {sessionId} = await this.mainFrame()
.context()