2020-01-06 18:22:35 -08:00
/ * *
* Copyright ( c ) Microsoft Corporation .
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
2019-11-27 16:02:31 -08:00
2020-04-01 14:42:47 -07:00
import * as fs from 'fs' ;
import * as mime from 'mime' ;
import * as path from 'path' ;
import * as util from 'util' ;
2019-11-27 16:02:31 -08:00
import * as frames from './frames' ;
2020-04-20 07:52:26 -07:00
import { assert , helper } from './helper' ;
2020-04-18 18:29:31 -07:00
import { Injected , InjectedResult } from './injected/injected' ;
2019-11-27 16:03:51 -08:00
import * as input from './input' ;
import * as js from './javascript' ;
2019-12-12 21:11:52 -08:00
import { Page } from './page' ;
2020-03-25 14:08:46 -07:00
import { selectors } from './selectors' ;
2020-04-01 14:42:47 -07:00
import * as types from './types' ;
2020-04-18 18:29:31 -07:00
import { NotConnectedError , TimeoutError } from './errors' ;
2020-04-20 07:52:26 -07:00
import { Log , logError } from './logger' ;
2019-11-27 16:02:31 -08:00
2020-02-24 21:12:02 -08:00
export type PointerActionOptions = {
modifiers? : input.Modifier [ ] ;
2020-03-23 12:05:08 -07:00
position? : types.Point ;
2020-02-24 21:12:02 -08:00
} ;
export type ClickOptions = PointerActionOptions & input . MouseClickOptions ;
export type MultiClickOptions = PointerActionOptions & input . MouseMultiClickOptions ;
2020-04-20 07:52:26 -07:00
export const inputLog : Log = {
name : 'input' ,
color : 'cyan'
} ;
2019-12-12 21:11:52 -08:00
export class FrameExecutionContext extends js . ExecutionContext {
2019-12-19 15:19:22 -08:00
readonly frame : frames.Frame ;
2019-11-28 12:50:52 -08:00
private _injectedPromise? : Promise < js.JSHandle > ;
2019-12-12 21:11:52 -08:00
constructor ( delegate : js.ExecutionContextDelegate , frame : frames.Frame ) {
2020-04-20 07:52:26 -07:00
super ( delegate , frame . _page ) ;
2019-12-19 15:19:22 -08:00
this . frame = frame ;
2019-12-02 13:12:28 -08:00
}
2020-03-19 13:07:33 -07:00
_adoptIfNeeded ( handle : js.JSHandle ) : Promise < js.JSHandle > | null {
if ( handle instanceof ElementHandle && handle . _context !== this )
return this . frame . _page . _delegate . adoptElementHandle ( handle , this ) ;
return null ;
}
2019-12-19 11:44:07 -08:00
2020-03-20 15:08:17 -07:00
async _doEvaluateInternal ( returnByValue : boolean , waitForNavigations : boolean , pageFunction : string | Function , . . . args : any [ ] ) : Promise < any > {
2020-04-16 13:09:24 -07:00
return await this . frame . _page . _frameManager . waitForSignalsCreatedBy ( async ( ) = > {
2020-03-19 13:07:33 -07:00
return this . _delegate . evaluate ( this , returnByValue , pageFunction , . . . args ) ;
2020-04-16 20:31:04 -07:00
} , Number . MAX_SAFE_INTEGER , waitForNavigations ? undefined : { noWaitAfter : true } ) ;
2019-12-19 11:44:07 -08:00
}
2020-01-13 13:33:25 -08:00
_createHandle ( remoteObject : any ) : js . JSHandle {
2019-12-19 15:19:22 -08:00
if ( this . frame . _page . _delegate . isElementHandle ( remoteObject ) )
2019-12-12 21:11:52 -08:00
return new ElementHandle ( this , remoteObject ) ;
return super . _createHandle ( remoteObject ) ;
}
2020-03-05 17:45:41 -08:00
_injected ( ) : Promise < js.JSHandle < Injected > > {
2019-11-28 12:50:52 -08:00
if ( ! this . _injectedPromise ) {
2020-03-25 14:08:46 -07:00
this . _injectedPromise = selectors . _prepareEvaluator ( this ) . then ( evaluator = > {
return this . evaluateHandleInternal ( evaluator = > evaluator . injected , evaluator ) ;
} ) ;
2019-11-28 12:50:52 -08:00
}
return this . _injectedPromise ;
}
2019-12-02 17:33:44 -08:00
}
2019-12-02 13:12:28 -08:00
2019-12-05 16:26:09 -08:00
export class ElementHandle < T extends Node = Node > extends js . JSHandle < T > {
2019-12-12 21:11:52 -08:00
readonly _context : FrameExecutionContext ;
readonly _page : Page ;
2019-11-27 16:02:31 -08:00
2019-12-12 21:11:52 -08:00
constructor ( context : FrameExecutionContext , remoteObject : any ) {
2019-12-02 13:12:28 -08:00
super ( context , remoteObject ) ;
2020-01-13 13:33:25 -08:00
this . _context = context ;
2019-12-19 15:19:22 -08:00
this . _page = context . frame . _page ;
2019-12-18 13:51:45 -08:00
}
2019-12-05 16:26:09 -08:00
asElement ( ) : ElementHandle < T > | null {
2019-11-27 16:02:31 -08:00
return this ;
}
2020-04-23 14:58:37 -07:00
async _evaluateInMain < R , Arg > ( pageFunction : types.FuncOn < { injected : Injected , node : T } , Arg , R > , arg : Arg ) : Promise < R > {
const main = await this . _context . frame . _mainContext ( ) ;
return main . _doEvaluateInternal ( true /* returnByValue */ , true /* waitForNavigations */ , pageFunction , { injected : await main . _injected ( ) , node : this } , arg ) ;
}
2020-03-20 15:08:17 -07:00
async _evaluateInUtility < R , Arg > ( pageFunction : types.FuncOn < { injected : Injected , node : T } , Arg , R > , arg : Arg ) : Promise < R > {
2019-12-19 15:19:22 -08:00
const utility = await this . _context . frame . _utilityContext ( ) ;
2020-03-20 15:08:17 -07:00
return utility . _doEvaluateInternal ( true /* returnByValue */ , true /* waitForNavigations */ , pageFunction , { injected : await utility . _injected ( ) , node : this } , arg ) ;
2019-12-19 15:19:22 -08:00
}
async ownerFrame ( ) : Promise < frames.Frame | null > {
2020-01-27 11:43:43 -08:00
const frameId = await this . _page . _delegate . getOwnerFrame ( this ) ;
if ( ! frameId )
return null ;
2020-04-20 13:01:06 -07:00
const frame = this . _page . _frameManager . frame ( frameId ) ;
if ( frame )
return frame ;
2020-03-13 11:33:33 -07:00
for ( const page of this . _page . _browserContext . pages ( ) ) {
2020-01-27 11:43:43 -08:00
const frame = page . _frameManager . frame ( frameId ) ;
if ( frame )
return frame ;
}
return null ;
2019-12-19 15:19:22 -08:00
}
2019-11-27 16:02:31 -08:00
async contentFrame ( ) : Promise < frames.Frame | null > {
2020-03-20 15:08:17 -07:00
const isFrameElement = await this . _evaluateInUtility ( ( { node } ) = > node && ( node . nodeName === 'IFRAME' || node . nodeName === 'FRAME' ) , { } ) ;
2019-12-19 15:19:22 -08:00
if ( ! isFrameElement )
return null ;
2019-12-12 21:11:52 -08:00
return this . _page . _delegate . getContentFrame ( this ) ;
2019-11-27 16:02:31 -08:00
}
2020-04-09 16:49:23 -07:00
async getAttribute ( name : string ) : Promise < string | null > {
return this . _evaluateInUtility ( ( { node } , name : string ) = > {
if ( node . nodeType !== Node . ELEMENT_NODE )
throw new Error ( 'Not an element' ) ;
const element = node as unknown as Element ;
return element . getAttribute ( name ) ;
} , name ) ;
}
async textContent ( ) : Promise < string | null > {
return this . _evaluateInUtility ( ( { node } ) = > node . textContent , { } ) ;
}
async innerText ( ) : Promise < string | null > {
return this . _evaluateInUtility ( ( { node } ) = > {
if ( node . nodeType !== Node . ELEMENT_NODE )
throw new Error ( 'Not an element' ) ;
const element = node as unknown as HTMLElement ;
return element . innerText ;
} , { } ) ;
}
async innerHTML ( ) : Promise < string | null > {
return this . _evaluateInUtility ( ( { node } ) = > {
if ( node . nodeType !== Node . ELEMENT_NODE )
throw new Error ( 'Not an element' ) ;
const element = node as unknown as Element ;
return element . innerHTML ;
} , { } ) ;
}
2020-04-23 14:58:37 -07:00
async dispatchEvent ( type : string , eventInit : Object = { } ) {
await this . _evaluateInMain ( ( { injected , node } , { type , eventInit } ) = >
injected . dispatchEvent ( node , type , eventInit ) , { type , eventInit } ) ;
}
2020-02-11 10:30:09 -08:00
async _scrollRectIntoViewIfNeeded ( rect? : types.Rect ) : Promise < void > {
await this . _page . _delegate . scrollRectIntoViewIfNeeded ( this , rect ) ;
2019-11-27 16:02:31 -08:00
}
2020-02-11 10:30:09 -08:00
async scrollIntoViewIfNeeded() {
await this . _scrollRectIntoViewIfNeeded ( ) ;
2019-12-05 09:54:50 -08:00
}
private async _clickablePoint ( ) : Promise < types.Point > {
const intersectQuadWithViewport = ( quad : types.Quad ) : types . Quad = > {
return quad . map ( point = > ( {
x : Math.min ( Math . max ( point . x , 0 ) , metrics . width ) ,
y : Math.min ( Math . max ( point . y , 0 ) , metrics . height ) ,
} ) ) as types . Quad ;
} ;
const computeQuadArea = ( quad : types.Quad ) = > {
// Compute sum of all directed areas of adjacent triangles
// https://en.wikipedia.org/wiki/Polygon#Simple_polygons
let area = 0 ;
for ( let i = 0 ; i < quad . length ; ++ i ) {
const p1 = quad [ i ] ;
const p2 = quad [ ( i + 1 ) % quad . length ] ;
area += ( p1 . x * p2 . y - p2 . x * p1 . y ) / 2 ;
}
return Math . abs ( area ) ;
} ;
const [ quads , metrics ] = await Promise . all ( [
2019-12-12 21:11:52 -08:00
this . _page . _delegate . getContentQuads ( this ) ,
this . _page . _delegate . layoutViewport ( ) ,
2020-02-05 16:53:36 -08:00
] as const ) ;
2019-12-05 09:54:50 -08:00
if ( ! quads || ! quads . length )
throw new Error ( 'Node is either not visible or not an HTMLElement' ) ;
const filtered = quads . map ( quad = > intersectQuadWithViewport ( quad ) ) . filter ( quad = > computeQuadArea ( quad ) > 1 ) ;
if ( ! filtered . length )
throw new Error ( 'Node is either not visible or not an HTMLElement' ) ;
// Return the middle point of the first quad.
const result = { x : 0 , y : 0 } ;
for ( const point of filtered [ 0 ] ) {
result . x += point . x / 4 ;
result . y += point . y / 4 ;
}
return result ;
}
2020-02-24 21:12:02 -08:00
private async _offsetPoint ( offset : types.Point ) : Promise < types.Point > {
2019-12-05 09:54:50 -08:00
const [ box , border ] = await Promise . all ( [
this . boundingBox ( ) ,
2020-04-20 07:52:26 -07:00
this . _evaluateInUtility ( ( { injected , node } ) = > injected . getElementBorderWidth ( node ) , { } ) . catch ( logError ( this . _context . _logger ) ) ,
2019-12-05 09:54:50 -08:00
] ) ;
2020-02-24 21:12:02 -08:00
const point = { x : offset.x , y : offset.y } ;
2019-12-05 09:54:50 -08:00
if ( box ) {
point . x += box . x ;
point . y += box . y ;
}
if ( border ) {
// Make point relative to the padding box to align with offsetX/offsetY.
2020-02-25 07:06:20 -08:00
point . x += border . left ;
point . y += border . top ;
2019-12-05 09:54:50 -08:00
}
2020-02-11 10:30:09 -08:00
return point ;
2019-12-05 09:54:50 -08:00
}
2020-05-04 21:44:33 -07:00
async _retryPointerAction ( actionName : string , action : ( point : types.Point ) = > Promise < void > , options : PointerActionOptions & types . PointerActionWaitOptions & types . NavigatingActionWaitOptions = { } ) : Promise < void > {
this . _page . _log ( inputLog , ` elementHandle. ${ actionName } () ` ) ;
2020-04-07 14:35:34 -07:00
const deadline = this . _page . _timeoutSettings . computeDeadline ( options ) ;
2020-04-29 11:05:23 -07:00
while ( ! helper . isPastDeadline ( deadline ) ) {
2020-05-04 21:44:33 -07:00
const result = await this . _performPointerAction ( actionName , action , deadline , options ) ;
2020-04-29 11:05:23 -07:00
if ( result === 'done' )
return ;
}
2020-05-04 21:44:33 -07:00
throw new TimeoutError ( ` waiting for element to receive pointer events failed: timeout exceeded. Re-run with the DEBUG=pw:input env variable to see the debug log. ` ) ;
2020-04-29 11:05:23 -07:00
}
2020-05-04 21:44:33 -07:00
async _performPointerAction ( actionName : string , action : ( point : types.Point ) = > Promise < void > , deadline : number , options : PointerActionOptions & types . PointerActionWaitOptions & types . NavigatingActionWaitOptions = { } ) : Promise < 'done' | 'retry' > {
2020-04-29 11:05:23 -07:00
const { force = false , position } = options ;
2020-04-30 14:13:47 -07:00
if ( ! force )
2020-04-07 14:35:34 -07:00
await this . _waitForDisplayedAtStablePosition ( deadline ) ;
2020-04-29 11:05:23 -07:00
let paused = false ;
try {
await this . _page . _delegate . setActivityPaused ( true ) ;
2020-04-29 17:02:24 -07:00
paused = true ;
2020-04-29 16:17:03 -07:00
2020-04-29 11:05:23 -07:00
// Scroll into view and calculate the point again while paused just in case something has moved.
this . _page . _log ( inputLog , 'scrolling into view if needed...' ) ;
await this . _scrollRectIntoViewIfNeeded ( position ? { x : position.x , y : position.y , width : 0 , height : 0 } : undefined ) ;
this . _page . _log ( inputLog , '...done scrolling' ) ;
const point = roundPoint ( position ? await this . _offsetPoint ( position ) : await this . _clickablePoint ( ) ) ;
if ( ! force ) {
if ( ( options as any ) . __testHookBeforeHitTarget )
await ( options as any ) . __testHookBeforeHitTarget ( ) ;
this . _page . _log ( inputLog , ` checking that element receives pointer events at ( ${ point . x } , ${ point . y } )... ` ) ;
const matchesHitTarget = await this . _checkHitTargetAt ( point ) ;
if ( ! matchesHitTarget ) {
this . _page . _log ( inputLog , '...element does not receive pointer events, retrying input action' ) ;
await this . _page . _delegate . setActivityPaused ( false ) ;
paused = false ;
return 'retry' ;
}
this . _page . _log ( inputLog , ` ...element does receive pointer events, continuing input action ` ) ;
}
await this . _page . _frameManager . waitForSignalsCreatedBy ( async ( ) = > {
let restoreModifiers : input.Modifier [ ] | undefined ;
if ( options && options . modifiers )
restoreModifiers = await this . _page . keyboard . _ensureModifiers ( options . modifiers ) ;
2020-05-04 21:44:33 -07:00
this . _page . _log ( inputLog , ` performing " ${ actionName } " action... ` ) ;
2020-04-29 11:05:23 -07:00
await action ( point ) ;
2020-05-04 21:44:33 -07:00
this . _page . _log ( inputLog , ` ... " ${ actionName } " action done ` ) ;
this . _page . _log ( inputLog , 'waiting for scheduled navigations to finish...' ) ;
2020-04-29 11:05:23 -07:00
await this . _page . _delegate . setActivityPaused ( false ) ;
paused = false ;
if ( restoreModifiers )
await this . _page . keyboard . _ensureModifiers ( restoreModifiers ) ;
} , deadline , options , true ) ;
this . _page . _log ( inputLog , '...navigations have finished' ) ;
return 'done' ;
} finally {
if ( paused )
await this . _page . _delegate . setActivityPaused ( false ) ;
}
2019-11-27 16:02:31 -08:00
}
2020-03-06 16:24:21 -08:00
hover ( options? : PointerActionOptions & types . PointerActionWaitOptions ) : Promise < void > {
2020-05-04 21:44:33 -07:00
return this . _retryPointerAction ( 'hover' , point = > this . _page . mouse . move ( point . x , point . y ) , options ) ;
2019-11-27 16:02:31 -08:00
}
2020-03-06 16:24:21 -08:00
click ( options? : ClickOptions & types . PointerActionWaitOptions & types . NavigatingActionWaitOptions ) : Promise < void > {
2020-05-04 21:44:33 -07:00
return this . _retryPointerAction ( 'click' , point = > this . _page . mouse . click ( point . x , point . y , options ) , options ) ;
2019-11-27 16:02:31 -08:00
}
2020-03-06 16:24:21 -08:00
dblclick ( options? : MultiClickOptions & types . PointerActionWaitOptions & types . NavigatingActionWaitOptions ) : Promise < void > {
2020-05-04 21:44:33 -07:00
return this . _retryPointerAction ( 'dblclick' , point = > this . _page . mouse . dblclick ( point . x , point . y , options ) , options ) ;
2019-11-27 16:02:31 -08:00
}
2020-03-12 21:30:12 -07:00
async selectOption ( values : string | ElementHandle | types . SelectOption | string [ ] | ElementHandle [ ] | types . SelectOption [ ] , options? : types.NavigatingActionWaitOptions ) : Promise < string [ ] > {
2020-05-04 21:44:33 -07:00
this . _page . _log ( inputLog , ` elementHandle.selectOption(%s) ` , values ) ;
2020-04-07 14:35:34 -07:00
const deadline = this . _page . _timeoutSettings . computeDeadline ( options ) ;
2020-03-06 08:24:32 -08:00
let vals : string [ ] | ElementHandle [ ] | types . SelectOption [ ] ;
if ( ! Array . isArray ( values ) )
vals = [ values ] as ( string [ ] | ElementHandle [ ] | types . SelectOption [ ] ) ;
else
vals = values ;
const selectOptions = ( vals as any ) . map ( ( value : any ) = > typeof value === 'object' ? value : { value } ) ;
for ( const option of selectOptions ) {
2019-11-27 16:02:31 -08:00
if ( option instanceof ElementHandle )
continue ;
if ( option . value !== undefined )
assert ( helper . isString ( option . value ) , 'Values must be strings. Found value "' + option . value + '" of type "' + ( typeof option . value ) + '"' ) ;
if ( option . label !== undefined )
assert ( helper . isString ( option . label ) , 'Labels must be strings. Found label "' + option . label + '" of type "' + ( typeof option . label ) + '"' ) ;
if ( option . index !== undefined )
assert ( helper . isNumber ( option . index ) , 'Indices must be numbers. Found index "' + option . index + '" of type "' + ( typeof option . index ) + '"' ) ;
}
2020-04-16 13:09:24 -07:00
return await this . _page . _frameManager . waitForSignalsCreatedBy < string [ ] > ( async ( ) = > {
2020-04-18 18:29:31 -07:00
const injectedResult = await this . _evaluateInUtility ( ( { injected , node } , selectOptions ) = > injected . selectOptions ( node , selectOptions ) , selectOptions ) ;
return handleInjectedResult ( injectedResult , '' ) ;
2020-04-07 14:35:34 -07:00
} , deadline , options ) ;
2019-11-27 16:02:31 -08:00
}
2020-03-06 16:24:21 -08:00
async fill ( value : string , options? : types.NavigatingActionWaitOptions ) : Promise < void > {
2020-05-04 21:44:33 -07:00
this . _page . _log ( inputLog , ` elementHandle.fill( ${ value } ) ` ) ;
2019-11-27 16:02:31 -08:00
assert ( helper . isString ( value ) , 'Value must be string. Found value "' + value + '" of type "' + ( typeof value ) + '"' ) ;
2020-04-07 14:35:34 -07:00
const deadline = this . _page . _timeoutSettings . computeDeadline ( options ) ;
2020-04-16 13:09:24 -07:00
await this . _page . _frameManager . waitForSignalsCreatedBy ( async ( ) = > {
2020-04-18 18:29:31 -07:00
const injectedResult = await this . _evaluateInUtility ( ( { injected , node } , value ) = > injected . fill ( node , value ) , value ) ;
const needsInput = handleInjectedResult ( injectedResult , '' ) ;
if ( needsInput ) {
2020-04-07 10:07:06 -07:00
if ( value )
await this . _page . keyboard . insertText ( value ) ;
else
await this . _page . keyboard . press ( 'Delete' ) ;
}
2020-04-07 14:35:34 -07:00
} , deadline , options , true ) ;
2019-11-27 16:02:31 -08:00
}
2020-04-14 17:09:26 -07:00
async selectText ( ) : Promise < void > {
2020-05-04 21:44:33 -07:00
this . _page . _log ( inputLog , ` elementHandle.selectText() ` ) ;
2020-04-18 18:29:31 -07:00
const injectedResult = await this . _evaluateInUtility ( ( { injected , node } ) = > injected . selectText ( node ) , { } ) ;
handleInjectedResult ( injectedResult , '' ) ;
2020-04-14 17:09:26 -07:00
}
2020-04-07 14:35:34 -07:00
async setInputFiles ( files : string | types . FilePayload | string [ ] | types . FilePayload [ ] , options? : types.NavigatingActionWaitOptions ) {
2020-05-04 21:44:33 -07:00
this . _page . _log ( inputLog , ` elementHandle.setInputFiles(...) ` ) ;
2020-04-07 14:35:34 -07:00
const deadline = this . _page . _timeoutSettings . computeDeadline ( options ) ;
2020-04-18 18:29:31 -07:00
const injectedResult = await this . _evaluateInUtility ( ( { node } ) : InjectedResult < boolean > = > {
2020-03-20 15:08:17 -07:00
if ( node . nodeType !== Node . ELEMENT_NODE || ( node as Node as Element ) . tagName !== 'INPUT' )
2020-04-18 18:29:31 -07:00
return { status : 'error' , error : 'Node is not an HTMLInputElement' } ;
if ( ! node . isConnected )
return { status : 'notconnected' } ;
2020-03-20 15:08:17 -07:00
const input = node as Node as HTMLInputElement ;
2020-04-18 18:29:31 -07:00
return { status : 'success' , value : input.multiple } ;
2020-03-20 15:08:17 -07:00
} , { } ) ;
2020-04-18 18:29:31 -07:00
const multiple = handleInjectedResult ( injectedResult , '' ) ;
2020-03-06 08:24:32 -08:00
let ff : string [ ] | types . FilePayload [ ] ;
if ( ! Array . isArray ( files ) )
ff = [ files ] as string [ ] | types . FilePayload [ ] ;
else
ff = files ;
assert ( multiple || ff . length <= 1 , 'Non-multiple file input can only accept single file!' ) ;
const filePayloads : types.FilePayload [ ] = [ ] ;
for ( const item of ff ) {
2020-01-03 12:59:06 -08:00
if ( typeof item === 'string' ) {
const file : types.FilePayload = {
2020-04-01 14:42:47 -07:00
name : path.basename ( item ) ,
2020-04-16 10:25:28 -07:00
mimeType : mime.getType ( item ) || 'application/octet-stream' ,
buffer : await util . promisify ( fs . readFile ) ( item )
2020-01-03 12:59:06 -08:00
} ;
2020-03-06 08:24:32 -08:00
filePayloads . push ( file ) ;
} else {
filePayloads . push ( item ) ;
2020-01-03 12:59:06 -08:00
}
2020-03-06 08:24:32 -08:00
}
2020-04-16 13:09:24 -07:00
await this . _page . _frameManager . waitForSignalsCreatedBy ( async ( ) = > {
2020-03-04 19:15:01 -08:00
await this . _page . _delegate . setInputFiles ( this as any as ElementHandle < HTMLInputElement > , filePayloads ) ;
2020-04-07 14:35:34 -07:00
} , deadline , options ) ;
2019-11-27 16:02:31 -08:00
}
async focus() {
2020-05-04 21:44:33 -07:00
this . _page . _log ( inputLog , ` elementHandle.focus() ` ) ;
2020-04-18 18:29:31 -07:00
const injectedResult = await this . _evaluateInUtility ( ( { injected , node } ) = > injected . focusNode ( node ) , { } ) ;
handleInjectedResult ( injectedResult , '' ) ;
2019-11-27 16:02:31 -08:00
}
2020-03-06 16:24:21 -08:00
async type ( text : string , options ? : { delay? : number } & types . NavigatingActionWaitOptions ) {
2020-05-04 21:44:33 -07:00
this . _page . _log ( inputLog , ` elementHandle.type(" ${ text } ") ` ) ;
2020-04-07 14:35:34 -07:00
const deadline = this . _page . _timeoutSettings . computeDeadline ( options ) ;
2020-04-16 13:09:24 -07:00
await this . _page . _frameManager . waitForSignalsCreatedBy ( async ( ) = > {
2020-03-04 19:15:01 -08:00
await this . focus ( ) ;
await this . _page . keyboard . type ( text , options ) ;
2020-04-07 14:35:34 -07:00
} , deadline , options , true ) ;
2019-11-27 16:02:31 -08:00
}
2020-03-12 22:02:19 -07:00
async press ( key : string , options ? : { delay? : number } & types . NavigatingActionWaitOptions ) {
2020-05-04 21:44:33 -07:00
this . _page . _log ( inputLog , ` elementHandle.press(" ${ key } ") ` ) ;
2020-04-07 14:35:34 -07:00
const deadline = this . _page . _timeoutSettings . computeDeadline ( options ) ;
2020-04-16 13:09:24 -07:00
await this . _page . _frameManager . waitForSignalsCreatedBy ( async ( ) = > {
2020-03-04 19:15:01 -08:00
await this . focus ( ) ;
await this . _page . keyboard . press ( key , options ) ;
2020-04-07 14:35:34 -07:00
} , deadline , options , true ) ;
2019-11-27 16:02:31 -08:00
}
2020-02-19 09:34:57 -08:00
2020-03-06 16:24:21 -08:00
async check ( options? : types.PointerActionWaitOptions & types . NavigatingActionWaitOptions ) {
2020-05-04 21:44:33 -07:00
this . _page . _log ( inputLog , ` elementHandle.check() ` ) ;
2020-02-19 09:34:57 -08:00
await this . _setChecked ( true , options ) ;
2020-02-04 14:39:11 -08:00
}
2020-03-06 16:24:21 -08:00
async uncheck ( options? : types.PointerActionWaitOptions & types . NavigatingActionWaitOptions ) {
2020-05-04 21:44:33 -07:00
this . _page . _log ( inputLog , ` elementHandle.uncheck() ` ) ;
2020-02-19 09:34:57 -08:00
await this . _setChecked ( false , options ) ;
2020-02-04 14:39:11 -08:00
}
2020-03-06 16:24:21 -08:00
private async _setChecked ( state : boolean , options? : types.PointerActionWaitOptions & types . NavigatingActionWaitOptions ) {
2020-03-20 15:08:17 -07:00
if ( await this . _evaluateInUtility ( ( { injected , node } ) = > injected . isCheckboxChecked ( node ) , { } ) === state )
2020-02-04 14:39:11 -08:00
return ;
2020-02-19 09:34:57 -08:00
await this . click ( options ) ;
2020-03-20 15:08:17 -07:00
if ( await this . _evaluateInUtility ( ( { injected , node } ) = > injected . isCheckboxChecked ( node ) , { } ) !== state )
2020-02-04 14:39:11 -08:00
throw new Error ( 'Unable to click checkbox' ) ;
}
2019-11-27 16:02:31 -08:00
2019-11-27 16:03:51 -08:00
async boundingBox ( ) : Promise < types.Rect | null > {
2019-12-12 21:11:52 -08:00
return this . _page . _delegate . getBoundingBox ( this ) ;
2019-11-27 16:02:31 -08:00
}
2020-04-01 14:42:47 -07:00
async screenshot ( options? : types.ElementScreenshotOptions ) : Promise < Buffer > {
2019-12-12 21:11:52 -08:00
return this . _page . _screenshotter . screenshotElement ( this , options ) ;
2019-11-27 16:02:31 -08:00
}
2020-04-20 13:01:06 -07:00
async $ ( selector : string ) : Promise < ElementHandle | null > {
2020-03-25 14:08:46 -07:00
return selectors . _query ( this . _context . frame , selector , this ) ;
2019-11-27 16:02:31 -08:00
}
2020-04-20 13:01:06 -07:00
async $ $ ( selector : string ) : Promise < ElementHandle < Element > [ ] > {
2020-03-25 14:08:46 -07:00
return selectors . _queryAll ( this . _context . frame , selector , this ) ;
2019-11-27 16:02:31 -08:00
}
2020-03-20 15:08:17 -07:00
async $eval < R , Arg > ( selector : string , pageFunction : types.FuncOn < Element , Arg , R > , arg : Arg ) : Promise < R > ;
async $eval < R > ( selector : string , pageFunction : types.FuncOn < Element , void , R > , arg? : any ) : Promise < R > ;
async $eval < R , Arg > ( selector : string , pageFunction : types.FuncOn < Element , Arg , R > , arg : Arg ) : Promise < R > {
2020-03-25 14:08:46 -07:00
const handle = await selectors . _query ( this . _context . frame , selector , this ) ;
if ( ! handle )
2019-12-17 14:30:02 -08:00
throw new Error ( ` Error: failed to find element matching selector " ${ selector } " ` ) ;
2020-03-25 14:08:46 -07:00
const result = await handle . evaluate ( pageFunction , arg ) ;
handle . dispose ( ) ;
2019-12-17 14:30:02 -08:00
return result ;
2019-12-04 13:11:10 -08:00
}
2020-03-20 15:08:17 -07:00
async $ $eval < R , Arg > ( selector : string , pageFunction : types.FuncOn < Element [ ] , Arg , R > , arg : Arg ) : Promise < R > ;
async $ $eval < R > ( selector : string , pageFunction : types.FuncOn < Element [ ] , void , R > , arg? : any ) : Promise < R > ;
async $ $eval < R , Arg > ( selector : string , pageFunction : types.FuncOn < Element [ ] , Arg , R > , arg : Arg ) : Promise < R > {
2020-03-25 14:08:46 -07:00
const arrayHandle = await selectors . _queryArray ( this . _context . frame , selector , this ) ;
2020-03-20 15:08:17 -07:00
const result = await arrayHandle . evaluate ( pageFunction , arg ) ;
2020-03-04 17:57:35 -08:00
arrayHandle . dispose ( ) ;
2019-12-17 14:30:02 -08:00
return result ;
2019-11-27 16:02:31 -08:00
}
2020-04-07 14:35:34 -07:00
async _waitForDisplayedAtStablePosition ( deadline : number ) : Promise < void > {
2020-04-20 07:52:26 -07:00
this . _page . _log ( inputLog , 'waiting for element to be displayed and not moving...' ) ;
2020-05-04 16:30:19 -07:00
const rafCount = this . _page . _delegate . rafCountForStablePosition ( ) ;
const stablePromise = this . _evaluateInUtility ( ( { injected , node } , { rafCount , timeout } ) = > {
return injected . waitForDisplayedAtStablePosition ( node , rafCount , timeout ) ;
} , { rafCount , timeout : helper.timeUntilDeadline ( deadline ) } ) ;
2020-04-18 18:29:31 -07:00
const timeoutMessage = 'element to be displayed and not moving' ;
const injectedResult = await helper . waitWithDeadline ( stablePromise , timeoutMessage , deadline ) ;
handleInjectedResult ( injectedResult , timeoutMessage ) ;
2020-04-29 11:05:23 -07:00
this . _page . _log ( inputLog , '...element is displayed and does not move' ) ;
2020-02-19 09:34:57 -08:00
}
2020-04-29 11:05:23 -07:00
async _checkHitTargetAt ( point : types.Point ) : Promise < boolean > {
2020-02-19 09:34:57 -08:00
const frame = await this . ownerFrame ( ) ;
if ( frame && frame . parentFrame ( ) ) {
const element = await frame . frameElement ( ) ;
const box = await element . boundingBox ( ) ;
if ( ! box )
2020-04-28 11:58:22 -07:00
throw new NotConnectedError ( ) ;
2020-02-19 09:34:57 -08:00
// Translate from viewport coordinates to frame coordinates.
point = { x : point.x - box . x , y : point.y - box . y } ;
}
2020-04-29 11:05:23 -07:00
const injectedResult = await this . _evaluateInUtility ( ( { injected , node } , { point } ) = > {
return injected . checkHitTargetAt ( node , point ) ;
} , { point } ) ;
return handleInjectedResult ( injectedResult , '' ) ;
2020-02-19 09:34:57 -08:00
}
2019-11-27 16:02:31 -08:00
}
2019-12-03 10:43:13 -08:00
2020-04-16 10:25:28 -07:00
export function toFileTransferPayload ( files : types.FilePayload [ ] ) : types . FileTransferPayload [ ] {
return files . map ( file = > ( {
name : file.name ,
type : file . mimeType ,
data : file.buffer.toString ( 'base64' )
2020-01-03 12:59:06 -08:00
} ) ) ;
2020-04-16 10:25:28 -07:00
}
2020-04-18 18:29:31 -07:00
function handleInjectedResult < T = undefined > ( injectedResult : InjectedResult < T > , timeoutMessage : string ) : T {
if ( injectedResult . status === 'notconnected' )
throw new NotConnectedError ( ) ;
if ( injectedResult . status === 'timeout' )
2020-05-04 21:44:33 -07:00
throw new TimeoutError ( ` waiting for ${ timeoutMessage } failed: timeout exceeded. Re-run with the DEBUG=pw:input env variable to see the debug log. ` ) ;
2020-04-18 18:29:31 -07:00
if ( injectedResult . status === 'error' )
throw new Error ( injectedResult . error ) ;
return injectedResult . value as T ;
}
2020-04-29 11:05:23 -07:00
function roundPoint ( point : types.Point ) : types . Point {
return {
x : ( point . x * 100 | 0 ) / 100 ,
y : ( point . y * 100 | 0 ) / 100 ,
} ;
}