@@ -193,6 +193,123 @@ test('runtime typeText validates refs and forwards text to the backend primitive
193193 ) ;
194194} ) ;
195195
196+ test ( 'runtime focus and longPress share selector/ref target resolution' , async ( ) => {
197+ const calls : unknown [ ] = [ ] ;
198+ const device = createInteractionDevice ( selectorSnapshot ( ) , {
199+ focus : async ( _context , point ) => {
200+ calls . push ( { command : 'focus' , point } ) ;
201+ return { focused : true } ;
202+ } ,
203+ longPress : async ( _context , point , options ) => {
204+ calls . push ( { command : 'longPress' , point, durationMs : options ?. durationMs } ) ;
205+ } ,
206+ } ) ;
207+
208+ const focused = await device . interactions . focus ( selector ( 'label=Continue' ) , {
209+ session : 'default' ,
210+ } ) ;
211+ const longPressed = await device . interactions . longPress ( ref ( '@e1' ) , {
212+ session : 'default' ,
213+ durationMs : 750 ,
214+ } ) ;
215+
216+ assert . equal ( focused . kind , 'selector' ) ;
217+ assert . deepEqual ( focused . backendResult , { focused : true } ) ;
218+ assert . equal ( longPressed . kind , 'ref' ) ;
219+ assert . deepEqual ( calls , [
220+ { command : 'focus' , point : { x : 60 , y : 40 } } ,
221+ { command : 'longPress' , point : { x : 60 , y : 40 } , durationMs : 750 } ,
222+ ] ) ;
223+ } ) ;
224+
225+ test ( 'runtime scroll resolves selector targets before calling the backend primitive' , async ( ) => {
226+ const calls : unknown [ ] = [ ] ;
227+ const device = createInteractionDevice ( selectorSnapshot ( ) , {
228+ scroll : async ( _context , target , options ) => {
229+ calls . push ( { target, options } ) ;
230+ return { scrolled : true } ;
231+ } ,
232+ } ) ;
233+
234+ const selectorResult = await device . interactions . scroll ( {
235+ session : 'default' ,
236+ target : selector ( 'label=Continue' ) ,
237+ direction : 'down' ,
238+ pixels : 120 ,
239+ } ) ;
240+ const viewportResult = await device . interactions . scroll ( {
241+ direction : 'up' ,
242+ amount : 0.5 ,
243+ } ) ;
244+
245+ assert . equal ( selectorResult . kind , 'selector' ) ;
246+ assert . equal ( viewportResult . kind , 'viewport' ) ;
247+ assert . deepEqual ( calls , [
248+ {
249+ target : { kind : 'point' , point : { x : 60 , y : 40 } } ,
250+ options : { direction : 'down' , pixels : 120 } ,
251+ } ,
252+ {
253+ target : { kind : 'viewport' } ,
254+ options : { direction : 'up' , amount : 0.5 } ,
255+ } ,
256+ ] ) ;
257+ } ) ;
258+
259+ test ( 'runtime swipe supports explicit and viewport-derived targets' , async ( ) => {
260+ const calls : unknown [ ] = [ ] ;
261+ const device = createInteractionDevice ( selectorSnapshot ( ) , {
262+ swipe : async ( _context , from , to , options ) => {
263+ calls . push ( { from, to, durationMs : options ?. durationMs } ) ;
264+ } ,
265+ } ) ;
266+
267+ const explicit = await device . interactions . swipe ( {
268+ from : selector ( 'label=Continue' ) ,
269+ to : { x : 200 , y : 40 } ,
270+ durationMs : 300 ,
271+ session : 'default' ,
272+ } ) ;
273+ const directional = await device . interactions . swipe ( {
274+ direction : 'left' ,
275+ distance : 25 ,
276+ session : 'default' ,
277+ } ) ;
278+
279+ assert . deepEqual ( explicit . from , { x : 60 , y : 40 } ) ;
280+ assert . deepEqual ( directional . from , { x : 60 , y : 40 } ) ;
281+ assert . deepEqual ( directional . to , { x : 35 , y : 40 } ) ;
282+ assert . deepEqual ( calls , [
283+ { from : { x : 60 , y : 40 } , to : { x : 200 , y : 40 } , durationMs : 300 } ,
284+ { from : { x : 60 , y : 40 } , to : { x : 35 , y : 40 } , durationMs : undefined } ,
285+ ] ) ;
286+ } ) ;
287+
288+ test ( 'runtime pinch is backend-gated and resolves optional center targets' , async ( ) => {
289+ const calls : unknown [ ] = [ ] ;
290+ const unsupported = createInteractionDevice ( selectorSnapshot ( ) ) ;
291+ await assert . rejects (
292+ ( ) => unsupported . interactions . pinch ( { scale : 1.2 } ) ,
293+ / p i n c h i s n o t s u p p o r t e d / ,
294+ ) ;
295+
296+ const device = createInteractionDevice ( selectorSnapshot ( ) , {
297+ pinch : async ( _context , options ) => {
298+ calls . push ( options ) ;
299+ } ,
300+ } ) ;
301+
302+ const result = await device . interactions . pinch ( {
303+ scale : 0.8 ,
304+ center : ref ( '@e1' ) ,
305+ session : 'default' ,
306+ } ) ;
307+
308+ assert . equal ( result . kind , 'pinch' ) ;
309+ assert . deepEqual ( result . center , { x : 60 , y : 40 } ) ;
310+ assert . deepEqual ( calls , [ { scale : 0.8 , center : { x : 60 , y : 40 } } ] ) ;
311+ } ) ;
312+
196313test ( 'runtime interaction commands are available from the command namespace' , async ( ) => {
197314 const device = createInteractionDevice ( selectorSnapshot ( ) , {
198315 tap : async ( ) => { } ,
@@ -235,7 +352,20 @@ function fillableSnapshot(): SnapshotState {
235352
236353function createInteractionDevice (
237354 snapshot : SnapshotState ,
238- overrides : Partial < Pick < AgentDeviceBackend , 'captureSnapshot' | 'tap' | 'fill' | 'typeText' > > & {
355+ overrides : Partial <
356+ Pick <
357+ AgentDeviceBackend ,
358+ | 'captureSnapshot'
359+ | 'tap'
360+ | 'fill'
361+ | 'typeText'
362+ | 'focus'
363+ | 'longPress'
364+ | 'scroll'
365+ | 'swipe'
366+ | 'pinch'
367+ >
368+ > & {
239369 platform ?: AgentDeviceBackend [ 'platform' ] ;
240370 sessionMetadata ?: Record < string , unknown > ;
241371 } = { } ,
@@ -248,6 +378,13 @@ function createInteractionDevice(
248378 tap : async ( ...args ) => await overrides . tap ?.( ...args ) ,
249379 fill : async ( ...args ) => await overrides . fill ?.( ...args ) ,
250380 typeText : async ( ...args ) => await overrides . typeText ?.( ...args ) ,
381+ focus : overrides . focus ? async ( ...args ) => await overrides . focus ?.( ...args ) : undefined ,
382+ longPress : overrides . longPress
383+ ? async ( ...args ) => await overrides . longPress ?.( ...args )
384+ : undefined ,
385+ scroll : overrides . scroll ? async ( ...args ) => await overrides . scroll ?.( ...args ) : undefined ,
386+ swipe : overrides . swipe ? async ( ...args ) => await overrides . swipe ?.( ...args ) : undefined ,
387+ pinch : overrides . pinch ? async ( ...args ) => await overrides . pinch ?.( ...args ) : undefined ,
251388 } satisfies AgentDeviceBackend ,
252389 artifacts : createLocalArtifactAdapter ( ) ,
253390 sessions : createMemorySessionStore ( [
0 commit comments