diff --git a/server/src/browser-management/inputHandlers.ts b/server/src/browser-management/inputHandlers.ts index ee31ba06..69b2697d 100644 --- a/server/src/browser-management/inputHandlers.ts +++ b/server/src/browser-management/inputHandlers.ts @@ -862,6 +862,157 @@ const onRemoveAction = async ( await handleWrapper(handleRemoveAction, userId, data); }; +/** + * Tests pagination by scrolling down and checking if new content loads + * @param data Object containing listSelector + * @param userId The user ID + * @param socket The socket connection to emit results + */ +const onTestPaginationScroll = async ( + data: { listSelector: string }, + userId: string, + socket: Socket +) => { + logger.log("debug", "Testing pagination scroll emitted from client"); + + const id = browserPool.getActiveBrowserId(userId, "recording"); + if (!id) { + logger.log("warn", `No active browser for id ${id}`); + socket.emit("paginationScrollTestResult", { + success: false, + error: "No active browser" + }); + return; + } + + const activeBrowser = browserPool.getRemoteBrowser(id); + const currentPage = activeBrowser?.getCurrentPage(); + + if (!currentPage || !activeBrowser) { + logger.log("warn", `No active page for browser ${id}`); + socket.emit("paginationScrollTestResult", { + success: false, + error: "No active page" + }); + return; + } + + try { + const { listSelector } = data; + + logger.log("info", `Starting pagination scroll test for selector: ${listSelector}`); + + const initialCount = await currentPage.evaluate((selector) => { + function evaluateSelector(sel: string): Element[] { + try { + const isXPath = sel.startsWith('//') || sel.startsWith('(//'); + if (isXPath) { + const result = document.evaluate( + sel, + document, + null, + XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, + null + ); + const elements: Element[] = []; + for (let i = 0; i < result.snapshotLength; i++) { + const node = result.snapshotItem(i); + if (node && node.nodeType === Node.ELEMENT_NODE) { + elements.push(node as Element); + } + } + return elements; + } else { + return Array.from(document.querySelectorAll(sel)); + } + } catch (err) { + console.error('Selector evaluation failed:', sel, err); + return []; + } + } + + return evaluateSelector(selector).length; + }, listSelector); + + logger.log("info", `Initial list count: ${initialCount}`); + + const scrollInfo = await currentPage.evaluate(() => { + return { + scrollY: window.scrollY, + scrollHeight: document.documentElement.scrollHeight, + viewportHeight: window.innerHeight + }; + }); + + logger.log("info", `Scroll info:`, scrollInfo); + + await currentPage.evaluate(() => { + window.scrollTo(0, document.body.scrollHeight); + }); + + logger.log("info", "Scrolled to bottom, waiting for potential content load..."); + + await currentPage.waitForTimeout(2000); + + const newCount = await currentPage.evaluate((selector) => { + function evaluateSelector(sel: string): Element[] { + try { + const isXPath = sel.startsWith('//') || sel.startsWith('(//'); + if (isXPath) { + const result = document.evaluate( + sel, + document, + null, + XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, + null + ); + const elements: Element[] = []; + for (let i = 0; i < result.snapshotLength; i++) { + const node = result.snapshotItem(i); + if (node && node.nodeType === Node.ELEMENT_NODE) { + elements.push(node as Element); + } + } + return elements; + } else { + return Array.from(document.querySelectorAll(sel)); + } + } catch (err) { + return []; + } + } + + return evaluateSelector(selector).length; + }, listSelector); + + logger.log("info", `New list count after scroll: ${newCount}`); + + await currentPage.evaluate((originalY) => { + window.scrollTo(0, originalY); + }, scrollInfo.scrollY); + + const contentLoaded = newCount > initialCount; + + logger.log("info", `Scroll test result: ${contentLoaded ? 'Content loaded' : 'No new content'}`); + + socket.emit("paginationScrollTestResult", { + success: true, + contentLoaded: contentLoaded, + initialCount: initialCount, + newCount: newCount, + itemsAdded: newCount - initialCount + }); + + } catch (error) { + const { message } = error as Error; + logger.log("error", `Error during pagination scroll test: ${message}`); + socket.emit("paginationScrollTestResult", { + success: false, + error: message + }); + } +}; + /** * Helper function for registering the handlers onto established websocket connection. * Registers various input handlers. @@ -895,6 +1046,7 @@ const registerInputHandlers = (socket: Socket, userId: string) => { socket.on("dom:click", (data) => onDOMClickAction(data, userId)); socket.on("dom:keypress", (data) => onDOMKeyboardAction(data, userId)); socket.on("dom:addpair", (data) => onDOMWorkflowPair(data, userId)); + socket.on("testPaginationScroll", (data) => onTestPaginationScroll(data, userId, socket)); }; /** @@ -925,6 +1077,7 @@ const removeInputHandlers = (socket: Socket) => { socket.removeAllListeners("dom:keypress"); socket.removeAllListeners("dom:addpair"); socket.removeAllListeners("removeAction"); + socket.removeAllListeners("testPaginationScroll"); } catch (error: any) { console.warn(`Error removing input handlers: ${error.message}`); }