11import { getSnapshotReferenceFrame } from '../../daemon/touch-reference-frame.ts' ;
22import type { DaemonRequest , DaemonResponse } from '../../daemon/types.ts' ;
3+ import {
4+ buildSwipeGesturePlan ,
5+ clampGesturePoint ,
6+ pointFromPercent ,
7+ type ScrollDirection ,
8+ } from '../../core/scroll-gesture.ts' ;
39import type { ReplayVarScope } from '../../replay/vars.ts' ;
410import { sleep } from '../../utils/timeouts.ts' ;
511import { pointForMaestroTapOnTarget , swipeCoordinatesFromTarget } from './runtime-geometry.ts' ;
@@ -122,13 +128,11 @@ export async function invokeMaestroTapPointPercent(params: {
122128 ) ;
123129 }
124130
131+ const point = pointFromPercent ( frame , xPercent , yPercent ) ;
125132 return await params . invoke ( {
126133 ...params . baseReq ,
127134 command : 'click' ,
128- positionals : [
129- String ( Math . round ( ( frame . referenceWidth * xPercent ) / 100 ) ) ,
130- String ( Math . round ( ( frame . referenceHeight * yPercent ) / 100 ) ) ,
131- ] ,
135+ positionals : [ String ( point . x ) , String ( point . y ) ] ,
132136 } ) ;
133137}
134138
@@ -263,22 +267,12 @@ function resolveDirectionalScreenSwipe(
263267 response : errorResponse ( 'INVALID_ARGS' , 'Maestro direction swipe requires a direction.' ) ,
264268 } ;
265269 }
266- const point = ( xPercent : number , yPercent : number ) => percentPoint ( frame , xPercent , yPercent , 8 ) ;
267270 switch ( direction ) {
268271 case 'up' :
269- return { ok : true , start : point ( 50 , 80 ) , end : point ( 50 , 20 ) , durationMs } ;
270272 case 'down' :
271- return { ok : true , start : point ( 50 , 20 ) , end : point ( 50 , 80 ) , durationMs } ;
272- case 'left' : {
273- const [ startX , endX ] = androidHorizontalDirectionalSwipeX ( platform , 80 , 20 ) ;
274- const yPercent = androidHorizontalContentSwipeY ( platform , startX , 50 , endX , 50 ) ;
275- return { ok : true , start : point ( startX , yPercent ) , end : point ( endX , yPercent ) , durationMs } ;
276- }
277- case 'right' : {
278- const [ startX , endX ] = androidHorizontalDirectionalSwipeX ( platform , 20 , 80 ) ;
279- const yPercent = androidHorizontalContentSwipeY ( platform , startX , 50 , endX , 50 ) ;
280- return { ok : true , start : point ( startX , yPercent ) , end : point ( endX , yPercent ) , durationMs } ;
281- }
273+ case 'left' :
274+ case 'right' :
275+ return buildMaestroDirectionalScreenSwipe ( direction , frame , platform , durationMs ) ;
282276 default :
283277 return {
284278 ok : false ,
@@ -290,13 +284,33 @@ function resolveDirectionalScreenSwipe(
290284 }
291285}
292286
293- function androidHorizontalDirectionalSwipeX (
287+ function buildMaestroDirectionalScreenSwipe (
288+ direction : ScrollDirection ,
289+ frame : { referenceWidth : number ; referenceHeight : number } ,
294290 platform : string ,
295- startX : number ,
296- endX : number ,
297- ) : [ number , number ] {
298- if ( platform !== 'android' ) return [ startX , endX ] ;
299- return startX < endX ? [ 20 , 80 ] : [ 80 , 20 ] ;
291+ durationMs : string | undefined ,
292+ ) : MaestroScreenSwipeResolution {
293+ const plan = buildSwipeGesturePlan ( {
294+ direction,
295+ amount : 0.6 ,
296+ referenceWidth : frame . referenceWidth ,
297+ referenceHeight : frame . referenceHeight ,
298+ } ) ;
299+ const start = clampGesturePoint ( { x : plan . x1 , y : plan . y1 } , frame , 8 ) ;
300+ const end = clampGesturePoint ( { x : plan . x2 , y : plan . y2 } , frame , 8 ) ;
301+
302+ if ( ( direction === 'left' || direction === 'right' ) && platform === 'android' ) {
303+ const contentLaneY = pointFromPercent ( frame , 50 , 65 , { marginPx : 8 } ) . y ;
304+ start . y = contentLaneY ;
305+ end . y = contentLaneY ;
306+ }
307+
308+ return {
309+ ok : true ,
310+ start,
311+ end,
312+ durationMs,
313+ } ;
300314}
301315
302316function resolvePercentScreenSwipe (
@@ -313,55 +327,30 @@ function resolvePercentScreenSwipe(
313327 } ;
314328 }
315329 const [ x1 , y1 , x2 , y2 ] = values as [ number , number , number , number ] ;
316- const adjustedY = androidHorizontalContentSwipeY ( platform , x1 , y1 , x2 , y2 ) ;
330+ const lane = maestroHorizontalContentSwipeLanePercent ( platform , x1 , y1 , x2 , y2 ) ;
317331 return {
318332 ok : true ,
319- start : percentPoint ( frame , x1 , adjustedY , 1 ) ,
320- end : percentPoint ( frame , x2 , adjustedY , 1 ) ,
333+ start : pointFromPercent ( frame , x1 , lane . startY , { marginPx : 1 } ) ,
334+ end : pointFromPercent ( frame , x2 , lane . endY , { marginPx : 1 } ) ,
321335 durationMs,
322336 } ;
323337}
324338
325- function androidHorizontalContentSwipeY (
339+ function maestroHorizontalContentSwipeLanePercent (
326340 platform : string ,
327341 x1 : number ,
328342 y1 : number ,
329343 x2 : number ,
330344 y2 : number ,
331- ) : number {
332- if ( platform !== 'android' ) return y2 ;
333- if ( y1 !== y2 || y1 !== 50 ) return y2 ;
334- if ( Math . abs ( x2 - x1 ) < 30 ) return y2 ;
345+ ) : { startY : number ; endY : number } {
346+ if ( platform !== 'android' ) return { startY : y1 , endY : y2 } ;
347+ if ( y1 !== y2 || y1 !== 50 ) return { startY : y1 , endY : y2 } ;
348+ if ( Math . abs ( x2 - x1 ) < 30 ) return { startY : y1 , endY : y2 } ;
335349 // Maestro's Android driver treats 50% horizontal swipes as content swipes.
336350 // Raw `adb input swipe` at the physical screen midpoint can land above
337351 // horizontally paged content in React Native layouts, so use a lower content
338352 // lane for full-width horizontal Maestro percentage swipes.
339- return 65 ;
340- }
341-
342- function percentPoint (
343- frame : { referenceWidth : number ; referenceHeight : number } ,
344- xPercent : number ,
345- yPercent : number ,
346- marginPx : number ,
347- ) : { x : number ; y : number } {
348- return {
349- x : clampPoint (
350- Math . round ( ( frame . referenceWidth * xPercent ) / 100 ) ,
351- marginPx ,
352- frame . referenceWidth ,
353- ) ,
354- y : clampPoint (
355- Math . round ( ( frame . referenceHeight * yPercent ) / 100 ) ,
356- marginPx ,
357- frame . referenceHeight ,
358- ) ,
359- } ;
360- }
361-
362- function clampPoint ( value : number , marginPx : number , size : number ) : number {
363- const max = Math . max ( marginPx , size - marginPx ) ;
364- return Math . min ( max , Math . max ( marginPx , value ) ) ;
353+ return { startY : 65 , endY : 65 } ;
365354}
366355
367356async function probeMaestroScrollVisibility (
0 commit comments