2017-06-17 21:27:51 +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-07-13 09:22:26 +00:00
import { EventEmitter } from './EventEmitter.js' ;
import { assert } from './assert.js' ;
2022-03-02 12:03:44 +00:00
import { helper , debugError } from './helper.js' ;
2020-07-13 09:22:26 +00:00
import { ExecutionContext , EVALUATION_SCRIPT_URL } from './ExecutionContext.js' ;
import {
LifecycleWatcher ,
PuppeteerLifeCycleEvent ,
} from './LifecycleWatcher.js' ;
import { DOMWorld , WaitForSelectorOptions } from './DOMWorld.js' ;
import { NetworkManager } from './NetworkManager.js' ;
import { TimeoutSettings } from './TimeoutSettings.js' ;
2021-10-28 09:25:49 +00:00
import { Connection , CDPSession } from './Connection.js' ;
2020-07-13 09:22:26 +00:00
import { JSHandle , ElementHandle } from './JSHandle.js' ;
import { MouseButton } from './Input.js' ;
import { Page } from './Page.js' ;
import { HTTPResponse } from './HTTPResponse.js' ;
2020-07-10 10:51:52 +00:00
import { Protocol } from 'devtools-protocol' ;
2020-07-01 11:44:08 +00:00
import {
SerializableOrJSHandle ,
EvaluateHandleFn ,
2020-07-02 09:09:34 +00:00
WrapElementHandle ,
2020-07-10 10:52:13 +00:00
EvaluateFn ,
EvaluateFnReturnType ,
UnwrapPromiseLike ,
2020-07-13 09:22:26 +00:00
} from './EvalTypes.js' ;
2017-10-12 08:26:44 +00:00
2019-01-22 22:55:33 +00:00
const UTILITY_WORLD_NAME = '__puppeteer_utility_world__' ;
2021-09-15 11:45:59 +00:00
const xPathPattern = /^\(\/\/[^\)]+\)|^\/\// ;
2019-01-22 22:55:33 +00:00
2020-07-08 10:00:11 +00:00
/ * *
* We use symbols to prevent external parties listening to these events .
* They are internal to Puppeteer .
*
* @internal
* /
export const FrameManagerEmittedEvents = {
FrameAttached : Symbol ( 'FrameManager.FrameAttached' ) ,
FrameNavigated : Symbol ( 'FrameManager.FrameNavigated' ) ,
FrameDetached : Symbol ( 'FrameManager.FrameDetached' ) ,
2022-03-09 11:24:17 +00:00
FrameSwapped : Symbol ( 'FrameManager.FrameSwapped' ) ,
2020-07-08 10:00:11 +00:00
LifecycleEvent : Symbol ( 'FrameManager.LifecycleEvent' ) ,
FrameNavigatedWithinDocument : Symbol (
'FrameManager.FrameNavigatedWithinDocument'
) ,
ExecutionContextCreated : Symbol ( 'FrameManager.ExecutionContextCreated' ) ,
ExecutionContextDestroyed : Symbol ( 'FrameManager.ExecutionContextDestroyed' ) ,
} ;
2020-07-02 15:13:22 +00:00
/ * *
* @internal
* /
2020-04-29 11:28:16 +00:00
export class FrameManager extends EventEmitter {
_client : CDPSession ;
2020-07-02 15:13:22 +00:00
private _page : Page ;
private _networkManager : NetworkManager ;
2020-04-29 11:28:16 +00:00
_timeoutSettings : TimeoutSettings ;
2020-07-02 15:13:22 +00:00
private _frames = new Map < string , Frame > ( ) ;
2021-10-28 09:25:49 +00:00
private _contextIdToContext = new Map < string , ExecutionContext > ( ) ;
2020-07-02 15:13:22 +00:00
private _isolatedWorlds = new Set < string > ( ) ;
private _mainFrame : Frame ;
2020-04-29 11:28:16 +00:00
2020-05-07 10:54:55 +00:00
constructor (
client : CDPSession ,
page : Page ,
ignoreHTTPSErrors : boolean ,
timeoutSettings : TimeoutSettings
) {
2017-06-21 20:51:06 +00:00
super ( ) ;
this . _client = client ;
2017-10-07 07:28:24 +00:00
this . _page = page ;
2019-09-05 01:11:58 +00:00
this . _networkManager = new NetworkManager ( client , ignoreHTTPSErrors , this ) ;
2019-01-29 01:16:12 +00:00
this . _timeoutSettings = timeoutSettings ;
2021-10-28 09:25:49 +00:00
this . setupEventListeners ( this . _client ) ;
}
private setupEventListeners ( session : CDPSession ) {
session . on ( 'Page.frameAttached' , ( event ) = > {
this . _onFrameAttached ( session , event . frameId , event . parentFrameId ) ;
} ) ;
session . on ( 'Page.frameNavigated' , ( event ) = > {
this . _onFrameNavigated ( event . frame ) ;
} ) ;
session . on ( 'Page.navigatedWithinDocument' , ( event ) = > {
this . _onFrameNavigatedWithinDocument ( event . frameId , event . url ) ;
} ) ;
session . on (
'Page.frameDetached' ,
( event : Protocol.Page.FrameDetachedEvent ) = > {
this . _onFrameDetached (
event . frameId ,
event . reason as Protocol . Page . FrameDetachedEventReason
) ;
}
2020-09-21 11:23:36 +00:00
) ;
2022-05-17 12:15:44 +00:00
session . on ( 'Page.frameStartedLoading' , ( event ) = > {
this . _onFrameStartedLoading ( event . frameId ) ;
} ) ;
2021-10-28 09:25:49 +00:00
session . on ( 'Page.frameStoppedLoading' , ( event ) = > {
this . _onFrameStoppedLoading ( event . frameId ) ;
} ) ;
session . on ( 'Runtime.executionContextCreated' , ( event ) = > {
this . _onExecutionContextCreated ( event . context , session ) ;
} ) ;
session . on ( 'Runtime.executionContextDestroyed' , ( event ) = > {
this . _onExecutionContextDestroyed ( event . executionContextId , session ) ;
} ) ;
session . on ( 'Runtime.executionContextsCleared' , ( ) = > {
this . _onExecutionContextsCleared ( session ) ;
} ) ;
session . on ( 'Page.lifecycleEvent' , ( event ) = > {
this . _onLifecycleEvent ( event ) ;
} ) ;
session . on ( 'Target.attachedToTarget' , async ( event ) = > {
this . _onAttachedToTarget ( event ) ;
} ) ;
session . on ( 'Target.detachedFromTarget' , async ( event ) = > {
this . _onDetachedFromTarget ( event ) ;
} ) ;
2019-04-10 04:42:42 +00:00
}
2021-10-28 09:25:49 +00:00
async initialize ( client : CDPSession = this . _client ) : Promise < void > {
try {
const result = await Promise . all ( [
client . send ( 'Page.enable' ) ,
client . send ( 'Page.getFrameTree' ) ,
2022-05-04 05:46:38 +00:00
client !== this . _client
? client . send ( 'Target.setAutoAttach' , {
autoAttach : true ,
waitForDebuggerOnStart : false ,
flatten : true ,
} )
: Promise . resolve ( ) ,
2021-10-28 09:25:49 +00:00
] ) ;
2020-03-31 16:42:32 +00:00
2021-10-28 09:25:49 +00:00
const { frameTree } = result [ 1 ] ;
this . _handleFrameTree ( client , frameTree ) ;
await Promise . all ( [
client . send ( 'Page.setLifecycleEventsEnabled' , { enabled : true } ) ,
client
. send ( 'Runtime.enable' )
. then ( ( ) = > this . _ensureIsolatedWorld ( client , UTILITY_WORLD_NAME ) ) ,
// TODO: Network manager is not aware of OOP iframes yet.
client === this . _client
? this . _networkManager . initialize ( )
: Promise . resolve ( ) ,
] ) ;
} catch ( error ) {
// The target might have been closed before the initialization finished.
if (
error . message . includes ( 'Target closed' ) ||
error . message . includes ( 'Session closed' )
) {
return ;
}
throw error ;
}
2019-04-10 04:42:42 +00:00
}
2020-04-30 10:15:27 +00:00
networkManager ( ) : NetworkManager {
2019-04-10 04:42:42 +00:00
return this . _networkManager ;
2017-10-18 02:14:57 +00:00
}
2020-05-07 10:54:55 +00:00
async navigateFrame (
frame : Frame ,
url : string ,
options : {
referer? : string ;
timeout? : number ;
waitUntil? : PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent [ ] ;
} = { }
2020-05-29 10:49:30 +00:00
) : Promise < HTTPResponse | null > {
2018-11-20 23:32:46 +00:00
assertNoLegacyNavigationOptions ( options ) ;
2018-11-12 20:59:21 +00:00
const {
referer = this . _networkManager . extraHTTPHeaders ( ) [ 'referer' ] ,
2018-11-20 23:32:46 +00:00
waitUntil = [ 'load' ] ,
2019-01-29 01:16:12 +00:00
timeout = this . _timeoutSettings . navigationTimeout ( ) ,
2018-11-12 20:59:21 +00:00
} = options ;
2018-09-19 20:12:28 +00:00
2018-11-20 23:32:46 +00:00
const watcher = new LifecycleWatcher ( this , frame , waitUntil , timeout ) ;
2018-09-19 20:12:28 +00:00
let error = await Promise . race ( [
2018-11-12 20:59:21 +00:00
navigate ( this . _client , url , referer , frame . _id ) ,
2018-09-19 20:12:28 +00:00
watcher . timeoutOrTerminationPromise ( ) ,
] ) ;
if ( ! error ) {
error = await Promise . race ( [
watcher . timeoutOrTerminationPromise ( ) ,
2022-05-30 08:30:30 +00:00
watcher . newDocumentNavigationPromise ( ) ,
watcher . sameDocumentNavigationPromise ( ) ,
2018-09-19 20:12:28 +00:00
] ) ;
}
watcher . dispose ( ) ;
2020-05-07 10:54:55 +00:00
if ( error ) throw error ;
2021-11-23 07:19:14 +00:00
return await watcher . navigationResponse ( ) ;
2018-09-19 20:12:28 +00:00
2020-05-07 10:54:55 +00:00
async function navigate (
client : CDPSession ,
url : string ,
referrer : string ,
frameId : string
) : Promise < Error | null > {
2018-09-19 20:12:28 +00:00
try {
2020-05-07 10:54:55 +00:00
const response = await client . send ( 'Page.navigate' , {
url ,
referrer ,
frameId ,
} ) ;
return response . errorText
? new Error ( ` ${ response . errorText } at ${ url } ` )
: null ;
2018-09-19 20:12:28 +00:00
} catch ( error ) {
return error ;
}
}
}
2020-05-07 10:54:55 +00:00
async waitForFrameNavigation (
frame : Frame ,
options : {
timeout? : number ;
waitUntil? : PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent [ ] ;
} = { }
2020-05-29 10:49:30 +00:00
) : Promise < HTTPResponse | null > {
2018-11-20 23:32:46 +00:00
assertNoLegacyNavigationOptions ( options ) ;
2018-11-12 20:59:21 +00:00
const {
2018-11-20 23:32:46 +00:00
waitUntil = [ 'load' ] ,
2019-01-29 01:16:12 +00:00
timeout = this . _timeoutSettings . navigationTimeout ( ) ,
2018-11-12 20:59:21 +00:00
} = options ;
2018-11-20 23:32:46 +00:00
const watcher = new LifecycleWatcher ( this , frame , waitUntil , timeout ) ;
2018-09-19 20:12:28 +00:00
const error = await Promise . race ( [
watcher . timeoutOrTerminationPromise ( ) ,
watcher . sameDocumentNavigationPromise ( ) ,
2020-05-07 10:54:55 +00:00
watcher . newDocumentNavigationPromise ( ) ,
2018-09-19 20:12:28 +00:00
] ) ;
watcher . dispose ( ) ;
2020-05-07 10:54:55 +00:00
if ( error ) throw error ;
2021-11-23 07:19:14 +00:00
return await watcher . navigationResponse ( ) ;
2018-09-19 20:12:28 +00:00
}
2021-10-28 09:25:49 +00:00
private async _onAttachedToTarget (
event : Protocol.Target.AttachedToTargetEvent
) {
2020-09-21 11:23:36 +00:00
if ( event . targetInfo . type !== 'iframe' ) {
return ;
}
2021-10-28 09:25:49 +00:00
const frame = this . _frames . get ( event . targetInfo . targetId ) ;
const session = Connection . fromSession ( this . _client ) . session (
event . sessionId
2020-09-21 11:23:36 +00:00
) ;
2022-01-17 07:00:00 +00:00
if ( frame ) frame . _updateClient ( session ) ;
2021-10-28 09:25:49 +00:00
this . setupEventListeners ( session ) ;
await this . initialize ( session ) ;
}
private async _onDetachedFromTarget (
event : Protocol.Target.DetachedFromTargetEvent
) {
const frame = this . _frames . get ( event . targetId ) ;
if ( frame && frame . isOOPFrame ( ) ) {
// When an OOP iframe is removed from the page, it
// will only get a Target.detachedFromTarget event.
this . _removeFramesRecursively ( frame ) ;
}
2020-09-21 11:23:36 +00:00
}
2020-07-10 10:51:52 +00:00
_onLifecycleEvent ( event : Protocol.Page.LifecycleEventEvent ) : void {
2017-11-10 23:33:14 +00:00
const frame = this . _frames . get ( event . frameId ) ;
2020-05-07 10:54:55 +00:00
if ( ! frame ) return ;
2017-11-10 23:33:14 +00:00
frame . _onLifecycleEvent ( event . loaderId , event . name ) ;
2020-07-08 10:00:11 +00:00
this . emit ( FrameManagerEmittedEvents . LifecycleEvent , frame ) ;
2017-11-10 23:33:14 +00:00
}
2022-05-17 12:15:44 +00:00
_onFrameStartedLoading ( frameId : string ) : void {
const frame = this . _frames . get ( frameId ) ;
if ( ! frame ) return ;
frame . _onLoadingStarted ( ) ;
}
2020-04-29 11:28:16 +00:00
_onFrameStoppedLoading ( frameId : string ) : void {
2018-04-10 22:59:41 +00:00
const frame = this . _frames . get ( frameId ) ;
2020-05-07 10:54:55 +00:00
if ( ! frame ) return ;
2018-04-10 22:59:41 +00:00
frame . _onLoadingStopped ( ) ;
2020-07-08 10:00:11 +00:00
this . emit ( FrameManagerEmittedEvents . LifecycleEvent , frame ) ;
2018-04-10 22:59:41 +00:00
}
2021-10-28 09:25:49 +00:00
_handleFrameTree (
session : CDPSession ,
frameTree : Protocol.Page.FrameTree
) : void {
if ( frameTree . frame . parentId ) {
this . _onFrameAttached (
session ,
frameTree . frame . id ,
frameTree . frame . parentId
) ;
}
2017-10-18 02:14:57 +00:00
this . _onFrameNavigated ( frameTree . frame ) ;
2020-05-07 10:54:55 +00:00
if ( ! frameTree . childFrames ) return ;
2017-10-18 02:14:57 +00:00
2021-10-28 09:25:49 +00:00
for ( const child of frameTree . childFrames ) {
this . _handleFrameTree ( session , child ) ;
}
2017-06-21 20:51:06 +00:00
}
2020-05-05 12:53:22 +00:00
page ( ) : Page {
2018-08-16 23:16:27 +00:00
return this . _page ;
}
2020-04-29 11:28:16 +00:00
mainFrame ( ) : Frame {
2017-06-21 20:51:06 +00:00
return this . _mainFrame ;
}
2017-06-21 20:36:04 +00:00
2020-04-29 11:28:16 +00:00
frames ( ) : Frame [ ] {
2017-06-21 20:51:06 +00:00
return Array . from ( this . _frames . values ( ) ) ;
}
2017-06-21 20:36:04 +00:00
2020-04-29 11:28:16 +00:00
frame ( frameId : string ) : Frame | null {
2018-01-10 02:47:21 +00:00
return this . _frames . get ( frameId ) || null ;
}
2021-10-28 09:25:49 +00:00
_onFrameAttached (
session : CDPSession ,
frameId : string ,
parentFrameId? : string
) : void {
if ( this . _frames . has ( frameId ) ) {
const frame = this . _frames . get ( frameId ) ;
if ( session && frame . isOOPFrame ( ) ) {
// If an OOP iframes becomes a normal iframe again
// it is first attached to the parent page before
// the target is removed.
frame . _updateClient ( session ) ;
}
return ;
}
2018-05-31 23:53:51 +00:00
assert ( parentFrameId ) ;
2017-08-21 23:39:04 +00:00
const parentFrame = this . _frames . get ( parentFrameId ) ;
2021-10-28 09:25:49 +00:00
const frame = new Frame ( this , parentFrame , frameId , session ) ;
2017-06-21 20:51:06 +00:00
this . _frames . set ( frame . _id , frame ) ;
2020-07-08 10:00:11 +00:00
this . emit ( FrameManagerEmittedEvents . FrameAttached , frame ) ;
2017-06-21 20:51:06 +00:00
}
2020-04-29 11:28:16 +00:00
_onFrameNavigated ( framePayload : Protocol.Page.Frame ) : void {
2017-07-28 23:52:38 +00:00
const isMainFrame = ! framePayload . parentId ;
2020-05-07 10:54:55 +00:00
let frame = isMainFrame
? this . _mainFrame
: this . _frames . get ( framePayload . id ) ;
assert (
isMainFrame || frame ,
'We either navigate top level or have old version of the navigated frame'
) ;
2017-07-28 23:52:38 +00:00
// Detach all child frames first.
if ( frame ) {
2017-08-21 23:39:04 +00:00
for ( const child of frame . childFrames ( ) )
2017-07-28 23:52:38 +00:00
this . _removeFramesRecursively ( child ) ;
}
// Update or create main frame.
if ( isMainFrame ) {
if ( frame ) {
// Update frame id to retain frame identity on cross-process navigation.
2017-10-10 05:31:40 +00:00
this . _frames . delete ( frame . _id ) ;
2017-07-28 23:52:38 +00:00
frame . _id = framePayload . id ;
} else {
// Initial main frame navigation.
2021-10-28 09:25:49 +00:00
frame = new Frame ( this , null , framePayload . id , this . _client ) ;
2017-07-28 23:52:38 +00:00
}
this . _frames . set ( framePayload . id , frame ) ;
this . _mainFrame = frame ;
2017-06-21 20:36:04 +00:00
}
2017-07-28 23:52:38 +00:00
// Update frame payload.
frame . _navigated ( framePayload ) ;
2020-07-08 10:00:11 +00:00
this . emit ( FrameManagerEmittedEvents . FrameNavigated , frame ) ;
2017-06-21 20:51:06 +00:00
}
2017-06-21 20:36:04 +00:00
2021-10-28 09:25:49 +00:00
async _ensureIsolatedWorld ( session : CDPSession , name : string ) : Promise < void > {
const key = ` ${ session . id ( ) } : ${ name } ` ;
if ( this . _isolatedWorlds . has ( key ) ) return ;
this . _isolatedWorlds . add ( key ) ;
await session . send ( 'Page.addScriptToEvaluateOnNewDocument' , {
2019-01-22 22:55:33 +00:00
source : ` //# sourceURL= ${ EVALUATION_SCRIPT_URL } ` ,
worldName : name ,
2020-11-26 11:43:42 +00:00
} ) ;
// Frames might be removed before we send this.
await Promise . all (
2021-10-28 09:25:49 +00:00
this . frames ( )
. filter ( ( frame ) = > frame . _client === session )
. map ( ( frame ) = >
2022-03-02 12:03:44 +00:00
session
. send ( 'Page.createIsolatedWorld' , {
frameId : frame._id ,
worldName : name ,
grantUniveralAccess : true ,
} )
. catch ( debugError )
2021-10-28 09:25:49 +00:00
)
2020-11-26 11:43:42 +00:00
) ;
2019-01-22 22:55:33 +00:00
}
2020-04-29 11:28:16 +00:00
_onFrameNavigatedWithinDocument ( frameId : string , url : string ) : void {
2018-04-10 06:38:20 +00:00
const frame = this . _frames . get ( frameId ) ;
2020-05-07 10:54:55 +00:00
if ( ! frame ) return ;
2018-04-10 06:38:20 +00:00
frame . _navigatedWithinDocument ( url ) ;
2020-07-08 10:00:11 +00:00
this . emit ( FrameManagerEmittedEvents . FrameNavigatedWithinDocument , frame ) ;
this . emit ( FrameManagerEmittedEvents . FrameNavigated , frame ) ;
2018-04-10 06:38:20 +00:00
}
2021-10-28 09:25:49 +00:00
_onFrameDetached (
frameId : string ,
reason : Protocol.Page.FrameDetachedEventReason
) : void {
2017-08-21 23:39:04 +00:00
const frame = this . _frames . get ( frameId ) ;
2021-10-28 09:25:49 +00:00
if ( reason === 'remove' ) {
// Only remove the frame if the reason for the detached event is
// an actual removement of the frame.
// For frames that become OOP iframes, the reason would be 'swap'.
if ( frame ) this . _removeFramesRecursively ( frame ) ;
2022-03-09 11:24:17 +00:00
} else if ( reason === 'swap' ) {
this . emit ( FrameManagerEmittedEvents . FrameSwapped , frame ) ;
2021-10-28 09:25:49 +00:00
}
2017-06-21 20:51:06 +00:00
}
2017-06-21 20:36:04 +00:00
2020-05-07 10:54:55 +00:00
_onExecutionContextCreated (
2021-10-28 09:25:49 +00:00
contextPayload : Protocol.Runtime.ExecutionContextDescription ,
session : CDPSession
2020-05-07 10:54:55 +00:00
) : void {
const auxData = contextPayload . auxData as { frameId? : string } ;
2020-04-29 11:28:16 +00:00
const frameId = auxData ? auxData.frameId : null ;
2018-08-09 21:57:08 +00:00
const frame = this . _frames . get ( frameId ) || null ;
2019-01-22 22:55:33 +00:00
let world = null ;
if ( frame ) {
2021-10-28 09:25:49 +00:00
// Only care about execution contexts created for the current session.
if ( frame . _client !== session ) return ;
2019-04-11 20:26:18 +00:00
if ( contextPayload . auxData && ! ! contextPayload . auxData [ 'isDefault' ] ) {
2019-01-22 22:55:33 +00:00
world = frame . _mainWorld ;
2020-05-07 10:54:55 +00:00
} else if (
contextPayload . name === UTILITY_WORLD_NAME &&
! frame . _secondaryWorld . _hasContext ( )
) {
2019-04-11 20:26:18 +00:00
// In case of multiple sessions to the same target, there's a race between
// connections so we might end up creating multiple isolated worlds.
// We can use either.
2019-01-22 22:55:33 +00:00
world = frame . _secondaryWorld ;
2019-04-11 20:26:18 +00:00
}
2019-01-22 22:55:33 +00:00
}
2021-10-28 09:25:49 +00:00
const context = new ExecutionContext (
2021-11-15 13:33:45 +00:00
frame ? . _client || this . _client ,
2021-10-28 09:25:49 +00:00
contextPayload ,
world
) ;
2020-05-07 10:54:55 +00:00
if ( world ) world . _setContext ( context ) ;
2021-10-28 09:25:49 +00:00
const key = ` ${ session . id ( ) } : ${ contextPayload . id } ` ;
this . _contextIdToContext . set ( key , context ) ;
2017-11-19 00:27:52 +00:00
}
2021-10-28 09:25:49 +00:00
private _onExecutionContextDestroyed (
executionContextId : number ,
session : CDPSession
) : void {
const key = ` ${ session . id ( ) } : ${ executionContextId } ` ;
const context = this . _contextIdToContext . get ( key ) ;
2020-05-07 10:54:55 +00:00
if ( ! context ) return ;
2021-10-28 09:25:49 +00:00
this . _contextIdToContext . delete ( key ) ;
2020-05-07 10:54:55 +00:00
if ( context . _world ) context . _world . _setContext ( null ) ;
2017-06-21 20:51:06 +00:00
}
2017-06-21 20:36:04 +00:00
2021-10-28 09:25:49 +00:00
private _onExecutionContextsCleared ( session : CDPSession ) : void {
for ( const [ key , context ] of this . _contextIdToContext . entries ( ) ) {
// Make sure to only clear execution contexts that belong
// to the current session.
if ( context . _client !== session ) continue ;
2020-05-07 10:54:55 +00:00
if ( context . _world ) context . _world . _setContext ( null ) ;
2021-10-28 09:25:49 +00:00
this . _contextIdToContext . delete ( key ) ;
2018-08-09 21:57:08 +00:00
}
2017-10-06 22:35:02 +00:00
}
2021-10-28 09:25:49 +00:00
executionContextById (
contextId : number ,
session : CDPSession = this . _client
) : ExecutionContext {
const key = ` ${ session . id ( ) } : ${ contextId } ` ;
const context = this . _contextIdToContext . get ( key ) ;
2018-05-31 23:53:51 +00:00
assert ( context , 'INTERNAL ERROR: missing context with id = ' + contextId ) ;
2018-08-01 20:53:08 +00:00
return context ;
}
2020-07-02 15:13:22 +00:00
private _removeFramesRecursively ( frame : Frame ) : void {
2017-08-21 23:39:04 +00:00
for ( const child of frame . childFrames ( ) )
2017-06-21 20:51:06 +00:00
this . _removeFramesRecursively ( child ) ;
frame . _detach ( ) ;
this . _frames . delete ( frame . _id ) ;
2020-07-08 10:00:11 +00:00
this . emit ( FrameManagerEmittedEvents . FrameDetached , frame ) ;
2017-06-21 20:51:06 +00:00
}
2017-06-17 21:27:51 +00:00
}
2020-07-02 15:13:22 +00:00
/ * *
* @public
* /
2020-07-09 13:22:58 +00:00
export interface FrameWaitForFunctionOptions {
/ * *
* An interval at which the ` pageFunction ` is executed , defaults to ` raf ` . If
* ` polling ` is a number , then it is treated as an interval in milliseconds at
* which the function would be executed . If ` polling ` is a string , then it can
* be one of the following values :
*
* - ` raf ` - to constantly execute ` pageFunction ` in ` requestAnimationFrame `
* callback . This is the tightest polling mode which is suitable to observe
* styling changes .
*
* - ` mutation ` - to execute ` pageFunction ` on every DOM mutation .
* /
polling? : string | number ;
/ * *
* Maximum time to wait in milliseconds . Defaults to ` 30000 ` ( 30 seconds ) .
* Pass ` 0 ` to disable the timeout . Puppeteer ' s default timeout can be changed
* using { @link Page . setDefaultTimeout } .
* /
timeout? : number ;
}
/ * *
* @public
* /
export interface FrameAddScriptTagOptions {
/ * *
* the URL of the script to be added .
* /
url? : string ;
/ * *
* The path to a JavaScript file to be injected into the frame .
* @remarks
* If ` path ` is a relative path , it is resolved relative to the current
* working directory ( ` process.cwd() ` in Node . js ) .
* /
path? : string ;
/ * *
* Raw JavaScript content to be injected into the frame .
* /
content? : string ;
/ * *
* Set the script ' s ` type ` . Use ` module ` in order to load an ES2015 module .
* /
type ? : string ;
}
/ * *
* @public
* /
export interface FrameAddStyleTagOptions {
/ * *
* the URL of the CSS file to be added .
* /
url? : string ;
/ * *
* The path to a CSS file to be injected into the frame .
* @remarks
* If ` path ` is a relative path , it is resolved relative to the current
* working directory ( ` process.cwd() ` in Node . js ) .
* /
path? : string ;
/ * *
* Raw CSS content to be injected into the frame .
* /
content? : string ;
}
/ * *
* At every point of time , page exposes its current frame tree via the
* { @link Page . mainFrame | page . mainFrame } and
* { @link Frame . childFrames | frame . childFrames } methods .
*
* @remarks
*
* ` Frame ` object lifecycles are controlled by three events that are all
* dispatched on the page object :
*
* - { @link PageEmittedEvents . FrameAttached }
*
* - { @link PageEmittedEvents . FrameNavigated }
*
* - { @link PageEmittedEvents . FrameDetached }
*
* @Example
* An example of dumping frame tree :
*
* ` ` ` js
* const puppeteer = require ( 'puppeteer' ) ;
*
* ( async ( ) = > {
* const browser = await puppeteer . launch ( ) ;
* const page = await browser . newPage ( ) ;
* await page . goto ( 'https://www.google.com/chrome/browser/canary.html' ) ;
* dumpFrameTree ( page . mainFrame ( ) , '' ) ;
* await browser . close ( ) ;
*
* function dumpFrameTree ( frame , indent ) {
* console . log ( indent + frame . url ( ) ) ;
* for ( const child of frame . childFrames ( ) ) {
* dumpFrameTree ( child , indent + ' ' ) ;
* }
* }
* } ) ( ) ;
* ` ` `
*
* @Example
* An example of getting text from an iframe element :
*
* ` ` ` js
* const frame = page . frames ( ) . find ( frame = > frame . name ( ) === 'myframe' ) ;
* const text = await frame . $eval ( '.selector' , element = > element . textContent ) ;
* console . log ( text ) ;
* ` ` `
*
* @public
* /
2020-04-29 11:28:16 +00:00
export class Frame {
2020-07-02 15:13:22 +00:00
/ * *
* @internal
* /
2020-04-29 11:28:16 +00:00
_frameManager : FrameManager ;
2020-07-02 15:13:22 +00:00
private _parentFrame? : Frame ;
/ * *
* @internal
* /
2020-04-29 11:28:16 +00:00
_id : string ;
2020-07-02 15:13:22 +00:00
private _url = '' ;
private _detached = false ;
/ * *
* @internal
* /
2020-04-29 11:28:16 +00:00
_loaderId = '' ;
2020-07-02 15:13:22 +00:00
/ * *
* @internal
* /
2020-04-29 11:28:16 +00:00
_name? : string ;
2022-05-17 12:15:44 +00:00
/ * *
* @internal
* /
_hasStartedLoading = false ;
2020-04-29 11:28:16 +00:00
2020-07-02 15:13:22 +00:00
/ * *
* @internal
* /
2020-04-29 11:28:16 +00:00
_lifecycleEvents = new Set < string > ( ) ;
2020-07-02 15:13:22 +00:00
/ * *
* @internal
* /
2020-04-29 11:28:16 +00:00
_mainWorld : DOMWorld ;
2020-07-02 15:13:22 +00:00
/ * *
* @internal
* /
2020-04-29 11:28:16 +00:00
_secondaryWorld : DOMWorld ;
2020-07-02 15:13:22 +00:00
/ * *
* @internal
* /
2020-04-29 11:28:16 +00:00
_childFrames : Set < Frame > ;
2021-10-28 09:25:49 +00:00
/ * *
* @internal
* /
_client : CDPSession ;
2020-04-29 11:28:16 +00:00
2020-07-02 15:13:22 +00:00
/ * *
* @internal
* /
2020-05-07 10:54:55 +00:00
constructor (
frameManager : FrameManager ,
parentFrame : Frame | null ,
2021-10-28 09:25:49 +00:00
frameId : string ,
client : CDPSession
2020-05-07 10:54:55 +00:00
) {
2018-08-16 23:16:27 +00:00
this . _frameManager = frameManager ;
2017-06-21 20:51:06 +00:00
this . _parentFrame = parentFrame ;
this . _url = '' ;
this . _id = frameId ;
2018-08-24 22:17:36 +00:00
this . _detached = false ;
2017-11-19 00:27:52 +00:00
2017-11-10 23:33:14 +00:00
this . _loaderId = '' ;
2021-10-28 09:25:49 +00:00
this . _childFrames = new Set ( ) ;
if ( this . _parentFrame ) this . _parentFrame . _childFrames . add ( this ) ;
this . _updateClient ( client ) ;
}
/ * *
* @internal
* /
_updateClient ( client : CDPSession ) : void {
this . _client = client ;
2020-05-07 10:54:55 +00:00
this . _mainWorld = new DOMWorld (
2021-10-28 09:25:49 +00:00
this . _client ,
this . _frameManager ,
2020-05-07 10:54:55 +00:00
this ,
2021-10-28 09:25:49 +00:00
this . _frameManager . _timeoutSettings
2020-05-07 10:54:55 +00:00
) ;
this . _secondaryWorld = new DOMWorld (
2021-10-28 09:25:49 +00:00
this . _client ,
this . _frameManager ,
2020-05-07 10:54:55 +00:00
this ,
2021-10-28 09:25:49 +00:00
this . _frameManager . _timeoutSettings
2020-05-07 10:54:55 +00:00
) ;
2021-10-28 09:25:49 +00:00
}
2017-07-20 02:04:51 +00:00
2022-02-21 14:58:37 +00:00
/ * *
* @remarks
*
* @returns ` true ` if the frame is an OOP frame , or ` false ` otherwise .
* /
2021-10-28 09:25:49 +00:00
isOOPFrame ( ) : boolean {
return this . _client !== this . _frameManager . _client ;
2017-06-21 20:51:06 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* @remarks
*
* ` frame.goto ` will throw an error if :
* - there ' s an SSL error ( e . g . in case of self - signed certificates ) .
*
* - target URL is invalid .
*
* - the ` timeout ` is exceeded during navigation .
*
* - the remote server does not respond or is unreachable .
*
* - the main resource failed to load .
*
* ` frame.goto ` will not throw an error when any valid HTTP status code is
* returned by the remote server , including 404 "Not Found" and 500 " Internal
* Server Error " . The status code for such responses can be retrieved by
* calling { @link HTTPResponse . status } .
*
* NOTE : ` frame.goto ` either throws an error or returns a main resource
* response . The only exceptions are navigation to ` about:blank ` or
* navigation to the same URL with a different hash , which would succeed and
* return ` null ` .
*
* NOTE : Headless mode doesn ' t support navigation to a PDF document . See
* the { @link https : //bugs.chromium.org/p/chromium/issues/detail?id=761295 | upstream
* issue } .
*
* @param url - the URL to navigate the frame to . This should include the
* scheme , e . g . ` https:// ` .
* @param options - navigation options . ` waitUntil ` is useful to define when
* the navigation should be considered successful - see the docs for
* { @link PuppeteerLifeCycleEvent } for more details .
*
* @returns A promise which resolves to the main resource response . In case of
* multiple redirects , the navigation will resolve with the response of the
* last redirect .
* /
2020-05-07 10:54:55 +00:00
async goto (
url : string ,
options : {
referer? : string ;
timeout? : number ;
waitUntil? : PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent [ ] ;
2020-06-18 11:44:46 +00:00
} = { }
2020-05-29 10:49:30 +00:00
) : Promise < HTTPResponse | null > {
2018-09-20 18:31:19 +00:00
return await this . _frameManager . navigateFrame ( this , url , options ) ;
}
2020-07-09 13:22:58 +00:00
/ * *
* @remarks
*
* This resolves when the frame navigates to a new URL . It is useful for when
* you run code which will indirectly cause the frame to navigate . Consider
* this example :
*
* ` ` ` js
* const [ response ] = await Promise . all ( [
* // The navigation promise resolves after navigation has finished
* frame . waitForNavigation ( ) ,
* // Clicking the link will indirectly cause a navigation
* frame . click ( 'a.my-link' ) ,
* ] ) ;
* ` ` `
*
* Usage of the { @link https : //developer.mozilla.org/en-US/docs/Web/API/History_API | History API} to change the URL is considered a navigation.
*
* @param options - options to configure when the navigation is consided finished .
* @returns a promise that resolves when the frame navigates to a new URL .
* /
2020-06-23 05:18:46 +00:00
async waitForNavigation (
options : {
timeout? : number ;
waitUntil? : PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent [ ] ;
} = { }
) : Promise < HTTPResponse | null > {
2018-09-20 18:31:19 +00:00
return await this . _frameManager . waitForFrameNavigation ( this , options ) ;
}
2022-02-21 14:58:37 +00:00
/ * *
* @internal
* /
client ( ) : CDPSession {
return this . _client ;
}
2020-07-09 13:22:58 +00:00
/ * *
* @returns a promise that resolves to the frame ' s default execution context .
* /
2020-04-29 11:28:16 +00:00
executionContext ( ) : Promise < ExecutionContext > {
2019-01-16 01:21:23 +00:00
return this . _mainWorld . executionContext ( ) ;
2017-10-06 22:35:02 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* @remarks
*
* The only difference between { @link Frame . evaluate } and
* ` frame.evaluateHandle ` is that ` evaluateHandle ` will return the value
* wrapped in an in - page object .
*
* This method behaves identically to { @link Page . evaluateHandle } except it ' s
* run within the context of the ` frame ` , rather than the entire page .
*
* @param pageFunction - a function that is run within the frame
* @param args - arguments to be passed to the pageFunction
* /
2020-07-01 11:44:08 +00:00
async evaluateHandle < HandlerType extends JSHandle = JSHandle > (
pageFunction : EvaluateHandleFn ,
. . . args : SerializableOrJSHandle [ ]
) : Promise < HandlerType > {
return this . _mainWorld . evaluateHandle < HandlerType > ( pageFunction , . . . args ) ;
2018-01-25 05:16:01 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* @remarks
*
* This method behaves identically to { @link Page . evaluate } except it ' s run
* within the context of the ` frame ` , rather than the entire page .
*
* @param pageFunction - a function that is run within the frame
* @param args - arguments to be passed to the pageFunction
* /
2020-07-10 10:52:13 +00:00
async evaluate < T extends EvaluateFn > (
pageFunction : T ,
. . . args : SerializableOrJSHandle [ ]
) : Promise < UnwrapPromiseLike < EvaluateFnReturnType < T > >> {
return this . _mainWorld . evaluate < T > ( pageFunction , . . . args ) ;
2017-08-15 21:54:02 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* This method queries the frame for the given selector .
*
* @param selector - a selector to query for .
* @returns A promise which resolves to an ` ElementHandle ` pointing at the
* element , or ` null ` if it was not found .
* /
2021-03-25 11:40:34 +00:00
async $ < T extends Element = Element > (
selector : string
) : Promise < ElementHandle < T > | null > {
return this . _mainWorld . $ < T > ( selector ) ;
2017-12-20 00:23:45 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* This method evaluates the given XPath expression and returns the results .
*
* @param expression - the XPath expression to evaluate .
* /
2020-04-29 11:28:16 +00:00
async $x ( expression : string ) : Promise < ElementHandle [ ] > {
2019-01-16 01:21:23 +00:00
return this . _mainWorld . $x ( expression ) ;
2017-08-15 21:54:02 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* @remarks
*
* This method runs ` document.querySelector ` within
* the frame and passes it as the first argument to ` pageFunction ` .
*
* If ` pageFunction ` returns a Promise , then ` frame. $ eval ` would wait for
* the promise to resolve and return its value .
*
* @example
*
* ` ` ` js
* const searchValue = await frame . $eval ( '#search' , el = > el . value ) ;
* ` ` `
*
* @param selector - the selector to query for
* @param pageFunction - the function to be evaluated in the frame ' s context
2022-04-06 08:32:31 +00:00
* @param args - additional arguments to pass to ` pageFunction `
2020-07-09 13:22:58 +00:00
* /
2020-07-02 09:09:34 +00:00
async $eval < ReturnType > (
2020-05-07 10:54:55 +00:00
selector : string ,
2020-07-02 09:09:34 +00:00
pageFunction : (
element : Element ,
. . . args : unknown [ ]
) = > ReturnType | Promise < ReturnType > ,
2020-06-25 12:38:01 +00:00
. . . args : SerializableOrJSHandle [ ]
2020-07-02 09:09:34 +00:00
) : Promise < WrapElementHandle < ReturnType > > {
2020-04-29 11:28:16 +00:00
return this . _mainWorld . $eval < ReturnType > ( selector , pageFunction , . . . args ) ;
2017-08-31 22:38:01 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* @remarks
*
* This method runs ` Array.from(document.querySelectorAll(selector)) ` within
* the frame and passes it as the first argument to ` pageFunction ` .
*
* If ` pageFunction ` returns a Promise , then ` frame. $ $ eval ` would wait for
* the promise to resolve and return its value .
*
* @example
*
* ` ` ` js
* const divsCounts = await frame . $ $eval ( 'div' , divs = > divs . length ) ;
* ` ` `
*
* @param selector - the selector to query for
* @param pageFunction - the function to be evaluated in the frame ' s context
2022-04-06 08:32:31 +00:00
* @param args - additional arguments to pass to ` pageFunction `
2020-07-09 13:22:58 +00:00
* /
2020-07-03 14:23:51 +00:00
async $ $eval < ReturnType > (
2020-05-07 10:54:55 +00:00
selector : string ,
2020-07-03 14:23:51 +00:00
pageFunction : (
elements : Element [ ] ,
. . . args : unknown [ ]
) = > ReturnType | Promise < ReturnType > ,
2020-06-25 12:38:01 +00:00
. . . args : SerializableOrJSHandle [ ]
2020-07-03 14:23:51 +00:00
) : Promise < WrapElementHandle < ReturnType > > {
2020-04-29 11:28:16 +00:00
return this . _mainWorld . $ $eval < ReturnType > ( selector , pageFunction , . . . args ) ;
2017-10-11 06:23:14 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* This runs ` document.querySelectorAll ` in the frame and returns the result .
*
* @param selector - a selector to search for
* @returns An array of element handles pointing to the found frame elements .
* /
2021-03-25 11:40:34 +00:00
async $ $ < T extends Element = Element > (
selector : string
) : Promise < Array < ElementHandle < T > >> {
return this . _mainWorld . $ $ < T > ( selector ) ;
2017-08-23 05:56:55 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* @returns the full HTML contents of the frame , including the doctype .
* /
2020-04-29 11:28:16 +00:00
async content ( ) : Promise < string > {
2019-01-23 04:24:14 +00:00
return this . _secondaryWorld . content ( ) ;
2017-11-23 02:44:33 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* Set the content of the frame .
*
* @param html - HTML markup to assign to the page .
* @param options - options to configure how long before timing out and at
* what point to consider the content setting successful .
* /
2020-05-07 10:54:55 +00:00
async setContent (
html : string ,
options : {
timeout? : number ;
waitUntil? : PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent [ ] ;
} = { }
) : Promise < void > {
2019-01-23 04:24:14 +00:00
return this . _secondaryWorld . setContent ( html , options ) ;
2017-11-23 02:44:33 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* @remarks
*
* If the name is empty , it returns the ` id ` attribute instead .
*
* Note : This value is calculated once when the frame is created , and will not
* update if the attribute is changed later .
*
* @returns the frame ' s ` name ` attribute as specified in the tag .
* /
2020-04-29 11:28:16 +00:00
name ( ) : string {
2017-06-21 20:51:06 +00:00
return this . _name || '' ;
}
2017-06-21 20:36:04 +00:00
2020-07-09 13:22:58 +00:00
/ * *
* @returns the frame ' s URL .
* /
2020-04-29 11:28:16 +00:00
url ( ) : string {
2017-06-21 20:51:06 +00:00
return this . _url ;
}
2017-06-21 20:36:04 +00:00
2020-07-09 13:22:58 +00:00
/ * *
* @returns the parent ` Frame ` , if any . Detached and main frames return ` null ` .
* /
2020-04-29 11:28:16 +00:00
parentFrame ( ) : Frame | null {
2017-06-21 20:51:06 +00:00
return this . _parentFrame ;
}
2017-06-21 20:36:04 +00:00
2020-07-09 13:22:58 +00:00
/ * *
* @returns an array of child frames .
* /
2020-04-29 11:28:16 +00:00
childFrames ( ) : Frame [ ] {
2017-06-21 20:51:06 +00:00
return Array . from ( this . _childFrames ) ;
}
2017-06-21 20:36:04 +00:00
2020-07-09 13:22:58 +00:00
/ * *
* @returns ` true ` if the frame has been detached , or ` false ` otherwise .
* /
2020-04-29 11:28:16 +00:00
isDetached ( ) : boolean {
2017-06-21 20:51:06 +00:00
return this . _detached ;
}
2017-06-21 20:36:04 +00:00
2020-07-09 13:22:58 +00:00
/ * *
* Adds a ` <script> ` tag into the page with the desired url or content .
*
* @param options - configure the script to add to the page .
*
* @returns a promise that resolves to the added tag when the script ' s
* ` onload ` event fires or when the script content was injected into the
* frame .
* /
async addScriptTag (
options : FrameAddScriptTagOptions
) : Promise < ElementHandle > {
2019-01-16 01:21:23 +00:00
return this . _mainWorld . addScriptTag ( options ) ;
2017-07-25 18:37:46 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* Adds a ` <link rel="stylesheet"> ` tag into the page with the desired url or
* a ` <style type="text/css"> ` tag with the content .
*
* @param options - configure the CSS to add to the page .
*
* @returns a promise that resolves to the added tag when the stylesheets ' s
* ` onload ` event fires or when the CSS content was injected into the
* frame .
* /
async addStyleTag ( options : FrameAddStyleTagOptions ) : Promise < ElementHandle > {
2019-01-16 01:21:23 +00:00
return this . _mainWorld . addStyleTag ( options ) ;
2017-10-04 20:42:26 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
*
* This method clicks the first element found that matches ` selector ` .
*
* @remarks
*
* This method scrolls the element into view if needed , and then uses
* { @link Page . mouse } to click in the center of the element . If there ' s no
* element matching ` selector ` , the method throws an error .
*
* Bear in mind that if ` click() ` triggers a navigation event and there ' s a
* separate ` page.waitForNavigation() ` promise to be resolved , you may end up
* with a race condition that yields unexpected results . The correct pattern
* for click and wait for navigation is the following :
*
* ` ` ` javascript
* const [ response ] = await Promise . all ( [
* page . waitForNavigation ( waitOptions ) ,
* frame . click ( selector , clickOptions ) ,
* ] ) ;
* ` ` `
* @param selector - the selector to search for to click . If there are
* multiple elements , the first will be clicked .
* /
2020-05-07 10:54:55 +00:00
async click (
selector : string ,
2020-06-23 05:18:46 +00:00
options : {
delay? : number ;
2020-07-02 11:15:39 +00:00
button? : MouseButton ;
2020-06-23 05:18:46 +00:00
clickCount? : number ;
} = { }
2020-05-07 10:54:55 +00:00
) : Promise < void > {
2019-01-23 04:24:14 +00:00
return this . _secondaryWorld . click ( selector , options ) ;
2018-02-05 22:58:03 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* This method fetches an element with ` selector ` and focuses it .
*
* @remarks
* If there ' s no element matching ` selector ` , the method throws an error .
*
* @param selector - the selector for the element to focus . If there are
* multiple elements , the first will be focused .
* /
2020-04-29 11:28:16 +00:00
async focus ( selector : string ) : Promise < void > {
2019-01-23 04:24:14 +00:00
return this . _secondaryWorld . focus ( selector ) ;
2018-02-05 22:58:03 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* This method fetches an element with ` selector ` , scrolls it into view if
* needed , and then uses { @link Page . mouse } to hover over the center of the
* element .
*
* @remarks
* If there ' s no element matching ` selector ` , the method throws an
*
* @param selector - the selector for the element to hover . If there are
* multiple elements , the first will be hovered .
* /
2020-04-29 11:28:16 +00:00
async hover ( selector : string ) : Promise < void > {
2019-01-23 04:24:14 +00:00
return this . _secondaryWorld . hover ( selector ) ;
2018-02-05 22:58:03 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* Triggers a ` change ` and ` input ` event once all the provided options have
* been selected .
*
* @remarks
*
* If there ' s no ` <select> ` element matching ` selector ` , the
* method throws an error .
*
* @example
* ` ` ` js
* frame . select ( 'select#colors' , 'blue' ) ; // single selection
* frame . select ( 'select#colors' , 'red' , 'green' , 'blue' ) ; // multiple selections
* ` ` `
*
* @param selector - a selector to query the frame for
* @param values - an array of values to select . If the ` <select> ` has the
* ` multiple ` attribute , all values are considered , otherwise only the first
* one is taken into account .
* @returns the list of values that were successfully selected .
* /
2020-04-29 11:28:16 +00:00
select ( selector : string , . . . values : string [ ] ) : Promise < string [ ] > {
2019-01-22 22:55:33 +00:00
return this . _secondaryWorld . select ( selector , . . . values ) ;
2017-11-02 05:06:04 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* This method fetches an element with ` selector ` , scrolls it into view if
* needed , and then uses { @link Page . touchscreen } to tap in the center of the
* element .
*
* @remarks
*
* If there ' s no element matching ` selector ` , the method throws an error .
*
* @param selector - the selector to tap .
* @returns a promise that resolves when the element has been tapped .
* /
2020-04-29 11:28:16 +00:00
async tap ( selector : string ) : Promise < void > {
2019-01-23 04:24:14 +00:00
return this . _secondaryWorld . tap ( selector ) ;
2018-02-05 22:58:03 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* Sends a ` keydown ` , ` keypress ` / ` input ` , and ` keyup ` event for each character
* in the text .
*
* @remarks
* To press a special key , like ` Control ` or ` ArrowDown ` , use
* { @link Keyboard . press } .
*
* @example
* ` ` ` js
* await frame . type ( '#mytextarea' , 'Hello' ) ; // Types instantly
* await frame . type ( '#mytextarea' , 'World' , { delay : 100 } ) ; // Types slower, like a user
* ` ` `
*
* @param selector - the selector for the element to type into . If there are
* multiple the first will be used .
* @param text - text to type into the element
* @param options - takes one option , ` delay ` , which sets the time to wait
* between key presses in milliseconds . Defaults to ` 0 ` .
*
* @returns a promise that resolves when the typing is complete .
* /
2020-05-07 10:54:55 +00:00
async type (
selector : string ,
text : string ,
options ? : { delay : number }
) : Promise < void > {
2019-01-16 01:21:23 +00:00
return this . _mainWorld . type ( selector , text , options ) ;
2018-02-05 22:58:03 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* @remarks
*
* This method behaves differently depending on the first parameter . If it ' s a
* ` string ` , it will be treated as a ` selector ` or ` xpath ` ( if the string
* starts with ` // ` ) . This method then is a shortcut for
* { @link Frame . waitForSelector } or { @link Frame . waitForXPath } .
*
* If the first argument is a function this method is a shortcut for
* { @link Frame . waitForFunction } .
*
* If the first argument is a ` number ` , it ' s treated as a timeout in
* milliseconds and the method returns a promise which resolves after the
* timeout .
*
* @param selectorOrFunctionOrTimeout - a selector , predicate or timeout to
* wait for .
* @param options - optional waiting parameters .
* @param args - arguments to pass to ` pageFunction ` .
2020-07-28 08:37:49 +00:00
*
* @deprecated Don ' t use this method directly . Instead use the more explicit
* methods available : { @link Frame . waitForSelector } ,
* { @link Frame . waitForXPath } , { @link Frame . waitForFunction } or
* { @link Frame . waitForTimeout } .
2020-07-09 13:22:58 +00:00
* /
2020-05-07 10:54:55 +00:00
waitFor (
selectorOrFunctionOrTimeout : string | number | Function ,
2020-10-12 09:30:35 +00:00
options : Record < string , unknown > = { } ,
2020-07-01 11:44:08 +00:00
. . . args : SerializableOrJSHandle [ ]
2020-05-07 10:54:55 +00:00
) : Promise < JSHandle | null > {
2020-07-28 08:37:49 +00:00
console . warn (
'waitFor is deprecated and will be removed in a future release. See https://github.com/puppeteer/puppeteer/issues/6214 for details and how to migrate your code.'
) ;
2019-01-22 22:55:33 +00:00
if ( helper . isString ( selectorOrFunctionOrTimeout ) ) {
2020-04-29 11:28:16 +00:00
const string = selectorOrFunctionOrTimeout ;
2021-09-15 11:45:59 +00:00
if ( xPathPattern . test ( string ) ) return this . waitForXPath ( string , options ) ;
2019-01-22 22:55:33 +00:00
return this . waitForSelector ( string , options ) ;
}
if ( helper . isNumber ( selectorOrFunctionOrTimeout ) )
2020-05-07 10:54:55 +00:00
return new Promise ( ( fulfill ) = >
setTimeout ( fulfill , selectorOrFunctionOrTimeout )
) ;
2019-01-22 22:55:33 +00:00
if ( typeof selectorOrFunctionOrTimeout === 'function' )
2020-05-07 10:54:55 +00:00
return this . waitForFunction (
selectorOrFunctionOrTimeout ,
options ,
. . . args
) ;
return Promise . reject (
new Error (
'Unsupported target type: ' + typeof selectorOrFunctionOrTimeout
)
) ;
}
2020-07-28 08:37:49 +00:00
/ * *
* Causes your script to wait for the given number of milliseconds .
*
* @remarks
* It ' s generally recommended to not wait for a number of seconds , but instead
* use { @link Frame . waitForSelector } , { @link Frame . waitForXPath } or
* { @link Frame . waitForFunction } to wait for exactly the conditions you want .
*
* @example
*
* Wait for 1 second :
*
* ` ` `
* await frame . waitForTimeout ( 1000 ) ;
* ` ` `
*
* @param milliseconds - the number of milliseconds to wait .
* /
waitForTimeout ( milliseconds : number ) : Promise < void > {
return new Promise ( ( resolve ) = > {
setTimeout ( resolve , milliseconds ) ;
} ) ;
}
2020-07-09 13:22:58 +00:00
/ * *
* @remarks
*
*
* Wait for the ` selector ` to appear in page . If at the moment of calling the
* method the ` selector ` already exists , the method will return immediately .
* If the selector doesn ' t appear after the ` timeout ` milliseconds of waiting ,
* the function will throw .
*
* This method works across navigations .
*
* @example
* ` ` ` js
* const puppeteer = require ( 'puppeteer' ) ;
*
* ( async ( ) = > {
* const browser = await puppeteer . launch ( ) ;
* const page = await browser . newPage ( ) ;
* let currentURL ;
* page . mainFrame ( )
* . waitForSelector ( 'img' )
* . then ( ( ) = > console . log ( 'First URL with image: ' + currentURL ) ) ;
*
* for ( currentURL of [ 'https://example.com' , 'https://google.com' , 'https://bbc.com' ] ) {
* await page . goto ( currentURL ) ;
* }
* await browser . close ( ) ;
* } ) ( ) ;
* ` ` `
* @param selector - the selector to wait for .
* @param options - options to define if the element should be visible and how
* long to wait before timing out .
* @returns a promise which resolves when an element matching the selector
* string is added to the DOM .
* /
2020-05-07 10:54:55 +00:00
async waitForSelector (
selector : string ,
2020-06-23 05:18:46 +00:00
options : WaitForSelectorOptions = { }
2020-05-07 10:54:55 +00:00
) : Promise < ElementHandle | null > {
const handle = await this . _secondaryWorld . waitForSelector (
selector ,
options
) ;
if ( ! handle ) return null ;
2019-01-28 20:24:27 +00:00
const mainExecutionContext = await this . _mainWorld . executionContext ( ) ;
const result = await mainExecutionContext . _adoptElementHandle ( handle ) ;
await handle . dispose ( ) ;
return result ;
2018-01-22 23:16:20 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* @remarks
* Wait for the ` xpath ` to appear in page . If at the moment of calling the
* method the ` xpath ` already exists , the method will return immediately . If
* the xpath doesn ' t appear after the ` timeout ` milliseconds of waiting , the
* function will throw .
*
* For a code example , see the example for { @link Frame . waitForSelector } . That
* function behaves identically other than taking a CSS selector rather than
* an XPath .
*
* @param xpath - the XPath expression to wait for .
* @param options - options to configure the visiblity of the element and how
* long to wait before timing out .
* /
2020-05-07 10:54:55 +00:00
async waitForXPath (
xpath : string ,
2020-06-23 05:18:46 +00:00
options : WaitForSelectorOptions = { }
2020-05-07 10:54:55 +00:00
) : Promise < ElementHandle | null > {
2019-01-28 20:24:27 +00:00
const handle = await this . _secondaryWorld . waitForXPath ( xpath , options ) ;
2020-05-07 10:54:55 +00:00
if ( ! handle ) return null ;
2019-01-28 20:24:27 +00:00
const mainExecutionContext = await this . _mainWorld . executionContext ( ) ;
const result = await mainExecutionContext . _adoptElementHandle ( handle ) ;
await handle . dispose ( ) ;
return result ;
2018-01-22 23:16:20 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* @remarks
*
* @example
*
* The ` waitForFunction ` can be used to observe viewport size change :
* ` ` ` js
* const puppeteer = require ( 'puppeteer' ) ;
*
* ( async ( ) = > {
* . const browser = await puppeteer . launch ( ) ;
* . const page = await browser . newPage ( ) ;
* . const watchDog = page . mainFrame ( ) . waitForFunction ( 'window.innerWidth < 100' ) ;
* . page . setViewport ( { width : 50 , height : 50 } ) ;
* . await watchDog ;
* . await browser . close ( ) ;
* } ) ( ) ;
* ` ` `
*
* To pass arguments from Node . js to the predicate of ` page.waitForFunction ` function :
*
* ` ` ` js
* const selector = '.foo' ;
* await frame . waitForFunction (
* selector = > ! ! document . querySelector ( selector ) ,
* { } , // empty options object
* selector
* ) ;
* ` ` `
*
* @param pageFunction - the function to evaluate in the frame context .
* @param options - options to configure the polling method and timeout .
* @param args - arguments to pass to the ` pageFunction ` .
* @returns the promise which resolve when the ` pageFunction ` returns a truthy value .
* /
2020-05-07 10:54:55 +00:00
waitForFunction (
pageFunction : Function | string ,
2020-07-09 13:22:58 +00:00
options : FrameWaitForFunctionOptions = { } ,
2020-07-01 11:44:08 +00:00
. . . args : SerializableOrJSHandle [ ]
2020-05-07 10:54:55 +00:00
) : Promise < JSHandle > {
2019-01-16 01:21:23 +00:00
return this . _mainWorld . waitForFunction ( pageFunction , options , . . . args ) ;
2018-01-22 23:16:20 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* @returns the frame ' s title .
* /
2020-04-29 11:28:16 +00:00
async title ( ) : Promise < string > {
2019-01-23 04:24:14 +00:00
return this . _secondaryWorld . title ( ) ;
2017-07-27 22:17:43 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* @internal
* /
2020-04-29 11:28:16 +00:00
_navigated ( framePayload : Protocol.Page.Frame ) : void {
2017-06-21 20:51:06 +00:00
this . _name = framePayload . name ;
2020-09-08 08:49:44 +00:00
this . _url = ` ${ framePayload . url } ${ framePayload . urlFragment || '' } ` ;
2017-11-10 23:33:14 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* @internal
* /
2020-04-29 11:28:16 +00:00
_navigatedWithinDocument ( url : string ) : void {
2018-04-10 06:38:20 +00:00
this . _url = url ;
}
2020-07-09 13:22:58 +00:00
/ * *
* @internal
* /
2020-04-29 11:28:16 +00:00
_onLifecycleEvent ( loaderId : string , name : string ) : void {
2017-11-10 23:33:14 +00:00
if ( name === 'init' ) {
this . _loaderId = loaderId ;
this . _lifecycleEvents . clear ( ) ;
}
this . _lifecycleEvents . add ( name ) ;
2017-06-21 20:51:06 +00:00
}
2020-07-09 13:22:58 +00:00
/ * *
* @internal
* /
2020-04-29 11:28:16 +00:00
_onLoadingStopped ( ) : void {
2018-04-10 22:59:41 +00:00
this . _lifecycleEvents . add ( 'DOMContentLoaded' ) ;
this . _lifecycleEvents . add ( 'load' ) ;
}
2022-05-17 12:15:44 +00:00
/ * *
* @internal
* /
_onLoadingStarted ( ) : void {
this . _hasStartedLoading = true ;
}
2020-07-09 13:22:58 +00:00
/ * *
* @internal
* /
2020-04-29 11:28:16 +00:00
_detach ( ) : void {
2017-06-21 20:51:06 +00:00
this . _detached = true ;
2019-01-16 01:21:23 +00:00
this . _mainWorld . _detach ( ) ;
2019-01-22 22:55:33 +00:00
this . _secondaryWorld . _detach ( ) ;
2020-05-07 10:54:55 +00:00
if ( this . _parentFrame ) this . _parentFrame . _childFrames . delete ( this ) ;
2017-06-21 20:51:06 +00:00
this . _parentFrame = null ;
}
2017-06-17 21:27:51 +00:00
}
2020-05-07 10:54:55 +00:00
function assertNoLegacyNavigationOptions ( options : {
[ optionName : string ] : unknown ;
} ) : void {
assert (
options [ 'networkIdleTimeout' ] === undefined ,
'ERROR: networkIdleTimeout option is no longer supported.'
) ;
assert (
options [ 'networkIdleInflight' ] === undefined ,
'ERROR: networkIdleInflight option is no longer supported.'
) ;
assert (
options . waitUntil !== 'networkidle' ,
'ERROR: "networkidle" option is no longer supported. Use "networkidle2" instead'
) ;
2018-11-20 23:32:46 +00:00
}