1/**
2 * Copyright 2017 Google Inc. All rights reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import type { Readable } from 'stream';
18
19import { EventEmitter, Handler } from './EventEmitter.js';
20import {
21  Connection,
22  CDPSession,
23  CDPSessionEmittedEvents,
24} from './Connection.js';
25import { Dialog } from './Dialog.js';
26import { EmulationManager } from './EmulationManager.js';
27import {
28  Frame,
29  FrameManager,
30  FrameManagerEmittedEvents,
31} from './FrameManager.js';
32import { Keyboard, Mouse, Touchscreen, MouseButton } from './Input.js';
33import { Tracing } from './Tracing.js';
34import { assert, assertNever } from './assert.js';
35import { helper, debugError } from './helper.js';
36import { Coverage } from './Coverage.js';
37import { WebWorker } from './WebWorker.js';
38import { Browser, BrowserContext } from './Browser.js';
39import { Target } from './Target.js';
40import { createJSHandle, JSHandle, ElementHandle } from './JSHandle.js';
41import { Viewport } from './PuppeteerViewport.js';
42import {
43  Credentials,
44  NetworkConditions,
45  NetworkManagerEmittedEvents,
46} from './NetworkManager.js';
47import { HTTPRequest } from './HTTPRequest.js';
48import { HTTPResponse } from './HTTPResponse.js';
49import { Accessibility } from './Accessibility.js';
50import { TimeoutSettings } from './TimeoutSettings.js';
51import { FileChooser } from './FileChooser.js';
52import { ConsoleMessage, ConsoleMessageType } from './ConsoleMessage.js';
53import { PuppeteerLifeCycleEvent } from './LifecycleWatcher.js';
54import { Protocol } from 'devtools-protocol';
55import {
56  SerializableOrJSHandle,
57  EvaluateHandleFn,
58  WrapElementHandle,
59  EvaluateFn,
60  EvaluateFnReturnType,
61  UnwrapPromiseLike,
62} from './EvalTypes.js';
63import { PDFOptions, paperFormats } from './PDFOptions.js';
64import { isNode } from '../environment.js';
65import { TaskQueue } from './TaskQueue.js';
66
67/**
68 * @public
69 */
70export interface Metrics {
71  Timestamp?: number;
72  Documents?: number;
73  Frames?: number;
74  JSEventListeners?: number;
75  Nodes?: number;
76  LayoutCount?: number;
77  RecalcStyleCount?: number;
78  LayoutDuration?: number;
79  RecalcStyleDuration?: number;
80  ScriptDuration?: number;
81  TaskDuration?: number;
82  JSHeapUsedSize?: number;
83  JSHeapTotalSize?: number;
84}
85
86/**
87 * @public
88 */
89export interface WaitTimeoutOptions {
90  /**
91   * Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to
92   * disable the timeout.
93   *
94   * @remarks
95   * The default value can be changed by using the
96   * {@link Page.setDefaultTimeout} method.
97   */
98  timeout?: number;
99}
100
101/**
102 * @public
103 */
104export interface WaitForOptions {
105  /**
106   * Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to
107   * disable the timeout.
108   *
109   * @remarks
110   * The default value can be changed by using the
111   * {@link Page.setDefaultTimeout} or {@link Page.setDefaultNavigationTimeout}
112   * methods.
113   */
114  timeout?: number;
115  waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[];
116}
117
118/**
119 * @public
120 */
121export interface GeolocationOptions {
122  /**
123   * Latitude between -90 and 90.
124   */
125  longitude: number;
126  /**
127   * Longitude between -180 and 180.
128   */
129  latitude: number;
130  /**
131   * Optional non-negative accuracy value.
132   */
133  accuracy?: number;
134}
135
136/**
137 * @public
138 */
139export interface MediaFeature {
140  name: string;
141  value: string;
142}
143
144/**
145 * @public
146 */
147export interface ScreenshotClip {
148  x: number;
149  y: number;
150  width: number;
151  height: number;
152}
153
154/**
155 * @public
156 */
157export interface ScreenshotOptions {
158  /**
159   * @defaultValue 'png'
160   */
161  type?: 'png' | 'jpeg' | 'webp';
162  /**
163   * The file path to save the image to. The screenshot type will be inferred
164   * from file extension. If path is a relative path, then it is resolved
165   * relative to current working directory. If no path is provided, the image
166   * won't be saved to the disk.
167   */
168  path?: string;
169  /**
170   * When true, takes a screenshot of the full page.
171   * @defaultValue false
172   */
173  fullPage?: boolean;
174  /**
175   * An object which specifies the clipping region of the page.
176   */
177  clip?: ScreenshotClip;
178  /**
179   * Quality of the image, between 0-100. Not applicable to `png` images.
180   */
181  quality?: number;
182  /**
183   * Hides default white background and allows capturing screenshots with transparency.
184   * @defaultValue false
185   */
186  omitBackground?: boolean;
187  /**
188   * Encoding of the image.
189   * @defaultValue 'binary'
190   */
191  encoding?: 'base64' | 'binary';
192  /**
193   * If you need a screenshot bigger than the Viewport
194   * @defaultValue true
195   */
196  captureBeyondViewport?: boolean;
197}
198
199/**
200 * All the events that a page instance may emit.
201 *
202 * @public
203 */
204export const enum PageEmittedEvents {
205  /** Emitted when the page closes.
206   * @eventProperty
207   */
208  Close = 'close',
209  /**
210   * Emitted when JavaScript within the page calls one of console API methods,
211   * e.g. `console.log` or `console.dir`. Also emitted if the page throws an
212   * error or a warning.
213   *
214   * @remarks
215   *
216   * A `console` event provides a {@link ConsoleMessage} representing the
217   * console message that was logged.
218   *
219   * @example
220   * An example of handling `console` event:
221   * ```js
222   * page.on('console', msg => {
223   *   for (let i = 0; i < msg.args().length; ++i)
224   *    console.log(`${i}: ${msg.args()[i]}`);
225   *  });
226   *  page.evaluate(() => console.log('hello', 5, {foo: 'bar'}));
227   * ```
228   */
229  Console = 'console',
230  /**
231   * Emitted when a JavaScript dialog appears, such as `alert`, `prompt`,
232   * `confirm` or `beforeunload`. Puppeteer can respond to the dialog via
233   * {@link Dialog.accept} or {@link Dialog.dismiss}.
234   */
235  Dialog = 'dialog',
236  /**
237   * Emitted when the JavaScript
238   * {@link https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded | DOMContentLoaded } event is dispatched.
239   */
240  DOMContentLoaded = 'domcontentloaded',
241  /**
242   * Emitted when the page crashes. Will contain an `Error`.
243   */
244  Error = 'error',
245  /** Emitted when a frame is attached. Will contain a {@link Frame}. */
246  FrameAttached = 'frameattached',
247  /** Emitted when a frame is detached. Will contain a {@link Frame}. */
248  FrameDetached = 'framedetached',
249  /** Emitted when a frame is navigated to a new URL. Will contain a {@link Frame}. */
250  FrameNavigated = 'framenavigated',
251  /**
252   * Emitted when the JavaScript
253   * {@link https://developer.mozilla.org/en-US/docs/Web/Events/load | load}
254   * event is dispatched.
255   */
256  Load = 'load',
257  /**
258   * Emitted when the JavaScript code makes a call to `console.timeStamp`. For
259   * the list of metrics see {@link Page.metrics | page.metrics}.
260   *
261   * @remarks
262   * Contains an object with two properties:
263   * - `title`: the title passed to `console.timeStamp`
264   * - `metrics`: objec containing metrics as key/value pairs. The values will
265   *   be `number`s.
266   */
267  Metrics = 'metrics',
268  /**
269   * Emitted when an uncaught exception happens within the page.
270   * Contains an `Error`.
271   */
272  PageError = 'pageerror',
273  /**
274   * Emitted when the page opens a new tab or window.
275   *
276   * Contains a {@link Page} corresponding to the popup window.
277   *
278   * @example
279   *
280   * ```js
281   * const [popup] = await Promise.all([
282   *   new Promise(resolve => page.once('popup', resolve)),
283   *   page.click('a[target=_blank]'),
284   * ]);
285   * ```
286   *
287   * ```js
288   * const [popup] = await Promise.all([
289   *   new Promise(resolve => page.once('popup', resolve)),
290   *   page.evaluate(() => window.open('https://example.com')),
291   * ]);
292   * ```
293   */
294  Popup = 'popup',
295  /**
296   * Emitted when a page issues a request and contains a {@link HTTPRequest}.
297   *
298   * @remarks
299   * The object is readonly. See {@link Page.setRequestInterception} for intercepting
300   * and mutating requests.
301   */
302  Request = 'request',
303  /**
304   * Emitted when a request ended up loading from cache. Contains a {@link HTTPRequest}.
305   *
306   * @remarks
307   * For certain requests, might contain undefined.
308   * {@link https://crbug.com/750469}
309   */
310  RequestServedFromCache = 'requestservedfromcache',
311  /**
312   * Emitted when a request fails, for example by timing out.
313   *
314   * Contains a {@link HTTPRequest}.
315   *
316   * @remarks
317   *
318   * NOTE: HTTP Error responses, such as 404 or 503, are still successful
319   * responses from HTTP standpoint, so request will complete with
320   * `requestfinished` event and not with `requestfailed`.
321   */
322  RequestFailed = 'requestfailed',
323  /**
324   * Emitted when a request finishes successfully. Contains a {@link HTTPRequest}.
325   */
326  RequestFinished = 'requestfinished',
327  /**
328   * Emitted when a response is received. Contains a {@link HTTPResponse}.
329   */
330  Response = 'response',
331  /**
332   * Emitted when a dedicated
333   * {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API | WebWorker}
334   * is spawned by the page.
335   */
336  WorkerCreated = 'workercreated',
337  /**
338   * Emitted when a dedicated
339   * {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API | WebWorker}
340   * is destroyed by the page.
341   */
342  WorkerDestroyed = 'workerdestroyed',
343}
344
345/**
346 * Denotes the objects received by callback functions for page events.
347 *
348 * See {@link PageEmittedEvents} for more detail on the events and when they are
349 * emitted.
350 * @public
351 */
352export interface PageEventObject {
353  close: never;
354  console: ConsoleMessage;
355  dialog: Dialog;
356  domcontentloaded: never;
357  error: Error;
358  frameattached: Frame;
359  framedetached: Frame;
360  framenavigated: Frame;
361  load: never;
362  metrics: { title: string; metrics: Metrics };
363  pageerror: Error;
364  popup: Page;
365  request: HTTPRequest;
366  response: HTTPResponse;
367  requestfailed: HTTPRequest;
368  requestfinished: HTTPRequest;
369  requestservedfromcache: HTTPRequest;
370  workercreated: WebWorker;
371  workerdestroyed: WebWorker;
372}
373
374/**
375 * Page provides methods to interact with a single tab or
376 * {@link https://developer.chrome.com/extensions/background_pages | extension background page} in Chromium.
377 *
378 * @remarks
379 *
380 * One Browser instance might have multiple Page instances.
381 *
382 * @example
383 * This example creates a page, navigates it to a URL, and then * saves a screenshot:
384 * ```js
385 * const puppeteer = require('puppeteer');
386 *
387 * (async () => {
388 *   const browser = await puppeteer.launch();
389 *   const page = await browser.newPage();
390 *   await page.goto('https://example.com');
391 *   await page.screenshot({path: 'screenshot.png'});
392 *   await browser.close();
393 * })();
394 * ```
395 *
396 * The Page class extends from Puppeteer's {@link EventEmitter} class and will
397 * emit various events which are documented in the {@link PageEmittedEvents} enum.
398 *
399 * @example
400 * This example logs a message for a single page `load` event:
401 * ```js
402 * page.once('load', () => console.log('Page loaded!'));
403 * ```
404 *
405 * To unsubscribe from events use the `off` method:
406 *
407 * ```js
408 * function logRequest(interceptedRequest) {
409 *   console.log('A request was made:', interceptedRequest.url());
410 * }
411 * page.on('request', logRequest);
412 * // Sometime later...
413 * page.off('request', logRequest);
414 * ```
415 * @public
416 */
417export class Page extends EventEmitter {
418  /**
419   * @internal
420   */
421  static async create(
422    client: CDPSession,
423    target: Target,
424    ignoreHTTPSErrors: boolean,
425    defaultViewport: Viewport | null,
426    screenshotTaskQueue: TaskQueue
427  ): Promise<Page> {
428    const page = new Page(
429      client,
430      target,
431      ignoreHTTPSErrors,
432      screenshotTaskQueue
433    );
434    await page._initialize();
435    if (defaultViewport) await page.setViewport(defaultViewport);
436    return page;
437  }
438
439  private _closed = false;
440  private _client: CDPSession;
441  private _target: Target;
442  private _keyboard: Keyboard;
443  private _mouse: Mouse;
444  private _timeoutSettings = new TimeoutSettings();
445  private _touchscreen: Touchscreen;
446  private _accessibility: Accessibility;
447  private _frameManager: FrameManager;
448  private _emulationManager: EmulationManager;
449  private _tracing: Tracing;
450  private _pageBindings = new Map<string, Function>();
451  private _coverage: Coverage;
452  private _javascriptEnabled = true;
453  private _viewport: Viewport | null;
454  private _screenshotTaskQueue: TaskQueue;
455  private _workers = new Map<string, WebWorker>();
456  // TODO: improve this typedef - it's a function that takes a file chooser or
457  // something?
458  private _fileChooserInterceptors = new Set<Function>();
459
460  private _disconnectPromise?: Promise<Error>;
461  private _userDragInterceptionEnabled = false;
462  private _handlerMap = new WeakMap<Handler, Handler>();
463
464  /**
465   * @internal
466   */
467  constructor(
468    client: CDPSession,
469    target: Target,
470    ignoreHTTPSErrors: boolean,
471    screenshotTaskQueue: TaskQueue
472  ) {
473    super();
474    this._client = client;
475    this._target = target;
476    this._keyboard = new Keyboard(client);
477    this._mouse = new Mouse(client, this._keyboard);
478    this._touchscreen = new Touchscreen(client, this._keyboard);
479    this._accessibility = new Accessibility(client);
480    this._frameManager = new FrameManager(
481      client,
482      this,
483      ignoreHTTPSErrors,
484      this._timeoutSettings
485    );
486    this._emulationManager = new EmulationManager(client);
487    this._tracing = new Tracing(client);
488    this._coverage = new Coverage(client);
489    this._screenshotTaskQueue = screenshotTaskQueue;
490    this._viewport = null;
491
492    client.on(
493      'Target.attachedToTarget',
494      (event: Protocol.Target.AttachedToTargetEvent) => {
495        if (
496          event.targetInfo.type !== 'worker' &&
497          event.targetInfo.type !== 'iframe'
498        ) {
499          // If we don't detach from service workers, they will never die.
500          // We still want to attach to workers for emitting events.
501          // We still want to attach to iframes so sessions may interact with them.
502          // We detach from all other types out of an abundance of caution.
503          // See https://source.chromium.org/chromium/chromium/src/+/main:content/browser/devtools/devtools_agent_host_impl.cc?ss=chromium&q=f:devtools%20-f:out%20%22::kTypePage%5B%5D%22
504          // for the complete list of available types.
505          client
506            .send('Target.detachFromTarget', {
507              sessionId: event.sessionId,
508            })
509            .catch(debugError);
510          return;
511        }
512        if (event.targetInfo.type === 'worker') {
513          const session = Connection.fromSession(client).session(
514            event.sessionId
515          );
516          const worker = new WebWorker(
517            session,
518            event.targetInfo.url,
519            this._addConsoleMessage.bind(this),
520            this._handleException.bind(this)
521          );
522          this._workers.set(event.sessionId, worker);
523          this.emit(PageEmittedEvents.WorkerCreated, worker);
524        }
525      }
526    );
527    client.on('Target.detachedFromTarget', (event) => {
528      const worker = this._workers.get(event.sessionId);
529      if (!worker) return;
530      this._workers.delete(event.sessionId);
531      this.emit(PageEmittedEvents.WorkerDestroyed, worker);
532    });
533
534    this._frameManager.on(FrameManagerEmittedEvents.FrameAttached, (event) =>
535      this.emit(PageEmittedEvents.FrameAttached, event)
536    );
537    this._frameManager.on(FrameManagerEmittedEvents.FrameDetached, (event) =>
538      this.emit(PageEmittedEvents.FrameDetached, event)
539    );
540    this._frameManager.on(FrameManagerEmittedEvents.FrameNavigated, (event) =>
541      this.emit(PageEmittedEvents.FrameNavigated, event)
542    );
543
544    const networkManager = this._frameManager.networkManager();
545    networkManager.on(NetworkManagerEmittedEvents.Request, (event) =>
546      this.emit(PageEmittedEvents.Request, event)
547    );
548    networkManager.on(
549      NetworkManagerEmittedEvents.RequestServedFromCache,
550      (event) => this.emit(PageEmittedEvents.RequestServedFromCache, event)
551    );
552    networkManager.on(NetworkManagerEmittedEvents.Response, (event) =>
553      this.emit(PageEmittedEvents.Response, event)
554    );
555    networkManager.on(NetworkManagerEmittedEvents.RequestFailed, (event) =>
556      this.emit(PageEmittedEvents.RequestFailed, event)
557    );
558    networkManager.on(NetworkManagerEmittedEvents.RequestFinished, (event) =>
559      this.emit(PageEmittedEvents.RequestFinished, event)
560    );
561    this._fileChooserInterceptors = new Set();
562
563    client.on('Page.domContentEventFired', () =>
564      this.emit(PageEmittedEvents.DOMContentLoaded)
565    );
566    client.on('Page.loadEventFired', () => this.emit(PageEmittedEvents.Load));
567    client.on('Runtime.consoleAPICalled', (event) => this._onConsoleAPI(event));
568    client.on('Runtime.bindingCalled', (event) => this._onBindingCalled(event));
569    client.on('Page.javascriptDialogOpening', (event) => this._onDialog(event));
570    client.on('Runtime.exceptionThrown', (exception) =>
571      this._handleException(exception.exceptionDetails)
572    );
573    client.on('Inspector.targetCrashed', () => this._onTargetCrashed());
574    client.on('Performance.metrics', (event) => this._emitMetrics(event));
575    client.on('Log.entryAdded', (event) => this._onLogEntryAdded(event));
576    client.on('Page.fileChooserOpened', (event) => this._onFileChooser(event));
577    this._target._isClosedPromise.then(() => {
578      this.emit(PageEmittedEvents.Close);
579      this._closed = true;
580    });
581  }
582
583  private async _initialize(): Promise<void> {
584    await Promise.all([
585      this._frameManager.initialize(),
586      this._client.send('Target.setAutoAttach', {
587        autoAttach: true,
588        waitForDebuggerOnStart: false,
589        flatten: true,
590      }),
591      this._client.send('Performance.enable'),
592      this._client.send('Log.enable'),
593    ]);
594  }
595
596  private async _onFileChooser(
597    event: Protocol.Page.FileChooserOpenedEvent
598  ): Promise<void> {
599    if (!this._fileChooserInterceptors.size) return;
600    const frame = this._frameManager.frame(event.frameId);
601    const context = await frame.executionContext();
602    const element = await context._adoptBackendNodeId(event.backendNodeId);
603    const interceptors = Array.from(this._fileChooserInterceptors);
604    this._fileChooserInterceptors.clear();
605    const fileChooser = new FileChooser(element, event);
606    for (const interceptor of interceptors) interceptor.call(null, fileChooser);
607  }
608
609  /**
610   * @returns `true` if drag events are being intercepted, `false` otherwise.
611   */
612  isDragInterceptionEnabled(): boolean {
613    return this._userDragInterceptionEnabled;
614  }
615
616  /**
617   * @returns `true` if the page has JavaScript enabled, `false` otherwise.
618   */
619  public isJavaScriptEnabled(): boolean {
620    return this._javascriptEnabled;
621  }
622
623  /**
624   * Listen to page events.
625   */
626  // Note: this method exists to define event typings and handle
627  // proper wireup of cooperative request interception. Actual event listening and
628  // dispatching is delegated to EventEmitter.
629  public on<K extends keyof PageEventObject>(
630    eventName: K,
631    handler: (event: PageEventObject[K]) => void
632  ): EventEmitter {
633    if (eventName === 'request') {
634      const wrap = (event: HTTPRequest) => {
635        event.enqueueInterceptAction(() =>
636          handler(event as PageEventObject[K])
637        );
638      };
639
640      this._handlerMap.set(handler, wrap);
641
642      return super.on(eventName, wrap);
643    }
644    return super.on(eventName, handler);
645  }
646
647  public once<K extends keyof PageEventObject>(
648    eventName: K,
649    handler: (event: PageEventObject[K]) => void
650  ): EventEmitter {
651    // Note: this method only exists to define the types; we delegate the impl
652    // to EventEmitter.
653    return super.once(eventName, handler);
654  }
655
656  off<K extends keyof PageEventObject>(
657    eventName: K,
658    handler: (event: PageEventObject[K]) => void
659  ): EventEmitter {
660    if (eventName === 'request') {
661      handler = this._handlerMap.get(handler) || handler;
662    }
663
664    return super.off(eventName, handler);
665  }
666
667  /**
668   * This method is typically coupled with an action that triggers file
669   * choosing. The following example clicks a button that issues a file chooser
670   * and then responds with `/tmp/myfile.pdf` as if a user has selected this file.
671   *
672   * ```js
673   * const [fileChooser] = await Promise.all([
674   * page.waitForFileChooser(),
675   * page.click('#upload-file-button'),
676   * // some button that triggers file selection
677   * ]);
678   * await fileChooser.accept(['/tmp/myfile.pdf']);
679   * ```
680   *
681   * NOTE: This must be called before the file chooser is launched. It will not
682   * return a currently active file chooser.
683   * @param options - Optional waiting parameters
684   * @returns Resolves after a page requests a file picker.
685   * @remarks
686   * NOTE: In non-headless Chromium, this method results in the native file picker
687   * dialog `not showing up` for the user.
688   */
689  async waitForFileChooser(
690    options: WaitTimeoutOptions = {}
691  ): Promise<FileChooser> {
692    if (!this._fileChooserInterceptors.size)
693      await this._client.send('Page.setInterceptFileChooserDialog', {
694        enabled: true,
695      });
696
697    const { timeout = this._timeoutSettings.timeout() } = options;
698    let callback: (value: FileChooser | PromiseLike<FileChooser>) => void;
699    const promise = new Promise<FileChooser>((x) => (callback = x));
700    this._fileChooserInterceptors.add(callback);
701    return helper
702      .waitWithTimeout<FileChooser>(
703        promise,
704        'waiting for file chooser',
705        timeout
706      )
707      .catch((error) => {
708        this._fileChooserInterceptors.delete(callback);
709        throw error;
710      });
711  }
712
713  /**
714   * Sets the page's geolocation.
715   * @remarks
716   * NOTE: Consider using {@link BrowserContext.overridePermissions} to grant
717   * permissions for the page to read its geolocation.
718   * @example
719   * ```js
720   * await page.setGeolocation({latitude: 59.95, longitude: 30.31667});
721   * ```
722   */
723  async setGeolocation(options: GeolocationOptions): Promise<void> {
724    const { longitude, latitude, accuracy = 0 } = options;
725    if (longitude < -180 || longitude > 180)
726      throw new Error(
727        `Invalid longitude "${longitude}": precondition -180 <= LONGITUDE <= 180 failed.`
728      );
729    if (latitude < -90 || latitude > 90)
730      throw new Error(
731        `Invalid latitude "${latitude}": precondition -90 <= LATITUDE <= 90 failed.`
732      );
733    if (accuracy < 0)
734      throw new Error(
735        `Invalid accuracy "${accuracy}": precondition 0 <= ACCURACY failed.`
736      );
737    await this._client.send('Emulation.setGeolocationOverride', {
738      longitude,
739      latitude,
740      accuracy,
741    });
742  }
743
744  /**
745   * @returns A target this page was created from.
746   */
747  target(): Target {
748    return this._target;
749  }
750
751  /**
752   * Get the CDP session client the page belongs to.
753   * @internal
754   */
755  client(): CDPSession {
756    return this._client;
757  }
758
759  /**
760   * Get the browser the page belongs to.
761   */
762  browser(): Browser {
763    return this._target.browser();
764  }
765
766  /**
767   * Get the browser context that the page belongs to.
768   */
769  browserContext(): BrowserContext {
770    return this._target.browserContext();
771  }
772
773  private _onTargetCrashed(): void {
774    this.emit('error', new Error('Page crashed!'));
775  }
776
777  private _onLogEntryAdded(event: Protocol.Log.EntryAddedEvent): void {
778    const { level, text, args, source, url, lineNumber } = event.entry;
779    if (args) args.map((arg) => helper.releaseObject(this._client, arg));
780    if (source !== 'worker')
781      this.emit(
782        PageEmittedEvents.Console,
783        new ConsoleMessage(level, text, [], [{ url, lineNumber }])
784      );
785  }
786
787  /**
788   * @returns The page's main frame.
789   * @remarks
790   * Page is guaranteed to have a main frame which persists during navigations.
791   */
792  mainFrame(): Frame {
793    return this._frameManager.mainFrame();
794  }
795
796  get keyboard(): Keyboard {
797    return this._keyboard;
798  }
799
800  get touchscreen(): Touchscreen {
801    return this._touchscreen;
802  }
803
804  get coverage(): Coverage {
805    return this._coverage;
806  }
807
808  get tracing(): Tracing {
809    return this._tracing;
810  }
811
812  get accessibility(): Accessibility {
813    return this._accessibility;
814  }
815
816  /**
817   * @returns An array of all frames attached to the page.
818   */
819  frames(): Frame[] {
820    return this._frameManager.frames();
821  }
822
823  /**
824   * @returns all of the dedicated
825   * {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API |
826   * WebWorkers}
827   * associated with the page.
828   * @remarks
829   * NOTE: This does not contain ServiceWorkers
830   */
831  workers(): WebWorker[] {
832    return Array.from(this._workers.values());
833  }
834
835  /**
836   * @param value - Whether to enable request interception.
837   *
838   * @remarks
839   * Activating request interception enables {@link HTTPRequest.abort},
840   * {@link HTTPRequest.continue} and {@link HTTPRequest.respond} methods.  This
841   * provides the capability to modify network requests that are made by a page.
842   *
843   * Once request interception is enabled, every request will stall unless it's
844   * continued, responded or aborted; or completed using the browser cache.
845   *
846   * @example
847   * An example of a naïve request interceptor that aborts all image requests:
848   * ```js
849   * const puppeteer = require('puppeteer');
850   * (async () => {
851   *   const browser = await puppeteer.launch();
852   *   const page = await browser.newPage();
853   *   await page.setRequestInterception(true);
854   *   page.on('request', interceptedRequest => {
855   *     if (interceptedRequest.url().endsWith('.png') ||
856   *         interceptedRequest.url().endsWith('.jpg'))
857   *       interceptedRequest.abort();
858   *     else
859   *       interceptedRequest.continue();
860   *     });
861   *   await page.goto('https://example.com');
862   *   await browser.close();
863   * })();
864   * ```
865   * NOTE: Enabling request interception disables page caching.
866   */
867  async setRequestInterception(value: boolean): Promise<void> {
868    return this._frameManager.networkManager().setRequestInterception(value);
869  }
870
871  /**
872   * @param enabled - Whether to enable drag interception.
873   *
874   * @remarks
875   * Activating drag interception enables the `Input.drag`,
876   * methods  This provides the capability to capture drag events emitted
877   * on the page, which can then be used to simulate drag-and-drop.
878   */
879  async setDragInterception(enabled: boolean): Promise<void> {
880    this._userDragInterceptionEnabled = enabled;
881    return this._client.send('Input.setInterceptDrags', { enabled });
882  }
883
884  /**
885   * @param enabled - When `true`, enables offline mode for the page.
886   * @remarks
887   * NOTE: while this method sets the network connection to offline, it does
888   * not change the parameters used in [page.emulateNetworkConditions(networkConditions)]
889   * (#pageemulatenetworkconditionsnetworkconditions)
890   */
891  setOfflineMode(enabled: boolean): Promise<void> {
892    return this._frameManager.networkManager().setOfflineMode(enabled);
893  }
894
895  /**
896   * @param networkConditions - Passing `null` disables network condition emulation.
897   * @example
898   * ```js
899   * const puppeteer = require('puppeteer');
900   * const slow3G = puppeteer.networkConditions['Slow 3G'];
901   *
902   * (async () => {
903   * const browser = await puppeteer.launch();
904   * const page = await browser.newPage();
905   * await page.emulateNetworkConditions(slow3G);
906   * await page.goto('https://www.google.com');
907   * // other actions...
908   * await browser.close();
909   * })();
910   * ```
911   * @remarks
912   * NOTE: This does not affect WebSockets and WebRTC PeerConnections (see
913   * https://crbug.com/563644). To set the page offline, you can use
914   * [page.setOfflineMode(enabled)](#pagesetofflinemodeenabled).
915   */
916  emulateNetworkConditions(
917    networkConditions: NetworkConditions | null
918  ): Promise<void> {
919    return this._frameManager
920      .networkManager()
921      .emulateNetworkConditions(networkConditions);
922  }
923
924  /**
925   * This setting will change the default maximum navigation time for the
926   * following methods and related shortcuts:
927   *
928   * - {@link Page.goBack | page.goBack(options)}
929   *
930   * - {@link Page.goForward | page.goForward(options)}
931   *
932   * - {@link Page.goto | page.goto(url,options)}
933   *
934   * - {@link Page.reload | page.reload(options)}
935   *
936   * - {@link Page.setContent | page.setContent(html,options)}
937   *
938   * - {@link Page.waitForNavigation | page.waitForNavigation(options)}
939   * @param timeout - Maximum navigation time in milliseconds.
940   */
941  setDefaultNavigationTimeout(timeout: number): void {
942    this._timeoutSettings.setDefaultNavigationTimeout(timeout);
943  }
944
945  /**
946   * @param timeout - Maximum time in milliseconds.
947   */
948  setDefaultTimeout(timeout: number): void {
949    this._timeoutSettings.setDefaultTimeout(timeout);
950  }
951
952  /**
953   * Runs `document.querySelector` within the page. If no element matches the
954   * selector, the return value resolves to `null`.
955   *
956   * @remarks
957   * Shortcut for {@link Frame.$ | Page.mainFrame().$(selector) }.
958   *
959   * @param selector - A `selector` to query page for
960   * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | selector}
961   * to query page for.
962   */
963  async $<T extends Element = Element>(
964    selector: string
965  ): Promise<ElementHandle<T> | null> {
966    return this.mainFrame().$<T>(selector);
967  }
968
969  /**
970   * @remarks
971   *
972   * The only difference between {@link Page.evaluate | page.evaluate} and
973   * `page.evaluateHandle` is that `evaluateHandle` will return the value
974   * wrapped in an in-page object.
975   *
976   * If the function passed to `page.evaluteHandle` returns a Promise, the
977   * function will wait for the promise to resolve and return its value.
978   *
979   * You can pass a string instead of a function (although functions are
980   * recommended as they are easier to debug and use with TypeScript):
981   *
982   * @example
983   * ```
984   * const aHandle = await page.evaluateHandle('document')
985   * ```
986   *
987   * @example
988   * {@link JSHandle} instances can be passed as arguments to the `pageFunction`:
989   * ```
990   * const aHandle = await page.evaluateHandle(() => document.body);
991   * const resultHandle = await page.evaluateHandle(body => body.innerHTML, aHandle);
992   * console.log(await resultHandle.jsonValue());
993   * await resultHandle.dispose();
994   * ```
995   *
996   * Most of the time this function returns a {@link JSHandle},
997   * but if `pageFunction` returns a reference to an element,
998   * you instead get an {@link ElementHandle} back:
999   *
1000   * @example
1001   * ```
1002   * const button = await page.evaluateHandle(() => document.querySelector('button'));
1003   * // can call `click` because `button` is an `ElementHandle`
1004   * await button.click();
1005   * ```
1006   *
1007   * The TypeScript definitions assume that `evaluateHandle` returns
1008   *  a `JSHandle`, but if you know it's going to return an
1009   * `ElementHandle`, pass it as the generic argument:
1010   *
1011   * ```
1012   * const button = await page.evaluateHandle<ElementHandle>(...);
1013   * ```
1014   *
1015   * @param pageFunction - a function that is run within the page
1016   * @param args - arguments to be passed to the pageFunction
1017   */
1018  async evaluateHandle<HandlerType extends JSHandle = JSHandle>(
1019    pageFunction: EvaluateHandleFn,
1020    ...args: SerializableOrJSHandle[]
1021  ): Promise<HandlerType> {
1022    const context = await this.mainFrame().executionContext();
1023    return context.evaluateHandle<HandlerType>(pageFunction, ...args);
1024  }
1025
1026  /**
1027   * This method iterates the JavaScript heap and finds all objects with the
1028   * given prototype.
1029   *
1030   * @remarks
1031   * Shortcut for
1032   * {@link ExecutionContext.queryObjects |
1033   * page.mainFrame().executionContext().queryObjects(prototypeHandle)}.
1034   *
1035   * @example
1036   *
1037   * ```js
1038   * // Create a Map object
1039   * await page.evaluate(() => window.map = new Map());
1040   * // Get a handle to the Map object prototype
1041   * const mapPrototype = await page.evaluateHandle(() => Map.prototype);
1042   * // Query all map instances into an array
1043   * const mapInstances = await page.queryObjects(mapPrototype);
1044   * // Count amount of map objects in heap
1045   * const count = await page.evaluate(maps => maps.length, mapInstances);
1046   * await mapInstances.dispose();
1047   * await mapPrototype.dispose();
1048   * ```
1049   * @param prototypeHandle - a handle to the object prototype.
1050   * @returns Promise which resolves to a handle to an array of objects with
1051   * this prototype.
1052   */
1053  async queryObjects(prototypeHandle: JSHandle): Promise<JSHandle> {
1054    const context = await this.mainFrame().executionContext();
1055    return context.queryObjects(prototypeHandle);
1056  }
1057
1058  /**
1059   * This method runs `document.querySelector` within the page and passes the
1060   * result as the first argument to the `pageFunction`.
1061   *
1062   * @remarks
1063   *
1064   * If no element is found matching `selector`, the method will throw an error.
1065   *
1066   * If `pageFunction` returns a promise `$eval` will wait for the promise to
1067   * resolve and then return its value.
1068   *
1069   * @example
1070   *
1071   * ```
1072   * const searchValue = await page.$eval('#search', el => el.value);
1073   * const preloadHref = await page.$eval('link[rel=preload]', el => el.href);
1074   * const html = await page.$eval('.main-container', el => el.outerHTML);
1075   * ```
1076   *
1077   * If you are using TypeScript, you may have to provide an explicit type to the
1078   * first argument of the `pageFunction`.
1079   * By default it is typed as `Element`, but you may need to provide a more
1080   * specific sub-type:
1081   *
1082   * @example
1083   *
1084   * ```
1085   * // if you don't provide HTMLInputElement here, TS will error
1086   * // as `value` is not on `Element`
1087   * const searchValue = await page.$eval('#search', (el: HTMLInputElement) => el.value);
1088   * ```
1089   *
1090   * The compiler should be able to infer the return type
1091   * from the `pageFunction` you provide. If it is unable to, you can use the generic
1092   * type to tell the compiler what return type you expect from `$eval`:
1093   *
1094   * @example
1095   *
1096   * ```
1097   * // The compiler can infer the return type in this case, but if it can't
1098   * // or if you want to be more explicit, provide it as the generic type.
1099   * const searchValue = await page.$eval<string>(
1100   *  '#search', (el: HTMLInputElement) => el.value
1101   * );
1102   * ```
1103   *
1104   * @param selector - the
1105   * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | selector}
1106   * to query for
1107   * @param pageFunction - the function to be evaluated in the page context.
1108   * Will be passed the result of `document.querySelector(selector)` as its
1109   * first argument.
1110   * @param args - any additional arguments to pass through to `pageFunction`.
1111   *
1112   * @returns The result of calling `pageFunction`. If it returns an element it
1113   * is wrapped in an {@link ElementHandle}, else the raw value itself is
1114   * returned.
1115   */
1116  async $eval<ReturnType>(
1117    selector: string,
1118    pageFunction: (
1119      element: Element,
1120      /* Unfortunately this has to be unknown[] because it's hard to get
1121       * TypeScript to understand that the arguments will be left alone unless
1122       * they are an ElementHandle, in which case they will be unwrapped.
1123       * The nice thing about unknown vs any is that unknown will force the user
1124       * to type the item before using it to avoid errors.
1125       *
1126       * TODO(@jackfranklin): We could fix this by using overloads like
1127       * DefinitelyTyped does:
1128       * https://github.com/DefinitelyTyped/DefinitelyTyped/blob/HEAD/types/puppeteer/index.d.ts#L114
1129       */
1130      ...args: unknown[]
1131    ) => ReturnType | Promise<ReturnType>,
1132    ...args: SerializableOrJSHandle[]
1133  ): Promise<WrapElementHandle<ReturnType>> {
1134    return this.mainFrame().$eval<ReturnType>(selector, pageFunction, ...args);
1135  }
1136
1137  /**
1138   * This method runs `Array.from(document.querySelectorAll(selector))` within
1139   * the page and passes the result as the first argument to the `pageFunction`.
1140   *
1141   * @remarks
1142   *
1143   * If `pageFunction` returns a promise `$$eval` will wait for the promise to
1144   * resolve and then return its value.
1145   *
1146   * @example
1147   *
1148   * ```
1149   * // get the amount of divs on the page
1150   * const divCount = await page.$$eval('div', divs => divs.length);
1151   *
1152   * // get the text content of all the `.options` elements:
1153   * const options = await page.$$eval('div > span.options', options => {
1154   *   return options.map(option => option.textContent)
1155   * });
1156   * ```
1157   *
1158   * If you are using TypeScript, you may have to provide an explicit type to the
1159   * first argument of the `pageFunction`.
1160   * By default it is typed as `Element[]`, but you may need to provide a more
1161   * specific sub-type:
1162   *
1163   * @example
1164   *
1165   * ```
1166   * // if you don't provide HTMLInputElement here, TS will error
1167   * // as `value` is not on `Element`
1168   * await page.$$eval('input', (elements: HTMLInputElement[]) => {
1169   *   return elements.map(e => e.value);
1170   * });
1171   * ```
1172   *
1173   * The compiler should be able to infer the return type
1174   * from the `pageFunction` you provide. If it is unable to, you can use the generic
1175   * type to tell the compiler what return type you expect from `$$eval`:
1176   *
1177   * @example
1178   *
1179   * ```
1180   * // The compiler can infer the return type in this case, but if it can't
1181   * // or if you want to be more explicit, provide it as the generic type.
1182   * const allInputValues = await page.$$eval<string[]>(
1183   *  'input', (elements: HTMLInputElement[]) => elements.map(e => e.textContent)
1184   * );
1185   * ```
1186   *
1187   * @param selector - the
1188   * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | selector}
1189   * to query for
1190   * @param pageFunction - the function to be evaluated in the page context. Will
1191   * be passed the result of `Array.from(document.querySelectorAll(selector))`
1192   * as its first argument.
1193   * @param args - any additional arguments to pass through to `pageFunction`.
1194   *
1195   * @returns The result of calling `pageFunction`. If it returns an element it
1196   * is wrapped in an {@link ElementHandle}, else the raw value itself is
1197   * returned.
1198   */
1199  async $$eval<ReturnType>(
1200    selector: string,
1201    pageFunction: (
1202      elements: Element[],
1203      /* These have to be typed as unknown[] for the same reason as the $eval
1204       * definition above, please see that comment for more details and the TODO
1205       * that will improve things.
1206       */
1207      ...args: unknown[]
1208    ) => ReturnType | Promise<ReturnType>,
1209    ...args: SerializableOrJSHandle[]
1210  ): Promise<WrapElementHandle<ReturnType>> {
1211    return this.mainFrame().$$eval<ReturnType>(selector, pageFunction, ...args);
1212  }
1213
1214  /**
1215   * The method runs `document.querySelectorAll` within the page. If no elements
1216   * match the selector, the return value resolves to `[]`.
1217   * @remarks
1218   * Shortcut for {@link Frame.$$ | Page.mainFrame().$$(selector) }.
1219   * @param selector - A `selector` to query page for
1220   */
1221  async $$<T extends Element = Element>(
1222    selector: string
1223  ): Promise<Array<ElementHandle<T>>> {
1224    return this.mainFrame().$$<T>(selector);
1225  }
1226
1227  /**
1228   * The method evaluates the XPath expression relative to the page document as
1229   * its context node. If there are no such elements, the method resolves to an
1230   * empty array.
1231   * @remarks
1232   * Shortcut for {@link Frame.$x | Page.mainFrame().$x(expression) }.
1233   * @param expression - Expression to evaluate
1234   */
1235  async $x(expression: string): Promise<ElementHandle[]> {
1236    return this.mainFrame().$x(expression);
1237  }
1238
1239  /**
1240   * If no URLs are specified, this method returns cookies for the current page
1241   * URL. If URLs are specified, only cookies for those URLs are returned.
1242   */
1243  async cookies(...urls: string[]): Promise<Protocol.Network.Cookie[]> {
1244    const originalCookies = (
1245      await this._client.send('Network.getCookies', {
1246        urls: urls.length ? urls : [this.url()],
1247      })
1248    ).cookies;
1249
1250    const unsupportedCookieAttributes = ['priority'];
1251    const filterUnsupportedAttributes = (
1252      cookie: Protocol.Network.Cookie
1253    ): Protocol.Network.Cookie => {
1254      for (const attr of unsupportedCookieAttributes) delete cookie[attr];
1255      return cookie;
1256    };
1257    return originalCookies.map(filterUnsupportedAttributes);
1258  }
1259
1260  async deleteCookie(
1261    ...cookies: Protocol.Network.DeleteCookiesRequest[]
1262  ): Promise<void> {
1263    const pageURL = this.url();
1264    for (const cookie of cookies) {
1265      const item = Object.assign({}, cookie);
1266      if (!cookie.url && pageURL.startsWith('http')) item.url = pageURL;
1267      await this._client.send('Network.deleteCookies', item);
1268    }
1269  }
1270
1271  /**
1272   * @example
1273   * ```js
1274   * await page.setCookie(cookieObject1, cookieObject2);
1275   * ```
1276   */
1277  async setCookie(...cookies: Protocol.Network.CookieParam[]): Promise<void> {
1278    const pageURL = this.url();
1279    const startsWithHTTP = pageURL.startsWith('http');
1280    const items = cookies.map((cookie) => {
1281      const item = Object.assign({}, cookie);
1282      if (!item.url && startsWithHTTP) item.url = pageURL;
1283      assert(
1284        item.url !== 'about:blank',
1285        `Blank page can not have cookie "${item.name}"`
1286      );
1287      assert(
1288        !String.prototype.startsWith.call(item.url || '', 'data:'),
1289        `Data URL page can not have cookie "${item.name}"`
1290      );
1291      return item;
1292    });
1293    await this.deleteCookie(...items);
1294    if (items.length)
1295      await this._client.send('Network.setCookies', { cookies: items });
1296  }
1297
1298  /**
1299   * Adds a `<script>` tag into the page with the desired URL or content.
1300   * @remarks
1301   * Shortcut for {@link Frame.addScriptTag | page.mainFrame().addScriptTag(options) }.
1302   * @returns Promise which resolves to the added tag when the script's onload fires or
1303   * when the script content was injected into frame.
1304   */
1305  async addScriptTag(options: {
1306    url?: string;
1307    path?: string;
1308    content?: string;
1309    type?: string;
1310    id?: string;
1311  }): Promise<ElementHandle> {
1312    return this.mainFrame().addScriptTag(options);
1313  }
1314
1315  /**
1316   * Adds a `<link rel="stylesheet">` tag into the page with the desired URL or a
1317   * `<style type="text/css">` tag with the content.
1318   * @returns Promise which resolves to the added tag when the stylesheet's
1319   * onload fires or when the CSS content was injected into frame.
1320   */
1321  async addStyleTag(options: {
1322    url?: string;
1323    path?: string;
1324    content?: string;
1325  }): Promise<ElementHandle> {
1326    return this.mainFrame().addStyleTag(options);
1327  }
1328
1329  /**
1330   * The method adds a function called `name` on the page's `window` object. When
1331   * called, the function executes `puppeteerFunction` in node.js and returns a
1332   * `Promise` which resolves to the return value of `puppeteerFunction`.
1333   *
1334   * If the puppeteerFunction returns a `Promise`, it will be awaited.
1335   *
1336   * NOTE: Functions installed via `page.exposeFunction` survive navigations.
1337   * @param name - Name of the function on the window object
1338   * @param puppeteerFunction -  Callback function which will be called in
1339   * Puppeteer's context.
1340   * @example
1341   * An example of adding an `md5` function into the page:
1342   * ```js
1343   * const puppeteer = require('puppeteer');
1344   * const crypto = require('crypto');
1345   *
1346   * (async () => {
1347   * const browser = await puppeteer.launch();
1348   * const page = await browser.newPage();
1349   * page.on('console', (msg) => console.log(msg.text()));
1350   * await page.exposeFunction('md5', (text) =>
1351   * crypto.createHash('md5').update(text).digest('hex')
1352   * );
1353   * await page.evaluate(async () => {
1354   * // use window.md5 to compute hashes
1355   * const myString = 'PUPPETEER';
1356   * const myHash = await window.md5(myString);
1357   * console.log(`md5 of ${myString} is ${myHash}`);
1358   * });
1359   * await browser.close();
1360   * })();
1361   * ```
1362   * An example of adding a `window.readfile` function into the page:
1363   * ```js
1364   * const puppeteer = require('puppeteer');
1365   * const fs = require('fs');
1366   *
1367   * (async () => {
1368   * const browser = await puppeteer.launch();
1369   * const page = await browser.newPage();
1370   * page.on('console', (msg) => console.log(msg.text()));
1371   * await page.exposeFunction('readfile', async (filePath) => {
1372   * return new Promise((resolve, reject) => {
1373   * fs.readFile(filePath, 'utf8', (err, text) => {
1374   *    if (err) reject(err);
1375   *    else resolve(text);
1376   *  });
1377   * });
1378   * });
1379   * await page.evaluate(async () => {
1380   * // use window.readfile to read contents of a file
1381   * const content = await window.readfile('/etc/hosts');
1382   * console.log(content);
1383   * });
1384   * await browser.close();
1385   * })();
1386   * ```
1387   */
1388  async exposeFunction(
1389    name: string,
1390    puppeteerFunction: Function | { default: Function }
1391  ): Promise<void> {
1392    if (this._pageBindings.has(name))
1393      throw new Error(
1394        `Failed to add page binding with name ${name}: window['${name}'] already exists!`
1395      );
1396
1397    let exposedFunction: Function;
1398    if (typeof puppeteerFunction === 'function') {
1399      exposedFunction = puppeteerFunction;
1400    } else if (typeof puppeteerFunction.default === 'function') {
1401      exposedFunction = puppeteerFunction.default;
1402    } else {
1403      throw new Error(
1404        `Failed to add page binding with name ${name}: ${puppeteerFunction} is not a function or a module with a default export.`
1405      );
1406    }
1407
1408    this._pageBindings.set(name, exposedFunction);
1409
1410    const expression = helper.pageBindingInitString('exposedFun', name);
1411    await this._client.send('Runtime.addBinding', { name: name });
1412    await this._client.send('Page.addScriptToEvaluateOnNewDocument', {
1413      source: expression,
1414    });
1415    await Promise.all(
1416      this.frames().map((frame) => frame.evaluate(expression).catch(debugError))
1417    );
1418  }
1419
1420  /**
1421   * Provide credentials for `HTTP authentication`.
1422   * @remarks To disable authentication, pass `null`.
1423   */
1424  async authenticate(credentials: Credentials): Promise<void> {
1425    return this._frameManager.networkManager().authenticate(credentials);
1426  }
1427
1428  /**
1429   * The extra HTTP headers will be sent with every request the page initiates.
1430   * NOTE: All HTTP header names are lowercased. (HTTP headers are
1431   * case-insensitive, so this shouldn’t impact your server code.)
1432   * NOTE: page.setExtraHTTPHeaders does not guarantee the order of headers in
1433   * the outgoing requests.
1434   * @param headers - An object containing additional HTTP headers to be sent
1435   * with every request. All header values must be strings.
1436   * @returns
1437   */
1438  async setExtraHTTPHeaders(headers: Record<string, string>): Promise<void> {
1439    return this._frameManager.networkManager().setExtraHTTPHeaders(headers);
1440  }
1441
1442  /**
1443   * @param userAgent - Specific user agent to use in this page
1444   * @param userAgentData - Specific user agent client hint data to use in this
1445   * page
1446   * @returns Promise which resolves when the user agent is set.
1447   */
1448  async setUserAgent(
1449    userAgent: string,
1450    userAgentMetadata?: Protocol.Emulation.UserAgentMetadata
1451  ): Promise<void> {
1452    return this._frameManager
1453      .networkManager()
1454      .setUserAgent(userAgent, userAgentMetadata);
1455  }
1456
1457  /**
1458   * @returns Object containing metrics as key/value pairs.
1459   *
1460   * - `Timestamp` : The timestamp when the metrics sample was taken.
1461   *
1462   * - `Documents` : Number of documents in the page.
1463   *
1464   * - `Frames` : Number of frames in the page.
1465   *
1466   * - `JSEventListeners` : Number of events in the page.
1467   *
1468   * - `Nodes` : Number of DOM nodes in the page.
1469   *
1470   * - `LayoutCount` : Total number of full or partial page layout.
1471   *
1472   * - `RecalcStyleCount` : Total number of page style recalculations.
1473   *
1474   * - `LayoutDuration` : Combined durations of all page layouts.
1475   *
1476   * - `RecalcStyleDuration` : Combined duration of all page style
1477   *   recalculations.
1478   *
1479   * - `ScriptDuration` : Combined duration of JavaScript execution.
1480   *
1481   * - `TaskDuration` : Combined duration of all tasks performed by the browser.
1482   *
1483   *
1484   * - `JSHeapUsedSize` : Used JavaScript heap size.
1485   *
1486   * - `JSHeapTotalSize` : Total JavaScript heap size.
1487   * @remarks
1488   * NOTE: All timestamps are in monotonic time: monotonically increasing time
1489   * in seconds since an arbitrary point in the past.
1490   */
1491  async metrics(): Promise<Metrics> {
1492    const response = await this._client.send('Performance.getMetrics');
1493    return this._buildMetricsObject(response.metrics);
1494  }
1495
1496  private _emitMetrics(event: Protocol.Performance.MetricsEvent): void {
1497    this.emit(PageEmittedEvents.Metrics, {
1498      title: event.title,
1499      metrics: this._buildMetricsObject(event.metrics),
1500    });
1501  }
1502
1503  private _buildMetricsObject(
1504    metrics?: Protocol.Performance.Metric[]
1505  ): Metrics {
1506    const result = {};
1507    for (const metric of metrics || []) {
1508      if (supportedMetrics.has(metric.name)) result[metric.name] = metric.value;
1509    }
1510    return result;
1511  }
1512
1513  private _handleException(
1514    exceptionDetails: Protocol.Runtime.ExceptionDetails
1515  ): void {
1516    const message = helper.getExceptionMessage(exceptionDetails);
1517    const err = new Error(message);
1518    err.stack = ''; // Don't report clientside error with a node stack attached
1519    this.emit(PageEmittedEvents.PageError, err);
1520  }
1521
1522  private async _onConsoleAPI(
1523    event: Protocol.Runtime.ConsoleAPICalledEvent
1524  ): Promise<void> {
1525    if (event.executionContextId === 0) {
1526      // DevTools protocol stores the last 1000 console messages. These
1527      // messages are always reported even for removed execution contexts. In
1528      // this case, they are marked with executionContextId = 0 and are
1529      // reported upon enabling Runtime agent.
1530      //
1531      // Ignore these messages since:
1532      // - there's no execution context we can use to operate with message
1533      //   arguments
1534      // - these messages are reported before Puppeteer clients can subscribe
1535      //   to the 'console'
1536      //   page event.
1537      //
1538      // @see https://github.com/puppeteer/puppeteer/issues/3865
1539      return;
1540    }
1541    const context = this._frameManager.executionContextById(
1542      event.executionContextId,
1543      this._client
1544    );
1545    const values = event.args.map((arg) => createJSHandle(context, arg));
1546    this._addConsoleMessage(event.type, values, event.stackTrace);
1547  }
1548
1549  private async _onBindingCalled(
1550    event: Protocol.Runtime.BindingCalledEvent
1551  ): Promise<void> {
1552    let payload: { type: string; name: string; seq: number; args: unknown[] };
1553    try {
1554      payload = JSON.parse(event.payload);
1555    } catch {
1556      // The binding was either called by something in the page or it was
1557      // called before our wrapper was initialized.
1558      return;
1559    }
1560    const { type, name, seq, args } = payload;
1561    if (type !== 'exposedFun' || !this._pageBindings.has(name)) return;
1562    let expression = null;
1563    try {
1564      const result = await this._pageBindings.get(name)(...args);
1565      expression = helper.pageBindingDeliverResultString(name, seq, result);
1566    } catch (error) {
1567      if (error instanceof Error)
1568        expression = helper.pageBindingDeliverErrorString(
1569          name,
1570          seq,
1571          error.message,
1572          error.stack
1573        );
1574      else
1575        expression = helper.pageBindingDeliverErrorValueString(
1576          name,
1577          seq,
1578          error
1579        );
1580    }
1581    this._client
1582      .send('Runtime.evaluate', {
1583        expression,
1584        contextId: event.executionContextId,
1585      })
1586      .catch(debugError);
1587  }
1588
1589  private _addConsoleMessage(
1590    type: ConsoleMessageType,
1591    args: JSHandle[],
1592    stackTrace?: Protocol.Runtime.StackTrace
1593  ): void {
1594    if (!this.listenerCount(PageEmittedEvents.Console)) {
1595      args.forEach((arg) => arg.dispose());
1596      return;
1597    }
1598    const textTokens = [];
1599    for (const arg of args) {
1600      const remoteObject = arg._remoteObject;
1601      if (remoteObject.objectId) textTokens.push(arg.toString());
1602      else textTokens.push(helper.valueFromRemoteObject(remoteObject));
1603    }
1604    const stackTraceLocations = [];
1605    if (stackTrace) {
1606      for (const callFrame of stackTrace.callFrames) {
1607        stackTraceLocations.push({
1608          url: callFrame.url,
1609          lineNumber: callFrame.lineNumber,
1610          columnNumber: callFrame.columnNumber,
1611        });
1612      }
1613    }
1614    const message = new ConsoleMessage(
1615      type,
1616      textTokens.join(' '),
1617      args,
1618      stackTraceLocations
1619    );
1620    this.emit(PageEmittedEvents.Console, message);
1621  }
1622
1623  private _onDialog(event: Protocol.Page.JavascriptDialogOpeningEvent): void {
1624    let dialogType = null;
1625    const validDialogTypes = new Set<Protocol.Page.DialogType>([
1626      'alert',
1627      'confirm',
1628      'prompt',
1629      'beforeunload',
1630    ]);
1631
1632    if (validDialogTypes.has(event.type)) {
1633      dialogType = event.type as Protocol.Page.DialogType;
1634    }
1635    assert(dialogType, 'Unknown javascript dialog type: ' + event.type);
1636
1637    const dialog = new Dialog(
1638      this._client,
1639      dialogType,
1640      event.message,
1641      event.defaultPrompt
1642    );
1643    this.emit(PageEmittedEvents.Dialog, dialog);
1644  }
1645
1646  /**
1647   * Resets default white background
1648   */
1649  private async _resetDefaultBackgroundColor() {
1650    await this._client.send('Emulation.setDefaultBackgroundColorOverride');
1651  }
1652
1653  /**
1654   * Hides default white background
1655   */
1656  private async _setTransparentBackgroundColor(): Promise<void> {
1657    await this._client.send('Emulation.setDefaultBackgroundColorOverride', {
1658      color: { r: 0, g: 0, b: 0, a: 0 },
1659    });
1660  }
1661
1662  /**
1663   *
1664   * @returns
1665   * @remarks Shortcut for
1666   * {@link Frame.url | page.mainFrame().url()}.
1667   */
1668  url(): string {
1669    return this.mainFrame().url();
1670  }
1671
1672  async content(): Promise<string> {
1673    return await this._frameManager.mainFrame().content();
1674  }
1675
1676  /**
1677   * @param html - HTML markup to assign to the page.
1678   * @param options - Parameters that has some properties.
1679   * @remarks
1680   * The parameter `options` might have the following options.
1681   *
1682   * - `timeout` : Maximum time in milliseconds for resources to load, defaults
1683   *   to 30 seconds, pass `0` to disable timeout. The default value can be
1684   *   changed by using the
1685   *   {@link Page.setDefaultNavigationTimeout |
1686   *   page.setDefaultNavigationTimeout(timeout)}
1687   *   or {@link Page.setDefaultTimeout | page.setDefaultTimeout(timeout)}
1688   *   methods.
1689   *
1690   * - `waitUntil`: When to consider setting markup succeeded, defaults to `load`.
1691   *    Given an array of event strings, setting content is considered to be
1692   *    successful after all events have been fired. Events can be either:<br/>
1693   *  - `load` : consider setting content to be finished when the `load` event is
1694   *    fired.<br/>
1695   *  - `domcontentloaded` : consider setting content to be finished when the
1696   *   `DOMContentLoaded` event is fired.<br/>
1697   *  - `networkidle0` : consider setting content to be finished when there are no
1698   *   more than 0 network connections for at least `500` ms.<br/>
1699   *  - `networkidle2` : consider setting content to be finished when there are no
1700   *   more than 2 network connections for at least `500` ms.
1701   */
1702  async setContent(html: string, options: WaitForOptions = {}): Promise<void> {
1703    await this._frameManager.mainFrame().setContent(html, options);
1704  }
1705
1706  /**
1707   * @param url - URL to navigate page to. The URL should include scheme, e.g.
1708   * `https://`
1709   * @param options - Navigation Parameter
1710   * @returns Promise which resolves to the main resource response. In case of
1711   * multiple redirects, the navigation will resolve with the response of the
1712   * last redirect.
1713   * @remarks
1714   * The argument `options` might have the following properties:
1715   *
1716   * - `timeout` : Maximum navigation time in milliseconds, defaults to 30
1717   *   seconds, pass 0 to disable timeout. The default value can be changed by
1718   *   using the
1719   *   {@link Page.setDefaultNavigationTimeout |
1720   *   page.setDefaultNavigationTimeout(timeout)}
1721   *   or {@link Page.setDefaultTimeout | page.setDefaultTimeout(timeout)}
1722   *   methods.
1723   *
1724   * - `waitUntil`:When to consider navigation succeeded, defaults to `load`.
1725   *    Given an array of event strings, navigation is considered to be successful
1726   *    after all events have been fired. Events can be either:<br/>
1727   *  - `load` : consider navigation to be finished when the load event is
1728   *    fired.<br/>
1729   *  - `domcontentloaded` : consider navigation to be finished when the
1730   *    DOMContentLoaded event is fired.<br/>
1731   *  - `networkidle0` : consider navigation to be finished when there are no
1732   *    more than 0 network connections for at least `500` ms.<br/>
1733   *  - `networkidle2` : consider navigation to be finished when there are no
1734   *    more than 2 network connections for at least `500` ms.
1735   *
1736   * - `referer` : Referer header value. If provided it will take preference
1737   *   over the referer header value set by
1738   *   {@link Page.setExtraHTTPHeaders |page.setExtraHTTPHeaders()}.
1739   *
1740   * `page.goto` will throw an error if:
1741   * - there's an SSL error (e.g. in case of self-signed certificates).
1742   * - target URL is invalid.
1743   * - the timeout is exceeded during navigation.
1744   * - the remote server does not respond or is unreachable.
1745   * - the main resource failed to load.
1746   *
1747   * `page.goto` will not throw an error when any valid HTTP status code is
1748   *   returned by the remote server, including 404 "Not Found" and 500
1749   *   "Internal Server Error". The status code for such responses can be
1750   *   retrieved by calling response.status().
1751   *
1752   * NOTE: `page.goto` either throws an error or returns a main resource
1753   * response. The only exceptions are navigation to about:blank or navigation
1754   * to the same URL with a different hash, which would succeed and return null.
1755   *
1756   * NOTE: Headless mode doesn't support navigation to a PDF document. See the
1757   * {@link https://bugs.chromium.org/p/chromium/issues/detail?id=761295
1758   * | upstream issue}.
1759   *
1760   * Shortcut for {@link Frame.goto | page.mainFrame().goto(url, options)}.
1761   */
1762  async goto(
1763    url: string,
1764    options: WaitForOptions & { referer?: string } = {}
1765  ): Promise<HTTPResponse> {
1766    return await this._frameManager.mainFrame().goto(url, options);
1767  }
1768
1769  /**
1770   * @param options - Navigation parameters which might have the following
1771   * properties:
1772   * @returns Promise which resolves to the main resource response. In case of
1773   * multiple redirects, the navigation will resolve with the response of the
1774   * last redirect.
1775   * @remarks
1776   * The argument `options` might have the following properties:
1777   *
1778   * - `timeout` : Maximum navigation time in milliseconds, defaults to 30
1779   *   seconds, pass 0 to disable timeout. The default value can be changed by
1780   *   using the
1781   *   {@link Page.setDefaultNavigationTimeout |
1782   *   page.setDefaultNavigationTimeout(timeout)}
1783   *   or {@link Page.setDefaultTimeout | page.setDefaultTimeout(timeout)}
1784   *   methods.
1785   *
1786   * - `waitUntil`: When to consider navigation succeeded, defaults to `load`.
1787   *    Given an array of event strings, navigation is considered to be
1788   *    successful after all events have been fired. Events can be either:<br/>
1789   *  - `load` : consider navigation to be finished when the load event is fired.<br/>
1790   *  - `domcontentloaded` : consider navigation to be finished when the
1791   *   DOMContentLoaded event is fired.<br/>
1792   *  - `networkidle0` : consider navigation to be finished when there are no
1793   *   more than 0 network connections for at least `500` ms.<br/>
1794   *  - `networkidle2` : consider navigation to be finished when there are no
1795   *   more than 2 network connections for at least `500` ms.
1796   */
1797  async reload(options?: WaitForOptions): Promise<HTTPResponse | null> {
1798    const result = await Promise.all<HTTPResponse, void>([
1799      this.waitForNavigation(options),
1800      this._client.send('Page.reload'),
1801    ]);
1802
1803    return result[0];
1804  }
1805
1806  /**
1807   * This resolves when the page navigates to a new URL or reloads. It is useful
1808   * when you run code that will indirectly cause the page to navigate. Consider
1809   * this example:
1810   * ```js
1811   * const [response] = await Promise.all([
1812   * page.waitForNavigation(), // The promise resolves after navigation has finished
1813   * page.click('a.my-link'), // Clicking the link will indirectly cause a navigation
1814   * ]);
1815   * ```
1816   *
1817   * @param options - Navigation parameters which might have the following properties:
1818   * @returns Promise which resolves to the main resource response. In case of
1819   * multiple redirects, the navigation will resolve with the response of the
1820   * last redirect. In case of navigation to a different anchor or navigation
1821   * due to History API usage, the navigation will resolve with `null`.
1822   * @remarks
1823   * NOTE: Usage of the
1824   * {@link https://developer.mozilla.org/en-US/docs/Web/API/History_API | History API}
1825   * to change the URL is considered a navigation.
1826   *
1827   * Shortcut for
1828   * {@link Frame.waitForNavigation | page.mainFrame().waitForNavigation(options)}.
1829   */
1830  async waitForNavigation(
1831    options: WaitForOptions = {}
1832  ): Promise<HTTPResponse | null> {
1833    return await this._frameManager.mainFrame().waitForNavigation(options);
1834  }
1835
1836  private _sessionClosePromise(): Promise<Error> {
1837    if (!this._disconnectPromise)
1838      this._disconnectPromise = new Promise((fulfill) =>
1839        this._client.once(CDPSessionEmittedEvents.Disconnected, () =>
1840          fulfill(new Error('Target closed'))
1841        )
1842      );
1843    return this._disconnectPromise;
1844  }
1845
1846  /**
1847   * @param urlOrPredicate - A URL or predicate to wait for
1848   * @param options - Optional waiting parameters
1849   * @returns Promise which resolves to the matched response
1850   * @example
1851   * ```js
1852   * const firstResponse = await page.waitForResponse(
1853   * 'https://example.com/resource'
1854   * );
1855   * const finalResponse = await page.waitForResponse(
1856   * (response) =>
1857   * response.url() === 'https://example.com' && response.status() === 200
1858   * );
1859   * const finalResponse = await page.waitForResponse(async (response) => {
1860   * return (await response.text()).includes('<html>');
1861   * });
1862   * return finalResponse.ok();
1863   * ```
1864   * @remarks
1865   * Optional Waiting Parameters have:
1866   *
1867   * - `timeout`: Maximum wait time in milliseconds, defaults to `30` seconds, pass
1868   * `0` to disable the timeout. The default value can be changed by using the
1869   * {@link Page.setDefaultTimeout} method.
1870   */
1871  async waitForRequest(
1872    urlOrPredicate: string | ((req: HTTPRequest) => boolean | Promise<boolean>),
1873    options: { timeout?: number } = {}
1874  ): Promise<HTTPRequest> {
1875    const { timeout = this._timeoutSettings.timeout() } = options;
1876    return helper.waitForEvent(
1877      this._frameManager.networkManager(),
1878      NetworkManagerEmittedEvents.Request,
1879      (request) => {
1880        if (helper.isString(urlOrPredicate))
1881          return urlOrPredicate === request.url();
1882        if (typeof urlOrPredicate === 'function')
1883          return !!urlOrPredicate(request);
1884        return false;
1885      },
1886      timeout,
1887      this._sessionClosePromise()
1888    );
1889  }
1890
1891  /**
1892   * @param urlOrPredicate - A URL or predicate to wait for.
1893   * @param options - Optional waiting parameters
1894   * @returns Promise which resolves to the matched response.
1895   * @example
1896   * ```js
1897   * const firstResponse = await page.waitForResponse(
1898   * 'https://example.com/resource'
1899   * );
1900   * const finalResponse = await page.waitForResponse(
1901   * (response) =>
1902   * response.url() === 'https://example.com' && response.status() === 200
1903   * );
1904   * const finalResponse = await page.waitForResponse(async (response) => {
1905   * return (await response.text()).includes('<html>');
1906   * });
1907   * return finalResponse.ok();
1908   * ```
1909   * @remarks
1910   * Optional Parameter have:
1911   *
1912   * - `timeout`: Maximum wait time in milliseconds, defaults to `30` seconds,
1913   * pass `0` to disable the timeout. The default value can be changed by using
1914   * the {@link Page.setDefaultTimeout} method.
1915   */
1916  async waitForResponse(
1917    urlOrPredicate:
1918      | string
1919      | ((res: HTTPResponse) => boolean | Promise<boolean>),
1920    options: { timeout?: number } = {}
1921  ): Promise<HTTPResponse> {
1922    const { timeout = this._timeoutSettings.timeout() } = options;
1923    return helper.waitForEvent(
1924      this._frameManager.networkManager(),
1925      NetworkManagerEmittedEvents.Response,
1926      async (response) => {
1927        if (helper.isString(urlOrPredicate))
1928          return urlOrPredicate === response.url();
1929        if (typeof urlOrPredicate === 'function')
1930          return !!(await urlOrPredicate(response));
1931        return false;
1932      },
1933      timeout,
1934      this._sessionClosePromise()
1935    );
1936  }
1937
1938  /**
1939   * @param options - Optional waiting parameters
1940   * @returns Promise which resolves when network is idle
1941   */
1942  async waitForNetworkIdle(
1943    options: { idleTime?: number; timeout?: number } = {}
1944  ): Promise<void> {
1945    const { idleTime = 500, timeout = this._timeoutSettings.timeout() } =
1946      options;
1947
1948    const networkManager = this._frameManager.networkManager();
1949
1950    let idleResolveCallback;
1951    const idlePromise = new Promise((resolve) => {
1952      idleResolveCallback = resolve;
1953    });
1954
1955    let abortRejectCallback;
1956    const abortPromise = new Promise<Error>((_, reject) => {
1957      abortRejectCallback = reject;
1958    });
1959
1960    let idleTimer;
1961    const onIdle = () => idleResolveCallback();
1962
1963    const cleanup = () => {
1964      idleTimer && clearTimeout(idleTimer);
1965      abortRejectCallback(new Error('abort'));
1966    };
1967
1968    const evaluate = () => {
1969      idleTimer && clearTimeout(idleTimer);
1970      if (networkManager.numRequestsInProgress() === 0)
1971        idleTimer = setTimeout(onIdle, idleTime);
1972    };
1973
1974    evaluate();
1975
1976    const eventHandler = () => {
1977      evaluate();
1978      return false;
1979    };
1980
1981    const listenToEvent = (event) =>
1982      helper.waitForEvent(
1983        networkManager,
1984        event,
1985        eventHandler,
1986        timeout,
1987        abortPromise
1988      );
1989
1990    const eventPromises = [
1991      listenToEvent(NetworkManagerEmittedEvents.Request),
1992      listenToEvent(NetworkManagerEmittedEvents.Response),
1993    ];
1994
1995    await Promise.race([
1996      idlePromise,
1997      ...eventPromises,
1998      this._sessionClosePromise(),
1999    ]).then(
2000      (r) => {
2001        cleanup();
2002        return r;
2003      },
2004      (error) => {
2005        cleanup();
2006        throw error;
2007      }
2008    );
2009  }
2010
2011  /**
2012   * @param urlOrPredicate - A URL or predicate to wait for.
2013   * @param options - Optional waiting parameters
2014   * @returns Promise which resolves to the matched frame.
2015   * @example
2016   * ```js
2017   * const frame = await page.waitForFrame(async (frame) => {
2018   *   return frame.name() === 'Test';
2019   * });
2020   * ```
2021   * @remarks
2022   * Optional Parameter have:
2023   *
2024   * - `timeout`: Maximum wait time in milliseconds, defaults to `30` seconds,
2025   * pass `0` to disable the timeout. The default value can be changed by using
2026   * the {@link Page.setDefaultTimeout} method.
2027   */
2028  async waitForFrame(
2029    urlOrPredicate: string | ((frame: Frame) => boolean | Promise<boolean>),
2030    options: { timeout?: number } = {}
2031  ): Promise<Frame> {
2032    const { timeout = this._timeoutSettings.timeout() } = options;
2033
2034    async function predicate(frame: Frame) {
2035      if (helper.isString(urlOrPredicate))
2036        return urlOrPredicate === frame.url();
2037      if (typeof urlOrPredicate === 'function')
2038        return !!(await urlOrPredicate(frame));
2039      return false;
2040    }
2041
2042    return Promise.race([
2043      helper.waitForEvent(
2044        this._frameManager,
2045        FrameManagerEmittedEvents.FrameAttached,
2046        predicate,
2047        timeout,
2048        this._sessionClosePromise()
2049      ),
2050      helper.waitForEvent(
2051        this._frameManager,
2052        FrameManagerEmittedEvents.FrameNavigated,
2053        predicate,
2054        timeout,
2055        this._sessionClosePromise()
2056      ),
2057    ]);
2058  }
2059
2060  /**
2061   * This method navigate to the previous page in history.
2062   * @param options - Navigation parameters
2063   * @returns Promise which resolves to the main resource response. In case of
2064   * multiple redirects, the navigation will resolve with the response of the
2065   * last redirect. If can not go back, resolves to `null`.
2066   * @remarks
2067   * The argument `options` might have the following properties:
2068   *
2069   * - `timeout` : Maximum navigation time in milliseconds, defaults to 30
2070   *   seconds, pass 0 to disable timeout. The default value can be changed by
2071   *   using the
2072   *   {@link Page.setDefaultNavigationTimeout
2073   *   | page.setDefaultNavigationTimeout(timeout)}
2074   *   or {@link Page.setDefaultTimeout | page.setDefaultTimeout(timeout)}
2075   *   methods.
2076   *
2077   * - `waitUntil` : When to consider navigation succeeded, defaults to `load`.
2078   *    Given an array of event strings, navigation is considered to be
2079   *    successful after all events have been fired. Events can be either:<br/>
2080   *  - `load` : consider navigation to be finished when the load event is fired.<br/>
2081   *  - `domcontentloaded` : consider navigation to be finished when the
2082   *   DOMContentLoaded event is fired.<br/>
2083   *  - `networkidle0` : consider navigation to be finished when there are no
2084   *   more than 0 network connections for at least `500` ms.<br/>
2085   *  - `networkidle2` : consider navigation to be finished when there are no
2086   *   more than 2 network connections for at least `500` ms.
2087   */
2088  async goBack(options: WaitForOptions = {}): Promise<HTTPResponse | null> {
2089    return this._go(-1, options);
2090  }
2091
2092  /**
2093   * This method navigate to the next page in history.
2094   * @param options - Navigation Parameter
2095   * @returns Promise which resolves to the main resource response. In case of
2096   * multiple redirects, the navigation will resolve with the response of the
2097   * last redirect. If can not go forward, resolves to `null`.
2098   * @remarks
2099   * The argument `options` might have the following properties:
2100   *
2101   * - `timeout` : Maximum navigation time in milliseconds, defaults to 30
2102   *   seconds, pass 0 to disable timeout. The default value can be changed by
2103   *   using the
2104   *   {@link Page.setDefaultNavigationTimeout
2105   *   | page.setDefaultNavigationTimeout(timeout)}
2106   *   or {@link Page.setDefaultTimeout | page.setDefaultTimeout(timeout)}
2107   *   methods.
2108   *
2109   * - `waitUntil`: When to consider navigation succeeded, defaults to `load`.
2110   *    Given an array of event strings, navigation is considered to be
2111   *    successful after all events have been fired. Events can be either:<br/>
2112   *  - `load` : consider navigation to be finished when the load event is fired.<br/>
2113   *  - `domcontentloaded` : consider navigation to be finished when the
2114   *   DOMContentLoaded event is fired.<br/>
2115   *  - `networkidle0` : consider navigation to be finished when there are no
2116   *   more than 0 network connections for at least `500` ms.<br/>
2117   *  - `networkidle2` : consider navigation to be finished when there are no
2118   *   more than 2 network connections for at least `500` ms.
2119   */
2120  async goForward(options: WaitForOptions = {}): Promise<HTTPResponse | null> {
2121    return this._go(+1, options);
2122  }
2123
2124  private async _go(
2125    delta: number,
2126    options: WaitForOptions
2127  ): Promise<HTTPResponse | null> {
2128    const history = await this._client.send('Page.getNavigationHistory');
2129    const entry = history.entries[history.currentIndex + delta];
2130    if (!entry) return null;
2131    const result = await Promise.all([
2132      this.waitForNavigation(options),
2133      this._client.send('Page.navigateToHistoryEntry', { entryId: entry.id }),
2134    ]);
2135    return result[0];
2136  }
2137
2138  /**
2139   * Brings page to front (activates tab).
2140   */
2141  async bringToFront(): Promise<void> {
2142    await this._client.send('Page.bringToFront');
2143  }
2144
2145  /**
2146   * Emulates given device metrics and user agent. This method is a shortcut for
2147   * calling two methods: {@link Page.setUserAgent} and {@link Page.setViewport}
2148   * To aid emulation, Puppeteer provides a list of device descriptors that can
2149   * be obtained via the {@link Puppeteer.devices} `page.emulate` will resize
2150   * the page. A lot of websites don't expect phones to change size, so you
2151   * should emulate before navigating to the page.
2152   * @example
2153   * ```js
2154   * const puppeteer = require('puppeteer');
2155   * const iPhone = puppeteer.devices['iPhone 6'];
2156   * (async () => {
2157   * const browser = await puppeteer.launch();
2158   * const page = await browser.newPage();
2159   * await page.emulate(iPhone);
2160   * await page.goto('https://www.google.com');
2161   * // other actions...
2162   * await browser.close();
2163   * })();
2164   * ```
2165   * @remarks List of all available devices is available in the source code:
2166   * {@link https://github.com/puppeteer/puppeteer/blob/main/src/common/DeviceDescriptors.ts | src/common/DeviceDescriptors.ts}.
2167   */
2168  async emulate(options: {
2169    viewport: Viewport;
2170    userAgent: string;
2171  }): Promise<void> {
2172    await Promise.all([
2173      this.setViewport(options.viewport),
2174      this.setUserAgent(options.userAgent),
2175    ]);
2176  }
2177
2178  /**
2179   * @param enabled - Whether or not to enable JavaScript on the page.
2180   * @returns
2181   * @remarks
2182   * NOTE: changing this value won't affect scripts that have already been run.
2183   * It will take full effect on the next navigation.
2184   */
2185  async setJavaScriptEnabled(enabled: boolean): Promise<void> {
2186    if (this._javascriptEnabled === enabled) return;
2187    this._javascriptEnabled = enabled;
2188    await this._client.send('Emulation.setScriptExecutionDisabled', {
2189      value: !enabled,
2190    });
2191  }
2192
2193  /**
2194   * Toggles bypassing page's Content-Security-Policy.
2195   * @param enabled - sets bypassing of page's Content-Security-Policy.
2196   * @remarks
2197   * NOTE: CSP bypassing happens at the moment of CSP initialization rather than
2198   * evaluation. Usually, this means that `page.setBypassCSP` should be called
2199   * before navigating to the domain.
2200   */
2201  async setBypassCSP(enabled: boolean): Promise<void> {
2202    await this._client.send('Page.setBypassCSP', { enabled });
2203  }
2204
2205  /**
2206   * @param type - Changes the CSS media type of the page. The only allowed
2207   * values are `screen`, `print` and `null`. Passing `null` disables CSS media
2208   * emulation.
2209   * @example
2210   * ```
2211   * await page.evaluate(() => matchMedia('screen').matches);
2212   * // → true
2213   * await page.evaluate(() => matchMedia('print').matches);
2214   * // → false
2215   *
2216   * await page.emulateMediaType('print');
2217   * await page.evaluate(() => matchMedia('screen').matches);
2218   * // → false
2219   * await page.evaluate(() => matchMedia('print').matches);
2220   * // → true
2221   *
2222   * await page.emulateMediaType(null);
2223   * await page.evaluate(() => matchMedia('screen').matches);
2224   * // → true
2225   * await page.evaluate(() => matchMedia('print').matches);
2226   * // → false
2227   * ```
2228   */
2229  async emulateMediaType(type?: string): Promise<void> {
2230    assert(
2231      type === 'screen' || type === 'print' || type === null,
2232      'Unsupported media type: ' + type
2233    );
2234    await this._client.send('Emulation.setEmulatedMedia', {
2235      media: type || '',
2236    });
2237  }
2238
2239  /**
2240   * Enables CPU throttling to emulate slow CPUs.
2241   * @param factor - slowdown factor (1 is no throttle, 2 is 2x slowdown, etc).
2242   */
2243  async emulateCPUThrottling(factor: number | null): Promise<void> {
2244    assert(
2245      factor === null || factor >= 1,
2246      'Throttling rate should be greater or equal to 1'
2247    );
2248    await this._client.send('Emulation.setCPUThrottlingRate', {
2249      rate: factor !== null ? factor : 1,
2250    });
2251  }
2252
2253  /**
2254   * @param features - `<?Array<Object>>` Given an array of media feature
2255   * objects, emulates CSS media features on the page. Each media feature object
2256   * must have the following properties:
2257   * @example
2258   * ```js
2259   * await page.emulateMediaFeatures([
2260   * { name: 'prefers-color-scheme', value: 'dark' },
2261   * ]);
2262   * await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches);
2263   * // → true
2264   * await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches);
2265   * // → false
2266   *
2267   * await page.emulateMediaFeatures([
2268   * { name: 'prefers-reduced-motion', value: 'reduce' },
2269   * ]);
2270   * await page.evaluate(
2271   * () => matchMedia('(prefers-reduced-motion: reduce)').matches
2272   * );
2273   * // → true
2274   * await page.evaluate(
2275   * () => matchMedia('(prefers-reduced-motion: no-preference)').matches
2276   * );
2277   * // → false
2278   *
2279   * await page.emulateMediaFeatures([
2280   * { name: 'prefers-color-scheme', value: 'dark' },
2281   * { name: 'prefers-reduced-motion', value: 'reduce' },
2282   * ]);
2283   * await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches);
2284   * // → true
2285   * await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches);
2286   * // → false
2287   * await page.evaluate(
2288   * () => matchMedia('(prefers-reduced-motion: reduce)').matches
2289   * );
2290   * // → true
2291   * await page.evaluate(
2292   * () => matchMedia('(prefers-reduced-motion: no-preference)').matches
2293   * );
2294   * // → false
2295   *
2296   * await page.emulateMediaFeatures([{ name: 'color-gamut', value: 'p3' }]);
2297   * await page.evaluate(() => matchMedia('(color-gamut: srgb)').matches);
2298   * // → true
2299   * await page.evaluate(() => matchMedia('(color-gamut: p3)').matches);
2300   * // → true
2301   * await page.evaluate(() => matchMedia('(color-gamut: rec2020)').matches);
2302   * // → false
2303   * ```
2304   */
2305  async emulateMediaFeatures(features?: MediaFeature[]): Promise<void> {
2306    if (features === null)
2307      await this._client.send('Emulation.setEmulatedMedia', { features: null });
2308    if (Array.isArray(features)) {
2309      features.every((mediaFeature) => {
2310        const name = mediaFeature.name;
2311        assert(
2312          /^(?:prefers-(?:color-scheme|reduced-motion)|color-gamut)$/.test(
2313            name
2314          ),
2315          'Unsupported media feature: ' + name
2316        );
2317        return true;
2318      });
2319      await this._client.send('Emulation.setEmulatedMedia', {
2320        features: features,
2321      });
2322    }
2323  }
2324
2325  /**
2326   * @param timezoneId - Changes the timezone of the page. See
2327   * {@link https://source.chromium.org/chromium/chromium/deps/icu.git/+/faee8bc70570192d82d2978a71e2a615788597d1:source/data/misc/metaZones.txt | ICU’s metaZones.txt}
2328   * for a list of supported timezone IDs. Passing
2329   * `null` disables timezone emulation.
2330   */
2331  async emulateTimezone(timezoneId?: string): Promise<void> {
2332    try {
2333      await this._client.send('Emulation.setTimezoneOverride', {
2334        timezoneId: timezoneId || '',
2335      });
2336    } catch (error) {
2337      if (error.message.includes('Invalid timezone'))
2338        throw new Error(`Invalid timezone ID: ${timezoneId}`);
2339      throw error;
2340    }
2341  }
2342
2343  /**
2344   * Emulates the idle state.
2345   * If no arguments set, clears idle state emulation.
2346   *
2347   * @example
2348   * ```js
2349   * // set idle emulation
2350   * await page.emulateIdleState({isUserActive: true, isScreenUnlocked: false});
2351   *
2352   * // do some checks here
2353   * ...
2354   *
2355   * // clear idle emulation
2356   * await page.emulateIdleState();
2357   * ```
2358   *
2359   * @param overrides - Mock idle state. If not set, clears idle overrides
2360   */
2361  async emulateIdleState(overrides?: {
2362    isUserActive: boolean;
2363    isScreenUnlocked: boolean;
2364  }): Promise<void> {
2365    if (overrides) {
2366      await this._client.send('Emulation.setIdleOverride', {
2367        isUserActive: overrides.isUserActive,
2368        isScreenUnlocked: overrides.isScreenUnlocked,
2369      });
2370    } else {
2371      await this._client.send('Emulation.clearIdleOverride');
2372    }
2373  }
2374
2375  /**
2376   * Simulates the given vision deficiency on the page.
2377   *
2378   * @example
2379   * ```js
2380   * const puppeteer = require('puppeteer');
2381   *
2382   * (async () => {
2383   *   const browser = await puppeteer.launch();
2384   *   const page = await browser.newPage();
2385   *   await page.goto('https://v8.dev/blog/10-years');
2386   *
2387   *   await page.emulateVisionDeficiency('achromatopsia');
2388   *   await page.screenshot({ path: 'achromatopsia.png' });
2389   *
2390   *   await page.emulateVisionDeficiency('deuteranopia');
2391   *   await page.screenshot({ path: 'deuteranopia.png' });
2392   *
2393   *   await page.emulateVisionDeficiency('blurredVision');
2394   *   await page.screenshot({ path: 'blurred-vision.png' });
2395   *
2396   *   await browser.close();
2397   * })();
2398   * ```
2399   *
2400   * @param type - the type of deficiency to simulate, or `'none'` to reset.
2401   */
2402  async emulateVisionDeficiency(
2403    type?: Protocol.Emulation.SetEmulatedVisionDeficiencyRequest['type']
2404  ): Promise<void> {
2405    const visionDeficiencies = new Set<
2406      Protocol.Emulation.SetEmulatedVisionDeficiencyRequest['type']
2407    >([
2408      'none',
2409      'achromatopsia',
2410      'blurredVision',
2411      'deuteranopia',
2412      'protanopia',
2413      'tritanopia',
2414    ]);
2415    try {
2416      assert(
2417        !type || visionDeficiencies.has(type),
2418        `Unsupported vision deficiency: ${type}`
2419      );
2420      await this._client.send('Emulation.setEmulatedVisionDeficiency', {
2421        type: type || 'none',
2422      });
2423    } catch (error) {
2424      throw error;
2425    }
2426  }
2427
2428  /**
2429   * `page.setViewport` will resize the page. A lot of websites don't expect
2430   * phones to change size, so you should set the viewport before navigating to
2431   * the page.
2432   *
2433   * In the case of multiple pages in a single browser, each page can have its
2434   * own viewport size.
2435   * @example
2436   * ```js
2437   * const page = await browser.newPage();
2438   * await page.setViewport({
2439   * width: 640,
2440   * height: 480,
2441   * deviceScaleFactor: 1,
2442   * });
2443   * await page.goto('https://example.com');
2444   * ```
2445   *
2446   * @param viewport -
2447   * @remarks
2448   * Argument viewport have following properties:
2449   *
2450   * - `width`: page width in pixels. required
2451   *
2452   * - `height`: page height in pixels. required
2453   *
2454   * - `deviceScaleFactor`: Specify device scale factor (can be thought of as
2455   *   DPR). Defaults to `1`.
2456   *
2457   * - `isMobile`: Whether the meta viewport tag is taken into account. Defaults
2458   *   to `false`.
2459   *
2460   * - `hasTouch`: Specifies if viewport supports touch events. Defaults to `false`
2461   *
2462   * - `isLandScape`: Specifies if viewport is in landscape mode. Defaults to false.
2463   *
2464   * NOTE: in certain cases, setting viewport will reload the page in order to
2465   * set the isMobile or hasTouch properties.
2466   */
2467  async setViewport(viewport: Viewport): Promise<void> {
2468    const needsReload = await this._emulationManager.emulateViewport(viewport);
2469    this._viewport = viewport;
2470    if (needsReload) await this.reload();
2471  }
2472
2473  /**
2474   * @returns
2475   *
2476   * - `width`: page's width in pixels
2477   *
2478   * - `height`: page's height in pixels
2479   *
2480   * - `deviceScalarFactor`: Specify device scale factor (can be though of as
2481   *   dpr). Defaults to `1`.
2482   *
2483   * - `isMobile`: Whether the meta viewport tag is taken into account. Defaults
2484   *   to `false`.
2485   *
2486   * - `hasTouch`: Specifies if viewport supports touch events. Defaults to
2487   *   `false`.
2488   *
2489   * - `isLandScape`: Specifies if viewport is in landscape mode. Defaults to
2490   *   `false`.
2491   */
2492  viewport(): Viewport | null {
2493    return this._viewport;
2494  }
2495
2496  /**
2497   * @remarks
2498   *
2499   * Evaluates a function in the page's context and returns the result.
2500   *
2501   * If the function passed to `page.evaluteHandle` returns a Promise, the
2502   * function will wait for the promise to resolve and return its value.
2503   *
2504   * @example
2505   *
2506   * ```js
2507   * const result = await frame.evaluate(() => {
2508   *   return Promise.resolve(8 * 7);
2509   * });
2510   * console.log(result); // prints "56"
2511   * ```
2512   *
2513   * You can pass a string instead of a function (although functions are
2514   * recommended as they are easier to debug and use with TypeScript):
2515   *
2516   * @example
2517   * ```
2518   * const aHandle = await page.evaluate('1 + 2');
2519   * ```
2520   *
2521   * To get the best TypeScript experience, you should pass in as the
2522   * generic the type of `pageFunction`:
2523   *
2524   * ```
2525   * const aHandle = await page.evaluate<() => number>(() => 2);
2526   * ```
2527   *
2528   * @example
2529   *
2530   * {@link ElementHandle} instances (including {@link JSHandle}s) can be passed
2531   * as arguments to the `pageFunction`:
2532   *
2533   * ```
2534   * const bodyHandle = await page.$('body');
2535   * const html = await page.evaluate(body => body.innerHTML, bodyHandle);
2536   * await bodyHandle.dispose();
2537   * ```
2538   *
2539   * @param pageFunction - a function that is run within the page
2540   * @param args - arguments to be passed to the pageFunction
2541   *
2542   * @returns the return value of `pageFunction`.
2543   */
2544  async evaluate<T extends EvaluateFn>(
2545    pageFunction: T,
2546    ...args: SerializableOrJSHandle[]
2547  ): Promise<UnwrapPromiseLike<EvaluateFnReturnType<T>>> {
2548    return this._frameManager.mainFrame().evaluate<T>(pageFunction, ...args);
2549  }
2550
2551  /**
2552   * Adds a function which would be invoked in one of the following scenarios:
2553   *
2554   * - whenever the page is navigated
2555   *
2556   * - whenever the child frame is attached or navigated. In this case, the
2557   * function is invoked in the context of the newly attached frame.
2558   *
2559   * The function is invoked after the document was created but before any of
2560   * its scripts were run. This is useful to amend the JavaScript environment,
2561   * e.g. to seed `Math.random`.
2562   * @param pageFunction - Function to be evaluated in browser context
2563   * @param args - Arguments to pass to `pageFunction`
2564   * @example
2565   * An example of overriding the navigator.languages property before the page loads:
2566   * ```js
2567   * // preload.js
2568   *
2569   * // overwrite the `languages` property to use a custom getter
2570   * Object.defineProperty(navigator, 'languages', {
2571   * get: function () {
2572   * return ['en-US', 'en', 'bn'];
2573   * },
2574   * });
2575   *
2576   * // In your puppeteer script, assuming the preload.js file is
2577   * in same folder of our script
2578   * const preloadFile = fs.readFileSync('./preload.js', 'utf8');
2579   * await page.evaluateOnNewDocument(preloadFile);
2580   * ```
2581   */
2582  async evaluateOnNewDocument(
2583    pageFunction: Function | string,
2584    ...args: unknown[]
2585  ): Promise<void> {
2586    const source = helper.evaluationString(pageFunction, ...args);
2587    await this._client.send('Page.addScriptToEvaluateOnNewDocument', {
2588      source,
2589    });
2590  }
2591
2592  /**
2593   * Toggles ignoring cache for each request based on the enabled state. By
2594   * default, caching is enabled.
2595   * @param enabled - sets the `enabled` state of cache
2596   */
2597  async setCacheEnabled(enabled = true): Promise<void> {
2598    await this._frameManager.networkManager().setCacheEnabled(enabled);
2599  }
2600
2601  /**
2602   * @remarks
2603   * Options object which might have the following properties:
2604   *
2605   * - `path` : The file path to save the image to. The screenshot type
2606   *   will be inferred from file extension. If `path` is a relative path, then
2607   *   it is resolved relative to
2608   *   {@link https://nodejs.org/api/process.html#process_process_cwd
2609   *   | current working directory}.
2610   *   If no path is provided, the image won't be saved to the disk.
2611   *
2612   * - `type` : Specify screenshot type, can be either `jpeg` or `png`.
2613   *   Defaults to 'png'.
2614   *
2615   * - `quality` : The quality of the image, between 0-100. Not
2616   *   applicable to `png` images.
2617   *
2618   * - `fullPage` : When true, takes a screenshot of the full
2619   *   scrollable page. Defaults to `false`
2620   *
2621   * - `clip` : An object which specifies clipping region of the page.
2622   *   Should have the following fields:<br/>
2623   *  - `x` : x-coordinate of top-left corner of clip area.<br/>
2624   *  - `y` :  y-coordinate of top-left corner of clip area.<br/>
2625   *  - `width` : width of clipping area.<br/>
2626   *  - `height` : height of clipping area.
2627   *
2628   * - `omitBackground` : Hides default white background and allows
2629   *   capturing screenshots with transparency. Defaults to `false`
2630   *
2631   * - `encoding` : The encoding of the image, can be either base64 or
2632   *   binary. Defaults to `binary`.
2633   *
2634   *
2635   * NOTE: Screenshots take at least 1/6 second on OS X. See
2636   * {@link https://crbug.com/741689} for discussion.
2637   * @returns Promise which resolves to buffer or a base64 string (depending on
2638   * the value of `encoding`) with captured screenshot.
2639   */
2640  async screenshot(options: ScreenshotOptions = {}): Promise<Buffer | string> {
2641    let screenshotType = null;
2642    // options.type takes precedence over inferring the type from options.path
2643    // because it may be a 0-length file with no extension created beforehand
2644    // (i.e. as a temp file).
2645    if (options.type) {
2646      const type = options.type;
2647      if (type !== 'png' && type !== 'jpeg' && type !== 'webp') {
2648        assertNever(type, 'Unknown options.type value: ' + type);
2649      }
2650      screenshotType = options.type;
2651    } else if (options.path) {
2652      const filePath = options.path;
2653      const extension = filePath
2654        .slice(filePath.lastIndexOf('.') + 1)
2655        .toLowerCase();
2656      if (extension === 'png') screenshotType = 'png';
2657      else if (extension === 'jpg' || extension === 'jpeg')
2658        screenshotType = 'jpeg';
2659      else if (extension === 'webp') screenshotType = 'webp';
2660      assert(
2661        screenshotType,
2662        `Unsupported screenshot type for extension \`.${extension}\``
2663      );
2664    }
2665
2666    if (!screenshotType) screenshotType = 'png';
2667
2668    if (options.quality) {
2669      assert(
2670        screenshotType === 'jpeg' || screenshotType === 'webp',
2671        'options.quality is unsupported for the ' +
2672          screenshotType +
2673          ' screenshots'
2674      );
2675      assert(
2676        typeof options.quality === 'number',
2677        'Expected options.quality to be a number but found ' +
2678          typeof options.quality
2679      );
2680      assert(
2681        Number.isInteger(options.quality),
2682        'Expected options.quality to be an integer'
2683      );
2684      assert(
2685        options.quality >= 0 && options.quality <= 100,
2686        'Expected options.quality to be between 0 and 100 (inclusive), got ' +
2687          options.quality
2688      );
2689    }
2690    assert(
2691      !options.clip || !options.fullPage,
2692      'options.clip and options.fullPage are exclusive'
2693    );
2694    if (options.clip) {
2695      assert(
2696        typeof options.clip.x === 'number',
2697        'Expected options.clip.x to be a number but found ' +
2698          typeof options.clip.x
2699      );
2700      assert(
2701        typeof options.clip.y === 'number',
2702        'Expected options.clip.y to be a number but found ' +
2703          typeof options.clip.y
2704      );
2705      assert(
2706        typeof options.clip.width === 'number',
2707        'Expected options.clip.width to be a number but found ' +
2708          typeof options.clip.width
2709      );
2710      assert(
2711        typeof options.clip.height === 'number',
2712        'Expected options.clip.height to be a number but found ' +
2713          typeof options.clip.height
2714      );
2715      assert(
2716        options.clip.width !== 0,
2717        'Expected options.clip.width not to be 0.'
2718      );
2719      assert(
2720        options.clip.height !== 0,
2721        'Expected options.clip.height not to be 0.'
2722      );
2723    }
2724    return this._screenshotTaskQueue.postTask(() =>
2725      this._screenshotTask(screenshotType, options)
2726    );
2727  }
2728
2729  private async _screenshotTask(
2730    format: Protocol.Page.CaptureScreenshotRequestFormat,
2731    options?: ScreenshotOptions
2732  ): Promise<Buffer | string> {
2733    await this._client.send('Target.activateTarget', {
2734      targetId: this._target._targetId,
2735    });
2736    let clip = options.clip ? processClip(options.clip) : undefined;
2737    let { captureBeyondViewport = true } = options;
2738    captureBeyondViewport =
2739      typeof captureBeyondViewport === 'boolean' ? captureBeyondViewport : true;
2740
2741    if (options.fullPage) {
2742      const metrics = await this._client.send('Page.getLayoutMetrics');
2743      // Fallback to `contentSize` in case of using Firefox.
2744      const { width, height } = metrics.cssContentSize || metrics.contentSize;
2745
2746      // Overwrite clip for full page.
2747      clip = { x: 0, y: 0, width, height, scale: 1 };
2748
2749      if (!captureBeyondViewport) {
2750        const {
2751          isMobile = false,
2752          deviceScaleFactor = 1,
2753          isLandscape = false,
2754        } = this._viewport || {};
2755        const screenOrientation: Protocol.Emulation.ScreenOrientation =
2756          isLandscape
2757            ? { angle: 90, type: 'landscapePrimary' }
2758            : { angle: 0, type: 'portraitPrimary' };
2759        await this._client.send('Emulation.setDeviceMetricsOverride', {
2760          mobile: isMobile,
2761          width,
2762          height,
2763          deviceScaleFactor,
2764          screenOrientation,
2765        });
2766      }
2767    }
2768    const shouldSetDefaultBackground =
2769      options.omitBackground && (format === 'png' || format === 'webp');
2770    if (shouldSetDefaultBackground) {
2771      await this._setTransparentBackgroundColor();
2772    }
2773
2774    const result = await this._client.send('Page.captureScreenshot', {
2775      format,
2776      quality: options.quality,
2777      clip,
2778      captureBeyondViewport,
2779    });
2780    if (shouldSetDefaultBackground) {
2781      await this._resetDefaultBackgroundColor();
2782    }
2783
2784    if (options.fullPage && this._viewport)
2785      await this.setViewport(this._viewport);
2786
2787    const buffer =
2788      options.encoding === 'base64'
2789        ? result.data
2790        : Buffer.from(result.data, 'base64');
2791
2792    if (options.path) {
2793      if (!isNode) {
2794        throw new Error(
2795          'Screenshots can only be written to a file path in a Node environment.'
2796        );
2797      }
2798      const fs = await helper.importFSModule();
2799      await fs.promises.writeFile(options.path, buffer);
2800    }
2801    return buffer;
2802
2803    function processClip(
2804      clip: ScreenshotClip
2805    ): ScreenshotClip & { scale: number } {
2806      const x = Math.round(clip.x);
2807      const y = Math.round(clip.y);
2808      const width = Math.round(clip.width + clip.x - x);
2809      const height = Math.round(clip.height + clip.y - y);
2810      return { x, y, width, height, scale: 1 };
2811    }
2812  }
2813
2814  /**
2815   * Generatees a PDF of the page with the `print` CSS media type.
2816   * @remarks
2817   *
2818   * NOTE: PDF generation is only supported in Chrome headless mode.
2819   *
2820   * To generate a PDF with the `screen` media type, call
2821   * {@link Page.emulateMediaType | `page.emulateMediaType('screen')`} before
2822   * calling `page.pdf()`.
2823   *
2824   * By default, `page.pdf()` generates a pdf with modified colors for printing.
2825   * Use the
2826   * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-print-color-adjust | `-webkit-print-color-adjust`}
2827   * property to force rendering of exact colors.
2828   *
2829   *
2830   * @param options - options for generating the PDF.
2831   */
2832  async createPDFStream(options: PDFOptions = {}): Promise<Readable> {
2833    const {
2834      scale = 1,
2835      displayHeaderFooter = false,
2836      headerTemplate = '',
2837      footerTemplate = '',
2838      printBackground = false,
2839      landscape = false,
2840      pageRanges = '',
2841      preferCSSPageSize = false,
2842      margin = {},
2843      omitBackground = false,
2844      timeout = 30000,
2845    } = options;
2846
2847    let paperWidth = 8.5;
2848    let paperHeight = 11;
2849    if (options.format) {
2850      const format = paperFormats[options.format.toLowerCase()];
2851      assert(format, 'Unknown paper format: ' + options.format);
2852      paperWidth = format.width;
2853      paperHeight = format.height;
2854    } else {
2855      paperWidth = convertPrintParameterToInches(options.width) || paperWidth;
2856      paperHeight =
2857        convertPrintParameterToInches(options.height) || paperHeight;
2858    }
2859
2860    const marginTop = convertPrintParameterToInches(margin.top) || 0;
2861    const marginLeft = convertPrintParameterToInches(margin.left) || 0;
2862    const marginBottom = convertPrintParameterToInches(margin.bottom) || 0;
2863    const marginRight = convertPrintParameterToInches(margin.right) || 0;
2864
2865    if (omitBackground) {
2866      await this._setTransparentBackgroundColor();
2867    }
2868
2869    const printCommandPromise = this._client.send('Page.printToPDF', {
2870      transferMode: 'ReturnAsStream',
2871      landscape,
2872      displayHeaderFooter,
2873      headerTemplate,
2874      footerTemplate,
2875      printBackground,
2876      scale,
2877      paperWidth,
2878      paperHeight,
2879      marginTop,
2880      marginBottom,
2881      marginLeft,
2882      marginRight,
2883      pageRanges,
2884      preferCSSPageSize,
2885    });
2886
2887    const result = await helper.waitWithTimeout(
2888      printCommandPromise,
2889      'Page.printToPDF',
2890      timeout
2891    );
2892
2893    if (omitBackground) {
2894      await this._resetDefaultBackgroundColor();
2895    }
2896
2897    return helper.getReadableFromProtocolStream(this._client, result.stream);
2898  }
2899
2900  /**
2901   * @param options -
2902   * @returns
2903   */
2904  async pdf(options: PDFOptions = {}): Promise<Buffer> {
2905    const { path = undefined } = options;
2906    const readable = await this.createPDFStream(options);
2907    return await helper.getReadableAsBuffer(readable, path);
2908  }
2909
2910  /**
2911   * @returns The page's title
2912   * @remarks
2913   * Shortcut for {@link Frame.title | page.mainFrame().title()}.
2914   */
2915  async title(): Promise<string> {
2916    return this.mainFrame().title();
2917  }
2918
2919  async close(
2920    options: { runBeforeUnload?: boolean } = { runBeforeUnload: undefined }
2921  ): Promise<void> {
2922    assert(
2923      !!this._client._connection,
2924      'Protocol error: Connection closed. Most likely the page has been closed.'
2925    );
2926    const runBeforeUnload = !!options.runBeforeUnload;
2927    if (runBeforeUnload) {
2928      await this._client.send('Page.close');
2929    } else {
2930      await this._client._connection.send('Target.closeTarget', {
2931        targetId: this._target._targetId,
2932      });
2933      await this._target._isClosedPromise;
2934    }
2935  }
2936
2937  /**
2938   * Indicates that the page has been closed.
2939   * @returns
2940   */
2941  isClosed(): boolean {
2942    return this._closed;
2943  }
2944
2945  get mouse(): Mouse {
2946    return this._mouse;
2947  }
2948
2949  /**
2950   * This method fetches an element with `selector`, scrolls it into view if
2951   * needed, and then uses {@link Page.mouse} to click in the center of the
2952   * element. If there's no element matching `selector`, the method throws an
2953   * error.
2954   * @remarks Bear in mind that if `click()` triggers a navigation event and
2955   * there's a separate `page.waitForNavigation()` promise to be resolved, you
2956   * may end up with a race condition that yields unexpected results. The
2957   * correct pattern for click and wait for navigation is the following:
2958   * ```js
2959   * const [response] = await Promise.all([
2960   * page.waitForNavigation(waitOptions),
2961   * page.click(selector, clickOptions),
2962   * ]);
2963   * ```
2964   * Shortcut for {@link Frame.click | page.mainFrame().click(selector[, options]) }.
2965   * @param selector - A `selector` to search for element to click. If there are
2966   * multiple elements satisfying the `selector`, the first will be clicked
2967   * @param options - `Object`
2968   * @returns Promise which resolves when the element matching `selector` is
2969   * successfully clicked. The Promise will be rejected if there is no element
2970   * matching `selector`.
2971   */
2972  click(
2973    selector: string,
2974    options: {
2975      delay?: number;
2976      button?: MouseButton;
2977      clickCount?: number;
2978    } = {}
2979  ): Promise<void> {
2980    return this.mainFrame().click(selector, options);
2981  }
2982
2983  /**
2984   * This method fetches an element with `selector` and focuses it. If there's no
2985   * element matching `selector`, the method throws an error.
2986   * @param selector - A
2987   * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | selector }
2988   * of an element to focus. If there are multiple elements satisfying the
2989   * selector, the first will be focused.
2990   * @returns  Promise which resolves when the element matching selector is
2991   * successfully focused. The promise will be rejected if there is no element
2992   * matching selector.
2993   * @remarks
2994   * Shortcut for {@link Frame.focus | page.mainFrame().focus(selector)}.
2995   */
2996  focus(selector: string): Promise<void> {
2997    return this.mainFrame().focus(selector);
2998  }
2999
3000  /**
3001   * This method fetches an element with `selector`, scrolls it into view if
3002   * needed, and then uses {@link Page.mouse} to hover over the center of the element.
3003   * If there's no element matching `selector`, the method throws an error.
3004   * @param selector - A
3005   * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | selector}
3006   * to search for element to hover. If there are multiple elements satisfying
3007   * the selector, the first will be hovered.
3008   * @returns Promise which resolves when the element matching `selector` is
3009   * successfully hovered. Promise gets rejected if there's no element matching
3010   * `selector`.
3011   * @remarks
3012   * Shortcut for {@link Page.hover | page.mainFrame().hover(selector)}.
3013   */
3014  hover(selector: string): Promise<void> {
3015    return this.mainFrame().hover(selector);
3016  }
3017
3018  /**
3019   * Triggers a `change` and `input` event once all the provided options have been
3020   * selected. If there's no `<select>` element matching `selector`, the method
3021   * throws an error.
3022   *
3023   * @example
3024   * ```js
3025   * page.select('select#colors', 'blue'); // single selection
3026   * page.select('select#colors', 'red', 'green', 'blue'); // multiple selections
3027   * ```
3028   * @param selector - A
3029   * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | Selector}
3030   * to query the page for
3031   * @param values - Values of options to select. If the `<select>` has the
3032   * `multiple` attribute, all values are considered, otherwise only the first one
3033   * is taken into account.
3034   * @returns
3035   *
3036   * @remarks
3037   * Shortcut for {@link Frame.select | page.mainFrame().select()}
3038   */
3039  select(selector: string, ...values: string[]): Promise<string[]> {
3040    return this.mainFrame().select(selector, ...values);
3041  }
3042
3043  /**
3044   * This method fetches an element with `selector`, scrolls it into view if
3045   * needed, and then uses {@link Page.touchscreen} to tap in the center of the element.
3046   * If there's no element matching `selector`, the method throws an error.
3047   * @param selector - A
3048   * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | Selector}
3049   * to search for element to tap. If there are multiple elements satisfying the
3050   * selector, the first will be tapped.
3051   * @returns
3052   * @remarks
3053   * Shortcut for {@link Frame.tap | page.mainFrame().tap(selector)}.
3054   */
3055  tap(selector: string): Promise<void> {
3056    return this.mainFrame().tap(selector);
3057  }
3058
3059  /**
3060   * Sends a `keydown`, `keypress/input`, and `keyup` event for each character
3061   * in the text.
3062   *
3063   * To press a special key, like `Control` or `ArrowDown`, use {@link Keyboard.press}.
3064   * @example
3065   * ```
3066   * await page.type('#mytextarea', 'Hello');
3067   * // Types instantly
3068   * await page.type('#mytextarea', 'World', { delay: 100 });
3069   * // Types slower, like a user
3070   * ```
3071   * @param selector - A
3072   * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | selector}
3073   * of an element to type into. If there are multiple elements satisfying the
3074   * selector, the first will be used.
3075   * @param text - A text to type into a focused element.
3076   * @param options - have property `delay` which is the Time to wait between
3077   * key presses in milliseconds. Defaults to `0`.
3078   * @returns
3079   * @remarks
3080   */
3081  type(
3082    selector: string,
3083    text: string,
3084    options?: { delay: number }
3085  ): Promise<void> {
3086    return this.mainFrame().type(selector, text, options);
3087  }
3088
3089  /**
3090   * @remarks
3091   *
3092   * This method behaves differently depending on the first parameter. If it's a
3093   * `string`, it will be treated as a `selector` or `xpath` (if the string
3094   * starts with `//`). This method then is a shortcut for
3095   * {@link Page.waitForSelector} or {@link Page.waitForXPath}.
3096   *
3097   * If the first argument is a function this method is a shortcut for
3098   * {@link Page.waitForFunction}.
3099   *
3100   * If the first argument is a `number`, it's treated as a timeout in
3101   * milliseconds and the method returns a promise which resolves after the
3102   * timeout.
3103   *
3104   * @param selectorOrFunctionOrTimeout - a selector, predicate or timeout to
3105   * wait for.
3106   * @param options - optional waiting parameters.
3107   * @param args - arguments to pass to `pageFunction`.
3108   *
3109   * @deprecated Don't use this method directly. Instead use the more explicit
3110   * methods available: {@link Page.waitForSelector},
3111   * {@link Page.waitForXPath}, {@link Page.waitForFunction} or
3112   * {@link Page.waitForTimeout}.
3113   */
3114  waitFor(
3115    selectorOrFunctionOrTimeout: string | number | Function,
3116    options: {
3117      visible?: boolean;
3118      hidden?: boolean;
3119      timeout?: number;
3120      polling?: string | number;
3121    } = {},
3122    ...args: SerializableOrJSHandle[]
3123  ): Promise<JSHandle> {
3124    return this.mainFrame().waitFor(
3125      selectorOrFunctionOrTimeout,
3126      options,
3127      ...args
3128    );
3129  }
3130
3131  /**
3132   * Causes your script to wait for the given number of milliseconds.
3133   *
3134   * @remarks
3135   *
3136   * It's generally recommended to not wait for a number of seconds, but instead
3137   * use {@link Page.waitForSelector}, {@link Page.waitForXPath} or
3138   * {@link Page.waitForFunction} to wait for exactly the conditions you want.
3139   *
3140   * @example
3141   *
3142   * Wait for 1 second:
3143   *
3144   * ```
3145   * await page.waitForTimeout(1000);
3146   * ```
3147   *
3148   * @param milliseconds - the number of milliseconds to wait.
3149   */
3150  waitForTimeout(milliseconds: number): Promise<void> {
3151    return this.mainFrame().waitForTimeout(milliseconds);
3152  }
3153
3154  /**
3155   * Wait for the `selector` to appear in page. If at the moment of calling the
3156   * method the `selector` already exists, the method will return immediately. If
3157   * the `selector` doesn't appear after the `timeout` milliseconds of waiting, the
3158   * function will throw.
3159   *
3160   * This method works across navigations:
3161   * ```js
3162   * const puppeteer = require('puppeteer');
3163   * (async () => {
3164   * const browser = await puppeteer.launch();
3165   * const page = await browser.newPage();
3166   * let currentURL;
3167   * page
3168   * .waitForSelector('img')
3169   * .then(() => console.log('First URL with image: ' + currentURL));
3170   * for (currentURL of [
3171   * 'https://example.com',
3172   * 'https://google.com',
3173   * 'https://bbc.com',
3174   * ]) {
3175   * await page.goto(currentURL);
3176   * }
3177   * await browser.close();
3178   * })();
3179   * ```
3180   * @param selector - A
3181   * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | selector}
3182   * of an element to wait for
3183   * @param options - Optional waiting parameters
3184   * @returns Promise which resolves when element specified by selector string
3185   * is added to DOM. Resolves to `null` if waiting for hidden: `true` and
3186   * selector is not found in DOM.
3187   * @remarks
3188   * The optional Parameter in Arguments `options` are :
3189   *
3190   * - `Visible`: A boolean wait for element to be present in DOM and to be
3191   * visible, i.e. to not have `display: none` or `visibility: hidden` CSS
3192   * properties. Defaults to `false`.
3193   *
3194   * - `hidden`: ait for element to not be found in the DOM or to be hidden,
3195   * i.e. have `display: none` or `visibility: hidden` CSS properties. Defaults to
3196   * `false`.
3197   *
3198   * - `timeout`: maximum time to wait for in milliseconds. Defaults to `30000`
3199   * (30 seconds). Pass `0` to disable timeout. The default value can be changed
3200   * by using the {@link Page.setDefaultTimeout} method.
3201   */
3202  waitForSelector(
3203    selector: string,
3204    options: {
3205      visible?: boolean;
3206      hidden?: boolean;
3207      timeout?: number;
3208    } = {}
3209  ): Promise<ElementHandle | null> {
3210    return this.mainFrame().waitForSelector(selector, options);
3211  }
3212
3213  /**
3214   * Wait for the `xpath` to appear in page. If at the moment of calling the
3215   * method the `xpath` already exists, the method will return immediately. If
3216   * the `xpath` doesn't appear after the `timeout` milliseconds of waiting, the
3217   * function will throw.
3218   *
3219   * This method works across navigation
3220   * ```js
3221   * const puppeteer = require('puppeteer');
3222   * (async () => {
3223   * const browser = await puppeteer.launch();
3224   * const page = await browser.newPage();
3225   * let currentURL;
3226   * page
3227   * .waitForXPath('//img')
3228   * .then(() => console.log('First URL with image: ' + currentURL));
3229   * for (currentURL of [
3230   * 'https://example.com',
3231   * 'https://google.com',
3232   * 'https://bbc.com',
3233   * ]) {
3234   * await page.goto(currentURL);
3235   * }
3236   * await browser.close();
3237   * })();
3238   * ```
3239   * @param xpath - A
3240   * {@link https://developer.mozilla.org/en-US/docs/Web/XPath | xpath} of an
3241   * element to wait for
3242   * @param options - Optional waiting parameters
3243   * @returns Promise which resolves when element specified by xpath string is
3244   * added to DOM. Resolves to `null` if waiting for `hidden: true` and xpath is
3245   * not found in DOM.
3246   * @remarks
3247   * The optional Argument `options` have properties:
3248   *
3249   * - `visible`: A boolean to wait for element to be present in DOM and to be
3250   * visible, i.e. to not have `display: none` or `visibility: hidden` CSS
3251   * properties. Defaults to `false`.
3252   *
3253   * - `hidden`: A boolean wait for element to not be found in the DOM or to be
3254   * hidden, i.e. have `display: none` or `visibility: hidden` CSS properties.
3255   * Defaults to `false`.
3256   *
3257   * - `timeout`: A number which is maximum time to wait for in milliseconds.
3258   * Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default
3259   * value can be changed by using the {@link Page.setDefaultTimeout} method.
3260   */
3261  waitForXPath(
3262    xpath: string,
3263    options: {
3264      visible?: boolean;
3265      hidden?: boolean;
3266      timeout?: number;
3267    } = {}
3268  ): Promise<ElementHandle | null> {
3269    return this.mainFrame().waitForXPath(xpath, options);
3270  }
3271
3272  /**
3273   * The `waitForFunction` can be used to observe viewport size change:
3274   *
3275   * ```
3276   * const puppeteer = require('puppeteer');
3277   * (async () => {
3278   * const browser = await puppeteer.launch();
3279   * const page = await browser.newPage();
3280   * const watchDog = page.waitForFunction('window.innerWidth < 100');
3281   * await page.setViewport({ width: 50, height: 50 });
3282   * await watchDog;
3283   * await browser.close();
3284   * })();
3285   * ```
3286   * To pass arguments from node.js to the predicate of `page.waitForFunction` function:
3287   * ```
3288   * const selector = '.foo';
3289   * await page.waitForFunction(
3290   * (selector) => !!document.querySelector(selector),
3291   * {},
3292   * selector
3293   * );
3294   * ```
3295   * The predicate of `page.waitForFunction` can be asynchronous too:
3296   * ```
3297   * const username = 'github-username';
3298   * await page.waitForFunction(
3299   * async (username) => {
3300   * const githubResponse = await fetch(
3301   *  `https://api.github.com/users/${username}`
3302   * );
3303   * const githubUser = await githubResponse.json();
3304   * // show the avatar
3305   * const img = document.createElement('img');
3306   * img.src = githubUser.avatar_url;
3307   * // wait 3 seconds
3308   * await new Promise((resolve, reject) => setTimeout(resolve, 3000));
3309   * img.remove();
3310   * },
3311   * {},
3312   * username
3313   * );
3314   * ```
3315   * @param pageFunction - Function to be evaluated in browser context
3316   * @param options - Optional waiting parameters
3317   * @param args -  Arguments to pass to `pageFunction`
3318   * @returns Promise which resolves when the `pageFunction` returns a truthy
3319   * value. It resolves to a JSHandle of the truthy value.
3320   *
3321   * The optional waiting parameter can be:
3322   *
3323   * - `Polling`: An interval at which the `pageFunction` is executed, defaults to
3324   *   `raf`. If `polling` is a number, then it is treated as an interval in
3325   *   milliseconds at which the function would be executed. If polling is a
3326   *   string, then it can be one of the following values:<br/>
3327   *    - `raf`: to constantly execute `pageFunction` in `requestAnimationFrame`
3328   *      callback. This is the tightest polling mode which is suitable to
3329   *      observe styling changes.<br/>
3330   *    - `mutation`: to execute pageFunction on every DOM mutation.
3331   *
3332   * - `timeout`: maximum time to wait for in milliseconds. Defaults to `30000`
3333   * (30 seconds). Pass `0` to disable timeout. The default value can be changed
3334   * by using the
3335   * {@link Page.setDefaultTimeout | page.setDefaultTimeout(timeout)} method.
3336   *
3337   */
3338  waitForFunction(
3339    pageFunction: Function | string,
3340    options: {
3341      timeout?: number;
3342      polling?: string | number;
3343    } = {},
3344    ...args: SerializableOrJSHandle[]
3345  ): Promise<JSHandle> {
3346    return this.mainFrame().waitForFunction(pageFunction, options, ...args);
3347  }
3348}
3349
3350const supportedMetrics = new Set<string>([
3351  'Timestamp',
3352  'Documents',
3353  'Frames',
3354  'JSEventListeners',
3355  'Nodes',
3356  'LayoutCount',
3357  'RecalcStyleCount',
3358  'LayoutDuration',
3359  'RecalcStyleDuration',
3360  'ScriptDuration',
3361  'TaskDuration',
3362  'JSHeapUsedSize',
3363  'JSHeapTotalSize',
3364]);
3365
3366const unitToPixels = {
3367  px: 1,
3368  in: 96,
3369  cm: 37.8,
3370  mm: 3.78,
3371};
3372
3373function convertPrintParameterToInches(
3374  parameter?: string | number
3375): number | undefined {
3376  if (typeof parameter === 'undefined') return undefined;
3377  let pixels;
3378  if (helper.isNumber(parameter)) {
3379    // Treat numbers as pixel values to be aligned with phantom's paperSize.
3380    pixels = /** @type {number} */ parameter;
3381  } else if (helper.isString(parameter)) {
3382    const text = /** @type {string} */ parameter;
3383    let unit = text.substring(text.length - 2).toLowerCase();
3384    let valueText = '';
3385    if (unitToPixels.hasOwnProperty(unit)) {
3386      valueText = text.substring(0, text.length - 2);
3387    } else {
3388      // In case of unknown unit try to parse the whole parameter as number of pixels.
3389      // This is consistent with phantom's paperSize behavior.
3390      unit = 'px';
3391      valueText = text;
3392    }
3393    const value = Number(valueText);
3394    assert(!isNaN(value), 'Failed to parse parameter value: ' + text);
3395    pixels = value * unitToPixels[unit];
3396  } else {
3397    throw new Error(
3398      'page.pdf() Cannot handle parameter type: ' + typeof parameter
3399    );
3400  }
3401  return pixels / 96;
3402}
3403