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-05-15 15:21:49 -07:00
import InjectedScript from './injected/injectedScript' ;
import * as injectedScriptSource from './generated/injectedScriptSource' ;
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' ;
2020-06-01 15:48:23 -07:00
import { Progress } from './progress' ;
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-05-15 15:21:49 -07:00
adoptIfNeeded ( handle : js.JSHandle ) : Promise < js.JSHandle > | null {
2020-03-19 13:07:33 -07:00
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-05-15 15:21:49 -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-05-27 22:19:05 -07:00
createHandle ( remoteObject : js.RemoteObject ) : js . JSHandle {
2019-12-19 15:19:22 -08:00
if ( this . frame . _page . _delegate . isElementHandle ( remoteObject ) )
2020-05-27 22:19:05 -07:00
return new ElementHandle ( this , remoteObject . objectId ! ) ;
2020-05-15 15:21:49 -07:00
return super . createHandle ( remoteObject ) ;
2019-12-12 21:11:52 -08:00
}
2020-05-15 15:21:49 -07:00
injectedScript ( ) : Promise < js.JSHandle < InjectedScript > > {
2019-11-28 12:50:52 -08:00
if ( ! this . _injectedPromise ) {
2020-05-15 15:21:49 -07:00
const custom : string [ ] = [ ] ;
for ( const [ name , { source } ] of selectors . _engines )
custom . push ( ` { name: ' ${ name } ', engine: ( ${ source } ) } ` ) ;
const source = `
new ( $ { injectedScriptSource . source } ) ( [
$ { custom . join ( ',\n' ) }
] )
` ;
2020-05-27 22:19:05 -07:00
this . _injectedPromise = this . _delegate . rawEvaluate ( source ) . then ( objectId = > new js . JSHandle ( this , 'object' , objectId ) ) ;
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 ;
2020-05-27 22:19:05 -07:00
readonly _objectId : string ;
2019-11-27 16:02:31 -08:00
2020-05-27 22:19:05 -07:00
constructor ( context : FrameExecutionContext , objectId : string ) {
super ( context , 'node' , objectId ) ;
this . _objectId = objectId ;
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-05-15 15:21:49 -07:00
async _evaluateInMain < R , Arg > ( pageFunction : types.FuncOn < { injected : InjectedScript , node : T } , Arg , R > , arg : Arg ) : Promise < R > {
2020-04-23 14:58:37 -07:00
const main = await this . _context . frame . _mainContext ( ) ;
2020-05-15 15:21:49 -07:00
return main . doEvaluateInternal ( true /* returnByValue */ , true /* waitForNavigations */ , pageFunction , { injected : await main . injectedScript ( ) , node : this } , arg ) ;
2020-04-23 14:58:37 -07:00
}
2020-05-15 15:21:49 -07:00
async _evaluateInUtility < R , Arg > ( pageFunction : types.FuncOn < { injected : InjectedScript , node : T } , Arg , R > , arg : Arg ) : Promise < R > {
2019-12-19 15:19:22 -08:00
const utility = await this . _context . frame . _utilityContext ( ) ;
2020-05-15 15:21:49 -07:00
return utility . doEvaluateInternal ( true /* returnByValue */ , true /* waitForNavigations */ , pageFunction , { injected : await utility . injectedScript ( ) , node : this } , arg ) ;
2019-12-19 15:19:22 -08:00
}
2020-05-30 15:00:53 -07:00
async _evaluateHandleInUtility < R , Arg > ( pageFunction : types.FuncOn < { injected : InjectedScript , node : T } , Arg , R > , arg : Arg ) : Promise < js.JSHandle < R > > {
const utility = await this . _context . frame . _utilityContext ( ) ;
return utility . doEvaluateInternal ( false /* returnByValue */ , true /* waitForNavigations */ , pageFunction , { injected : await utility . injectedScript ( ) , 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 , { } ) ;
}
2020-05-18 17:58:23 -07:00
async innerText ( ) : Promise < string > {
2020-04-09 16:49:23 -07:00
return this . _evaluateInUtility ( ( { node } ) = > {
if ( node . nodeType !== Node . ELEMENT_NODE )
throw new Error ( 'Not an element' ) ;
2020-05-18 17:58:23 -07:00
if ( node . namespaceURI !== 'http://www.w3.org/1999/xhtml' )
throw new Error ( 'Not an HTMLElement' ) ;
2020-04-09 16:49:23 -07:00
const element = node as unknown as HTMLElement ;
return element . innerText ;
} , { } ) ;
}
2020-05-18 17:58:23 -07:00
async innerHTML ( ) : Promise < string > {
2020-04-09 16:49:23 -07:00
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-05-22 11:15:57 -07:00
async _scrollRectIntoViewIfNeeded ( rect? : types.Rect ) : Promise < 'success' | 'invisible' > {
return 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
}
2020-05-22 11:15:57 -07:00
private async _clickablePoint ( ) : Promise < types.Point | 'invisible' | 'outsideviewport' > {
2019-12-05 09:54:50 -08:00
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 )
2020-05-22 11:15:57 -07:00
return 'invisible' ;
2019-12-05 09:54:50 -08:00
const filtered = quads . map ( quad = > intersectQuadWithViewport ( quad ) ) . filter ( quad = > computeQuadArea ( quad ) > 1 ) ;
if ( ! filtered . length )
2020-05-22 11:15:57 -07:00
return 'outsideviewport' ;
2019-12-05 09:54:50 -08:00
// 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-05-22 11:15:57 -07:00
private async _offsetPoint ( offset : types.Point ) : Promise < types.Point | 'invisible' > {
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-05-22 11:15:57 -07:00
if ( ! box || ! border )
return 'invisible' ;
// Make point relative to the padding box to align with offsetX/offsetY.
return {
x : box.x + border . left + offset . x ,
y : box.y + border . top + offset . y ,
} ;
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-05-30 13:29:46 -07:00
await this . _waitForDisplayedAtStablePositionAndEnabled ( deadline ) ;
2020-04-07 14:35:34 -07:00
2020-06-01 11:14:16 -07:00
this . _page . _log ( inputLog , 'scrolling into view if needed...' ) ;
const scrolled = await this . _scrollRectIntoViewIfNeeded ( position ? { x : position.x , y : position.y , width : 0 , height : 0 } : undefined ) ;
if ( scrolled === 'invisible' ) {
if ( force )
throw new Error ( 'Element is not visible' ) ;
this . _page . _log ( inputLog , '...element is not visible, retrying input action' ) ;
return 'retry' ;
}
this . _page . _log ( inputLog , '...done scrolling' ) ;
const maybePoint = position ? await this . _offsetPoint ( position ) : await this . _clickablePoint ( ) ;
if ( maybePoint === 'invisible' ) {
if ( force )
throw new Error ( 'Element is not visible' ) ;
this . _page . _log ( inputLog , 'element is not visibile, retrying input action' ) ;
return 'retry' ;
}
if ( maybePoint === 'outsideviewport' ) {
if ( force )
throw new Error ( 'Element is outside of the viewport' ) ;
this . _page . _log ( inputLog , 'element is outside of the viewport, retrying input action' ) ;
return 'retry' ;
}
const point = roundPoint ( maybePoint ) ;
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' ) ;
2020-05-22 11:15:57 -07:00
return 'retry' ;
}
2020-06-01 11:14:16 -07:00
this . _page . _log ( inputLog , ` ...element does receive pointer events, continuing input action ` ) ;
2020-04-29 11:05:23 -07:00
}
2020-06-01 11:14:16 -07:00
await this . _page . _frameManager . waitForSignalsCreatedBy ( async ( ) = > {
let restoreModifiers : input.Modifier [ ] | undefined ;
if ( options && options . modifiers )
restoreModifiers = await this . _page . keyboard . _ensureModifiers ( options . modifiers ) ;
this . _page . _log ( inputLog , ` performing " ${ actionName } " action... ` ) ;
await action ( point ) ;
this . _page . _log ( inputLog , ` ... " ${ actionName } " action done ` ) ;
this . _page . _log ( inputLog , 'waiting for scheduled navigations to finish...' ) ;
if ( restoreModifiers )
await this . _page . keyboard . _ensureModifiers ( restoreModifiers ) ;
} , deadline , options , true ) ;
this . _page . _log ( inputLog , '...navigations have finished' ) ;
return 'done' ;
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 ) ;
2020-05-30 15:00:53 -07:00
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 ) ;
2020-05-30 15:00:53 -07:00
const needsInput = handleInjectedResult ( injectedResult ) ;
2020-04-18 18:29:31 -07:00
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 ) , { } ) ;
2020-05-30 15:00:53 -07:00
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-05-15 15:21:49 -07:00
const injectedResult = await this . _evaluateInUtility ( ( { node } ) : types . InjectedScriptResult < 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-05-30 15:00:53 -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 ) , { } ) ;
2020-05-30 15:00:53 -07:00
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-05-30 13:29:46 -07:00
async _waitForDisplayedAtStablePositionAndEnabled ( deadline : number ) : Promise < void > {
this . _page . _log ( inputLog , 'waiting for element to be displayed, enabled and not moving...' ) ;
2020-05-04 16:30:19 -07:00
const rafCount = this . _page . _delegate . rafCountForStablePosition ( ) ;
2020-05-30 15:00:53 -07:00
const poll = await this . _evaluateHandleInUtility ( ( { injected , node } , { rafCount } ) = > {
return injected . waitForDisplayedAtStablePositionAndEnabled ( node , rafCount ) ;
} , { rafCount } ) ;
try {
const stablePromise = poll . evaluate ( poll = > poll . result ) ;
const injectedResult = await helper . waitWithDeadline ( stablePromise , 'element to be displayed and not moving' , deadline , 'pw:input' ) ;
handleInjectedResult ( injectedResult ) ;
} finally {
poll . evaluate ( poll = > poll . cancel ( ) ) . catch ( e = > { } ) . then ( ( ) = > poll . dispose ( ) ) ;
}
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 } ) ;
2020-05-30 15:00:53 -07:00
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-06-01 15:48:23 -07:00
// Handles an InjectedScriptPoll running in injected script:
// - streams logs into progress;
// - cancels the poll when progress cancels.
export class InjectedScriptPollHandler {
private _progress : Progress ;
private _poll : js.JSHandle < types.InjectedScriptPoll < any > > | null ;
constructor ( progress : Progress , poll : js.JSHandle < types.InjectedScriptPoll < any > > ) {
this . _progress = progress ;
this . _poll = poll ;
this . _progress . cleanupWhenCanceled ( ( ) = > this . cancel ( ) ) ;
this . _streamLogs ( poll . evaluateHandle ( poll = > poll . logs ) ) ;
}
private _streamLogs ( logsPromise : Promise < js.JSHandle < types.InjectedScriptLogs > > ) {
// We continuously get a chunk of logs, stream them to the progress and wait for the next chunk.
logsPromise . catch ( e = > null ) . then ( logs = > {
if ( ! logs || ! this . _poll || this . _progress . isCanceled ( ) )
return ;
logs . evaluate ( logs = > logs . current ) . catch ( e = > [ ] as string [ ] ) . then ( messages = > {
for ( const message of messages )
this . _progress . log ( inputLog , message ) ;
} ) ;
this . _streamLogs ( logs . evaluateHandle ( logs = > logs . next ) ) ;
} ) ;
}
cancel() {
if ( ! this . _poll )
return ;
const copy = this . _poll ;
this . _poll = null ;
copy . evaluate ( p = > p . cancel ( ) ) . catch ( e = > { } ) . then ( ( ) = > copy . dispose ( ) ) ;
}
}
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
2020-05-30 15:00:53 -07:00
function handleInjectedResult < T = undefined > ( injectedResult : types.InjectedScriptResult < T > ) : T {
2020-04-18 18:29:31 -07:00
if ( injectedResult . status === 'notconnected' )
throw new NotConnectedError ( ) ;
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 ,
} ;
}