2017-05-11 07:06:41 +00:00
/ * *
* Copyright 2017 Google Inc . All rights reserved .
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
2020-05-05 12:53:22 +00:00
import * as fs from 'fs' ;
2020-06-15 10:52:19 +00:00
import { EventEmitter } from './EventEmitter' ;
2020-05-05 12:53:22 +00:00
import * as mime from 'mime' ;
2020-05-07 10:54:55 +00:00
import { Events } from './Events' ;
import { Connection , CDPSession } from './Connection' ;
import { Dialog } from './Dialog' ;
import { EmulationManager } from './EmulationManager' ;
import { Frame , FrameManager } from './FrameManager' ;
import { Keyboard , Mouse , Touchscreen , MouseButtonInput } from './Input' ;
import { Tracing } from './Tracing' ;
2020-06-15 15:34:50 +00:00
import { assert } from './assert' ;
import { helper , debugError } from './helper' ;
2020-05-07 10:54:55 +00:00
import { Coverage } from './Coverage' ;
2020-05-29 11:57:54 +00:00
import { WebWorker } from './WebWorker' ;
2020-05-07 10:54:55 +00:00
import { Browser , BrowserContext } from './Browser' ;
import { Target } from './Target' ;
import { createJSHandle , JSHandle , ElementHandle } from './JSHandle' ;
2020-06-04 10:47:13 +00:00
import { Viewport } from './PuppeteerViewport' ;
2020-05-13 13:57:21 +00:00
import { Credentials } from './NetworkManager' ;
2020-05-29 08:38:40 +00:00
import { HTTPRequest } from './HTTPRequest' ;
2020-05-29 10:49:30 +00:00
import { HTTPResponse } from './HTTPResponse' ;
2020-05-07 10:54:55 +00:00
import { Accessibility } from './Accessibility' ;
import { TimeoutSettings } from './TimeoutSettings' ;
2020-05-13 10:30:29 +00:00
import { FileChooser } from './FileChooser' ;
import { ConsoleMessage } from './ConsoleMessage' ;
2020-05-07 10:54:55 +00:00
import { PuppeteerLifeCycleEvent } from './LifecycleWatcher' ;
2020-06-18 14:53:23 +00:00
import Protocol from '../protocol' ;
2020-05-05 12:53:22 +00:00
2017-10-11 07:55:48 +00:00
const writeFileAsync = helper . promisify ( fs . writeFile ) ;
2020-05-05 12:53:22 +00:00
interface Metrics {
2020-05-07 10:54:55 +00:00
Timestamp? : number ;
Documents? : number ;
Frames? : number ;
JSEventListeners? : number ;
Nodes? : number ;
LayoutCount? : number ;
RecalcStyleCount? : number ;
LayoutDuration? : number ;
RecalcStyleDuration? : number ;
ScriptDuration? : number ;
TaskDuration? : number ;
JSHeapUsedSize? : number ;
JSHeapTotalSize? : number ;
2020-05-05 12:53:22 +00:00
}
interface WaitForOptions {
timeout? : number ;
2020-05-07 10:54:55 +00:00
waitUntil? : PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent [ ] ;
2020-05-05 12:53:22 +00:00
}
interface MediaFeature {
name : string ;
value : string ;
}
interface ScreenshotClip {
x : number ;
y : number ;
width : number ;
height : number ;
}
interface ScreenshotOptions {
type ? : 'png' | 'jpeg' ;
path? : string ;
fullPage? : boolean ;
clip? : ScreenshotClip ;
quality? : number ;
omitBackground? : boolean ;
encoding? : string ;
}
interface PDFMargin {
2020-05-07 10:54:55 +00:00
top? : string | number ;
bottom? : string | number ;
left? : string | number ;
right? : string | number ;
2020-05-05 12:53:22 +00:00
}
interface PDFOptions {
scale? : number ;
displayHeaderFooter? : boolean ;
headerTemplate? : string ;
footerTemplate? : string ;
printBackground? : boolean ;
landscape? : boolean ;
pageRanges? : string ;
format? : string ;
2020-05-07 10:54:55 +00:00
width? : string | number ;
height? : string | number ;
2020-05-05 12:53:22 +00:00
preferCSSPageSize? : boolean ;
margin? : PDFMargin ;
path? : string ;
}
interface PaperFormat {
width : number ;
height : number ;
}
const paperFormats : Record < string , PaperFormat > = {
2020-05-07 10:54:55 +00:00
letter : { width : 8.5 , height : 11 } ,
legal : { width : 8.5 , height : 14 } ,
tabloid : { width : 11 , height : 17 } ,
ledger : { width : 17 , height : 11 } ,
a0 : { width : 33.1 , height : 46.8 } ,
a1 : { width : 23.4 , height : 33.1 } ,
a2 : { width : 16.54 , height : 23.4 } ,
a3 : { width : 11.7 , height : 16.54 } ,
a4 : { width : 8.27 , height : 11.7 } ,
a5 : { width : 5.83 , height : 8.27 } ,
a6 : { width : 4.13 , height : 5.83 } ,
2020-05-05 12:53:22 +00:00
} as const ;
2020-05-26 15:14:20 +00:00
enum VisionDeficiency {
none = 'none' ,
achromatopsia = 'achromatopsia' ,
blurredVision = 'blurredVision' ,
deuteranopia = 'deuteranopia' ,
protanopia = 'protanopia' ,
tritanopia = 'tritanopia' ,
}
2020-06-12 10:10:12 +00:00
/ * *
* All the events that a page instance may emit .
* /
export const enum PageEmittedEvents {
/ * *
* Emitted when a dedicated { @link https : //developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API | WebWorker} is spawned by the page.
* @eventProperty
* /
WorkerCreated = 'workercreated' ,
}
2020-05-07 13:49:42 +00:00
class ScreenshotTaskQueue {
_chain : Promise < Buffer | string | void > ;
constructor ( ) {
this . _chain = Promise . resolve < Buffer | string | void > ( undefined ) ;
}
public postTask (
task : ( ) = > Promise < Buffer | string >
) : Promise < Buffer | string | void > {
const result = this . _chain . then ( task ) ;
this . _chain = result . catch ( ( ) = > { } ) ;
return result ;
}
}
2020-06-12 10:10:12 +00:00
/ * *
* Page provides methods to interact with a single tab or [ extension background page ] ( https : //developer.chrome.com/extensions/background_pages) in Chromium. One [Browser] instance might have multiple [Page] instances.
*
* @remarks
*
* @example
* This example creates a page , navigates it to a URL , and then * saves a screenshot :
* ` ` ` js
* const puppeteer = require ( 'puppeteer' ) ;
*
* ( async ( ) = > {
* const browser = await puppeteer . launch ( ) ;
* const page = await browser . newPage ( ) ;
* await page . goto ( 'https://example.com' ) ;
* await page . screenshot ( { path : 'screenshot.png' } ) ;
* await browser . close ( ) ;
* } ) ( ) ;
* ` ` `
*
2020-06-15 10:52:19 +00:00
* The Page class extends from Puppeteer ' s { @link EventEmitter } class and will emit various events which are documented in the { @link PageEmittedEvents } enum .
2020-06-12 10:10:12 +00:00
*
* @example
* This example logs a message for a single page ` load ` event :
* ` ` ` js
* page . once ( 'load' , ( ) = > console . log ( 'Page loaded!' ) ) ;
* ` ` `
*
* To unsubscribe from events use the ` off ` method :
*
* ` ` ` js
* function logRequest ( interceptedRequest ) {
* console . log ( 'A request was made:' , interceptedRequest . url ( ) ) ;
* }
* page . on ( 'request' , logRequest ) ;
* // Sometime later...
* page . off ( 'request' , logRequest ) ;
* ` ` `
* @public
* /
2020-05-05 12:53:22 +00:00
export class Page extends EventEmitter {
2020-06-12 10:10:12 +00:00
/ * *
* @internal
* /
2020-05-07 10:54:55 +00:00
static async create (
client : CDPSession ,
target : Target ,
ignoreHTTPSErrors : boolean ,
2020-05-07 13:49:42 +00:00
defaultViewport : Viewport | null
2020-05-07 10:54:55 +00:00
) : Promise < Page > {
2020-05-07 13:49:42 +00:00
const page = new Page ( client , target , ignoreHTTPSErrors ) ;
2019-07-23 04:30:49 +00:00
await page . _initialize ( ) ;
2020-05-07 10:54:55 +00:00
if ( defaultViewport ) await page . setViewport ( defaultViewport ) ;
2017-06-21 20:51:06 +00:00
return page ;
}
2020-06-10 15:15:02 +00:00
private _closed = false ;
private _client : CDPSession ;
private _target : Target ;
private _keyboard : Keyboard ;
private _mouse : Mouse ;
private _timeoutSettings = new TimeoutSettings ( ) ;
private _touchscreen : Touchscreen ;
private _accessibility : Accessibility ;
private _frameManager : FrameManager ;
private _emulationManager : EmulationManager ;
private _tracing : Tracing ;
private _pageBindings = new Map < string , Function > ( ) ;
private _coverage : Coverage ;
private _javascriptEnabled = true ;
private _viewport : Viewport | null ;
private _screenshotTaskQueue : ScreenshotTaskQueue ;
private _workers = new Map < string , WebWorker > ( ) ;
2020-05-05 12:53:22 +00:00
// TODO: improve this typedef - it's a function that takes a file chooser or something?
2020-06-10 15:15:02 +00:00
private _fileChooserInterceptors = new Set < Function > ( ) ;
2020-05-05 12:53:22 +00:00
2020-06-10 15:15:02 +00:00
private _disconnectPromise? : Promise < Error > ;
2020-05-05 12:53:22 +00:00
2020-06-12 10:10:12 +00:00
/ * *
* @internal
* /
2020-05-07 13:49:42 +00:00
constructor ( client : CDPSession , target : Target , ignoreHTTPSErrors : boolean ) {
2017-06-21 20:51:06 +00:00
super ( ) ;
this . _client = client ;
2018-01-11 03:33:22 +00:00
this . _target = target ;
2017-07-30 02:12:17 +00:00
this . _keyboard = new Keyboard ( client ) ;
this . _mouse = new Mouse ( client , this . _keyboard ) ;
2017-09-02 02:03:51 +00:00
this . _touchscreen = new Touchscreen ( client , this . _keyboard ) ;
2018-11-02 01:54:51 +00:00
this . _accessibility = new Accessibility ( client ) ;
2020-05-07 10:54:55 +00:00
this . _frameManager = new FrameManager (
client ,
this ,
ignoreHTTPSErrors ,
this . _timeoutSettings
) ;
2017-07-30 02:12:17 +00:00
this . _emulationManager = new EmulationManager ( client ) ;
2017-08-02 17:45:11 +00:00
this . _tracing = new Tracing ( client ) ;
2018-01-03 03:53:53 +00:00
this . _coverage = new Coverage ( client ) ;
2020-05-07 13:49:42 +00:00
this . _screenshotTaskQueue = new ScreenshotTaskQueue ( ) ;
2020-05-05 12:53:22 +00:00
this . _viewport = null ;
2017-06-21 20:51:06 +00:00
2020-05-07 10:54:55 +00:00
client . on ( 'Target.attachedToTarget' , ( event ) = > {
2018-05-21 21:31:11 +00:00
if ( event . targetInfo . type !== 'worker' ) {
// If we don't detach from service workers, they will never die.
2020-05-07 10:54:55 +00:00
client
. send ( 'Target.detachFromTarget' , {
sessionId : event.sessionId ,
} )
. catch ( debugError ) ;
2018-05-21 21:31:11 +00:00
return ;
}
2019-01-22 23:10:11 +00:00
const session = Connection . fromSession ( client ) . session ( event . sessionId ) ;
2020-05-29 11:57:54 +00:00
const worker = new WebWorker (
2020-05-07 10:54:55 +00:00
session ,
event . targetInfo . url ,
this . _addConsoleMessage . bind ( this ) ,
this . _handleException . bind ( this )
) ;
2018-05-21 21:31:11 +00:00
this . _workers . set ( event . sessionId , worker ) ;
2020-06-12 10:10:12 +00:00
this . emit ( PageEmittedEvents . WorkerCreated , worker ) ;
2018-05-21 21:31:11 +00:00
} ) ;
2020-05-07 10:54:55 +00:00
client . on ( 'Target.detachedFromTarget' , ( event ) = > {
2018-05-21 21:31:11 +00:00
const worker = this . _workers . get ( event . sessionId ) ;
2020-05-07 10:54:55 +00:00
if ( ! worker ) return ;
2019-01-15 03:57:05 +00:00
this . emit ( Events . Page . WorkerDestroyed , worker ) ;
2018-05-21 21:31:11 +00:00
this . _workers . delete ( event . sessionId ) ;
} ) ;
2020-05-07 10:54:55 +00:00
this . _frameManager . on ( Events . FrameManager . FrameAttached , ( event ) = >
this . emit ( Events . Page . FrameAttached , event )
) ;
this . _frameManager . on ( Events . FrameManager . FrameDetached , ( event ) = >
this . emit ( Events . Page . FrameDetached , event )
) ;
this . _frameManager . on ( Events . FrameManager . FrameNavigated , ( event ) = >
this . emit ( Events . Page . FrameNavigated , event )
) ;
2017-06-21 20:51:06 +00:00
2019-04-10 04:42:42 +00:00
const networkManager = this . _frameManager . networkManager ( ) ;
2020-05-07 10:54:55 +00:00
networkManager . on ( Events . NetworkManager . Request , ( event ) = >
this . emit ( Events . Page . Request , event )
) ;
networkManager . on ( Events . NetworkManager . Response , ( event ) = >
this . emit ( Events . Page . Response , event )
) ;
networkManager . on ( Events . NetworkManager . RequestFailed , ( event ) = >
this . emit ( Events . Page . RequestFailed , event )
) ;
networkManager . on ( Events . NetworkManager . RequestFinished , ( event ) = >
this . emit ( Events . Page . RequestFinished , event )
) ;
2019-07-23 04:30:49 +00:00
this . _fileChooserInterceptors = new Set ( ) ;
2017-06-29 06:09:28 +00:00
2020-05-07 10:54:55 +00:00
client . on ( 'Page.domContentEventFired' , ( ) = >
this . emit ( Events . Page . DOMContentLoaded )
) ;
2020-05-05 12:53:22 +00:00
client . on ( 'Page.loadEventFired' , ( ) = > this . emit ( Events . Page . Load ) ) ;
2020-05-07 10:54:55 +00:00
client . on ( 'Runtime.consoleAPICalled' , ( event ) = > this . _onConsoleAPI ( event ) ) ;
client . on ( 'Runtime.bindingCalled' , ( event ) = > this . _onBindingCalled ( event ) ) ;
client . on ( 'Page.javascriptDialogOpening' , ( event ) = > this . _onDialog ( event ) ) ;
client . on ( 'Runtime.exceptionThrown' , ( exception ) = >
this . _handleException ( exception . exceptionDetails )
) ;
2020-05-05 12:53:22 +00:00
client . on ( 'Inspector.targetCrashed' , ( ) = > this . _onTargetCrashed ( ) ) ;
2020-05-07 10:54:55 +00:00
client . on ( 'Performance.metrics' , ( event ) = > this . _emitMetrics ( event ) ) ;
client . on ( 'Log.entryAdded' , ( event ) = > this . _onLogEntryAdded ( event ) ) ;
client . on ( 'Page.fileChooserOpened' , ( event ) = > this . _onFileChooser ( event ) ) ;
2018-05-25 23:53:57 +00:00
this . _target . _isClosedPromise . then ( ( ) = > {
2019-01-15 03:57:05 +00:00
this . emit ( Events . Page . Close ) ;
2018-05-25 23:53:57 +00:00
this . _closed = true ;
} ) ;
2017-08-15 18:13:05 +00:00
}
2020-06-10 15:15:02 +00:00
private async _initialize ( ) : Promise < void > {
2019-07-23 04:30:49 +00:00
await Promise . all ( [
this . _frameManager . initialize ( ) ,
2020-05-07 10:54:55 +00:00
this . _client . send ( 'Target.setAutoAttach' , {
autoAttach : true ,
waitForDebuggerOnStart : false ,
flatten : true ,
} ) ,
2019-07-23 04:30:49 +00:00
this . _client . send ( 'Performance.enable' , { } ) ,
this . _client . send ( 'Log.enable' , { } ) ,
] ) ;
}
2020-06-10 15:15:02 +00:00
private async _onFileChooser (
2020-05-07 10:54:55 +00:00
event : Protocol.Page.fileChooserOpenedPayload
) : Promise < void > {
if ( ! this . _fileChooserInterceptors . size ) return ;
2020-01-27 13:44:53 +00:00
const frame = this . _frameManager . frame ( event . frameId ) ;
const context = await frame . executionContext ( ) ;
const element = await context . _adoptBackendNodeId ( event . backendNodeId ) ;
2019-07-23 04:30:49 +00:00
const interceptors = Array . from ( this . _fileChooserInterceptors ) ;
this . _fileChooserInterceptors . clear ( ) ;
2020-05-13 10:30:29 +00:00
const fileChooser = new FileChooser ( element , event ) ;
2020-05-07 10:54:55 +00:00
for ( const interceptor of interceptors ) interceptor . call ( null , fileChooser ) ;
2019-07-23 04:30:49 +00:00
}
2020-06-10 15:15:02 +00:00
public isJavaScriptEnabled ( ) : boolean {
return this . _javascriptEnabled ;
}
2020-05-07 10:54:55 +00:00
async waitForFileChooser (
options : { timeout? : number } = { }
) : Promise < FileChooser > {
2020-01-31 07:43:34 +00:00
if ( ! this . _fileChooserInterceptors . size )
2020-05-07 10:54:55 +00:00
await this . _client . send ( 'Page.setInterceptFileChooserDialog' , {
enabled : true ,
} ) ;
2020-01-31 07:43:34 +00:00
2020-05-07 10:54:55 +00:00
const { timeout = this . _timeoutSettings . timeout ( ) } = options ;
2019-07-23 04:30:49 +00:00
let callback ;
2020-05-07 10:54:55 +00:00
const promise = new Promise < FileChooser > ( ( x ) = > ( callback = x ) ) ;
2019-07-23 04:30:49 +00:00
this . _fileChooserInterceptors . add ( callback ) ;
2020-05-07 10:54:55 +00:00
return helper
. waitWithTimeout < FileChooser > (
promise ,
'waiting for file chooser' ,
timeout
)
. catch ( ( error ) = > {
this . _fileChooserInterceptors . delete ( callback ) ;
throw error ;
} ) ;
2019-07-23 04:30:49 +00:00
}
2020-05-07 10:54:55 +00:00
async setGeolocation ( options : {
longitude : number ;
latitude : number ;
accuracy? : number ;
} ) : Promise < void > {
const { longitude , latitude , accuracy = 0 } = options ;
2018-08-31 17:04:12 +00:00
if ( longitude < - 180 || longitude > 180 )
2020-05-07 10:54:55 +00:00
throw new Error (
` Invalid longitude " ${ longitude } ": precondition -180 <= LONGITUDE <= 180 failed. `
) ;
2018-08-31 17:04:12 +00:00
if ( latitude < - 90 || latitude > 90 )
2020-05-07 10:54:55 +00:00
throw new Error (
` Invalid latitude " ${ latitude } ": precondition -90 <= LATITUDE <= 90 failed. `
) ;
2018-08-31 17:04:12 +00:00
if ( accuracy < 0 )
2020-05-07 10:54:55 +00:00
throw new Error (
` Invalid accuracy " ${ accuracy } ": precondition 0 <= ACCURACY failed. `
) ;
await this . _client . send ( 'Emulation.setGeolocationOverride' , {
longitude ,
latitude ,
accuracy ,
} ) ;
2018-08-31 17:04:12 +00:00
}
2020-05-05 12:53:22 +00:00
target ( ) : Target {
2018-01-11 03:33:22 +00:00
return this . _target ;
}
2020-05-05 12:53:22 +00:00
browser ( ) : Browser {
2018-04-17 17:37:17 +00:00
return this . _target . browser ( ) ;
}
2020-05-05 12:53:22 +00:00
browserContext ( ) : BrowserContext {
2018-12-12 23:08:31 +00:00
return this . _target . browserContext ( ) ;
}
2020-06-12 10:10:12 +00:00
private _onTargetCrashed ( ) : void {
2017-08-15 18:13:05 +00:00
this . emit ( 'error' , new Error ( 'Page crashed!' ) ) ;
2017-06-21 20:51:06 +00:00
}
2020-06-12 10:10:12 +00:00
private _onLogEntryAdded ( event : Protocol.Log.entryAddedPayload ) : void {
2020-05-07 10:54:55 +00:00
const { level , text , args , source , url , lineNumber } = event . entry ;
if ( args ) args . map ( ( arg ) = > helper . releaseObject ( this . _client , arg ) ) ;
2018-06-07 18:21:35 +00:00
if ( source !== 'worker' )
2020-05-07 10:54:55 +00:00
this . emit (
Events . Page . Console ,
new ConsoleMessage ( level , text , [ ] , { url , lineNumber } )
) ;
2018-04-28 04:40:09 +00:00
}
2020-05-05 12:53:22 +00:00
mainFrame ( ) : Frame {
2017-06-21 20:51:06 +00:00
return this . _frameManager . mainFrame ( ) ;
}
2017-06-21 20:36:04 +00:00
2020-05-05 12:53:22 +00:00
get keyboard ( ) : Keyboard {
2017-07-18 01:49:52 +00:00
return this . _keyboard ;
}
2020-05-05 12:53:22 +00:00
get touchscreen ( ) : Touchscreen {
2017-09-02 02:03:51 +00:00
return this . _touchscreen ;
}
2020-05-05 12:53:22 +00:00
get coverage ( ) : Coverage {
2018-01-03 03:53:53 +00:00
return this . _coverage ;
}
2020-05-05 12:53:22 +00:00
get tracing ( ) : Tracing {
2017-08-02 17:45:11 +00:00
return this . _tracing ;
}
2020-05-05 12:53:22 +00:00
get accessibility ( ) : Accessibility {
2018-11-02 01:54:51 +00:00
return this . _accessibility ;
}
2020-05-05 12:53:22 +00:00
frames ( ) : Frame [ ] {
2017-06-21 20:51:06 +00:00
return this . _frameManager . frames ( ) ;
}
2017-06-21 20:36:04 +00:00
2020-05-29 11:57:54 +00:00
workers ( ) : WebWorker [ ] {
2018-05-21 21:31:11 +00:00
return Array . from ( this . _workers . values ( ) ) ;
}
2020-05-05 12:53:22 +00:00
async setRequestInterception ( value : boolean ) : Promise < void > {
2019-04-10 04:42:42 +00:00
return this . _frameManager . networkManager ( ) . setRequestInterception ( value ) ;
2017-06-21 20:51:06 +00:00
}
2017-06-15 07:20:37 +00:00
2020-05-05 12:53:22 +00:00
setOfflineMode ( enabled : boolean ) : Promise < void > {
2019-04-10 04:42:42 +00:00
return this . _frameManager . networkManager ( ) . setOfflineMode ( enabled ) ;
2017-10-13 21:41:39 +00:00
}
2020-05-05 12:53:22 +00:00
setDefaultNavigationTimeout ( timeout : number ) : void {
2019-01-29 01:16:12 +00:00
this . _timeoutSettings . setDefaultNavigationTimeout ( timeout ) ;
}
2020-05-05 12:53:22 +00:00
setDefaultTimeout ( timeout : number ) : void {
2019-01-29 01:16:12 +00:00
this . _timeoutSettings . setDefaultTimeout ( timeout ) ;
2018-01-10 21:04:01 +00:00
}
2020-05-05 12:53:22 +00:00
async $ ( selector : string ) : Promise < ElementHandle | null > {
2017-08-15 21:54:02 +00:00
return this . mainFrame ( ) . $ ( selector ) ;
}
2017-10-06 22:35:02 +00:00
2020-05-07 10:54:55 +00:00
async evaluateHandle (
pageFunction : Function | string ,
. . . args : unknown [ ]
) : Promise < JSHandle > {
2017-11-19 00:27:52 +00:00
const context = await this . mainFrame ( ) . executionContext ( ) ;
return context . evaluateHandle ( pageFunction , . . . args ) ;
2017-10-06 22:35:02 +00:00
}
2017-08-15 21:54:02 +00:00
2020-05-05 12:53:22 +00:00
async queryObjects ( prototypeHandle : JSHandle ) : Promise < JSHandle > {
2017-11-19 00:27:52 +00:00
const context = await this . mainFrame ( ) . executionContext ( ) ;
return context . queryObjects ( prototypeHandle ) ;
2017-10-11 21:41:20 +00:00
}
2020-05-07 10:54:55 +00:00
async $eval < ReturnType extends any > (
selector : string ,
pageFunction : Function | string ,
. . . args : unknown [ ]
) : Promise < ReturnType > {
2020-05-05 12:53:22 +00:00
return this . mainFrame ( ) . $eval < ReturnType > ( selector , pageFunction , . . . args ) ;
2017-08-31 22:38:01 +00:00
}
2020-05-07 10:54:55 +00:00
async $ $eval < ReturnType extends any > (
selector : string ,
pageFunction : Function | string ,
. . . args : unknown [ ]
) : Promise < ReturnType > {
2020-05-05 12:53:22 +00:00
return this . mainFrame ( ) . $ $eval < ReturnType > ( selector , pageFunction , . . . args ) ;
2017-10-11 06:23:14 +00:00
}
2020-05-05 12:53:22 +00:00
async $ $ ( selector : string ) : Promise < ElementHandle [ ] > {
2017-08-23 05:56:55 +00:00
return this . mainFrame ( ) . $ $ ( selector ) ;
}
2020-05-05 12:53:22 +00:00
async $x ( expression : string ) : Promise < ElementHandle [ ] > {
2018-01-03 23:37:08 +00:00
return this . mainFrame ( ) . $x ( expression ) ;
2017-12-20 00:23:45 +00:00
}
2020-05-05 12:53:22 +00:00
async cookies ( . . . urls : string [ ] ) : Promise < Protocol.Network.Cookie [ ] > {
2020-05-07 10:54:55 +00:00
const originalCookies = (
await this . _client . send ( 'Network.getCookies' , {
urls : urls.length ? urls : [ this . url ( ) ] ,
} )
) . cookies ;
2020-04-16 07:20:27 +00:00
const unsupportedCookieAttributes = [ 'priority' ] ;
2020-05-07 10:54:55 +00:00
const filterUnsupportedAttributes = (
cookie : Protocol.Network.Cookie
) : Protocol . Network . Cookie = > {
for ( const attr of unsupportedCookieAttributes ) delete cookie [ attr ] ;
2020-04-16 07:20:27 +00:00
return cookie ;
} ;
return originalCookies . map ( filterUnsupportedAttributes ) ;
2017-08-24 19:21:46 +00:00
}
2020-05-07 10:54:55 +00:00
async deleteCookie (
. . . cookies : Protocol.Network.deleteCookiesParameters [ ]
) : Promise < void > {
2017-08-24 19:21:46 +00:00
const pageURL = this . url ( ) ;
for ( const cookie of cookies ) {
const item = Object . assign ( { } , cookie ) ;
2020-05-07 10:54:55 +00:00
if ( ! cookie . url && pageURL . startsWith ( 'http' ) ) item . url = pageURL ;
2017-08-24 19:21:46 +00:00
await this . _client . send ( 'Network.deleteCookies' , item ) ;
}
}
2020-05-05 12:53:22 +00:00
async setCookie ( . . . cookies : Protocol.Network.CookieParam [ ] ) : Promise < void > {
2017-12-16 09:17:20 +00:00
const pageURL = this . url ( ) ;
const startsWithHTTP = pageURL . startsWith ( 'http' ) ;
2020-05-07 10:54:55 +00:00
const items = cookies . map ( ( cookie ) = > {
2017-08-24 19:21:46 +00:00
const item = Object . assign ( { } , cookie ) ;
2020-05-07 10:54:55 +00:00
if ( ! item . url && startsWithHTTP ) item . url = pageURL ;
assert (
item . url !== 'about:blank' ,
` Blank page can not have cookie " ${ item . name } " `
) ;
assert (
! String . prototype . startsWith . call ( item . url || '' , 'data:' ) ,
` Data URL page can not have cookie " ${ item . name } " `
) ;
2017-08-24 19:21:46 +00:00
return item ;
} ) ;
await this . deleteCookie ( . . . items ) ;
if ( items . length )
2020-05-07 10:54:55 +00:00
await this . _client . send ( 'Network.setCookies' , { cookies : items } ) ;
2017-08-24 19:21:46 +00:00
}
2020-05-07 10:54:55 +00:00
async addScriptTag ( options : {
url? : string ;
path? : string ;
content? : string ;
type ? : string ;
} ) : Promise < ElementHandle > {
2017-10-12 08:26:44 +00:00
return this . mainFrame ( ) . addScriptTag ( options ) ;
2017-06-21 20:51:06 +00:00
}
2017-06-21 20:36:04 +00:00
2020-05-07 10:54:55 +00:00
async addStyleTag ( options : {
url? : string ;
path? : string ;
content? : string ;
} ) : Promise < ElementHandle > {
2017-10-12 08:26:44 +00:00
return this . mainFrame ( ) . addStyleTag ( options ) ;
2017-06-21 20:51:06 +00:00
}
2020-05-07 10:54:55 +00:00
async exposeFunction (
name : string ,
puppeteerFunction : Function
) : Promise < void > {
2018-07-31 19:18:10 +00:00
if ( this . _pageBindings . has ( name ) )
2020-05-07 10:54:55 +00:00
throw new Error (
` Failed to add page binding with name ${ name } : window[' ${ name } '] already exists! `
) ;
2018-07-31 19:18:10 +00:00
this . _pageBindings . set ( name , puppeteerFunction ) ;
2017-06-21 20:51:06 +00:00
2017-08-21 23:39:04 +00:00
const expression = helper . evaluationString ( addPageBinding , name ) ;
2020-05-07 10:54:55 +00:00
await this . _client . send ( 'Runtime.addBinding' , { name : name } ) ;
await this . _client . send ( 'Page.addScriptToEvaluateOnNewDocument' , {
source : expression ,
} ) ;
await Promise . all (
this . frames ( ) . map ( ( frame ) = > frame . evaluate ( expression ) . catch ( debugError ) )
) ;
2017-06-21 20:51:06 +00:00
2020-05-05 12:53:22 +00:00
function addPageBinding ( bindingName ) : void {
/ * C a s t w i n d o w t o a n y h e r e a s w e ' r e a b o u t t o a d d p r o p e r t i e s t o i t
* via win [ bindingName ] which TypeScript doesn ' t like .
* /
const win = window as any ;
const binding = win [ bindingName ] ;
2020-03-31 08:48:09 +00:00
2020-05-05 12:53:22 +00:00
win [ bindingName ] = ( . . . args : unknown [ ] ) : Promise < unknown > = > {
2017-08-11 04:44:49 +00:00
const me = window [ bindingName ] ;
2017-06-21 20:51:06 +00:00
let callbacks = me [ 'callbacks' ] ;
if ( ! callbacks ) {
callbacks = new Map ( ) ;
me [ 'callbacks' ] = callbacks ;
2017-06-20 21:54:53 +00:00
}
2017-06-21 20:51:06 +00:00
const seq = ( me [ 'lastSeq' ] || 0 ) + 1 ;
me [ 'lastSeq' ] = seq ;
2020-05-07 10:54:55 +00:00
const promise = new Promise ( ( resolve , reject ) = >
callbacks . set ( seq , { resolve , reject } )
) ;
binding ( JSON . stringify ( { name : bindingName , seq , args } ) ) ;
2017-06-21 20:51:06 +00:00
return promise ;
} ;
2017-05-11 07:06:41 +00:00
}
2017-06-21 20:51:06 +00:00
}
2017-06-21 20:36:04 +00:00
2020-05-05 12:53:22 +00:00
async authenticate ( credentials : Credentials ) : Promise < void > {
2019-04-10 04:42:42 +00:00
return this . _frameManager . networkManager ( ) . authenticate ( credentials ) ;
2017-09-11 23:32:13 +00:00
}
2020-05-05 12:53:22 +00:00
async setExtraHTTPHeaders ( headers : Record < string , string > ) : Promise < void > {
2019-04-10 04:42:42 +00:00
return this . _frameManager . networkManager ( ) . setExtraHTTPHeaders ( headers ) ;
2017-06-21 20:51:06 +00:00
}
2020-05-05 12:53:22 +00:00
async setUserAgent ( userAgent : string ) : Promise < void > {
2019-04-10 04:42:42 +00:00
return this . _frameManager . networkManager ( ) . setUserAgent ( userAgent ) ;
2017-06-21 20:51:06 +00:00
}
2017-05-11 07:06:41 +00:00
2020-05-05 12:53:22 +00:00
async metrics ( ) : Promise < Metrics > {
2017-10-10 21:50:38 +00:00
const response = await this . _client . send ( 'Performance.getMetrics' ) ;
return this . _buildMetricsObject ( response . metrics ) ;
}
2020-06-10 15:15:02 +00:00
private _emitMetrics ( event : Protocol.Performance.metricsPayload ) : void {
2019-01-15 03:57:05 +00:00
this . emit ( Events . Page . Metrics , {
2017-10-10 21:50:38 +00:00
title : event.title ,
2020-05-07 10:54:55 +00:00
metrics : this._buildMetricsObject ( event . metrics ) ,
2017-10-10 21:50:38 +00:00
} ) ;
}
2020-06-10 15:15:02 +00:00
private _buildMetricsObject (
metrics? : Protocol.Performance.Metric [ ]
) : Metrics {
2017-10-10 21:50:38 +00:00
const result = { } ;
for ( const metric of metrics || [ ] ) {
2020-05-07 10:54:55 +00:00
if ( supportedMetrics . has ( metric . name ) ) result [ metric . name ] = metric . value ;
2017-10-10 21:50:38 +00:00
}
return result ;
}
2020-06-10 15:15:02 +00:00
private _handleException (
exceptionDetails : Protocol.Runtime.ExceptionDetails
) : void {
2017-08-21 23:39:04 +00:00
const message = helper . getExceptionMessage ( exceptionDetails ) ;
2018-05-25 23:44:25 +00:00
const err = new Error ( message ) ;
err . stack = '' ; // Don't report clientside error with a node stack attached
2019-01-15 03:57:05 +00:00
this . emit ( Events . Page . PageError , err ) ;
2017-06-21 20:51:06 +00:00
}
2020-06-10 15:15:02 +00:00
private async _onConsoleAPI (
2020-05-07 10:54:55 +00:00
event : Protocol.Runtime.consoleAPICalledPayload
) : Promise < void > {
2019-01-31 00:19:02 +00:00
if ( event . executionContextId === 0 ) {
// DevTools protocol stores the last 1000 console messages. These
// messages are always reported even for removed execution contexts. In
// this case, they are marked with executionContextId = 0 and are
// reported upon enabling Runtime agent.
//
// Ignore these messages since:
// - there's no execution context we can use to operate with message
// arguments
// - these messages are reported before Puppeteer clients can subscribe
// to the 'console'
// page event.
//
2019-11-26 12:12:25 +00:00
// @see https://github.com/puppeteer/puppeteer/issues/3865
2019-01-31 00:19:02 +00:00
return ;
}
2020-05-07 10:54:55 +00:00
const context = this . _frameManager . executionContextById (
event . executionContextId
) ;
const values = event . args . map ( ( arg ) = > createJSHandle ( context , arg ) ) ;
2019-01-11 02:05:28 +00:00
this . _addConsoleMessage ( event . type , values , event . stackTrace ) ;
2018-06-07 18:21:35 +00:00
}
2020-06-10 15:15:02 +00:00
private async _onBindingCalled (
2020-05-07 10:54:55 +00:00
event : Protocol.Runtime.bindingCalledPayload
) : Promise < void > {
const { name , seq , args } = JSON . parse ( event . payload ) ;
2018-11-15 22:51:34 +00:00
let expression = null ;
try {
const result = await this . _pageBindings . get ( name ) ( . . . args ) ;
expression = helper . evaluationString ( deliverResult , name , seq , result ) ;
} catch ( error ) {
if ( error instanceof Error )
2020-05-07 10:54:55 +00:00
expression = helper . evaluationString (
deliverError ,
name ,
seq ,
error . message ,
error . stack
) ;
2018-11-15 22:51:34 +00:00
else
2020-05-07 10:54:55 +00:00
expression = helper . evaluationString (
deliverErrorValue ,
name ,
seq ,
error
) ;
2018-11-15 22:51:34 +00:00
}
2020-05-07 10:54:55 +00:00
this . _client
. send ( 'Runtime.evaluate' , {
expression ,
contextId : event.executionContextId ,
} )
. catch ( debugError ) ;
2018-06-18 20:41:03 +00:00
2020-05-05 12:53:22 +00:00
function deliverResult ( name : string , seq : number , result : unknown ) : void {
2018-11-15 22:51:34 +00:00
window [ name ] [ 'callbacks' ] . get ( seq ) . resolve ( result ) ;
window [ name ] [ 'callbacks' ] . delete ( seq ) ;
}
2020-05-07 10:54:55 +00:00
function deliverError (
name : string ,
seq : number ,
message : string ,
stack : string
) : void {
2018-11-15 22:51:34 +00:00
const error = new Error ( message ) ;
error . stack = stack ;
window [ name ] [ 'callbacks' ] . get ( seq ) . reject ( error ) ;
window [ name ] [ 'callbacks' ] . delete ( seq ) ;
}
2020-05-07 10:54:55 +00:00
function deliverErrorValue (
name : string ,
seq : number ,
value : unknown
) : void {
2018-11-15 22:51:34 +00:00
window [ name ] [ 'callbacks' ] . get ( seq ) . reject ( value ) ;
2018-06-18 20:41:03 +00:00
window [ name ] [ 'callbacks' ] . delete ( seq ) ;
}
}
2020-06-10 15:15:02 +00:00
private _addConsoleMessage (
2020-05-07 10:54:55 +00:00
type : string ,
args : JSHandle [ ] ,
stackTrace? : Protocol.Runtime.StackTrace
) : void {
2019-01-15 03:57:05 +00:00
if ( ! this . listenerCount ( Events . Page . Console ) ) {
2020-05-07 10:54:55 +00:00
args . forEach ( ( arg ) = > arg . dispose ( ) ) ;
2017-07-25 04:43:54 +00:00
return ;
}
2017-10-10 17:54:20 +00:00
const textTokens = [ ] ;
2018-06-07 18:21:35 +00:00
for ( const arg of args ) {
const remoteObject = arg . _remoteObject ;
2020-05-07 10:54:55 +00:00
if ( remoteObject . objectId ) textTokens . push ( arg . toString ( ) ) ;
else textTokens . push ( helper . valueFromRemoteObject ( remoteObject ) ) ;
2017-10-10 17:54:20 +00:00
}
2020-05-07 10:54:55 +00:00
const location =
stackTrace && stackTrace . callFrames . length
? {
url : stackTrace.callFrames [ 0 ] . url ,
lineNumber : stackTrace.callFrames [ 0 ] . lineNumber ,
columnNumber : stackTrace.callFrames [ 0 ] . columnNumber ,
}
: { } ;
const message = new ConsoleMessage (
type ,
textTokens . join ( ' ' ) ,
args ,
location
) ;
2019-01-15 03:57:05 +00:00
this . emit ( Events . Page . Console , message ) ;
2017-06-21 20:51:06 +00:00
}
2020-06-10 15:15:02 +00:00
private _onDialog ( event : Protocol.Page.javascriptDialogOpeningPayload ) : void {
2017-06-22 20:38:10 +00:00
let dialogType = null ;
2020-06-09 08:19:42 +00:00
const validDialogTypes = new Set < Protocol.Page.DialogType > ( [
'alert' ,
'confirm' ,
'prompt' ,
'beforeunload' ,
] ) ;
if ( validDialogTypes . has ( event . type ) ) {
dialogType = event . type as Protocol . Page . DialogType ;
}
2018-05-31 23:53:51 +00:00
assert ( dialogType , 'Unknown javascript dialog type: ' + event . type ) ;
2020-05-05 12:53:22 +00:00
2020-05-07 10:54:55 +00:00
const dialog = new Dialog (
this . _client ,
dialogType ,
event . message ,
event . defaultPrompt
) ;
2019-01-15 03:57:05 +00:00
this . emit ( Events . Page . Dialog , dialog ) ;
2017-06-21 20:51:06 +00:00
}
2020-05-05 12:53:22 +00:00
url ( ) : string {
2017-07-14 21:05:27 +00:00
return this . mainFrame ( ) . url ( ) ;
2017-06-21 20:51:06 +00:00
}
2017-06-21 20:36:04 +00:00
2020-05-05 12:53:22 +00:00
async content ( ) : Promise < string > {
2017-11-23 02:44:33 +00:00
return await this . _frameManager . mainFrame ( ) . content ( ) ;
2017-08-21 16:02:30 +00:00
}
2020-05-05 12:53:22 +00:00
async setContent ( html : string , options : WaitForOptions ) : Promise < void > {
2018-11-20 23:32:46 +00:00
await this . _frameManager . mainFrame ( ) . setContent ( html , options ) ;
2017-06-21 20:51:06 +00:00
}
2020-05-07 10:54:55 +00:00
async goto (
url : string ,
2020-06-18 11:44:46 +00:00
options : WaitForOptions & { referer? : string } = { }
2020-05-29 10:49:30 +00:00
) : Promise < HTTPResponse > {
2018-09-20 18:31:19 +00:00
return await this . _frameManager . mainFrame ( ) . goto ( url , options ) ;
2017-07-18 01:13:04 +00:00
}
2020-05-29 10:49:30 +00:00
async reload ( options? : WaitForOptions ) : Promise < HTTPResponse | null > {
2020-05-07 10:54:55 +00:00
const result = await Promise . all <
2020-05-29 10:49:30 +00:00
HTTPResponse ,
2020-05-07 10:54:55 +00:00
Protocol . Page . reloadReturnValue
> ( [ this . waitForNavigation ( options ) , this . _client . send ( 'Page.reload' ) ] ) ;
2020-03-31 08:48:09 +00:00
2020-05-05 12:53:22 +00:00
return result [ 0 ] ;
2017-07-19 01:54:24 +00:00
}
2017-07-18 01:13:04 +00:00
2020-05-07 10:54:55 +00:00
async waitForNavigation (
options : WaitForOptions = { }
2020-05-29 10:49:30 +00:00
) : Promise < HTTPResponse | null > {
2018-09-20 18:31:19 +00:00
return await this . _frameManager . mainFrame ( ) . waitForNavigation ( options ) ;
2017-06-21 20:51:06 +00:00
}
2017-06-21 20:36:04 +00:00
2020-06-10 15:15:02 +00:00
private _sessionClosePromise ( ) : Promise < Error > {
2019-08-21 17:26:48 +00:00
if ( ! this . _disconnectPromise )
2020-05-07 10:54:55 +00:00
this . _disconnectPromise = new Promise ( ( fulfill ) = >
this . _client . once ( Events . CDPSession . Disconnected , ( ) = >
fulfill ( new Error ( 'Target closed' ) )
)
) ;
2019-08-21 17:26:48 +00:00
return this . _disconnectPromise ;
}
2020-05-07 10:54:55 +00:00
async waitForRequest (
urlOrPredicate : string | Function ,
options : { timeout? : number } = { }
2020-05-29 08:38:40 +00:00
) : Promise < HTTPRequest > {
2020-05-07 10:54:55 +00:00
const { timeout = this . _timeoutSettings . timeout ( ) } = options ;
return helper . waitForEvent (
this . _frameManager . networkManager ( ) ,
Events . NetworkManager . Request ,
( request ) = > {
if ( helper . isString ( urlOrPredicate ) )
return urlOrPredicate === request . url ( ) ;
if ( typeof urlOrPredicate === 'function' )
return ! ! urlOrPredicate ( request ) ;
return false ;
} ,
timeout ,
this . _sessionClosePromise ( )
) ;
}
async waitForResponse (
urlOrPredicate : string | Function ,
options : { timeout? : number } = { }
2020-05-29 10:49:30 +00:00
) : Promise < HTTPResponse > {
2020-05-07 10:54:55 +00:00
const { timeout = this . _timeoutSettings . timeout ( ) } = options ;
return helper . waitForEvent (
this . _frameManager . networkManager ( ) ,
Events . NetworkManager . Response ,
( response ) = > {
if ( helper . isString ( urlOrPredicate ) )
return urlOrPredicate === response . url ( ) ;
if ( typeof urlOrPredicate === 'function' )
return ! ! urlOrPredicate ( response ) ;
return false ;
} ,
timeout ,
this . _sessionClosePromise ( )
) ;
2018-07-12 21:36:31 +00:00
}
2020-05-29 10:49:30 +00:00
async goBack ( options : WaitForOptions ) : Promise < HTTPResponse | null > {
2017-07-19 02:11:37 +00:00
return this . _go ( - 1 , options ) ;
}
2020-05-29 10:49:30 +00:00
async goForward ( options : WaitForOptions ) : Promise < HTTPResponse | null > {
2017-07-19 02:11:37 +00:00
return this . _go ( + 1 , options ) ;
}
2020-06-12 10:10:12 +00:00
private async _go (
2020-05-07 10:54:55 +00:00
delta : number ,
options : WaitForOptions
2020-05-29 10:49:30 +00:00
) : Promise < HTTPResponse | null > {
2017-07-19 02:11:37 +00:00
const history = await this . _client . send ( 'Page.getNavigationHistory' ) ;
const entry = history . entries [ history . currentIndex + delta ] ;
2020-05-07 10:54:55 +00:00
if ( ! entry ) return null ;
const result = await Promise . all <
2020-05-29 10:49:30 +00:00
HTTPResponse ,
2020-05-07 10:54:55 +00:00
Protocol . Page . navigateToHistoryEntryReturnValue
> ( [
2017-09-11 23:21:51 +00:00
this . waitForNavigation ( options ) ,
2020-05-07 10:54:55 +00:00
this . _client . send ( 'Page.navigateToHistoryEntry' , { entryId : entry.id } ) ,
2017-09-11 23:21:51 +00:00
] ) ;
2020-05-05 12:53:22 +00:00
return result [ 0 ] ;
2017-07-19 02:11:37 +00:00
}
2020-05-05 12:53:22 +00:00
async bringToFront ( ) : Promise < void > {
2017-11-07 21:17:36 +00:00
await this . _client . send ( 'Page.bringToFront' ) ;
}
2020-05-07 10:54:55 +00:00
async emulate ( options : {
viewport : Viewport ;
userAgent : string ;
} ) : Promise < void > {
2018-11-08 06:48:43 +00:00
await Promise . all ( [
2017-08-11 01:42:30 +00:00
this . setViewport ( options . viewport ) ,
2020-05-07 10:54:55 +00:00
this . setUserAgent ( options . userAgent ) ,
2017-08-11 01:42:30 +00:00
] ) ;
}
2020-05-05 12:53:22 +00:00
async setJavaScriptEnabled ( enabled : boolean ) : Promise < void > {
2020-05-07 10:54:55 +00:00
if ( this . _javascriptEnabled === enabled ) return ;
2018-07-19 01:51:18 +00:00
this . _javascriptEnabled = enabled ;
2020-05-07 10:54:55 +00:00
await this . _client . send ( 'Emulation.setScriptExecutionDisabled' , {
value : ! enabled ,
} ) ;
2017-08-23 21:08:56 +00:00
}
2020-05-05 12:53:22 +00:00
async setBypassCSP ( enabled : boolean ) : Promise < void > {
2020-05-07 10:54:55 +00:00
await this . _client . send ( 'Page.setBypassCSP' , { enabled } ) ;
2018-04-06 23:35:50 +00:00
}
2020-05-05 12:53:22 +00:00
async emulateMediaType ( type ? : string ) : Promise < void > {
2020-05-07 10:54:55 +00:00
assert (
type === 'screen' || type === 'print' || type === null ,
'Unsupported media type: ' + type
) ;
await this . _client . send ( 'Emulation.setEmulatedMedia' , {
media : type || '' ,
} ) ;
2019-10-23 11:55:00 +00:00
}
2020-05-05 12:53:22 +00:00
async emulateMediaFeatures ( features? : MediaFeature [ ] ) : Promise < void > {
2019-10-23 11:55:00 +00:00
if ( features === null )
2020-05-07 10:54:55 +00:00
await this . _client . send ( 'Emulation.setEmulatedMedia' , { features : null } ) ;
2019-10-23 11:55:00 +00:00
if ( Array . isArray ( features ) ) {
2020-05-07 10:54:55 +00:00
features . every ( ( mediaFeature ) = > {
2019-10-23 11:55:00 +00:00
const name = mediaFeature . name ;
2020-05-07 10:54:55 +00:00
assert (
/^prefers-(?:color-scheme|reduced-motion)$/ . test ( name ) ,
'Unsupported media feature: ' + name
) ;
2019-10-23 11:55:00 +00:00
return true ;
} ) ;
2020-05-07 10:54:55 +00:00
await this . _client . send ( 'Emulation.setEmulatedMedia' , {
features : features ,
} ) ;
2019-10-23 11:55:00 +00:00
}
2017-08-18 23:49:02 +00:00
}
2020-05-05 12:53:22 +00:00
async emulateTimezone ( timezoneId? : string ) : Promise < void > {
2019-10-23 13:49:39 +00:00
try {
2020-05-07 10:54:55 +00:00
await this . _client . send ( 'Emulation.setTimezoneOverride' , {
timezoneId : timezoneId || '' ,
} ) ;
2020-04-28 13:16:28 +00:00
} catch ( error ) {
if ( error . message . includes ( 'Invalid timezone' ) )
2019-10-23 13:49:39 +00:00
throw new Error ( ` Invalid timezone ID: ${ timezoneId } ` ) ;
2020-04-28 13:16:28 +00:00
throw error ;
2019-10-23 13:49:39 +00:00
}
}
2020-05-26 15:14:20 +00:00
async emulateVisionDeficiency ( type ? : VisionDeficiency ) : Promise < void > {
const visionDeficiencies = new Set ( Object . keys ( VisionDeficiency ) ) ;
try {
assert (
! type || visionDeficiencies . has ( type ) ,
` Unsupported vision deficiency: ${ type } `
) ;
await this . _client . send ( 'Emulation.setEmulatedVisionDeficiency' , {
type : type || 'none' ,
} ) ;
} catch ( error ) {
throw error ;
}
}
2020-05-06 13:23:07 +00:00
async setViewport ( viewport : Viewport ) : Promise < void > {
2018-02-05 20:51:43 +00:00
const needsReload = await this . _emulationManager . emulateViewport ( viewport ) ;
2017-07-18 01:13:04 +00:00
this . _viewport = viewport ;
2020-05-07 10:54:55 +00:00
if ( needsReload ) await this . reload ( ) ;
2017-06-21 20:51:06 +00:00
}
2020-05-06 13:23:07 +00:00
viewport ( ) : Viewport | null {
2017-07-18 01:13:04 +00:00
return this . _viewport ;
2017-06-21 20:51:06 +00:00
}
2017-06-21 20:36:04 +00:00
2020-05-07 10:54:55 +00:00
async evaluate < ReturnType extends any > (
pageFunction : Function | string ,
. . . args : unknown [ ]
) : Promise < ReturnType > {
return this . _frameManager
. mainFrame ( )
. evaluate < ReturnType > ( pageFunction , . . . args ) ;
2017-06-21 20:51:06 +00:00
}
2017-06-21 20:36:04 +00:00
2020-05-07 10:54:55 +00:00
async evaluateOnNewDocument (
pageFunction : Function | string ,
. . . args : unknown [ ]
) : Promise < void > {
2017-08-21 23:39:04 +00:00
const source = helper . evaluationString ( pageFunction , . . . args ) ;
2020-05-07 10:54:55 +00:00
await this . _client . send ( 'Page.addScriptToEvaluateOnNewDocument' , {
source ,
} ) ;
2017-06-21 20:51:06 +00:00
}
2017-06-20 01:03:01 +00:00
2020-05-05 12:53:22 +00:00
async setCacheEnabled ( enabled = true ) : Promise < void > {
2019-04-10 04:42:42 +00:00
await this . _frameManager . networkManager ( ) . setCacheEnabled ( enabled ) ;
2018-02-08 05:58:48 +00:00
}
2020-05-07 13:49:42 +00:00
async screenshot (
options : ScreenshotOptions = { }
) : Promise < Buffer | string | void > {
2017-06-22 20:38:10 +00:00
let screenshotType = null ;
2017-12-04 22:04:36 +00:00
// options.type takes precedence over inferring the type from options.path
// because it may be a 0-length file with no extension created beforehand (i.e. as a temp file).
if ( options . type ) {
2020-05-07 10:54:55 +00:00
assert (
options . type === 'png' || options . type === 'jpeg' ,
'Unknown options.type value: ' + options . type
) ;
2017-12-04 22:04:36 +00:00
screenshotType = options . type ;
} else if ( options . path ) {
2018-04-17 21:51:03 +00:00
const mimeType = mime . getType ( options . path ) ;
2020-05-07 10:54:55 +00:00
if ( mimeType === 'image/png' ) screenshotType = 'png' ;
else if ( mimeType === 'image/jpeg' ) screenshotType = 'jpeg' ;
2018-05-31 23:53:51 +00:00
assert ( screenshotType , 'Unsupported screenshot mime type: ' + mimeType ) ;
2017-06-21 20:51:06 +00:00
}
2017-12-04 22:04:36 +00:00
2020-05-07 10:54:55 +00:00
if ( ! screenshotType ) screenshotType = 'png' ;
2017-06-21 20:51:06 +00:00
if ( options . quality ) {
2020-05-07 10:54:55 +00:00
assert (
screenshotType === 'jpeg' ,
'options.quality is unsupported for the ' +
screenshotType +
' screenshots'
) ;
assert (
typeof options . quality === 'number' ,
'Expected options.quality to be a number but found ' +
typeof options . quality
) ;
assert (
Number . isInteger ( options . quality ) ,
'Expected options.quality to be an integer'
) ;
assert (
options . quality >= 0 && options . quality <= 100 ,
'Expected options.quality to be between 0 and 100 (inclusive), got ' +
options . quality
) ;
2017-06-21 20:51:06 +00:00
}
2020-05-07 10:54:55 +00:00
assert (
! options . clip || ! options . fullPage ,
'options.clip and options.fullPage are exclusive'
) ;
2017-06-21 20:51:06 +00:00
if ( options . clip ) {
2020-05-07 10:54:55 +00:00
assert (
typeof options . clip . x === 'number' ,
'Expected options.clip.x to be a number but found ' +
typeof options . clip . x
) ;
assert (
typeof options . clip . y === 'number' ,
'Expected options.clip.y to be a number but found ' +
typeof options . clip . y
) ;
assert (
typeof options . clip . width === 'number' ,
'Expected options.clip.width to be a number but found ' +
typeof options . clip . width
) ;
assert (
typeof options . clip . height === 'number' ,
'Expected options.clip.height to be a number but found ' +
typeof options . clip . height
) ;
assert (
options . clip . width !== 0 ,
'Expected options.clip.width not to be 0.'
) ;
assert (
options . clip . height !== 0 ,
'Expected options.clip.height not to be 0.'
) ;
2017-06-21 20:51:06 +00:00
}
2020-05-07 13:49:42 +00:00
return this . _screenshotTaskQueue . postTask ( ( ) = >
this . _screenshotTask ( screenshotType , options )
2020-05-07 10:54:55 +00:00
) ;
2017-06-21 20:51:06 +00:00
}
2020-06-12 10:10:12 +00:00
private async _screenshotTask (
2020-05-07 10:54:55 +00:00
format : 'png' | 'jpeg' ,
options? : ScreenshotOptions
) : Promise < Buffer | string > {
await this . _client . send ( 'Target.activateTarget' , {
targetId : this._target._targetId ,
} ) ;
2019-01-15 22:34:31 +00:00
let clip = options . clip ? processClip ( options . clip ) : undefined ;
2017-07-26 22:28:44 +00:00
2017-07-17 19:15:06 +00:00
if ( options . fullPage ) {
2017-07-18 01:13:04 +00:00
const metrics = await this . _client . send ( 'Page.getLayoutMetrics' ) ;
2017-07-17 19:15:06 +00:00
const width = Math . ceil ( metrics . contentSize . width ) ;
const height = Math . ceil ( metrics . contentSize . height ) ;
2017-08-11 01:42:30 +00:00
2017-07-26 22:28:44 +00:00
// Overwrite clip for full page at all times.
2020-05-07 10:54:55 +00:00
clip = { x : 0 , y : 0 , width , height , scale : 1 } ;
const { isMobile = false , deviceScaleFactor = 1 , isLandscape = false } =
this . _viewport || { } ;
const screenOrientation : Protocol.Emulation.ScreenOrientation = isLandscape
? { angle : 90 , type : 'landscapePrimary' }
: { angle : 0 , type : 'portraitPrimary' } ;
await this . _client . send ( 'Emulation.setDeviceMetricsOverride' , {
mobile : isMobile ,
width ,
height ,
deviceScaleFactor ,
screenOrientation ,
} ) ;
2017-06-21 20:51:06 +00:00
}
2020-05-07 10:54:55 +00:00
const shouldSetDefaultBackground =
options . omitBackground && format === 'png' ;
2018-09-14 10:03:33 +00:00
if ( shouldSetDefaultBackground )
2020-05-07 10:54:55 +00:00
await this . _client . send ( 'Emulation.setDefaultBackgroundColorOverride' , {
color : { r : 0 , g : 0 , b : 0 , a : 0 } ,
} ) ;
const result = await this . _client . send ( 'Page.captureScreenshot' , {
format ,
quality : options.quality ,
clip ,
} ) ;
2018-09-14 10:03:33 +00:00
if ( shouldSetDefaultBackground )
2017-08-18 04:11:39 +00:00
await this . _client . send ( 'Emulation.setDefaultBackgroundColorOverride' ) ;
2017-07-17 19:15:06 +00:00
2018-09-27 17:50:21 +00:00
if ( options . fullPage && this . _viewport )
2017-07-18 01:13:04 +00:00
await this . setViewport ( this . _viewport ) ;
2017-07-17 19:15:06 +00:00
2020-05-07 10:54:55 +00:00
const buffer =
options . encoding === 'base64'
? result . data
: Buffer . from ( result . data , 'base64' ) ;
if ( options . path ) await writeFileAsync ( options . path , buffer ) ;
2017-06-21 20:51:06 +00:00
return buffer ;
2019-01-15 22:34:31 +00:00
2020-05-07 10:54:55 +00:00
function processClip (
clip : ScreenshotClip
) : ScreenshotClip & { scale : number } {
2019-01-15 22:34:31 +00:00
const x = Math . round ( clip . x ) ;
const y = Math . round ( clip . y ) ;
const width = Math . round ( clip . width + clip . x - x ) ;
const height = Math . round ( clip . height + clip . y - y ) ;
2020-05-07 10:54:55 +00:00
return { x , y , width , height , scale : 1 } ;
2019-01-15 22:34:31 +00:00
}
2017-06-21 20:51:06 +00:00
}
2020-05-05 12:53:22 +00:00
async pdf ( options : PDFOptions = { } ) : Promise < Buffer > {
2018-11-12 20:59:21 +00:00
const {
scale = 1 ,
displayHeaderFooter = false ,
headerTemplate = '' ,
footerTemplate = '' ,
printBackground = false ,
landscape = false ,
pageRanges = '' ,
preferCSSPageSize = false ,
margin = { } ,
2020-05-07 10:54:55 +00:00
path = null ,
2018-11-12 20:59:21 +00:00
} = options ;
2017-06-21 20:51:06 +00:00
2017-06-22 20:38:10 +00:00
let paperWidth = 8.5 ;
let paperHeight = 11 ;
2017-06-21 20:51:06 +00:00
if ( options . format ) {
2020-05-05 12:53:22 +00:00
const format = paperFormats [ options . format . toLowerCase ( ) ] ;
2018-05-31 23:53:51 +00:00
assert ( format , 'Unknown paper format: ' + options . format ) ;
2017-06-21 20:51:06 +00:00
paperWidth = format . width ;
paperHeight = format . height ;
} else {
paperWidth = convertPrintParameterToInches ( options . width ) || paperWidth ;
2020-05-07 10:54:55 +00:00
paperHeight =
convertPrintParameterToInches ( options . height ) || paperHeight ;
2017-06-21 20:51:06 +00:00
}
2018-11-12 20:59:21 +00:00
const marginTop = convertPrintParameterToInches ( margin . top ) || 0 ;
const marginLeft = convertPrintParameterToInches ( margin . left ) || 0 ;
const marginBottom = convertPrintParameterToInches ( margin . bottom ) || 0 ;
const marginRight = convertPrintParameterToInches ( margin . right ) || 0 ;
2017-06-21 20:51:06 +00:00
2017-08-21 23:39:04 +00:00
const result = await this . _client . send ( 'Page.printToPDF' , {
2019-06-15 05:36:06 +00:00
transferMode : 'ReturnAsStream' ,
2018-11-12 20:59:21 +00:00
landscape ,
displayHeaderFooter ,
headerTemplate ,
footerTemplate ,
printBackground ,
scale ,
paperWidth ,
paperHeight ,
marginTop ,
marginBottom ,
marginLeft ,
marginRight ,
pageRanges ,
2020-05-07 10:54:55 +00:00
preferCSSPageSize ,
2017-06-21 20:51:06 +00:00
} ) ;
2019-06-15 05:36:06 +00:00
return await helper . readProtocolStream ( this . _client , result . stream , path ) ;
2017-06-21 20:51:06 +00:00
}
2020-05-05 12:53:22 +00:00
async title ( ) : Promise < string > {
2017-07-25 18:37:46 +00:00
return this . mainFrame ( ) . title ( ) ;
2017-06-21 20:51:06 +00:00
}
2017-05-11 07:06:41 +00:00
2020-05-07 10:54:55 +00:00
async close (
options : { runBeforeUnload? : boolean } = { runBeforeUnload : undefined }
) : Promise < void > {
assert (
! ! this . _client . _connection ,
'Protocol error: Connection closed. Most likely the page has been closed.'
) ;
2018-05-02 22:51:45 +00:00
const runBeforeUnload = ! ! options . runBeforeUnload ;
if ( runBeforeUnload ) {
await this . _client . send ( 'Page.close' ) ;
} else {
2020-05-07 10:54:55 +00:00
await this . _client . _connection . send ( 'Target.closeTarget' , {
targetId : this._target._targetId ,
} ) ;
2018-05-02 22:51:45 +00:00
await this . _target . _isClosedPromise ;
}
2017-06-21 20:51:06 +00:00
}
2017-06-28 01:27:22 +00:00
2020-05-05 12:53:22 +00:00
isClosed ( ) : boolean {
2018-05-25 23:53:57 +00:00
return this . _closed ;
}
2020-05-05 12:53:22 +00:00
get mouse ( ) : Mouse {
2017-07-22 03:29:31 +00:00
return this . _mouse ;
}
2020-05-07 10:54:55 +00:00
click (
selector : string ,
options : {
delay? : number ;
button? : MouseButtonInput ;
clickCount? : number ;
} = { }
) : Promise < void > {
2018-02-05 22:58:03 +00:00
return this . mainFrame ( ) . click ( selector , options ) ;
2017-07-22 03:29:31 +00:00
}
2020-05-05 12:53:22 +00:00
focus ( selector : string ) : Promise < void > {
2018-02-05 22:58:03 +00:00
return this . mainFrame ( ) . focus ( selector ) ;
2017-06-28 01:27:22 +00:00
}
2020-05-05 12:53:22 +00:00
hover ( selector : string ) : Promise < void > {
2018-02-05 22:58:03 +00:00
return this . mainFrame ( ) . hover ( selector ) ;
2017-06-28 01:27:22 +00:00
}
2020-05-05 12:53:22 +00:00
select ( selector : string , . . . values : string [ ] ) : Promise < string [ ] > {
2017-11-02 05:06:04 +00:00
return this . mainFrame ( ) . select ( selector , . . . values ) ;
2017-09-25 09:23:34 +00:00
}
2020-05-05 12:53:22 +00:00
tap ( selector : string ) : Promise < void > {
2018-02-05 22:58:03 +00:00
return this . mainFrame ( ) . tap ( selector ) ;
}
2020-05-07 10:54:55 +00:00
type (
selector : string ,
text : string ,
options ? : { delay : number }
) : Promise < void > {
2018-02-05 22:58:03 +00:00
return this . mainFrame ( ) . type ( selector , text , options ) ;
2017-07-19 21:43:07 +00:00
}
2020-05-07 10:54:55 +00:00
waitFor (
selectorOrFunctionOrTimeout : string | number | Function ,
options : {
visible? : boolean ;
hidden? : boolean ;
timeout? : number ;
polling? : string | number ;
} = { } ,
. . . args : unknown [ ]
) : Promise < JSHandle > {
return this . mainFrame ( ) . waitFor (
selectorOrFunctionOrTimeout ,
options ,
. . . args
) ;
}
waitForSelector (
selector : string ,
options : {
visible? : boolean ;
hidden? : boolean ;
timeout? : number ;
} = { }
) : Promise < ElementHandle | null > {
2017-07-21 19:41:49 +00:00
return this . mainFrame ( ) . waitForSelector ( selector , options ) ;
2017-07-07 22:39:02 +00:00
}
2017-07-10 18:21:46 +00:00
2020-05-07 10:54:55 +00:00
waitForXPath (
xpath : string ,
options : {
visible? : boolean ;
hidden? : boolean ;
timeout? : number ;
} = { }
) : Promise < ElementHandle | null > {
2018-01-22 23:16:20 +00:00
return this . mainFrame ( ) . waitForXPath ( xpath , options ) ;
}
2020-05-07 10:54:55 +00:00
waitForFunction (
pageFunction : Function ,
options : {
timeout? : number ;
polling? : string | number ;
} = { } ,
. . . args : unknown [ ]
) : Promise < JSHandle > {
2017-07-27 22:17:43 +00:00
return this . mainFrame ( ) . waitForFunction ( pageFunction , options , . . . args ) ;
}
2017-05-11 07:06:41 +00:00
}
2020-05-05 12:53:22 +00:00
const supportedMetrics = new Set < string > ( [
2017-10-10 21:50:38 +00:00
'Timestamp' ,
2017-10-12 08:17:06 +00:00
'Documents' ,
'Frames' ,
'JSEventListeners' ,
'Nodes' ,
2017-10-10 21:50:38 +00:00
'LayoutCount' ,
'RecalcStyleCount' ,
'LayoutDuration' ,
'RecalcStyleDuration' ,
'ScriptDuration' ,
'TaskDuration' ,
'JSHeapUsedSize' ,
'JSHeapTotalSize' ,
] ) ;
2017-08-21 23:39:04 +00:00
const unitToPixels = {
2020-05-07 10:54:55 +00:00
px : 1 ,
in : 96 ,
cm : 37.8 ,
mm : 3.78 ,
2017-05-14 18:29:42 +00:00
} ;
2020-05-07 10:54:55 +00:00
function convertPrintParameterToInches (
parameter? : string | number
) : number | undefined {
if ( typeof parameter === 'undefined' ) return undefined ;
2017-06-22 20:38:10 +00:00
let pixels ;
2017-07-27 22:17:43 +00:00
if ( helper . isNumber ( parameter ) ) {
2017-06-21 20:51:06 +00:00
// Treat numbers as pixel values to be aligned with phantom's paperSize.
2020-05-07 10:54:55 +00:00
pixels = /** @type {number} */ parameter ;
2017-07-27 22:17:43 +00:00
} else if ( helper . isString ( parameter ) ) {
2020-05-07 10:54:55 +00:00
const text = /** @type {string} */ parameter ;
2017-06-22 20:38:10 +00:00
let unit = text . substring ( text . length - 2 ) . toLowerCase ( ) ;
let valueText = '' ;
2017-06-21 20:51:06 +00:00
if ( unitToPixels . hasOwnProperty ( unit ) ) {
valueText = text . substring ( 0 , text . length - 2 ) ;
2017-05-14 18:29:42 +00:00
} else {
2017-06-21 20:51:06 +00:00
// In case of unknown unit try to parse the whole parameter as number of pixels.
// This is consistent with phantom's paperSize behavior.
unit = 'px' ;
valueText = text ;
}
2017-08-21 23:39:04 +00:00
const value = Number ( valueText ) ;
2018-05-31 23:53:51 +00:00
assert ( ! isNaN ( value ) , 'Failed to parse parameter value: ' + text ) ;
2017-06-21 20:51:06 +00:00
pixels = value * unitToPixels [ unit ] ;
} else {
2020-05-07 10:54:55 +00:00
throw new Error (
'page.pdf() Cannot handle parameter type: ' + typeof parameter
) ;
2017-06-21 20:51:06 +00:00
}
return pixels / 96 ;
2017-06-11 08:32:59 +00:00
}