diff --git a/packages/app/e2e/helpers/agent-bottom-anchor.ts b/packages/app/e2e/helpers/agent-bottom-anchor.ts index fc0371829b..c13244ef21 100644 --- a/packages/app/e2e/helpers/agent-bottom-anchor.ts +++ b/packages/app/e2e/helpers/agent-bottom-anchor.ts @@ -148,47 +148,78 @@ export async function clickToolCallBesideScrollToBottomButton(page: Page): Promi bounds: await toolCalls.nth(index).boundingBox(), })), ); - const candidate = toolCallBounds.find(({ bounds }) => { - if (!bounds) { - return false; - } - const centerY = bounds.y + bounds.height / 2; - return ( - bounds.width > 0 && - centerY >= visibleButtonBounds.y && - centerY <= visibleButtonBounds.y + visibleButtonBounds.height - ); - }); + const buttonCenterY = visibleButtonBounds.y + visibleButtonBounds.height / 2; + const candidate = toolCallBounds + .filter( + (entry): entry is { index: number; bounds: NonNullable } => + entry.bounds !== null && entry.bounds.width > 0, + ) + .sort( + (left, right) => + Math.abs(left.bounds.y + left.bounds.height / 2 - buttonCenterY) - + Math.abs(right.bounds.y + right.bounds.height / 2 - buttonCenterY), + )[0]; expect( candidate, - `No tool-call badge visible within the scroll-button band: ${JSON.stringify({ + `Expected at least one rendered tool-call badge: ${JSON.stringify({ buttonBounds, scrollMetrics: await readScrollMetrics(page), toolCallBounds, })}`, ).toBeDefined(); const visibleToolCall = candidate!; - const visibleToolCallBounds = visibleToolCall.bounds!; + const initialToolCallCenterY = visibleToolCall.bounds.y + visibleToolCall.bounds.height / 2; + await getVisibleChatScroll(page).evaluate((scroll, deltaY) => { + (scroll as HTMLElement).scrollTop += deltaY; + }, initialToolCallCenterY - buttonCenterY); + + const alignedToolCall = toolCalls.nth(visibleToolCall.index); + await expect + .poll(async () => { + const [currentButtonBounds, currentToolCallBounds] = await Promise.all([ + scrollToBottomButton.boundingBox(), + alignedToolCall.boundingBox(), + ]); + if (!currentButtonBounds || !currentToolCallBounds) { + return false; + } + const toolCallCenterY = currentToolCallBounds.y + currentToolCallBounds.height / 2; + return ( + toolCallCenterY >= currentButtonBounds.y && + toolCallCenterY <= currentButtonBounds.y + currentButtonBounds.height + ); + }) + .toBe(true); + + const [alignedButtonBounds, visibleToolCallBounds] = await Promise.all([ + scrollToBottomButton.boundingBox(), + alignedToolCall.boundingBox(), + ]); + expect(alignedButtonBounds, "Expected scroll-to-bottom button to remain visible").not.toBeNull(); + expect( + visibleToolCallBounds, + "Expected aligned tool-call badge to remain visible", + ).not.toBeNull(); + const finalButtonBounds = alignedButtonBounds!; + const finalToolCallBounds = visibleToolCallBounds!; const clickPoint = { - x: visibleToolCallBounds.x + 24, - y: visibleToolCallBounds.y + visibleToolCallBounds.height / 2, + x: finalToolCallBounds.x + 24, + y: finalToolCallBounds.y + finalToolCallBounds.height / 2, }; - const toolCallReceivesPointer = await toolCalls - .nth(visibleToolCall.index) - .evaluate((toolCall, point) => { - const hit = document.elementFromPoint(point.x, point.y); - return hit !== null && toolCall.contains(hit); - }, clickPoint); + const toolCallReceivesPointer = await alignedToolCall.evaluate((toolCall, point) => { + const hit = document.elementFromPoint(point.x, point.y); + return hit !== null && toolCall.contains(hit); + }, clickPoint); const hitArea = { clickPoint, outsideButton: - clickPoint.x < visibleButtonBounds.x || - clickPoint.x > visibleButtonBounds.x + visibleButtonBounds.width, + clickPoint.x < finalButtonBounds.x || + clickPoint.x > finalButtonBounds.x + finalButtonBounds.width, toolCallReceivesPointer, withinButtonBand: - clickPoint.y >= visibleButtonBounds.y && - clickPoint.y <= visibleButtonBounds.y + visibleButtonBounds.height, + clickPoint.y >= finalButtonBounds.y && + clickPoint.y <= finalButtonBounds.y + finalButtonBounds.height, }; await page.mouse.click(hitArea.clickPoint.x, hitArea.clickPoint.y); return {