Skip to content

Commit 3e5f40a

Browse files
authored
fix(tests): Wait for scroll event delivery before streaming in auto-scroll test (#2355)
1 parent 70b9be4 commit 3e5f40a

1 file changed

Lines changed: 34 additions & 10 deletions

File tree

tests/playwright/shiny/components/MarkdownStream/auto_scroll/test_stream_auto_scroll.py

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,33 @@
4444
}"""
4545

4646

47+
# Scrolls the container and flags when the resulting scroll *event* is
48+
# delivered. shinychat updates its pinned/unpinned state inside its own scroll
49+
# handler, and scroll events are dispatched asynchronously (on Firefox, tied to
50+
# paint ticks that can lag under CI load) — so waiting on scrollTop alone races
51+
# the event delivery. Our once-listener registers after shinychat's, so by the
52+
# time the flag is set, shinychat has already processed the same event.
53+
SCROLL_TO_SCRIPT = """(args) => {
54+
const el = document.querySelector(args.selector);
55+
delete el.__testScrollEventSeen;
56+
el.addEventListener(
57+
"scroll",
58+
() => { el.__testScrollEventSeen = true; },
59+
{ once: true },
60+
);
61+
el.scrollTo(0, args.to === "bottom" ? el.scrollHeight : 0);
62+
}"""
63+
64+
65+
def scroll_to_and_wait_for_scroll_event(page: Page, selector: str, to: str) -> None:
66+
page.evaluate(SCROLL_TO_SCRIPT, {"selector": selector, "to": to})
67+
page.wait_for_function(
68+
"""(sel) => document.querySelector(sel).__testScrollEventSeen === true""",
69+
arg=selector,
70+
timeout=30_000,
71+
)
72+
73+
4774
def scroll_state(page: Page, selector: str) -> dict[str, float]:
4875
return page.evaluate(
4976
"""(sel) => {
@@ -120,20 +147,17 @@ def test_auto_scroll_pinning(page: Page, local_app: ShinyAppProc) -> None:
120147
expect_not_scrolled(page, UNPINNED)
121148

122149
# Phase B: scrolling away from the bottom unpins; new content must NOT
123-
# drag the container back down.
124-
page.evaluate(f"""() => document.querySelector({PINNED!r}).scrollTo(0, 0)""")
125-
page.wait_for_function(
126-
f"""() => document.querySelector({PINNED!r}).scrollTop === 0"""
127-
)
150+
# drag the container back down. Waiting for the scroll event (not just
151+
# scrollTop) guarantees shinychat has registered the unpin before the next
152+
# chunk arrives.
153+
scroll_to_and_wait_for_scroll_event(page, PINNED, "top")
128154
next_chunk_and_wait_for_render(page, next_chunk, 4)
129155
expect_not_scrolled(page, PINNED)
130156

131157
# Phase C: scrolling back to the bottom re-pins; the next chunk is
132-
# followed again.
133-
page.evaluate(f"""() => {{
134-
const el = document.querySelector({PINNED!r});
135-
el.scrollTo(0, el.scrollHeight);
136-
}}""")
158+
# followed again. Same rationale: shinychat only re-pins inside its scroll
159+
# handler, so wait for the event to be delivered before streaming more.
160+
scroll_to_and_wait_for_scroll_event(page, PINNED, "bottom")
137161
expect_at_bottom(page, PINNED)
138162
next_chunk_and_wait_for_render(page, next_chunk, 5)
139163
expect_at_bottom(page, PINNED)

0 commit comments

Comments
 (0)