diff --git a/lib/PuppeteerSharp.Tests/PageTests/CloseTests.cs b/lib/PuppeteerSharp.Tests/PageTests/CloseTests.cs index e973f502b..d88c60176 100644 --- a/lib/PuppeteerSharp.Tests/PageTests/CloseTests.cs +++ b/lib/PuppeteerSharp.Tests/PageTests/CloseTests.cs @@ -78,6 +78,24 @@ public async Task ShouldSetThePageCloseState() Assert.True(Page.IsClosed); } + [Fact] + public async Task ShouldTerminateNetworkWaiters() + { + var newPage = await Context.NewPageAsync(); + var requestTask = newPage.WaitForRequestAsync(TestConstants.EmptyPage); + var responseTask = newPage.WaitForResponseAsync(TestConstants.EmptyPage); + + await newPage.CloseAsync(); + + var exception = await Assert.ThrowsAsync(() => requestTask); + Assert.Contains("Target closed", exception.Message); + Assert.DoesNotContain("Timeout", exception.Message); + + exception = await Assert.ThrowsAsync(() => responseTask); + Assert.Contains("Target closed", exception.Message); + Assert.DoesNotContain("Timeout", exception.Message); + } + [Fact(Timeout = 10000)] public async Task ShouldCloseWhenConnectionBreaksPrematurely() { diff --git a/lib/PuppeteerSharp.Tests/PuppeteerTests/BrowserCloseTests.cs b/lib/PuppeteerSharp.Tests/PuppeteerTests/BrowserCloseTests.cs new file mode 100644 index 000000000..13d536050 --- /dev/null +++ b/lib/PuppeteerSharp.Tests/PuppeteerTests/BrowserCloseTests.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace PuppeteerSharp.Tests.BrowserTests.Events +{ + [Collection(TestConstants.TestFixtureCollectionName)] + public class BrowserCloseTests : PuppeteerBrowserBaseTest + { + public BrowserCloseTests(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public async Task ShouldTerminateNetworkWaiters() + { + using (var browser = await Puppeteer.LaunchAsync(TestConstants.DefaultBrowserOptions())) + using (var remote = await Puppeteer.ConnectAsync(new ConnectOptions { BrowserWSEndpoint = browser.WebSocketEndpoint })) + { + var newPage = await remote.NewPageAsync(); + var requestTask = newPage.WaitForRequestAsync(TestConstants.EmptyPage); + var responseTask = newPage.WaitForResponseAsync(TestConstants.EmptyPage); + + await browser.CloseAsync(); + + var exception = await Assert.ThrowsAsync(() => requestTask); + Assert.Contains("Target closed", exception.Message); + Assert.DoesNotContain("Timeout", exception.Message); + + exception = await Assert.ThrowsAsync(() => responseTask); + Assert.Contains("Target closed", exception.Message); + Assert.DoesNotContain("Timeout", exception.Message); + } + } + } +} \ No newline at end of file diff --git a/lib/PuppeteerSharp/Page.cs b/lib/PuppeteerSharp/Page.cs index 8eab900ed..e1c5485cd 100644 --- a/lib/PuppeteerSharp/Page.cs +++ b/lib/PuppeteerSharp/Page.cs @@ -46,6 +46,7 @@ public class Page : IDisposable private bool _screenshotBurstModeOn; private ScreenshotOptions _screenshotBurstModeOptions; private readonly TaskCompletionSource _closeCompletedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + private TaskCompletionSource _sessionClosedTcs; private readonly TimeoutSettings _timeoutSettings; private bool _fileChooserInterceptionIsDisabled; private ConcurrentDictionary> _fileChooserInterceptors; @@ -347,6 +348,25 @@ public int DefaultTimeout internal bool HasPopupEventListeners => Popup?.GetInvocationList().Any() == true; internal FrameManager FrameManager { get; private set; } + private Task SessionClosedTask + { + get + { + if (_sessionClosedTcs == null) + { + _sessionClosedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + Client.Disconnected += clientDisconnected; + + void clientDisconnected(object sender, EventArgs e) + { + _sessionClosedTcs.TrySetException(new TargetClosedException("Target closed", "Session closed")); + Client.Disconnected -= clientDisconnected; + } + } + + return _sessionClosedTcs.Task; + } + } #endregion #region Public Methods @@ -1520,11 +1540,17 @@ void requestEventListener(object sender, RequestEventArgs e) FrameManager.NetworkManager.Request += requestEventListener; - return await requestTcs.Task.WithTimeout(timeout, t => + await Task.WhenAny(requestTcs.Task, SessionClosedTask).WithTimeout(timeout, t => { FrameManager.NetworkManager.Request -= requestEventListener; return new TimeoutException($"Timeout Exceeded: {t.TotalMilliseconds}ms exceeded"); }).ConfigureAwait(false); + + if (SessionClosedTask.IsFaulted) + { + await SessionClosedTask; + } + return await requestTcs.Task; } /// @@ -1574,7 +1600,13 @@ void responseEventListener(object sender, ResponseCreatedEventArgs e) FrameManager.NetworkManager.Response += responseEventListener; - return await responseTcs.Task.WithTimeout(timeout).ConfigureAwait(false); + await Task.WhenAny(responseTcs.Task, SessionClosedTask).WithTimeout(timeout).ConfigureAwait(false); + + if (SessionClosedTask.IsFaulted) + { + await SessionClosedTask; + } + return await responseTcs.Task; } ///