diff --git a/lib/PuppeteerSharp.Tests/CDPSessionTests/CreateCDPSessionTests.cs b/lib/PuppeteerSharp.Tests/CDPSessionTests/CreateCDPSessionTests.cs index 516b05b33..c16754cd2 100644 --- a/lib/PuppeteerSharp.Tests/CDPSessionTests/CreateCDPSessionTests.cs +++ b/lib/PuppeteerSharp.Tests/CDPSessionTests/CreateCDPSessionTests.cs @@ -1,8 +1,11 @@ using System; using System.Collections.Generic; +using System.Reflection; using System.Threading.Tasks; using NUnit.Framework; +using PuppeteerSharp.Cdp; using PuppeteerSharp.Cdp.Messaging; +using PuppeteerSharp.Helpers; using PuppeteerSharp.Nunit; namespace PuppeteerSharp.Tests.CDPSessionTests @@ -183,5 +186,23 @@ public async Task ShouldExposeDetachedState() await client.DetachAsync(); Assert.That(client.Detached, Is.True); } + + [Test, PuppeteerTest("CDPSession.spec", "Target.createCDPSession", "should handle session callbacks when Chrome sends error without sessionId")] + public async Task ShouldHandleSessionCallbacksWhenChromeSendsErrorWithoutSessionId() + { + var client = (CDPSession)await Page.CreateCDPSessionAsync(); + var connection = client.Connection; + + var fakeSession = new CdpCDPSession(connection, TargetType.Other, "fake-session-id", null); + + var sessionsField = typeof(Connection).GetField("_sessions", BindingFlags.Instance | BindingFlags.NonPublic); + var sessions = (AsyncDictionaryHelper)sessionsField!.GetValue(connection); + sessions!.AddItem("fake-session-id", fakeSession); + + var exception = Assert.ThrowsAsync(() => fakeSession.SendAsync( + "Runtime.evaluate", + new RuntimeEvaluateRequest { Expression = "1 + 1" })); + Assert.That(exception!.Message, Does.Contain("Session with given id not found")); + } } } diff --git a/lib/PuppeteerSharp/Cdp/CdpCDPSession.cs b/lib/PuppeteerSharp/Cdp/CdpCDPSession.cs index ea64f5eb9..478eaea25 100644 --- a/lib/PuppeteerSharp/Cdp/CdpCDPSession.cs +++ b/lib/PuppeteerSharp/Cdp/CdpCDPSession.cs @@ -144,6 +144,8 @@ public override void Close(string closeReason) internal bool HasPendingCallbacks() => !_callbacks.IsEmpty; + internal bool HasCallback(int id) => _callbacks.ContainsKey(id); + internal List GetPendingProtocolErrors() { var result = new List(); diff --git a/lib/PuppeteerSharp/Cdp/Connection.cs b/lib/PuppeteerSharp/Cdp/Connection.cs index 51b06adbb..1a74caad0 100644 --- a/lib/PuppeteerSharp/Cdp/Connection.cs +++ b/lib/PuppeteerSharp/Cdp/Connection.cs @@ -368,6 +368,20 @@ private void ProcessIncomingMessage(ConnectionResponse obj) { MessageQueue.Enqueue(callback, obj); } + else + { + // Chrome can occasionally omit the sessionId on responses. Fall back + // to a per-session callback lookup so the response is routed to the + // session that originally issued the command (upstream #14975). + foreach (var session in _sessions.Values) + { + if (session.HasCallback(obj.Id.Value)) + { + session.OnMessage(obj); + break; + } + } + } } else {