@@ -20,7 +20,7 @@ import fs from 'fs';
2020import { isUnderTest } from '../utils' ;
2121import { BrowserContext } from './browserContext' ;
2222import { Debugger } from './debugger' ;
23- import { buildFullSelector , generateFrameSelector , metadataToCallLog } from './recorder/recorderUtils' ;
23+ import { buildFullSelector , generateFrameSelector , isAssertAction , metadataToCallLog , shouldMergeAction } from './recorder/recorderUtils' ;
2424import { locatorOrSelectorAsSelector } from '../utils/isomorphic/locatorParser' ;
2525import { stringifySelector } from '../utils/isomorphic/selectorParser' ;
2626import { ProgressController } from './progress' ;
@@ -31,6 +31,8 @@ import { eventsHelper, monotonicTime } from './../utils';
3131import { Frame } from './frames' ;
3232import { Page } from './page' ;
3333import { performAction } from './recorder/recorderRunner' ;
34+ import { findNewElementRef } from '../utils/isomorphic/ariaSnapshot' ;
35+ import { yaml } from '../utilsBundle' ;
3436
3537import type { Language } from './codegen/types' ;
3638import type { CallMetadata , InstrumentationListener , SdkObject } from './instrumentation' ;
@@ -520,16 +522,69 @@ export class Recorder extends EventEmitter<RecorderEventMap> implements Instrume
520522 return actionInContext ;
521523 }
522524
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+
523572 private async _performAction ( frame : Frame , action : actions . PerformOnRecordAction ) {
524573 const actionInContext = await this . _createActionInContext ( frame , action ) ;
574+ const assertActionInContext = await this . _maybeGenerateAssertAction ( frame , actionInContext ) ;
575+ if ( assertActionInContext )
576+ this . _signalProcessor . addAction ( assertActionInContext ) ;
525577 this . _signalProcessor . addAction ( actionInContext ) ;
578+ setLastFrameAction ( frame , actionInContext ) ;
526579 if ( actionInContext . action . name !== 'openPage' && actionInContext . action . name !== 'closePage' )
527580 await performAction ( this . _pageAliases , actionInContext ) ;
528581 actionInContext . endTime = monotonicTime ( ) ;
529582 }
530583
531584 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 ) ;
533588 }
534589
535590 private _onFrameNavigated ( frame : Frame , page : Page ) {
@@ -569,3 +624,11 @@ function languageForFile(file: string) {
569624 return 'csharp' ;
570625 return 'javascript' ;
571626}
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