Repository navigation
fix: ignore Playwright replies that belong to an earlier request - #284
Open
aimerge-studio wants to merge 1 commit into
Open
aimerge-studio wants to merge 1 commit into
aimerge-studio wants to merge 1 commit into
Conversation
A navigation with waitUntil returns on its load event, which can arrive before its own reply. That reply stayed in the socket, and the next request read it: a late error failed an unrelated request, and a late value was taken as its result, so every later call received the previous call's reply.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On a slow machine or CI runner, tests that call
refresh()(or navigate) intermittently fail withor with an assertion receiving a value that belongs to the previous call.
Cause
Client::execute()yields every message it reads until it sees its own reply, and throws on any message that carries anerror, whichever request that error belongs to.Page::reload()sendsreloadwithwaitUntil: load. The loop breaks on the matchingloadstateevent, which can arrive before the reload's own reply. That reply stays in the socket. With the 1 s per-attempt timeout ofwaitForExpectation(), it arrives later asTimeout 1000ms exceeded.evaluateExpression, reads that stale error first and throws. The attempt counts as failed and is retried.evaluateExpression's reply is still unread. The retry reads it as its own result, becauseprocessResultResponse()takes the firstresult.valueit sees. From then on every call receives the previous call's reply, until the test ends.javaScriptErrors()receives the integer meant for the precedingassertScript().Instrumented trace from a failing run (ids shortened):
Fix
Skip any message whose
idis set and differs from the current request's id. Such a message answers a request that has already stopped reading; it must neither fail the current request nor be taken as its result. Events carry noidand pass through unchanged, sowaitUntiland__create__handling keep working.Tests
tests/Unit/Playwright/ClientTest.phpfeedsexecute()a scripted socket: a stale error and a stale value with foreign ids, an event, then the request's own reply. It asserts that only the event and the own reply are yielded, and that a request's own error still throws.In the project where this showed up, the affected browser test failed 5 of 9 runs before the change and passed 6 of 6 after it.
🤖 Generated with Claude Code