Fix RuntimeError when click handler deletes the clicked element during testing#5938
Conversation
…g testing Closes #5937 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… alignment The real event dispatch (element._handle_event) does a single dict lookup per listener_id — no iteration. If a handler deletes the element, subsequent browser messages simply never arrive. The list() snapshot approach prevented the RuntimeError but diverged from real behavior: it would continue dispatching to already-deleted listeners. Checking is_deleted and breaking matches what actually happens in production. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Hi — this is Claude Code, acting on behalf of @evnchn. The core question isn't just "does it stop erroring?" but "does the test simulation still behave like real life?" The Detailed analysisReal event dispatch (
The difference only matters for multi-listener scenarios, but aligning the simulation with reality prevents a class of false-positive tests where handlers run on deleted elements. Commit: |
evnchn
left a comment
There was a problem hiding this comment.
Feel free to merge if you agree, or discuss if not. Thanks!
|
And be sure to update PR desc. |
|
Great catch, @evnchn! I didn't think about checking the real-world behavior, but you're completely right. I updated the pytest and PR description accordingly. 👍🏻 |
evnchn
left a comment
There was a problem hiding this comment.
The updated test, comment, and PR description all show that me, you, test suite, and reality, all align 4-way.
Motivation
Fixes #5937. When a button's click handler clears its parent container (destroying the button itself),
User.click()raisesRuntimeError: dictionary changed size during iterationbecauseelement._event_listenersis mutated mid-loop via_handle_delete()->_event_listeners.clear().Implementation
After each handler fires, check
element.is_deletedandbreakout of the listener loop. This avoids the dict-mutation error without copying the dict, and — more importantly — matches real browser behavior: in production,element._handle_eventdoes a direct dict lookup perlistener_id(no iteration), so once an element is deleted, subsequent browser messages simply never arrive and no more listeners fire. Theis_deleted+breakapproach faithfully mirrors this, preventing false-positive tests where handlers would run on already-deleted elements.The test registers three click handlers on a button inside the container it clears — the first handler fires, the second (rebuild) deletes and recreates the button, and the third must not fire since the element is now deleted.
Progress