@@ -163,9 +163,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
163
163
}
164
164
165
165
async _scrollRectIntoViewIfNeeded ( rect ?: types . Rect ) : Promise < void > {
166
- this . _page . _log ( inputLog , 'scrolling into view if needed...' ) ;
167
166
await this . _page . _delegate . scrollRectIntoViewIfNeeded ( this , rect ) ;
168
- this . _page . _log ( inputLog , '...done' ) ;
169
167
}
170
168
171
169
async scrollIntoViewIfNeeded ( ) {
@@ -229,44 +227,78 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
229
227
return point ;
230
228
}
231
229
232
- async _performPointerAction ( action : ( point : types . Point ) => Promise < void > , options : PointerActionOptions & types . PointerActionWaitOptions & types . NavigatingActionWaitOptions = { } ) : Promise < void > {
230
+ async _retryPointerAction ( action : ( point : types . Point ) => Promise < void > , options : PointerActionOptions & types . PointerActionWaitOptions & types . NavigatingActionWaitOptions = { } ) : Promise < void > {
233
231
const deadline = this . _page . _timeoutSettings . computeDeadline ( options ) ;
234
- const { force = false } = ( options || { } ) ;
235
- if ( ! force )
232
+ while ( ! helper . isPastDeadline ( deadline ) ) {
233
+ const result = await this . _performPointerAction ( action , deadline , options ) ;
234
+ if ( result === 'done' )
235
+ return ;
236
+ }
237
+ throw new TimeoutError ( `waiting for element to receive pointer events failed: timeout exceeded` ) ;
238
+ }
239
+
240
+ async _performPointerAction ( action : ( point : types . Point ) => Promise < void > , deadline : number , options : PointerActionOptions & types . PointerActionWaitOptions & types . NavigatingActionWaitOptions = { } ) : Promise < 'done' | 'retry' > {
241
+ const { force = false , position } = options ;
242
+ if ( ! force && ! ( options as any ) . __testHookSkipStablePosition )
236
243
await this . _waitForDisplayedAtStablePosition ( deadline ) ;
237
- const position = options ? options . position : undefined ;
238
- await this . _scrollRectIntoViewIfNeeded ( position ? { x : position . x , y : position . y , width : 0 , height : 0 } : undefined ) ;
239
- const point = position ? await this . _offsetPoint ( position ) : await this . _clickablePoint ( ) ;
240
- point . x = ( point . x * 100 | 0 ) / 100 ;
241
- point . y = ( point . y * 100 | 0 ) / 100 ;
242
- await this . _page . mouse . move ( point . x , point . y ) ; // Force any hover effects before waiting for hit target.
243
- if ( options && ( options as any ) . __testHookBeforeWaitForHitTarget )
244
- await ( options as any ) . __testHookBeforeWaitForHitTarget ( ) ;
245
- if ( ! force )
246
- await this . _waitForHitTargetAt ( point , deadline ) ;
247
244
248
- await this . _page . _frameManager . waitForSignalsCreatedBy ( async ( ) => {
249
- let restoreModifiers : input . Modifier [ ] | undefined ;
250
- if ( options && options . modifiers )
251
- restoreModifiers = await this . _page . keyboard . _ensureModifiers ( options . modifiers ) ;
252
- this . _page . _log ( inputLog , 'performing input action...' ) ;
253
- await action ( point ) ;
254
- this . _page . _log ( inputLog , '...done' ) ;
255
- if ( restoreModifiers )
256
- await this . _page . keyboard . _ensureModifiers ( restoreModifiers ) ;
257
- } , deadline , options , true ) ;
245
+ let paused = false ;
246
+ try {
247
+ await this . _page . _delegate . setActivityPaused ( true ) ;
248
+ paused = true ;
249
+
250
+ // Scroll into view and calculate the point again while paused just in case something has moved.
251
+ this . _page . _log ( inputLog , 'scrolling into view if needed...' ) ;
252
+ await this . _scrollRectIntoViewIfNeeded ( position ? { x : position . x , y : position . y , width : 0 , height : 0 } : undefined ) ;
253
+ this . _page . _log ( inputLog , '...done scrolling' ) ;
254
+ const point = roundPoint ( position ? await this . _offsetPoint ( position ) : await this . _clickablePoint ( ) ) ;
255
+
256
+ if ( ! force ) {
257
+ if ( ( options as any ) . __testHookBeforeHitTarget )
258
+ await ( options as any ) . __testHookBeforeHitTarget ( ) ;
259
+ this . _page . _log ( inputLog , `checking that element receives pointer events at (${ point . x } ,${ point . y } )...` ) ;
260
+ const matchesHitTarget = await this . _checkHitTargetAt ( point ) ;
261
+ if ( ! matchesHitTarget ) {
262
+ this . _page . _log ( inputLog , '...element does not receive pointer events, retrying input action' ) ;
263
+ await this . _page . _delegate . setActivityPaused ( false ) ;
264
+ paused = false ;
265
+ return 'retry' ;
266
+ }
267
+ this . _page . _log ( inputLog , `...element does receive pointer events, continuing input action` ) ;
268
+ }
269
+
270
+ await this . _page . _frameManager . waitForSignalsCreatedBy ( async ( ) => {
271
+ let restoreModifiers : input . Modifier [ ] | undefined ;
272
+ if ( options && options . modifiers )
273
+ restoreModifiers = await this . _page . keyboard . _ensureModifiers ( options . modifiers ) ;
274
+ this . _page . _log ( inputLog , 'performing input action...' ) ;
275
+ await action ( point ) ;
276
+ this . _page . _log ( inputLog , '...input action done' ) ;
277
+ this . _page . _log ( inputLog , 'waiting for navigations to finish...' ) ;
278
+ await this . _page . _delegate . setActivityPaused ( false ) ;
279
+ paused = false ;
280
+ if ( restoreModifiers )
281
+ await this . _page . keyboard . _ensureModifiers ( restoreModifiers ) ;
282
+ } , deadline , options , true ) ;
283
+ this . _page . _log ( inputLog , '...navigations have finished' ) ;
284
+
285
+ return 'done' ;
286
+ } finally {
287
+ if ( paused )
288
+ await this . _page . _delegate . setActivityPaused ( false ) ;
289
+ }
258
290
}
259
291
260
292
hover ( options ?: PointerActionOptions & types . PointerActionWaitOptions ) : Promise < void > {
261
- return this . _performPointerAction ( point => this . _page . mouse . move ( point . x , point . y ) , options ) ;
293
+ return this . _retryPointerAction ( point => this . _page . mouse . move ( point . x , point . y ) , options ) ;
262
294
}
263
295
264
296
click ( options ?: ClickOptions & types . PointerActionWaitOptions & types . NavigatingActionWaitOptions ) : Promise < void > {
265
- return this . _performPointerAction ( point => this . _page . mouse . click ( point . x , point . y , options ) , options ) ;
297
+ return this . _retryPointerAction ( point => this . _page . mouse . click ( point . x , point . y , options ) , options ) ;
266
298
}
267
299
268
300
dblclick ( options ?: MultiClickOptions & types . PointerActionWaitOptions & types . NavigatingActionWaitOptions ) : Promise < void > {
269
- return this . _performPointerAction ( point => this . _page . mouse . dblclick ( point . x , point . y , options ) , options ) ;
301
+ return this . _retryPointerAction ( point => this . _page . mouse . dblclick ( point . x , point . y , options ) , options ) ;
270
302
}
271
303
272
304
async selectOption ( values : string | ElementHandle | types . SelectOption | string [ ] | ElementHandle [ ] | types . SelectOption [ ] , options ?: types . NavigatingActionWaitOptions ) : Promise < string [ ] > {
@@ -429,11 +461,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
429
461
const timeoutMessage = 'element to be displayed and not moving' ;
430
462
const injectedResult = await helper . waitWithDeadline ( stablePromise , timeoutMessage , deadline ) ;
431
463
handleInjectedResult ( injectedResult , timeoutMessage ) ;
432
- this . _page . _log ( inputLog , '...done ' ) ;
464
+ this . _page . _log ( inputLog , '...element is displayed and does not move ' ) ;
433
465
}
434
466
435
- async _waitForHitTargetAt ( point : types . Point , deadline : number ) : Promise < void > {
436
- this . _page . _log ( inputLog , `waiting for element to receive pointer events at (${ point . x } ,${ point . y } ) ...` ) ;
467
+ async _checkHitTargetAt ( point : types . Point ) : Promise < boolean > {
437
468
const frame = await this . ownerFrame ( ) ;
438
469
if ( frame && frame . parentFrame ( ) ) {
439
470
const element = await frame . frameElement ( ) ;
@@ -443,13 +474,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
443
474
// Translate from viewport coordinates to frame coordinates.
444
475
point = { x : point . x - box . x , y : point . y - box . y } ;
445
476
}
446
- const hitTargetPromise = this . _evaluateInUtility ( ( { injected, node } , { timeout, point } ) => {
447
- return injected . waitForHitTargetAt ( node , timeout , point ) ;
448
- } , { timeout : helper . timeUntilDeadline ( deadline ) , point } ) ;
449
- const timeoutMessage = 'element to receive pointer events' ;
450
- const injectedResult = await helper . waitWithDeadline ( hitTargetPromise , timeoutMessage , deadline ) ;
451
- handleInjectedResult ( injectedResult , timeoutMessage ) ;
452
- this . _page . _log ( inputLog , '...done' ) ;
477
+ const injectedResult = await this . _evaluateInUtility ( ( { injected, node } , { point } ) => {
478
+ return injected . checkHitTargetAt ( node , point ) ;
479
+ } , { point } ) ;
480
+ return handleInjectedResult ( injectedResult , '' ) ;
453
481
}
454
482
}
455
483
@@ -470,3 +498,10 @@ function handleInjectedResult<T = undefined>(injectedResult: InjectedResult<T>,
470
498
throw new Error ( injectedResult . error ) ;
471
499
return injectedResult . value as T ;
472
500
}
501
+
502
+ function roundPoint ( point : types . Point ) : types . Point {
503
+ return {
504
+ x : ( point . x * 100 | 0 ) / 100 ,
505
+ y : ( point . y * 100 | 0 ) / 100 ,
506
+ } ;
507
+ }
0 commit comments