1
1
import { Page , Locator , FrameLocator } from "playwright" ;
2
2
import { PlaywrightCommandException } from "../../../types/playwright" ;
3
3
import { StagehandPage } from "../../StagehandPage" ;
4
- import { getNodeFromXpath } from "@/lib/dom/utils" ;
5
4
import { Logger } from "../../../types/log" ;
6
5
import { MethodHandlerContext } from "@/types/act" ;
7
6
import { StagehandClickError } from "@/types/stagehandErrors" ;
@@ -59,7 +58,7 @@ export const methodHandlerMap: Record<
59
58
} ;
60
59
61
60
export async function scrollToNextChunk ( ctx : MethodHandlerContext ) {
62
- const { stagehandPage , xpath , logger } = ctx ;
61
+ const { locator , logger , xpath } = ctx ;
63
62
64
63
logger ( {
65
64
category : "action" ,
@@ -71,40 +70,45 @@ export async function scrollToNextChunk(ctx: MethodHandlerContext) {
71
70
} ) ;
72
71
73
72
try {
74
- await stagehandPage . page . evaluate (
75
- ( { xpath } ) => {
76
- const elementNode = getNodeFromXpath ( xpath ) ;
77
- if ( ! elementNode || elementNode . nodeType !== Node . ELEMENT_NODE ) {
78
- throw Error ( `Could not locate element to scroll on.` ) ;
79
- }
73
+ await locator . evaluate (
74
+ ( element ) => {
75
+ const waitForScrollEnd = ( el : HTMLElement | Element ) =>
76
+ new Promise < void > ( ( resolve ) => {
77
+ let last = el . scrollTop ?? 0 ;
78
+ const check = ( ) => {
79
+ const cur = el . scrollTop ?? 0 ;
80
+ if ( cur === last ) return resolve ( ) ;
81
+ last = cur ;
82
+ requestAnimationFrame ( check ) ;
83
+ } ;
84
+ requestAnimationFrame ( check ) ;
85
+ } ) ;
80
86
81
- const element = elementNode as HTMLElement ;
82
87
const tagName = element . tagName . toLowerCase ( ) ;
83
- let height : number ;
84
88
85
89
if ( tagName === "html" || tagName === "body" ) {
86
- height = window . visualViewport . height ;
87
- window . scrollBy ( {
88
- top : height ,
89
- left : 0 ,
90
- behavior : "smooth" ,
91
- } ) ;
90
+ const height = window . visualViewport ?. height ?? window . innerHeight ;
92
91
93
- const scrollingEl =
94
- document . scrollingElement || document . documentElement ;
95
- return window . waitForElementScrollEnd ( scrollingEl as HTMLElement ) ;
96
- } else {
97
- height = element . getBoundingClientRect ( ) . height ;
98
- element . scrollBy ( {
99
- top : height ,
100
- left : 0 ,
101
- behavior : "smooth" ,
102
- } ) ;
92
+ window . scrollBy ( { top : height , left : 0 , behavior : "smooth" } ) ;
93
+
94
+ const scrollingRoot = ( document . scrollingElement ??
95
+ document . documentElement ) as HTMLElement ;
103
96
104
- return window . waitForElementScrollEnd ( element ) ;
97
+ return waitForScrollEnd ( scrollingRoot ) ;
105
98
}
99
+
100
+ const height = ( element as HTMLElement ) . getBoundingClientRect ( ) . height ;
101
+
102
+ ( element as HTMLElement ) . scrollBy ( {
103
+ top : height ,
104
+ left : 0 ,
105
+ behavior : "smooth" ,
106
+ } ) ;
107
+
108
+ return waitForScrollEnd ( element ) ;
106
109
} ,
107
- { xpath } ,
110
+ undefined ,
111
+ { timeout : 10_000 } ,
108
112
) ;
109
113
} catch ( e ) {
110
114
logger ( {
@@ -122,7 +126,7 @@ export async function scrollToNextChunk(ctx: MethodHandlerContext) {
122
126
}
123
127
124
128
export async function scrollToPreviousChunk ( ctx : MethodHandlerContext ) {
125
- const { stagehandPage , xpath , logger } = ctx ;
129
+ const { locator , logger , xpath } = ctx ;
126
130
127
131
logger ( {
128
132
category : "action" ,
@@ -134,39 +138,41 @@ export async function scrollToPreviousChunk(ctx: MethodHandlerContext) {
134
138
} ) ;
135
139
136
140
try {
137
- await stagehandPage . page . evaluate (
138
- ( { xpath } ) => {
139
- const elementNode = getNodeFromXpath ( xpath ) ;
140
- if ( ! elementNode || elementNode . nodeType !== Node . ELEMENT_NODE ) {
141
- throw Error ( `Could not locate element to scroll on.` ) ;
142
- }
141
+ await locator . evaluate (
142
+ ( element ) => {
143
+ const waitForScrollEnd = ( el : HTMLElement | Element ) =>
144
+ new Promise < void > ( ( resolve ) => {
145
+ let last = el . scrollTop ?? 0 ;
146
+ const check = ( ) => {
147
+ const cur = el . scrollTop ?? 0 ;
148
+ if ( cur === last ) return resolve ( ) ;
149
+ last = cur ;
150
+ requestAnimationFrame ( check ) ;
151
+ } ;
152
+ requestAnimationFrame ( check ) ;
153
+ } ) ;
143
154
144
- const element = elementNode as HTMLElement ;
145
155
const tagName = element . tagName . toLowerCase ( ) ;
146
- let height : number ;
147
156
148
157
if ( tagName === "html" || tagName === "body" ) {
149
- height = window . visualViewport . height ;
150
- window . scrollBy ( {
151
- top : - height ,
152
- left : 0 ,
153
- behavior : "smooth" ,
154
- } ) ;
158
+ const height = window . visualViewport ?. height ?? window . innerHeight ;
159
+ window . scrollBy ( { top : - height , left : 0 , behavior : "smooth" } ) ;
155
160
156
- const scrollingEl =
157
- document . scrollingElement || document . documentElement ;
158
- return window . waitForElementScrollEnd ( scrollingEl as HTMLElement ) ;
159
- } else {
160
- height = element . getBoundingClientRect ( ) . height ;
161
- element . scrollBy ( {
162
- top : - height ,
163
- left : 0 ,
164
- behavior : "smooth" ,
165
- } ) ;
166
- return window . waitForElementScrollEnd ( element ) ;
161
+ const rootScrollingEl = ( document . scrollingElement ??
162
+ document . documentElement ) as HTMLElement ;
163
+
164
+ return waitForScrollEnd ( rootScrollingEl ) ;
167
165
}
166
+ const height = ( element as HTMLElement ) . getBoundingClientRect ( ) . height ;
167
+ ( element as HTMLElement ) . scrollBy ( {
168
+ top : - height ,
169
+ left : 0 ,
170
+ behavior : "smooth" ,
171
+ } ) ;
172
+ return waitForScrollEnd ( element ) ;
168
173
} ,
169
- { xpath } ,
174
+ undefined ,
175
+ { timeout : 10_000 } ,
170
176
) ;
171
177
} catch ( e ) {
172
178
logger ( {
@@ -215,7 +221,7 @@ export async function scrollElementIntoView(ctx: MethodHandlerContext) {
215
221
}
216
222
217
223
export async function scrollElementToPercentage ( ctx : MethodHandlerContext ) {
218
- const { args, stagehandPage , xpath, logger } = ctx ;
224
+ const { args, xpath, logger, locator } = ctx ;
219
225
220
226
logger ( {
221
227
category : "action" ,
@@ -230,20 +236,14 @@ export async function scrollElementToPercentage(ctx: MethodHandlerContext) {
230
236
try {
231
237
const [ yArg = "0%" ] = args as string [ ] ;
232
238
233
- await stagehandPage . page . evaluate (
234
- ( { xpath , yArg } ) => {
239
+ await locator . evaluate < void , { yArg : string } > (
240
+ ( element , { yArg } ) => {
235
241
function parsePercent ( val : string ) : number {
236
242
const cleaned = val . trim ( ) . replace ( "%" , "" ) ;
237
243
const num = parseFloat ( cleaned ) ;
238
244
return Number . isNaN ( num ) ? 0 : Math . max ( 0 , Math . min ( num , 100 ) ) ;
239
245
}
240
246
241
- const elementNode = getNodeFromXpath ( xpath ) ;
242
- if ( ! elementNode || elementNode . nodeType !== Node . ELEMENT_NODE ) {
243
- throw Error ( `Could not locate element to scroll on.` ) ;
244
- }
245
-
246
- const element = elementNode as HTMLElement ;
247
247
const yPct = parsePercent ( yArg ) ;
248
248
249
249
if ( element . tagName . toLowerCase ( ) === "html" ) {
@@ -266,7 +266,8 @@ export async function scrollElementToPercentage(ctx: MethodHandlerContext) {
266
266
} ) ;
267
267
}
268
268
} ,
269
- { xpath, yArg } ,
269
+ { yArg } ,
270
+ { timeout : 10_000 } ,
270
271
) ;
271
272
} catch ( e ) {
272
273
logger ( {
0 commit comments