@@ -2474,6 +2474,75 @@ describe('credential protection', () => {
24742474 expect ( cdpCalls ( contents , 'Input.dispatchKeyEvent' ) . length ) . toBeGreaterThan ( 0 )
24752475 } )
24762476
2477+ it . each ( [ 'cancelled' , 'timed out' ] as const ) (
2478+ 'retains a completed action when its observation is %s' ,
2479+ async ( stop ) => {
2480+ const contents = await openPage ( )
2481+ respondWith ( contents , {
2482+ activeElementSecrecy : 'safe' ,
2483+ readActiveElementState : { } ,
2484+ readPageActionState : { } ,
2485+ } )
2486+ const pageCall = vi . mocked ( contents . executeJavaScript ) . getMockImplementation ( )
2487+ let releaseObservation : ( value : unknown ) => void = ( ) => { }
2488+ const observation = new Promise < unknown > ( ( resolve ) => {
2489+ releaseObservation = resolve
2490+ } )
2491+ let observing = false
2492+ vi . mocked ( contents . executeJavaScript ) . mockImplementation ( ( expression , ...args ) => {
2493+ if ( isPageCall ( expression , 'collectSnapshot' ) ) {
2494+ observing = true
2495+ return observation
2496+ }
2497+ return pageCall ?.( expression , ...args ) ?? Promise . resolve ( undefined )
2498+ } )
2499+ vi . useFakeTimers ( )
2500+ try {
2501+ const timersBefore = vi . getTimerCount ( )
2502+ const pending = driver . executeTool (
2503+ 'chat-test' ,
2504+ 'browser_press_key' ,
2505+ { key : 'a' , observe : { } } ,
2506+ 'observed-action'
2507+ )
2508+ await vi . advanceTimersByTimeAsync ( 200 )
2509+ expect ( observing ) . toBe ( true )
2510+
2511+ if ( stop === 'cancelled' ) driver . cancelTool ( 'chat-test' , 'observed-action' )
2512+ else
2513+ await vi . advanceTimersByTimeAsync ( driver . browserToolWatchdogMs ( 'browser_press_key' , { } ) ! )
2514+
2515+ await expect ( pending ) . resolves . toMatchObject ( {
2516+ ok : true ,
2517+ result : {
2518+ pressed : 'a' ,
2519+ trusted : true ,
2520+ observation : {
2521+ ok : false ,
2522+ doNotRetry : true ,
2523+ note : expect . stringContaining ( 'The action already ran' ) ,
2524+ } ,
2525+ } ,
2526+ } )
2527+ await expect (
2528+ driver . executeTool ( 'chat-test' , 'browser_list_tabs' , { } )
2529+ ) . resolves . toMatchObject ( {
2530+ ok : true ,
2531+ } )
2532+ expect ( vi . getTimerCount ( ) ) . toBe ( timersBefore )
2533+ expect (
2534+ cdpCalls ( contents , 'Input.dispatchKeyEvent' ) . filter ( ( [ , event ] ) =>
2535+ [ 'keyDown' , 'rawKeyDown' ] . includes ( ( event as { type : string } ) . type )
2536+ )
2537+ ) . toHaveLength ( 1 )
2538+ } finally {
2539+ releaseObservation ( { outline : 'Late snapshot' , refIds : [ ] , nextElementId : 1 } )
2540+ await vi . advanceTimersByTimeAsync ( 0 )
2541+ vi . useRealTimers ( )
2542+ }
2543+ }
2544+ )
2545+
24772546 it ( 'reports when a platform-mismatched shortcut produces no observable effect' , async ( ) => {
24782547 const contents = await openPage ( )
24792548 respondWith ( contents , {
@@ -3641,6 +3710,47 @@ describe('credential protection', () => {
36413710 expect ( dismissed ) . toMatchObject ( { ok : true } )
36423711 } )
36433712
3713+ it ( 'dismisses background tab dialogs while the action target accepts its dialog' , async ( ) => {
3714+ const background = await openPage ( )
3715+ await driver . executeTool ( 'chat-test' , 'browser_open_tab' , { } )
3716+ const contents = session . requireAutomationTab ( ) . view . webContents
3717+ vi . mocked ( contents . getURL ) . mockReturnValue ( 'https://example.com/target' )
3718+ respondWith ( contents , {
3719+ describePointTarget : { found : true , element : 'button "Delete"' , editable : false } ,
3720+ readActiveElementState : { } ,
3721+ readPageActionState : { } ,
3722+ } )
3723+ const listeners = [ background , contents ] . map (
3724+ ( tab ) =>
3725+ vi . mocked ( tab . debugger . on ) . mock . calls . find ( ( [ event ] ) => event === 'message' ) ?. [ 1 ] as
3726+ | ( ( event : unknown , method : string , params : unknown ) => void )
3727+ | undefined
3728+ )
3729+ vi . mocked ( contents . debugger . sendCommand ) . mockImplementation ( async ( method , params ) => {
3730+ if ( method === 'Input.dispatchMouseEvent' && params ?. type === 'mouseReleased' ) {
3731+ for ( const listener of listeners ) {
3732+ listener ?.( { } , 'Page.javascriptDialogOpening' , { type : 'confirm' , message : 'Delete?' } )
3733+ }
3734+ }
3735+ return { }
3736+ } )
3737+
3738+ await expect (
3739+ driver . executeTool ( 'chat-test' , 'browser_click_at' , {
3740+ x : 10 ,
3741+ y : 20 ,
3742+ dialog : { accept : true } ,
3743+ } )
3744+ ) . resolves . toMatchObject ( { ok : true } )
3745+
3746+ expect ( cdpCalls ( background , 'Page.handleJavaScriptDialog' ) . map ( ( [ , answer ] ) => answer ) ) . toEqual (
3747+ [ { accept : false } ]
3748+ )
3749+ expect ( cdpCalls ( contents , 'Page.handleJavaScriptDialog' ) . map ( ( [ , answer ] ) => answer ) ) . toEqual ( [
3750+ { accept : true } ,
3751+ ] )
3752+ } )
3753+
36443754 it ( 'rejects a malformed dialog response before dispatch' , async ( ) => {
36453755 const contents = await openPage ( )
36463756
0 commit comments