@@ -2145,6 +2145,50 @@ describe('credential protection', () => {
21452145 return { contents, values, writes, dialogs, selectionReads : ( ) => selectionReads }
21462146 }
21472147
2148+ it . each ( [
2149+ { values : [ 'a' , 'b' ] , labels : [ 'A' , 'B' ] , expected : true } ,
2150+ { values : [ 'a' ] , labels : [ 'A' ] , expected : false } ,
2151+ { values : [ 'a' , 'b' ] , labels : [ 'A' , 'Other' ] , expected : false } ,
2152+ ] ) (
2153+ 'verifies the entire multiple selection %j' ,
2154+ async ( { values : readbackValues , labels, expected } ) => {
2155+ const contents = await openPage ( )
2156+ respondWith ( contents , {
2157+ selectOptionInElement : {
2158+ selected : 'A' ,
2159+ value : 'a' ,
2160+ values : [ 'a' , 'b' ] ,
2161+ labels : [ 'A' , 'B' ] ,
2162+ } ,
2163+ readSelectElementState : { selected : 'A' , value : 'a' , values : readbackValues , labels } ,
2164+ } )
2165+ const result = await driver . executeTool ( 'chat-test' , 'browser_select_option' , {
2166+ elementId : 0 ,
2167+ values : [ 'a' , 'b' ] ,
2168+ } )
2169+ expect ( result , JSON . stringify ( result ) ) . toMatchObject ( {
2170+ ok : true ,
2171+ result : { effectObserved : expected , readback : { values : readbackValues } } ,
2172+ } )
2173+ }
2174+ )
2175+
2176+ it . each ( [
2177+ { value : 'a' , values : [ 'b' ] } ,
2178+ { values : [ 1 ] } ,
2179+ { values : Array . from ( { length : 101 } , ( ) => 'a' ) } ,
2180+ { } ,
2181+ ] ) ( 'rejects invalid selection arguments before dispatch' , async ( params ) => {
2182+ const contents = await openPage ( )
2183+ vi . mocked ( contents . executeJavaScript ) . mockClear ( )
2184+ const result = await driver . executeTool ( 'chat-test' , 'browser_select_option' , {
2185+ elementId : 0 ,
2186+ ...params ,
2187+ } )
2188+ expect ( result . ok ) . toBe ( false )
2189+ expect ( contents . executeJavaScript ) . not . toHaveBeenCalled ( )
2190+ } )
2191+
21482192 const formFields = [
21492193 { elementId : 1 , kind : 'select' , value : 'first' } ,
21502194 { elementId : 2 , kind : 'select' , value : 'second' } ,
@@ -2475,6 +2519,97 @@ describe('credential protection', () => {
24752519 expect ( cdpCalls ( contents , 'Input.insertText' ) ) . toHaveLength ( 1 )
24762520 } )
24772521
2522+ it ( 'sets structured input values without dispatching text or select-all keystrokes' , async ( ) => {
2523+ const contents = await openPage ( )
2524+ respondWith ( contents , {
2525+ focusElementForTyping : { focused : true , kind : 'input' , valueInput : true , x : 24 , y : 48 } ,
2526+ setFocusedInputValue : { dispatched : true } ,
2527+ readActiveElementState : { activeElement : 'input' , valueLength : 10 } ,
2528+ readPageActionState : { } ,
2529+ } )
2530+ const result = await driver . executeTool ( 'chat-test' , 'browser_type' , {
2531+ elementId : 0 ,
2532+ text : '2026-09-15' ,
2533+ } )
2534+ expect ( result ) . toMatchObject ( { ok : true , result : { dispatched : true , trusted : false } } )
2535+ expect ( cdpCalls ( contents , 'Input.insertText' ) ) . toHaveLength ( 0 )
2536+ expect ( cdpCalls ( contents , 'Input.dispatchKeyEvent' ) ) . toHaveLength ( 0 )
2537+ } )
2538+
2539+ it ( 'does not retry a rejected structured value through synthetic typing' , async ( ) => {
2540+ const contents = await openPage ( )
2541+ respondWith ( contents , {
2542+ focusElementForTyping : { focused : true , kind : 'input' , valueInput : true , x : 24 , y : 48 } ,
2543+ setFocusedInputValue : { error : 'Invalid value; the field was not changed.' } ,
2544+ readActiveElementState : { } ,
2545+ readPageActionState : { } ,
2546+ } )
2547+ const result = await driver . executeTool ( 'chat-test' , 'browser_type' , {
2548+ elementId : 0 ,
2549+ text : 'invalid-date' ,
2550+ } )
2551+ expect ( result ) . toMatchObject ( { ok : false , error : expect . stringContaining ( 'Invalid value' ) } )
2552+ expect (
2553+ vi
2554+ . mocked ( contents . executeJavaScript )
2555+ . mock . calls . filter ( ( [ expression ] ) => isPageCall ( String ( expression ) , 'typeIntoElement' ) )
2556+ ) . toHaveLength ( 0 )
2557+ expect ( cdpCalls ( contents , 'Input.insertText' ) ) . toHaveLength ( 0 )
2558+ } )
2559+
2560+ it ( 'reports an interrupted structured write as uncertain without replaying it' , async ( ) => {
2561+ const contents = await openPage ( )
2562+ let writes = 0
2563+ vi . mocked ( contents . executeJavaScript ) . mockImplementation ( ( expression : string ) => {
2564+ if ( isPageCall ( expression , 'focusElementForTyping' ) )
2565+ return Promise . resolve ( { focused : true , valueInput : true , x : 24 , y : 48 } )
2566+ if ( isPageCall ( expression , 'setFocusedInputValue' ) ) {
2567+ writes ++
2568+ return Promise . reject ( new Error ( 'Execution context was destroyed' ) )
2569+ }
2570+ return Promise . resolve ( { } )
2571+ } )
2572+ const result = await driver . executeTool ( 'chat-test' , 'browser_type' , {
2573+ elementId : 0 ,
2574+ text : '2026-09-15' ,
2575+ } )
2576+ expect ( result ) . toMatchObject ( {
2577+ ok : false ,
2578+ error : expect . stringContaining ( 'may have reached the field and was not retried' ) ,
2579+ } )
2580+ expect ( writes ) . toBe ( 1 )
2581+ expect ( cdpCalls ( contents , 'Input.insertText' ) ) . toHaveLength ( 0 )
2582+ expect (
2583+ vi
2584+ . mocked ( contents . executeJavaScript )
2585+ . mock . calls . some ( ( [ expression ] ) => isPageCall ( String ( expression ) , 'typeIntoElement' ) )
2586+ ) . toBe ( false )
2587+ } )
2588+
2589+ it ( 'refuses a field whose input mode changes before dispatch' , async ( ) => {
2590+ const contents = await openPage ( )
2591+ let reads = 0
2592+ vi . mocked ( contents . executeJavaScript ) . mockImplementation ( ( expression : string ) => {
2593+ if ( isPageCall ( expression , 'focusElementForTyping' ) )
2594+ return Promise . resolve ( { focused : true , valueInput : ++ reads === 1 , x : 24 , y : 48 } )
2595+ return Promise . resolve ( { } )
2596+ } )
2597+ const result = await driver . executeTool ( 'chat-test' , 'browser_type' , {
2598+ elementId : 0 ,
2599+ text : '2026-09-15' ,
2600+ } )
2601+ expect ( result ) . toMatchObject ( {
2602+ ok : false ,
2603+ error : expect . stringContaining ( 'field type changed' ) ,
2604+ } )
2605+ expect ( cdpCalls ( contents , 'Input.insertText' ) ) . toHaveLength ( 0 )
2606+ expect (
2607+ vi
2608+ . mocked ( contents . executeJavaScript )
2609+ . mock . calls . some ( ( [ expression ] ) => isPageCall ( String ( expression ) , 'setFocusedInputValue' ) )
2610+ ) . toBe ( false )
2611+ } )
2612+
24782613 it ( 'accepts empty text and sends it through native insertion to clear a field' , async ( ) => {
24792614 const contents = await openPage ( )
24802615 respondWith ( contents , {
0 commit comments