Skip to content

Commit 9b51d4b

Browse files
committed
Fix: separate scrollIntoView from click in open_header_menu to drain scroll events
Combining scrollIntoView+click in a single evaluate call caused sporadic failures in Firefox and Chromium: browsers dispatch scroll events from scrollIntoView asynchronously (200 ms+ after the call), so they arrived after the menu opened and after our 150 ms React scroll-to-close guard expired — immediately closing the menu. Splitting into two evaluate calls with a 300 ms wait between them ensures all scroll events fire and drain before the menu opens, so none can trigger the close listener. Made-with: Cursor
1 parent 83d0404 commit 9b51d4b

1 file changed

Lines changed: 12 additions & 5 deletions

File tree

e2e_playwright/pivot_table_interactions_test.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,17 +106,24 @@ def open_header_menu(page: Page, trigger_locator, menu_test_id: str):
106106
opening the menu. The trigger button is hidden at rest (opacity: 0) so we
107107
check it is attached rather than visible, and click via JS to bypass
108108
Playwright's visibility check.
109+
110+
Scroll and click are intentionally separated by a short wait. Even with
111+
``behavior:'instant'``, browsers dispatch scroll events asynchronously — in
112+
Firefox and Chromium they can fire 200 ms+ after the scrollIntoView call.
113+
Our React scroll-to-close listener has a 150 ms grace period after the menu
114+
opens, but if the scroll event arrives later than that it closes the menu
115+
immediately. By scrolling first, waiting for those events to drain, and
116+
only then clicking to open the menu, we ensure no scroll events are
117+
in-flight when the listener registers.
109118
"""
110119
expect(trigger_locator).to_be_attached(timeout=5000)
111120
menu = page.get_by_test_id(menu_test_id)
112121
for attempt in range(2):
113122
trigger_locator.evaluate(
114-
# behavior:'instant' ensures the scroll completes synchronously before
115-
# click() fires. Without it, smooth-scroll animation events can arrive
116-
# after the menu opens and trigger our capture-phase scroll-to-close
117-
# listener, closing the menu immediately after it appears.
118-
"el => { el.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'instant' }); el.click(); }"
123+
"el => el.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'instant' })"
119124
)
125+
page.wait_for_timeout(300) # let scroll events drain before opening menu
126+
trigger_locator.evaluate("el => el.click()")
120127
try:
121128
expect(menu).to_be_visible(timeout=5000)
122129
return menu

0 commit comments

Comments
 (0)