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