/** * Copyright 2017 Google Inc. All rights reserved. * Modifications copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import fs from 'fs'; import path from 'path'; import type * as structs from '../../types/structs'; import type * as api from '../../types/types'; import { serializeError, isTargetClosedError, TargetClosedError } from './errors'; import { TimeoutSettings } from '../common/timeoutSettings'; import type * as channels from '@protocol/channels'; import { assert, headersObjectToArray, isObject, isRegExp, isString, LongStandingScope, urlMatches, urlMatchesEqual, mkdirIfNeeded, trimStringWithEllipsis, type URLMatch } from '../utils'; import { Accessibility } from './accessibility'; import { Artifact } from './artifact'; import type { BrowserContext } from './browserContext'; import { ChannelOwner } from './channelOwner'; import { evaluationScript } from './clientHelper'; import { Coverage } from './coverage'; import { Download } from './download'; import { determineScreenshotType, ElementHandle } from './elementHandle'; import { Events } from './events'; import type { APIRequestContext } from './fetch'; import { FileChooser } from './fileChooser'; import type { WaitForNavigationOptions } from './frame'; import { Frame, verifyLoadState } from './frame'; import { Keyboard, Mouse, Touchscreen } from './input'; import { assertMaxArguments, JSHandle, parseResult, serializeArgument } from './jsHandle'; import type { FrameLocator, Locator, LocatorOptions } from './locator'; import type { ByRoleOptions } from '../utils/isomorphic/locatorUtils'; import { type RouteHandlerCallback, type Request, Response, Route, RouteHandler, validateHeaders, WebSocket, type WebSocketRouteHandlerCallback, WebSocketRoute, WebSocketRouteHandler } from './network'; import type { FilePayload, Headers, LifecycleEvent, SelectOption, SelectOptionOptions, Size, WaitForEventOptions, WaitForFunctionOptions } from './types'; import { Video } from './video'; import { Waiter } from './waiter'; import { Worker } from './worker'; import { HarRouter } from './harRouter'; import type { Clock } from './clock'; type PDFOptions = Omit & { width?: string | number, height?: string | number, margin?: { top?: string | number, bottom?: string | number, left?: string | number, right?: string | number }, path?: string, }; export type ExpectScreenshotOptions = Omit & { expected?: Buffer, locator?: api.Locator, timeout: number, isNot: boolean, mask?: api.Locator[], }; export class Page extends ChannelOwner implements api.Page { private _browserContext: BrowserContext; _ownedContext: BrowserContext | undefined; private _mainFrame: Frame; private _frames = new Set(); _workers = new Set(); private _closed = false; readonly _closedOrCrashedScope = new LongStandingScope(); private _viewportSize: Size | null; _routes: RouteHandler[] = []; _webSocketRoutes: WebSocketRouteHandler[] = []; readonly accessibility: Accessibility; readonly coverage: Coverage; readonly keyboard: Keyboard; readonly mouse: Mouse; readonly request: APIRequestContext; readonly touchscreen: Touchscreen; readonly clock: Clock; readonly _bindings = new Map any>(); readonly _timeoutSettings: TimeoutSettings; private _video: Video | null = null; readonly _opener: Page | null; private _closeReason: string | undefined; _closeWasCalled: boolean = false; private _harRouters: HarRouter[] = []; private _locatorHandlers = new Map any, times: number | undefined }>(); static from(page: channels.PageChannel): Page { return (page as any)._object; } static fromNullable(page: channels.PageChannel | undefined): Page | null { return page ? Page.from(page) : null; } constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.PageInitializer) { super(parent, type, guid, initializer); this._browserContext = parent as unknown as BrowserContext; this._timeoutSettings = new TimeoutSettings(this._browserContext._timeoutSettings); this.accessibility = new Accessibility(this._channel); this.keyboard = new Keyboard(this); this.mouse = new Mouse(this); this.request = this._browserContext.request; this.touchscreen = new Touchscreen(this); this.clock = this._browserContext.clock; this._mainFrame = Frame.from(initializer.mainFrame); this._mainFrame._page = this; this._frames.add(this._mainFrame); this._viewportSize = initializer.viewportSize || null; this._closed = initializer.isClosed; this._opener = Page.fromNullable(initializer.opener); this._channel.on('bindingCall', ({ binding }) => this._onBinding(BindingCall.from(binding))); this._channel.on('close', () => this._onClose()); this._channel.on('crash', () => this._onCrash()); this._channel.on('download', ({ url, suggestedFilename, artifact }) => { const artifactObject = Artifact.from(artifact); this.emit(Events.Page.Download, new Download(this, url, suggestedFilename, artifactObject)); }); this._channel.on('fileChooser', ({ element, isMultiple }) => this.emit(Events.Page.FileChooser, new FileChooser(this, ElementHandle.from(element), isMultiple))); this._channel.on('frameAttached', ({ frame }) => this._onFrameAttached(Frame.from(frame))); this._channel.on('frameDetached', ({ frame }) => this._onFrameDetached(Frame.from(frame))); this._channel.on('locatorHandlerTriggered', ({ uid }) => this._onLocatorHandlerTriggered(uid)); this._channel.on('route', ({ route }) => this._onRoute(Route.from(route))); this._channel.on('webSocketRoute', ({ webSocketRoute }) => this._onWebSocketRoute(WebSocketRoute.from(webSocketRoute))); this._channel.on('video', ({ artifact }) => { const artifactObject = Artifact.from(artifact); this._forceVideo()._artifactReady(artifactObject); }); this._channel.on('webSocket', ({ webSocket }) => this.emit(Events.Page.WebSocket, WebSocket.from(webSocket))); this._channel.on('worker', ({ worker }) => this._onWorker(Worker.from(worker))); this.coverage = new Coverage(this._channel); this.once(Events.Page.Close, () => this._closedOrCrashedScope.close(this._closeErrorWithReason())); this.once(Events.Page.Crash, () => this._closedOrCrashedScope.close(new TargetClosedError())); this._setEventToSubscriptionMapping(new Map([ [Events.Page.Console, 'console'], [Events.Page.Dialog, 'dialog'], [Events.Page.Request, 'request'], [Events.Page.Response, 'response'], [Events.Page.RequestFinished, 'requestFinished'], [Events.Page.RequestFailed, 'requestFailed'], [Events.Page.FileChooser, 'fileChooser'], ])); } private _onFrameAttached(frame: Frame) { frame._page = this; this._frames.add(frame); if (frame._parentFrame) frame._parentFrame._childFrames.add(frame); this.emit(Events.Page.FrameAttached, frame); } private _onFrameDetached(frame: Frame) { this._frames.delete(frame); frame._detached = true; if (frame._parentFrame) frame._parentFrame._childFrames.delete(frame); this.emit(Events.Page.FrameDetached, frame); } private async _onRoute(route: Route) { route._context = this.context(); const routeHandlers = this._routes.slice(); for (const routeHandler of routeHandlers) { // If the page was closed we stall all requests right away. if (this._closeWasCalled || this._browserContext._closeWasCalled) return; if (!routeHandler.matches(route.request().url())) continue; const index = this._routes.indexOf(routeHandler); if (index === -1) continue; if (routeHandler.willExpire()) this._routes.splice(index, 1); const handled = await routeHandler.handle(route); if (!this._routes.length) this._wrapApiCall(() => this._updateInterceptionPatterns(), true).catch(() => {}); if (handled) return; } await this._browserContext._onRoute(route); } private async _onWebSocketRoute(webSocketRoute: WebSocketRoute) { const routeHandler = this._webSocketRoutes.find(route => route.matches(webSocketRoute.url())); if (routeHandler) await routeHandler.handle(webSocketRoute); else await this._browserContext._onWebSocketRoute(webSocketRoute); } async _onBinding(bindingCall: BindingCall) { const func = this._bindings.get(bindingCall._initializer.name); if (func) { await bindingCall.call(func); return; } await this._browserContext._onBinding(bindingCall); } _onWorker(worker: Worker): void { this._workers.add(worker); worker._page = this; this.emit(Events.Page.Worker, worker); } _onClose() { this._closed = true; this._browserContext._pages.delete(this); this._browserContext._backgroundPages.delete(this); this._disposeHarRouters(); this.emit(Events.Page.Close, this); } private _onCrash() { this.emit(Events.Page.Crash, this); } context(): BrowserContext { return this._browserContext; } async opener(): Promise { if (!this._opener || this._opener.isClosed()) return null; return this._opener; } mainFrame(): Frame { return this._mainFrame; } frame(frameSelector: string | { name?: string, url?: URLMatch }): Frame | null { const name = isString(frameSelector) ? frameSelector : frameSelector.name; const url = isObject(frameSelector) ? frameSelector.url : undefined; assert(name || url, 'Either name or url matcher should be specified'); return this.frames().find(f => { if (name) return f.name() === name; return urlMatches(this._browserContext._options.baseURL, f.url(), url); }) || null; } frames(): Frame[] { return [...this._frames]; } setDefaultNavigationTimeout(timeout: number) { this._timeoutSettings.setDefaultNavigationTimeout(timeout); this._wrapApiCall(async () => { this._channel.setDefaultNavigationTimeoutNoReply({ timeout }).catch(() => {}); }, true); } setDefaultTimeout(timeout: number) { this._timeoutSettings.setDefaultTimeout(timeout); this._wrapApiCall(async () => { this._channel.setDefaultTimeoutNoReply({ timeout }).catch(() => {}); }, true); } private _forceVideo(): Video { if (!this._video) this._video = new Video(this, this._connection); return this._video; } video(): Video | null { // Note: we are creating Video object lazily, because we do not know // BrowserContextOptions when constructing the page - it is assigned // too late during launchPersistentContext. if (!this._browserContext._options.recordVideo) return null; return this._forceVideo(); } async $(selector: string, options?: { strict?: boolean }): Promise | null> { return await this._mainFrame.$(selector, options); } waitForSelector(selector: string, options: channels.FrameWaitForSelectorOptions & { state: 'attached' | 'visible' }): Promise>; waitForSelector(selector: string, options?: channels.FrameWaitForSelectorOptions): Promise | null>; async waitForSelector(selector: string, options?: channels.FrameWaitForSelectorOptions): Promise | null> { return await this._mainFrame.waitForSelector(selector, options); } async dispatchEvent(selector: string, type: string, eventInit?: any, options?: channels.FrameDispatchEventOptions): Promise { return await this._mainFrame.dispatchEvent(selector, type, eventInit, options); } async evaluateHandle(pageFunction: structs.PageFunction, arg?: Arg): Promise> { assertMaxArguments(arguments.length, 2); return await this._mainFrame.evaluateHandle(pageFunction, arg); } async $eval(selector: string, pageFunction: structs.PageFunctionOn, arg?: Arg): Promise { assertMaxArguments(arguments.length, 3); return await this._mainFrame.$eval(selector, pageFunction, arg); } async $$eval(selector: string, pageFunction: structs.PageFunctionOn, arg?: Arg): Promise { assertMaxArguments(arguments.length, 3); return await this._mainFrame.$$eval(selector, pageFunction, arg); } async $$(selector: string): Promise[]> { return await this._mainFrame.$$(selector); } async addScriptTag(options: { url?: string; path?: string; content?: string; type?: string; } = {}): Promise { return await this._mainFrame.addScriptTag(options); } async addStyleTag(options: { url?: string; path?: string; content?: string; } = {}): Promise { return await this._mainFrame.addStyleTag(options); } async exposeFunction(name: string, callback: Function) { await this._channel.exposeBinding({ name }); const binding = (source: structs.BindingSource, ...args: any[]) => callback(...args); this._bindings.set(name, binding); } async exposeBinding(name: string, callback: (source: structs.BindingSource, ...args: any[]) => any, options: { handle?: boolean } = {}) { await this._channel.exposeBinding({ name, needsHandle: options.handle }); this._bindings.set(name, callback); } async setExtraHTTPHeaders(headers: Headers) { validateHeaders(headers); await this._channel.setExtraHTTPHeaders({ headers: headersObjectToArray(headers) }); } url(): string { return this._mainFrame.url(); } async content(): Promise { return await this._mainFrame.content(); } async setContent(html: string, options?: channels.FrameSetContentOptions): Promise { return await this._mainFrame.setContent(html, options); } async goto(url: string, options?: channels.FrameGotoOptions): Promise { return await this._mainFrame.goto(url, options); } async reload(options: channels.PageReloadOptions = {}): Promise { const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil); return Response.fromNullable((await this._channel.reload({ ...options, waitUntil })).response); } async addLocatorHandler(locator: Locator, handler: (locator: Locator) => any, options: { times?: number, noWaitAfter?: boolean } = {}): Promise { if (locator._frame !== this._mainFrame) throw new Error(`Locator must belong to the main frame of this page`); if (options.times === 0) return; const { uid } = await this._channel.registerLocatorHandler({ selector: locator._selector, noWaitAfter: options.noWaitAfter }); this._locatorHandlers.set(uid, { locator, handler, times: options.times }); } private async _onLocatorHandlerTriggered(uid: number) { let remove = false; try { const handler = this._locatorHandlers.get(uid); if (handler && handler.times !== 0) { if (handler.times !== undefined) handler.times--; await handler.handler(handler.locator); } remove = handler?.times === 0; } finally { if (remove) this._locatorHandlers.delete(uid); this._wrapApiCall(() => this._channel.resolveLocatorHandlerNoReply({ uid, remove }), true).catch(() => {}); } } async removeLocatorHandler(locator: Locator): Promise { for (const [uid, data] of this._locatorHandlers) { if (data.locator._equals(locator)) { this._locatorHandlers.delete(uid); await this._channel.unregisterLocatorHandler({ uid }).catch(() => {}); } } } async waitForLoadState(state?: LifecycleEvent, options?: { timeout?: number }): Promise { return await this._mainFrame.waitForLoadState(state, options); } async waitForNavigation(options?: WaitForNavigationOptions): Promise { return await this._mainFrame.waitForNavigation(options); } async waitForURL(url: URLMatch, options?: { waitUntil?: LifecycleEvent, timeout?: number }): Promise { return await this._mainFrame.waitForURL(url, options); } async waitForRequest(urlOrPredicate: string | RegExp | ((r: Request) => boolean | Promise), options: { timeout?: number } = {}): Promise { const predicate = async (request: Request) => { if (isString(urlOrPredicate) || isRegExp(urlOrPredicate)) return urlMatches(this._browserContext._options.baseURL, request.url(), urlOrPredicate); return await urlOrPredicate(request); }; const trimmedUrl = trimUrl(urlOrPredicate); const logLine = trimmedUrl ? `waiting for request ${trimmedUrl}` : undefined; return await this._waitForEvent(Events.Page.Request, { predicate, timeout: options.timeout }, logLine); } async waitForResponse(urlOrPredicate: string | RegExp | ((r: Response) => boolean | Promise), options: { timeout?: number } = {}): Promise { const predicate = async (response: Response) => { if (isString(urlOrPredicate) || isRegExp(urlOrPredicate)) return urlMatches(this._browserContext._options.baseURL, response.url(), urlOrPredicate); return await urlOrPredicate(response); }; const trimmedUrl = trimUrl(urlOrPredicate); const logLine = trimmedUrl ? `waiting for response ${trimmedUrl}` : undefined; return await this._waitForEvent(Events.Page.Response, { predicate, timeout: options.timeout }, logLine); } async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise { return await this._waitForEvent(event, optionsOrPredicate, `waiting for event "${event}"`); } _closeErrorWithReason(): TargetClosedError { return new TargetClosedError(this._closeReason || this._browserContext._effectiveCloseReason()); } private async _waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions, logLine?: string): Promise { return await this._wrapApiCall(async () => { const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate); const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate; const waiter = Waiter.createForEvent(this, event); if (logLine) waiter.log(logLine); waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`); if (event !== Events.Page.Crash) waiter.rejectOnEvent(this, Events.Page.Crash, new Error('Page crashed')); if (event !== Events.Page.Close) waiter.rejectOnEvent(this, Events.Page.Close, () => this._closeErrorWithReason()); const result = await waiter.waitForEvent(this, event, predicate as any); waiter.dispose(); return result; }); } async goBack(options: channels.PageGoBackOptions = {}): Promise { const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil); return Response.fromNullable((await this._channel.goBack({ ...options, waitUntil })).response); } async goForward(options: channels.PageGoForwardOptions = {}): Promise { const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil); return Response.fromNullable((await this._channel.goForward({ ...options, waitUntil })).response); } async requestGC() { await this._channel.requestGC(); } async emulateMedia(options: { media?: 'screen' | 'print' | null, colorScheme?: 'dark' | 'light' | 'no-preference' | null, reducedMotion?: 'reduce' | 'no-preference' | null, forcedColors?: 'active' | 'none' | null } = {}) { await this._channel.emulateMedia({ media: options.media === null ? 'no-override' : options.media, colorScheme: options.colorScheme === null ? 'no-override' : options.colorScheme, reducedMotion: options.reducedMotion === null ? 'no-override' : options.reducedMotion, forcedColors: options.forcedColors === null ? 'no-override' : options.forcedColors, }); } async setViewportSize(viewportSize: Size) { this._viewportSize = viewportSize; await this._channel.setViewportSize({ viewportSize }); } viewportSize(): Size | null { return this._viewportSize; } async evaluate(pageFunction: structs.PageFunction, arg?: Arg): Promise { assertMaxArguments(arguments.length, 2); return await this._mainFrame.evaluate(pageFunction, arg); } async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any) { const source = await evaluationScript(script, arg); await this._channel.addInitScript({ source }); } async route(url: URLMatch, handler: RouteHandlerCallback, options: { times?: number } = {}): Promise { this._routes.unshift(new RouteHandler(this._browserContext._options.baseURL, url, handler, options.times)); await this._updateInterceptionPatterns(); } async routeFromHAR(har: string, options: { url?: string | RegExp, notFound?: 'abort' | 'fallback', update?: boolean, updateContent?: 'attach' | 'embed', updateMode?: 'minimal' | 'full'} = {}): Promise { if (options.update) { await this._browserContext._recordIntoHAR(har, this, options); return; } const harRouter = await HarRouter.create(this._connection.localUtils(), har, options.notFound || 'abort', { urlMatch: options.url }); this._harRouters.push(harRouter); await harRouter.addPageRoute(this); } async routeWebSocket(url: URLMatch, handler: WebSocketRouteHandlerCallback): Promise { this._webSocketRoutes.unshift(new WebSocketRouteHandler(this._browserContext._options.baseURL, url, handler)); await this._updateWebSocketInterceptionPatterns(); } private _disposeHarRouters() { this._harRouters.forEach(router => router.dispose()); this._harRouters = []; } async unrouteAll(options?: { behavior?: 'wait'|'ignoreErrors'|'default' }): Promise { await this._unrouteInternal(this._routes, [], options?.behavior); this._disposeHarRouters(); } async unroute(url: URLMatch, handler?: RouteHandlerCallback): Promise { const removed = []; const remaining = []; for (const route of this._routes) { if (urlMatchesEqual(route.url, url) && (!handler || route.handler === handler)) removed.push(route); else remaining.push(route); } await this._unrouteInternal(removed, remaining, 'default'); } private async _unrouteInternal(removed: RouteHandler[], remaining: RouteHandler[], behavior?: 'wait'|'ignoreErrors'|'default'): Promise { this._routes = remaining; await this._updateInterceptionPatterns(); if (!behavior || behavior === 'default') return; const promises = removed.map(routeHandler => routeHandler.stop(behavior)); await Promise.all(promises); } private async _updateInterceptionPatterns() { const patterns = RouteHandler.prepareInterceptionPatterns(this._routes); await this._channel.setNetworkInterceptionPatterns({ patterns }); } private async _updateWebSocketInterceptionPatterns() { const patterns = WebSocketRouteHandler.prepareInterceptionPatterns(this._webSocketRoutes); await this._channel.setWebSocketInterceptionPatterns({ patterns }); } async screenshot(options: Omit & { path?: string, mask?: Locator[] } = {}): Promise { const copy: channels.PageScreenshotOptions = { ...options, mask: undefined }; if (!copy.type) copy.type = determineScreenshotType(options); if (options.mask) { copy.mask = options.mask.map(locator => ({ frame: locator._frame._channel, selector: locator._selector, })); } const result = await this._channel.screenshot(copy); if (options.path) { await mkdirIfNeeded(options.path); await fs.promises.writeFile(options.path, result.binary); } return result.binary; } async _expectScreenshot(options: ExpectScreenshotOptions): Promise<{ actual?: Buffer, previous?: Buffer, diff?: Buffer, errorMessage?: string, log?: string[], timedOut?: boolean}> { const mask = options?.mask ? options?.mask.map(locator => ({ frame: (locator as Locator)._frame._channel, selector: (locator as Locator)._selector, })) : undefined; const locator = options.locator ? { frame: (options.locator as Locator)._frame._channel, selector: (options.locator as Locator)._selector, } : undefined; return await this._channel.expectScreenshot({ ...options, isNot: !!options.isNot, locator, mask, }); } async title(): Promise { return await this._mainFrame.title(); } async bringToFront(): Promise { await this._channel.bringToFront(); } async [Symbol.asyncDispose]() { await this.close(); } async close(options: { runBeforeUnload?: boolean, reason?: string } = {}) { this._closeReason = options.reason; this._closeWasCalled = true; try { if (this._ownedContext) await this._ownedContext.close(); else await this._channel.close(options); } catch (e) { if (isTargetClosedError(e) && !options.runBeforeUnload) return; throw e; } } isClosed(): boolean { return this._closed; } async click(selector: string, options?: channels.FrameClickOptions) { return await this._mainFrame.click(selector, options); } async dragAndDrop(source: string, target: string, options?: channels.FrameDragAndDropOptions) { return await this._mainFrame.dragAndDrop(source, target, options); } async dblclick(selector: string, options?: channels.FrameDblclickOptions) { return await this._mainFrame.dblclick(selector, options); } async tap(selector: string, options?: channels.FrameTapOptions) { return await this._mainFrame.tap(selector, options); } async fill(selector: string, value: string, options?: channels.FrameFillOptions) { return await this._mainFrame.fill(selector, value, options); } locator(selector: string, options?: LocatorOptions): Locator { return this.mainFrame().locator(selector, options); } getByTestId(testId: string | RegExp): Locator { return this.mainFrame().getByTestId(testId); } getByAltText(text: string | RegExp, options?: { exact?: boolean }): Locator { return this.mainFrame().getByAltText(text, options); } getByLabel(text: string | RegExp, options?: { exact?: boolean }): Locator { return this.mainFrame().getByLabel(text, options); } getByPlaceholder(text: string | RegExp, options?: { exact?: boolean }): Locator { return this.mainFrame().getByPlaceholder(text, options); } getByText(text: string | RegExp, options?: { exact?: boolean }): Locator { return this.mainFrame().getByText(text, options); } getByTitle(text: string | RegExp, options?: { exact?: boolean }): Locator { return this.mainFrame().getByTitle(text, options); } getByRole(role: string, options: ByRoleOptions = {}): Locator { return this.mainFrame().getByRole(role, options); } frameLocator(selector: string): FrameLocator { return this.mainFrame().frameLocator(selector); } async focus(selector: string, options?: channels.FrameFocusOptions) { return await this._mainFrame.focus(selector, options); } async textContent(selector: string, options?: channels.FrameTextContentOptions): Promise { return await this._mainFrame.textContent(selector, options); } async innerText(selector: string, options?: channels.FrameInnerTextOptions): Promise { return await this._mainFrame.innerText(selector, options); } async innerHTML(selector: string, options?: channels.FrameInnerHTMLOptions): Promise { return await this._mainFrame.innerHTML(selector, options); } async getAttribute(selector: string, name: string, options?: channels.FrameGetAttributeOptions): Promise { return await this._mainFrame.getAttribute(selector, name, options); } async inputValue(selector: string, options?: channels.FrameInputValueOptions): Promise { return await this._mainFrame.inputValue(selector, options); } async isChecked(selector: string, options?: channels.FrameIsCheckedOptions): Promise { return await this._mainFrame.isChecked(selector, options); } async isDisabled(selector: string, options?: channels.FrameIsDisabledOptions): Promise { return await this._mainFrame.isDisabled(selector, options); } async isEditable(selector: string, options?: channels.FrameIsEditableOptions): Promise { return await this._mainFrame.isEditable(selector, options); } async isEnabled(selector: string, options?: channels.FrameIsEnabledOptions): Promise { return await this._mainFrame.isEnabled(selector, options); } async isHidden(selector: string, options?: channels.FrameIsHiddenOptions): Promise { return await this._mainFrame.isHidden(selector, options); } async isVisible(selector: string, options?: channels.FrameIsVisibleOptions): Promise { return await this._mainFrame.isVisible(selector, options); } async hover(selector: string, options?: channels.FrameHoverOptions) { return await this._mainFrame.hover(selector, options); } async selectOption(selector: string, values: string | api.ElementHandle | SelectOption | string[] | api.ElementHandle[] | SelectOption[] | null, options?: SelectOptionOptions): Promise { return await this._mainFrame.selectOption(selector, values, options); } async setInputFiles(selector: string, files: string | FilePayload | string[] | FilePayload[], options?: channels.FrameSetInputFilesOptions): Promise { return await this._mainFrame.setInputFiles(selector, files, options); } async type(selector: string, text: string, options?: channels.FrameTypeOptions) { return await this._mainFrame.type(selector, text, options); } async press(selector: string, key: string, options?: channels.FramePressOptions) { return await this._mainFrame.press(selector, key, options); } async check(selector: string, options?: channels.FrameCheckOptions) { return await this._mainFrame.check(selector, options); } async uncheck(selector: string, options?: channels.FrameUncheckOptions) { return await this._mainFrame.uncheck(selector, options); } async setChecked(selector: string, checked: boolean, options?: channels.FrameCheckOptions) { return await this._mainFrame.setChecked(selector, checked, options); } async waitForTimeout(timeout: number) { return await this._mainFrame.waitForTimeout(timeout); } async waitForFunction(pageFunction: structs.PageFunction, arg?: Arg, options?: WaitForFunctionOptions): Promise> { return await this._mainFrame.waitForFunction(pageFunction, arg, options); } workers(): Worker[] { return [...this._workers]; } async pause() { if (require('inspector').url()) return; const defaultNavigationTimeout = this._browserContext._timeoutSettings.defaultNavigationTimeout(); const defaultTimeout = this._browserContext._timeoutSettings.defaultTimeout(); this._browserContext.setDefaultNavigationTimeout(0); this._browserContext.setDefaultTimeout(0); this._instrumentation?.onWillPause(); await this._closedOrCrashedScope.safeRace(this.context()._channel.pause()); this._browserContext.setDefaultNavigationTimeout(defaultNavigationTimeout); this._browserContext.setDefaultTimeout(defaultTimeout); } async pdf(options: PDFOptions = {}): Promise { const transportOptions: channels.PagePdfParams = { ...options } as channels.PagePdfParams; if (transportOptions.margin) transportOptions.margin = { ...transportOptions.margin }; if (typeof options.width === 'number') transportOptions.width = options.width + 'px'; if (typeof options.height === 'number') transportOptions.height = options.height + 'px'; for (const margin of ['top', 'right', 'bottom', 'left']) { const index = margin as 'top' | 'right' | 'bottom' | 'left'; if (options.margin && typeof options.margin[index] === 'number') transportOptions.margin![index] = transportOptions.margin![index] + 'px'; } const result = await this._channel.pdf(transportOptions); if (options.path) { await fs.promises.mkdir(path.dirname(options.path), { recursive: true }); await fs.promises.writeFile(options.path, result.pdf); } return result.pdf; } } export class BindingCall extends ChannelOwner { static from(channel: channels.BindingCallChannel): BindingCall { return (channel as any)._object; } constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.BindingCallInitializer) { super(parent, type, guid, initializer); } async call(func: (source: structs.BindingSource, ...args: any[]) => any) { try { const frame = Frame.from(this._initializer.frame); const source = { context: frame._page!.context(), page: frame._page!, frame }; let result: any; if (this._initializer.handle) result = await func(source, JSHandle.from(this._initializer.handle)); else result = await func(source, ...this._initializer.args!.map(parseResult)); this._channel.resolve({ result: serializeArgument(result) }).catch(() => {}); } catch (e) { this._channel.reject({ error: serializeError(e) }).catch(() => {}); } } } function trimUrl(param: any): string | undefined { if (isRegExp(param)) return `/${trimStringWithEllipsis(param.source, 50)}/${param.flags}`; if (isString(param)) return `"${trimStringWithEllipsis(param, 50)}"`; }