Skip to content

Commit 7933e09

Browse files
committed
use locator.evaluate for scrolling functions
1 parent 9f54bcf commit 7933e09

File tree

1 file changed

+66
-65
lines changed

1 file changed

+66
-65
lines changed

lib/handlers/handlerUtils/actHandlerUtils.ts

Lines changed: 66 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Page, Locator, FrameLocator } from "playwright";
22
import { PlaywrightCommandException } from "../../../types/playwright";
33
import { StagehandPage } from "../../StagehandPage";
4-
import { getNodeFromXpath } from "@/lib/dom/utils";
54
import { Logger } from "../../../types/log";
65
import { MethodHandlerContext } from "@/types/act";
76
import { StagehandClickError } from "@/types/stagehandErrors";
@@ -59,7 +58,7 @@ export const methodHandlerMap: Record<
5958
};
6059

6160
export async function scrollToNextChunk(ctx: MethodHandlerContext) {
62-
const { stagehandPage, xpath, logger } = ctx;
61+
const { locator, logger, xpath } = ctx;
6362

6463
logger({
6564
category: "action",
@@ -71,40 +70,45 @@ export async function scrollToNextChunk(ctx: MethodHandlerContext) {
7170
});
7271

7372
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+
});
8086

81-
const element = elementNode as HTMLElement;
8287
const tagName = element.tagName.toLowerCase();
83-
let height: number;
8488

8589
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;
9291

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;
10396

104-
return window.waitForElementScrollEnd(element);
97+
return waitForScrollEnd(scrollingRoot);
10598
}
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);
106109
},
107-
{ xpath },
110+
undefined,
111+
{ timeout: 10_000 },
108112
);
109113
} catch (e) {
110114
logger({
@@ -122,7 +126,7 @@ export async function scrollToNextChunk(ctx: MethodHandlerContext) {
122126
}
123127

124128
export async function scrollToPreviousChunk(ctx: MethodHandlerContext) {
125-
const { stagehandPage, xpath, logger } = ctx;
129+
const { locator, logger, xpath } = ctx;
126130

127131
logger({
128132
category: "action",
@@ -134,39 +138,41 @@ export async function scrollToPreviousChunk(ctx: MethodHandlerContext) {
134138
});
135139

136140
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+
});
143154

144-
const element = elementNode as HTMLElement;
145155
const tagName = element.tagName.toLowerCase();
146-
let height: number;
147156

148157
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" });
155160

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);
167165
}
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);
168173
},
169-
{ xpath },
174+
undefined,
175+
{ timeout: 10_000 },
170176
);
171177
} catch (e) {
172178
logger({
@@ -215,7 +221,7 @@ export async function scrollElementIntoView(ctx: MethodHandlerContext) {
215221
}
216222

217223
export async function scrollElementToPercentage(ctx: MethodHandlerContext) {
218-
const { args, stagehandPage, xpath, logger } = ctx;
224+
const { args, xpath, logger, locator } = ctx;
219225

220226
logger({
221227
category: "action",
@@ -230,20 +236,14 @@ export async function scrollElementToPercentage(ctx: MethodHandlerContext) {
230236
try {
231237
const [yArg = "0%"] = args as string[];
232238

233-
await stagehandPage.page.evaluate(
234-
({ xpath, yArg }) => {
239+
await locator.evaluate<void, { yArg: string }>(
240+
(element, { yArg }) => {
235241
function parsePercent(val: string): number {
236242
const cleaned = val.trim().replace("%", "");
237243
const num = parseFloat(cleaned);
238244
return Number.isNaN(num) ? 0 : Math.max(0, Math.min(num, 100));
239245
}
240246

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;
247247
const yPct = parsePercent(yArg);
248248

249249
if (element.tagName.toLowerCase() === "html") {
@@ -266,7 +266,8 @@ export async function scrollElementToPercentage(ctx: MethodHandlerContext) {
266266
});
267267
}
268268
},
269-
{ xpath, yArg },
269+
{ yArg },
270+
{ timeout: 10_000 },
270271
);
271272
} catch (e) {
272273
logger({

0 commit comments

Comments
 (0)