@@ -20,7 +20,7 @@ import fs from 'fs';
20
20
import { isUnderTest } from '../utils' ;
21
21
import { BrowserContext } from './browserContext' ;
22
22
import { Debugger } from './debugger' ;
23
- import { buildFullSelector , generateFrameSelector , metadataToCallLog } from './recorder/recorderUtils' ;
23
+ import { buildFullSelector , generateFrameSelector , isAssertAction , metadataToCallLog , shouldMergeAction } from './recorder/recorderUtils' ;
24
24
import { locatorOrSelectorAsSelector } from '../utils/isomorphic/locatorParser' ;
25
25
import { stringifySelector } from '../utils/isomorphic/selectorParser' ;
26
26
import { ProgressController } from './progress' ;
@@ -31,6 +31,8 @@ import { eventsHelper, monotonicTime } from './../utils';
31
31
import { Frame } from './frames' ;
32
32
import { Page } from './page' ;
33
33
import { performAction } from './recorder/recorderRunner' ;
34
+ import { findNewElementRef } from '../utils/isomorphic/ariaSnapshot' ;
35
+ import { yaml } from '../utilsBundle' ;
34
36
35
37
import type { Language } from './codegen/types' ;
36
38
import type { CallMetadata , InstrumentationListener , SdkObject } from './instrumentation' ;
@@ -520,16 +522,69 @@ export class Recorder extends EventEmitter<RecorderEventMap> implements Instrume
520
522
return actionInContext ;
521
523
}
522
524
525
+ private async _maybeGenerateAssertAction ( frame : Frame , actionInContext : actions . ActionInContext ) {
526
+ const lastAction = getLastFrameAction ( frame ) ;
527
+ if ( isAssertAction ( actionInContext . action ) )
528
+ return ;
529
+ if ( lastAction && ( isAssertAction ( lastAction . action ) || shouldMergeAction ( actionInContext , lastAction ) ) )
530
+ return ;
531
+ const newSnapshot = actionInContext . action . ariaSnapshot ;
532
+ if ( ! newSnapshot )
533
+ return ;
534
+ const lastSnapshot = lastAction ?. action . ariaSnapshot || `- document [ref=e1]\n` ;
535
+ if ( ! lastSnapshot )
536
+ return ;
537
+ const callMetadata = serverSideCallMetadata ( ) ;
538
+ const controller = new ProgressController ( callMetadata , frame ) ;
539
+ const selector = await controller . run ( async progress => {
540
+ const ref = findNewElementRef ( yaml , lastSnapshot , newSnapshot ) ;
541
+ if ( ! ref )
542
+ return ;
543
+ // Note: recorder runs in the main world, so we need to resolve the ref in the main world.
544
+ // Note: resolveSelector always returns a |page|-based selector, not |frame|-based.
545
+ const { resolvedSelector } = await frame . resolveSelector ( progress , `aria-ref=${ ref } ` , { mainWorld : true } ) . catch ( ( ) => ( { resolvedSelector : undefined } ) ) ;
546
+ if ( ! resolvedSelector )
547
+ return ;
548
+ const isVisible = await frame . _page . mainFrame ( ) . isVisible ( progress , resolvedSelector , { strict : true } ) . catch ( ( ) => false ) ;
549
+ return isVisible ? resolvedSelector : undefined ;
550
+ } ) . catch ( ( ) => undefined ) ;
551
+ if ( ! selector )
552
+ return ;
553
+ if ( ! actionInContext . frame . framePath . length && 'selector' in actionInContext . action && actionInContext . action . selector === selector )
554
+ return ; // Do not assert the action target, auto-waiting takes care of it.
555
+ const assertActionInContext : actions . ActionInContext = {
556
+ frame : {
557
+ pageGuid : actionInContext . frame . pageGuid ,
558
+ pageAlias : actionInContext . frame . pageAlias ,
559
+ framePath : [ ] ,
560
+ } ,
561
+ action : {
562
+ name : 'assertVisible' ,
563
+ selector,
564
+ signals : [ ] ,
565
+ } ,
566
+ startTime : actionInContext . startTime ,
567
+ endTime : actionInContext . startTime ,
568
+ } ;
569
+ return assertActionInContext ;
570
+ }
571
+
523
572
private async _performAction ( frame : Frame , action : actions . PerformOnRecordAction ) {
524
573
const actionInContext = await this . _createActionInContext ( frame , action ) ;
574
+ const assertActionInContext = await this . _maybeGenerateAssertAction ( frame , actionInContext ) ;
575
+ if ( assertActionInContext )
576
+ this . _signalProcessor . addAction ( assertActionInContext ) ;
525
577
this . _signalProcessor . addAction ( actionInContext ) ;
578
+ setLastFrameAction ( frame , actionInContext ) ;
526
579
if ( actionInContext . action . name !== 'openPage' && actionInContext . action . name !== 'closePage' )
527
580
await performAction ( this . _pageAliases , actionInContext ) ;
528
581
actionInContext . endTime = monotonicTime ( ) ;
529
582
}
530
583
531
584
private async _recordAction ( frame : Frame , action : actions . Action ) {
532
- this . _signalProcessor . addAction ( await this . _createActionInContext ( frame , action ) ) ;
585
+ const actionInContext = await this . _createActionInContext ( frame , action ) ;
586
+ this . _signalProcessor . addAction ( actionInContext ) ;
587
+ setLastFrameAction ( frame , actionInContext ) ;
533
588
}
534
589
535
590
private _onFrameNavigated ( frame : Frame , page : Page ) {
@@ -569,3 +624,11 @@ function languageForFile(file: string) {
569
624
return 'csharp' ;
570
625
return 'javascript' ;
571
626
}
627
+
628
+ const kLastFrameActionSymbol = Symbol ( 'lastFrameAction' ) ;
629
+ function getLastFrameAction ( frame : Frame ) : actions . ActionInContext | undefined {
630
+ return ( frame as any ) [ kLastFrameActionSymbol ] ;
631
+ }
632
+ function setLastFrameAction ( frame : Frame , action : actions . ActionInContext | undefined ) {
633
+ ( frame as any ) [ kLastFrameActionSymbol ] = action ;
634
+ }
0 commit comments