From e1e982d69402f080bf291143d97e6196c543d6d6 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 20 Nov 2023 17:56:28 +0100 Subject: [PATCH 1/4] chore(roll): roll Playwright to 1.40.0-alpha-oct-18-2023 --- README.md | 4 +- src/Common/Version.props | 2 +- .../BrowserContextBasicTests.cs | 2 +- src/Playwright.Tests/BrowserTests.cs | 4 +- .../BrowserTypeConnectTests.cs | 4 +- .../ElementHandleEvalOnSelectorTests.cs | 2 +- src/Playwright.Tests/EvalOnSelectorTests.cs | 2 +- src/Playwright.Tests/PageBasicTests.cs | 6 +-- src/Playwright.Tests/TestConstants.cs | 2 + src/Playwright.Tests/WorkersTests.cs | 2 +- src/Playwright/API/Generated/IBrowser.cs | 3 +- .../API/Generated/IBrowserContext.cs | 3 +- src/Playwright/API/Generated/IDownload.cs | 4 +- src/Playwright/API/Generated/IFormData.cs | 1 - .../API/Generated/IPlaywrightAssertions.cs | 1 - src/Playwright/API/Generated/IWebError.cs | 1 - .../API/Generated/IWebSocketFrame.cs | 1 - .../Generated/Options/BrowserCloseOptions.cs | 50 +++++++++++++++++++ .../Options/BrowserContextCloseOptions.cs | 50 +++++++++++++++++++ .../API/Generated/Options/PageCloseOptions.cs | 5 ++ src/Playwright/API/TargetClosedException.cs | 43 ++++++++++++++++ src/Playwright/Core/APIRequestContext.cs | 20 ++++++-- src/Playwright/Core/APIResponse.cs | 4 +- src/Playwright/Core/APIResponseAssertions.cs | 9 ++-- src/Playwright/Core/AssertionsBase.cs | 7 +-- src/Playwright/Core/Browser.cs | 10 ++-- src/Playwright/Core/BrowserContext.cs | 13 +++-- src/Playwright/Core/BrowserType.cs | 8 +-- src/Playwright/Core/DriverMessages.cs | 14 +----- src/Playwright/Core/Frame.cs | 10 ++-- src/Playwright/Core/Page.cs | 26 ++++------ src/Playwright/Core/Waiter.cs | 11 +++- .../Channels/APIRequestContextChannel.cs | 6 +-- .../Transport/Channels/BrowserChannel.cs | 5 +- .../Channels/BrowserContextChannel.cs | 5 +- src/Playwright/Transport/Connection.cs | 47 ++++++++++++----- .../Transport/PlaywrightServerMessage.cs | 2 + src/Playwright/Transport/StdIOTransport.cs | 13 ++--- 38 files changed, 292 insertions(+), 110 deletions(-) create mode 100644 src/Playwright/API/Generated/Options/BrowserCloseOptions.cs create mode 100644 src/Playwright/API/Generated/Options/BrowserContextCloseOptions.cs create mode 100644 src/Playwright/API/TargetClosedException.cs diff --git a/README.md b/README.md index 86ad9f5377..35ee258c8c 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 119.0.6045.9 | ✅ | ✅ | ✅ | -| WebKit 17.0 | ✅ | ✅ | ✅ | +| Chromium 119.0.6045.21 | ✅ | ✅ | ✅ | +| WebKit 17.4 | ✅ | ✅ | ✅ | | Firefox 118.0.1 | ✅ | ✅ | ✅ | Playwright for .NET is the official language port of [Playwright](https://playwright.dev), the library to automate [Chromium](https://www.chromium.org/Home), [Firefox](https://www.mozilla.org/en-US/firefox/new/) and [WebKit](https://webkit.org/) with a single API. Playwright is built to enable cross-browser web automation that is **ever-green**, **capable**, **reliable** and **fast**. diff --git a/src/Common/Version.props b/src/Common/Version.props index 4afbe4e228..5f2eb630b5 100644 --- a/src/Common/Version.props +++ b/src/Common/Version.props @@ -2,7 +2,7 @@ 1.39.0 $(AssemblyVersion) - 1.39.0 + 1.40.0-alpha-oct-18-2023 $(AssemblyVersion) $(AssemblyVersion) true diff --git a/src/Playwright.Tests/BrowserContextBasicTests.cs b/src/Playwright.Tests/BrowserContextBasicTests.cs index 27d4b7d755..16accc008a 100644 --- a/src/Playwright.Tests/BrowserContextBasicTests.cs +++ b/src/Playwright.Tests/BrowserContextBasicTests.cs @@ -189,7 +189,7 @@ public async Task CloseShouldAbortWaitForEvent() var waitTask = context.WaitForPageAsync(); await context.CloseAsync(); var exception = await PlaywrightAssert.ThrowsAsync(() => waitTask); - Assert.AreEqual("Context closed", exception.Message); + Assert.AreEqual(TestConstants.TargetClosedErrorMessage, exception.Message); } [PlaywrightTest("browsercontext-basic.spec.ts", "should not report frameless pages on error")] diff --git a/src/Playwright.Tests/BrowserTests.cs b/src/Playwright.Tests/BrowserTests.cs index 6672766c34..4d2aca1248 100644 --- a/src/Playwright.Tests/BrowserTests.cs +++ b/src/Playwright.Tests/BrowserTests.cs @@ -32,13 +32,13 @@ public async Task ShouldCreateNewPage() { var browser = await Playwright[TestConstants.BrowserName].LaunchAsync(); var page1 = await browser.NewPageAsync(); - Assert.That(browser.Contexts, Has.Length.EqualTo(1)); + Assert.AreEqual(1, browser.Contexts.Count); var page2 = await browser.NewPageAsync(); Assert.AreEqual(2, browser.Contexts.Count); await page1.CloseAsync(); - Assert.That(browser.Contexts, Has.Length.EqualTo(1)); + Assert.AreEqual(1, browser.Contexts.Count); await page2.CloseAsync(); await browser.CloseAsync(); diff --git a/src/Playwright.Tests/BrowserTypeConnectTests.cs b/src/Playwright.Tests/BrowserTypeConnectTests.cs index 0c94b288c8..67f0c5ec6d 100644 --- a/src/Playwright.Tests/BrowserTypeConnectTests.cs +++ b/src/Playwright.Tests/BrowserTypeConnectTests.cs @@ -209,7 +209,7 @@ public async Task ShouldThrowWhenWhenCallingWaitForNavigationAfterDisconnect() Assert.AreEqual(browser.IsConnected, false); var exception = await PlaywrightAssert.ThrowsAsync(async () => await page.WaitForNavigationAsync()); - StringAssert.Contains("Navigation failed because page was closed", exception.Message); + StringAssert.Contains(TestConstants.TargetClosedErrorMessage, exception.Message); } [PlaywrightTest("browsertype-connect.spec.ts", "should reject navigation when browser closes")] @@ -271,7 +271,7 @@ public async Task ShouldTerminateNetworkWaiters() async Task CheckTaskHasException(Task task) { var exception = await PlaywrightAssert.ThrowsAsync(async () => await task); - StringAssert.Contains("Page closed", exception.Message); + StringAssert.Contains(TestConstants.TargetClosedErrorMessage, exception.Message); StringAssert.DoesNotContain("Timeout", exception.Message); } diff --git a/src/Playwright.Tests/ElementHandleEvalOnSelectorTests.cs b/src/Playwright.Tests/ElementHandleEvalOnSelectorTests.cs index edd0728f49..06a9bae214 100644 --- a/src/Playwright.Tests/ElementHandleEvalOnSelectorTests.cs +++ b/src/Playwright.Tests/ElementHandleEvalOnSelectorTests.cs @@ -81,6 +81,6 @@ public async Task ShouldThrowInCaseOfMissingSelector() await Page.SetContentAsync(htmlContent); var elementHandle = await Page.QuerySelectorAsync("#myId"); var exception = await PlaywrightAssert.ThrowsAsync(() => elementHandle.EvalOnSelectorAsync(".a", "node => node.innerText")); - StringAssert.Contains("failed to find element matching selector \".a\"", exception.Message); + StringAssert.Contains("Failed to find element matching selector \".a\"", exception.Message); } } diff --git a/src/Playwright.Tests/EvalOnSelectorTests.cs b/src/Playwright.Tests/EvalOnSelectorTests.cs index f5fe5ae46f..6902dbe5e3 100644 --- a/src/Playwright.Tests/EvalOnSelectorTests.cs +++ b/src/Playwright.Tests/EvalOnSelectorTests.cs @@ -136,7 +136,7 @@ public async Task ShouldThrowErrorIfNoElementIsFound() { var exception = await PlaywrightAssert.ThrowsAsync(() => Page.EvalOnSelectorAsync("section", "e => e.id")); - StringAssert.Contains("failed to find element matching selector \"section\"", exception.Message); + StringAssert.Contains("Failed to find element matching selector \"section\"", exception.Message); } [PlaywrightTest("eval-on-selector.spec.ts", "should support >> syntax")] diff --git a/src/Playwright.Tests/PageBasicTests.cs b/src/Playwright.Tests/PageBasicTests.cs index 225115910c..eb64dd7a60 100644 --- a/src/Playwright.Tests/PageBasicTests.cs +++ b/src/Playwright.Tests/PageBasicTests.cs @@ -36,7 +36,7 @@ public async Task ShouldRejectAllPromisesWhenPageIsClosed() newPage.EvaluateAsync("() => new Promise(r => { })"), newPage.CloseAsync() )); - StringAssert.Contains("Target closed", exception.Message); + StringAssert.Contains(TestConstants.TargetClosedErrorMessage, exception.Message); } [PlaywrightTest("page-basic.spec.ts", "async stacks should work")] @@ -147,7 +147,7 @@ public async Task ShouldFailWithErrorUponDisconnect() var task = Page.WaitForDownloadAsync(); await Page.CloseAsync(); var exception = await PlaywrightAssert.ThrowsAsync(() => task); - StringAssert.Contains("Page closed", exception.Message); + StringAssert.Contains(TestConstants.TargetClosedErrorMessage, exception.Message); } [PlaywrightTest("page-basic.spec.ts", "should have sane user agent")] @@ -245,7 +245,7 @@ public async Task ShouldTerminateNetworkWaiters() for (int i = 0; i < 2; i++) { string message = exception.Message; - StringAssert.Contains("Page closed", message); + StringAssert.Contains(TestConstants.TargetClosedErrorMessage, message); Assert.That(message, Does.Not.Contain("Timeout")); } } diff --git a/src/Playwright.Tests/TestConstants.cs b/src/Playwright.Tests/TestConstants.cs index 92ff864939..960b1b350d 100644 --- a/src/Playwright.Tests/TestConstants.cs +++ b/src/Playwright.Tests/TestConstants.cs @@ -44,4 +44,6 @@ internal static class TestConstants internal static readonly bool IsMacOSX = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); internal static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); internal static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + + internal static string TargetClosedErrorMessage = "Target page, context or browser has been closed"; } diff --git a/src/Playwright.Tests/WorkersTests.cs b/src/Playwright.Tests/WorkersTests.cs index 75e0ebe5f3..3cecbb1321 100644 --- a/src/Playwright.Tests/WorkersTests.cs +++ b/src/Playwright.Tests/WorkersTests.cs @@ -58,7 +58,7 @@ public async Task ShouldEmitCreatedAndDestroyedEvents() await Page.EvaluateAsync("workerObj => workerObj.terminate()", workerObj); Assert.AreEqual(worker, await workerDestroyedTcs.Task); var exception = await PlaywrightAssert.ThrowsAsync(() => workerThisObj.GetPropertyAsync("self")); - StringAssert.IsMatch("(Worker was closed)|(Target closed)", exception.Message); + StringAssert.Contains(TestConstants.TargetClosedErrorMessage, exception.Message); } [PlaywrightTest("workers.spec.ts", "should report console logs")] diff --git a/src/Playwright/API/Generated/IBrowser.cs b/src/Playwright/API/Generated/IBrowser.cs index c985d6081b..a283462dfd 100644 --- a/src/Playwright/API/Generated/IBrowser.cs +++ b/src/Playwright/API/Generated/IBrowser.cs @@ -84,7 +84,8 @@ public partial interface IBrowser /// cref="IBrowser.NewContextAsync"/> **before** calling . /// /// - Task CloseAsync(); + /// Call options + Task CloseAsync(BrowserCloseOptions? options = default); /// /// diff --git a/src/Playwright/API/Generated/IBrowserContext.cs b/src/Playwright/API/Generated/IBrowserContext.cs index 5aef173949..cb6784dcb9 100644 --- a/src/Playwright/API/Generated/IBrowserContext.cs +++ b/src/Playwright/API/Generated/IBrowserContext.cs @@ -266,7 +266,8 @@ public partial interface IBrowserContext /// /// /// The default browser context cannot be closed. - Task CloseAsync(); + /// Call options + Task CloseAsync(BrowserContextCloseOptions? options = default); /// /// diff --git a/src/Playwright/API/Generated/IDownload.cs b/src/Playwright/API/Generated/IDownload.cs index 9f14bded7f..7676254cd4 100644 --- a/src/Playwright/API/Generated/IDownload.cs +++ b/src/Playwright/API/Generated/IDownload.cs @@ -63,7 +63,7 @@ public partial interface IDownload Task CancelAsync(); /// Returns readable stream for current download or null if download failed. - Task CreateReadStreamAsync(); + Task CreateReadStreamAsync(); /// Deletes the downloaded file. Will wait for the download to finish if necessary. Task DeleteAsync(); @@ -84,7 +84,7 @@ public partial interface IDownload /// to get suggested file name. /// /// - Task PathAsync(); + Task PathAsync(); /// /// diff --git a/src/Playwright/API/Generated/IFormData.cs b/src/Playwright/API/Generated/IFormData.cs index 9dc2f87a1f..937b5de9cc 100644 --- a/src/Playwright/API/Generated/IFormData.cs +++ b/src/Playwright/API/Generated/IFormData.cs @@ -22,7 +22,6 @@ * SOFTWARE. */ - #nullable enable namespace Microsoft.Playwright; diff --git a/src/Playwright/API/Generated/IPlaywrightAssertions.cs b/src/Playwright/API/Generated/IPlaywrightAssertions.cs index 4793a69a91..da3c27a12a 100644 --- a/src/Playwright/API/Generated/IPlaywrightAssertions.cs +++ b/src/Playwright/API/Generated/IPlaywrightAssertions.cs @@ -22,7 +22,6 @@ * SOFTWARE. */ - #nullable enable namespace Microsoft.Playwright; diff --git a/src/Playwright/API/Generated/IWebError.cs b/src/Playwright/API/Generated/IWebError.cs index 5d0fa74ea8..de01e1fc9d 100644 --- a/src/Playwright/API/Generated/IWebError.cs +++ b/src/Playwright/API/Generated/IWebError.cs @@ -22,7 +22,6 @@ * SOFTWARE. */ - #nullable enable namespace Microsoft.Playwright; diff --git a/src/Playwright/API/Generated/IWebSocketFrame.cs b/src/Playwright/API/Generated/IWebSocketFrame.cs index 22bfee30e0..d20218582d 100644 --- a/src/Playwright/API/Generated/IWebSocketFrame.cs +++ b/src/Playwright/API/Generated/IWebSocketFrame.cs @@ -22,7 +22,6 @@ * SOFTWARE. */ - #nullable enable namespace Microsoft.Playwright; diff --git a/src/Playwright/API/Generated/Options/BrowserCloseOptions.cs b/src/Playwright/API/Generated/Options/BrowserCloseOptions.cs new file mode 100644 index 0000000000..d5da54a393 --- /dev/null +++ b/src/Playwright/API/Generated/Options/BrowserCloseOptions.cs @@ -0,0 +1,50 @@ +/* + * MIT License + * + * Copyright (c) Microsoft Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using System.Text.Json.Serialization; + +#nullable enable + +namespace Microsoft.Playwright; + +public class BrowserCloseOptions +{ + public BrowserCloseOptions() { } + + public BrowserCloseOptions(BrowserCloseOptions clone) + { + if (clone == null) + { + return; + } + + Reason = clone.Reason; + } + + /// The reason to be reported to the operations interrupted by the browser closure. + [JsonPropertyName("reason")] + public string? Reason { get; set; } +} + +#nullable disable diff --git a/src/Playwright/API/Generated/Options/BrowserContextCloseOptions.cs b/src/Playwright/API/Generated/Options/BrowserContextCloseOptions.cs new file mode 100644 index 0000000000..773e286cc3 --- /dev/null +++ b/src/Playwright/API/Generated/Options/BrowserContextCloseOptions.cs @@ -0,0 +1,50 @@ +/* + * MIT License + * + * Copyright (c) Microsoft Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using System.Text.Json.Serialization; + +#nullable enable + +namespace Microsoft.Playwright; + +public class BrowserContextCloseOptions +{ + public BrowserContextCloseOptions() { } + + public BrowserContextCloseOptions(BrowserContextCloseOptions clone) + { + if (clone == null) + { + return; + } + + Reason = clone.Reason; + } + + /// The reason to be reported to the operations interrupted by the context closure. + [JsonPropertyName("reason")] + public string? Reason { get; set; } +} + +#nullable disable diff --git a/src/Playwright/API/Generated/Options/PageCloseOptions.cs b/src/Playwright/API/Generated/Options/PageCloseOptions.cs index f73b590017..40c988ba21 100644 --- a/src/Playwright/API/Generated/Options/PageCloseOptions.cs +++ b/src/Playwright/API/Generated/Options/PageCloseOptions.cs @@ -39,9 +39,14 @@ public PageCloseOptions(PageCloseOptions clone) return; } + Reason = clone.Reason; RunBeforeUnload = clone.RunBeforeUnload; } + /// The reason to be reported to the operations interrupted by the page closure. + [JsonPropertyName("reason")] + public string? Reason { get; set; } + /// /// /// Defaults to false. Whether to run the before diff --git a/src/Playwright/API/TargetClosedException.cs b/src/Playwright/API/TargetClosedException.cs new file mode 100644 index 0000000000..4ef8dc33a0 --- /dev/null +++ b/src/Playwright/API/TargetClosedException.cs @@ -0,0 +1,43 @@ +/* + * MIT License + * + * Copyright (c) Microsoft Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using System; +using Microsoft.Playwright.Core; + +namespace Microsoft.Playwright; + +public class TargetClosedException : PlaywrightException +{ + public TargetClosedException() : base(DriverMessages.TargetClosedExceptionMessage) + { + } + + public TargetClosedException(string message) : base(message ?? DriverMessages.TargetClosedExceptionMessage) + { + } + + public TargetClosedException(string message, Exception innerException) : base(message, innerException) + { + } +} diff --git a/src/Playwright/Core/APIRequestContext.cs b/src/Playwright/Core/APIRequestContext.cs index dc2d55d4d3..aa0582e2bb 100644 --- a/src/Playwright/Core/APIRequestContext.cs +++ b/src/Playwright/Core/APIRequestContext.cs @@ -105,7 +105,7 @@ public async Task FetchAsync(string url, APIRequestContextOptions { if (IsJsonContentType(options.Headers?.ToDictionary(x => x.Key, x => x.Value))) { - jsonData = dataString; + jsonData = isJsonParsable(dataString) ? dataString : JsonSerializer.Serialize(dataString, _connection.DefaultJsonSerializerOptionsKeepNulls); } else { @@ -116,9 +116,9 @@ public async Task FetchAsync(string url, APIRequestContextOptions { postData = options.DataByte; } - else + else if (options.DataObject != null) { - jsonData = options.DataObject; + jsonData = JsonSerializer.Serialize(options.DataObject, _connection.DefaultJsonSerializerOptionsKeepNulls); } return await _channel.FetchAsync( @@ -150,6 +150,20 @@ private bool IsJsonContentType(IDictionary headers) return contentType.Value.Contains("application/json"); } + private bool isJsonParsable(string dataString) + { + try + { + JsonSerializer.Deserialize(dataString); + return true; + } + catch (JsonException) + { + return false; + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] public Task DeleteAsync(string url, APIRequestContextOptions options = null) => FetchAsync(url, WithMethod(options, "DELETE")); diff --git a/src/Playwright/Core/APIResponse.cs b/src/Playwright/Core/APIResponse.cs index d2fba3fd44..9c6ed84fa6 100644 --- a/src/Playwright/Core/APIResponse.cs +++ b/src/Playwright/Core/APIResponse.cs @@ -68,7 +68,7 @@ public async Task BodyAsync() } return Convert.FromBase64String(result); } - catch (Exception e) when (e.Message == DriverMessages.BrowserOrContextClosedExceptionMessage) + catch (Exception e) when (DriverMessages.IsTargetClosedError(e)) { throw new PlaywrightException("Response has been disposed"); } @@ -86,7 +86,7 @@ public async Task TextAsync() internal string FetchUid() => _initializer.FetchUid; - internal Task> FetchLogAsync() => _context._channel.FetchResponseLogAsync(FetchUid()); + internal Task FetchLogAsync() => _context._channel.FetchResponseLogAsync(FetchUid()); public ValueTask DisposeAsync() => new(_context._channel.DisposeAPIResponseAsync(FetchUid())); diff --git a/src/Playwright/Core/APIResponseAssertions.cs b/src/Playwright/Core/APIResponseAssertions.cs index 4360f47292..e129980004 100644 --- a/src/Playwright/Core/APIResponseAssertions.cs +++ b/src/Playwright/Core/APIResponseAssertions.cs @@ -25,6 +25,7 @@ using System; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Microsoft.Playwright.Transport; namespace Microsoft.Playwright.Core; @@ -57,11 +58,7 @@ public async Task ToBeOKAsync() message = message.Replace("expected to", "expected not to"); } var logList = await _actual.FetchLogAsync().ConfigureAwait(false); - var log = string.Join("\n", logList); - if (logList.Count > 0) - { - message += $"\nCall log:\n{log}"; - } + message += Connection.FormatCallLog(logList); var contentType = _actual.Headers.TryGetValue("content-type", out var contentTypeValue) ? contentTypeValue : string.Empty; bool isTextEncoding = IsTextualMimeType(contentType); @@ -75,7 +72,7 @@ public async Task ToBeOKAsync() responseText = $"\nResponse text:\n{trimmedText}"; } } - throw new PlaywrightException(message + log + responseText); + throw new PlaywrightException(message + responseText); } private static bool IsTextualMimeType(string contentType) diff --git a/src/Playwright/Core/AssertionsBase.cs b/src/Playwright/Core/AssertionsBase.cs index a532627bb4..90760a4fe2 100644 --- a/src/Playwright/Core/AssertionsBase.cs +++ b/src/Playwright/Core/AssertionsBase.cs @@ -29,6 +29,7 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.Playwright.Helpers; +using Microsoft.Playwright.Transport; using Microsoft.Playwright.Transport.Protocol; [assembly: InternalsVisibleToAttribute("Microsoft.Playwright.MSTest, PublicKey=0024000004800000940000000602000000240000525341310004000001000100059a04ca5ca77c9b4eb2addd1afe3f8464b20ee6aefe73b8c23c0e6ca278d1a378b33382e7e18d4aa8300dd22d81f146e528d88368f73a288e5b8157da9710fe6f9fa9911fb786193f983408c5ebae0b1ba5d1d00111af2816f5db55871db03d7536f4a7a6c5152d630c1e1886b1a0fb68ba5e7f64a7f24ac372090889be2ffb")] @@ -77,11 +78,7 @@ internal async Task ExpectImplAsync(string expression, FrameExpectOptions expect if (result.Matches == IsNot) { var actual = result.Received; - var log = string.Join("\n", result.Log); - if (!string.IsNullOrEmpty(log)) - { - log = "\nCall log:\n" + log; - } + var log = Connection.FormatCallLog(result.Log); if (expected == null) { throw new PlaywrightException($"{message} {log}"); diff --git a/src/Playwright/Core/Browser.cs b/src/Playwright/Core/Browser.cs index a9148c521a..b544c63066 100644 --- a/src/Playwright/Core/Browser.cs +++ b/src/Playwright/Core/Browser.cs @@ -38,6 +38,7 @@ internal class Browser : ChannelOwnerBase, IChannelOwner, IBrowser private readonly TaskCompletionSource _closedTcs = new(); internal readonly List _contexts = new(); internal BrowserType _browserType; + internal string _closeReason; internal Browser(IChannelOwner parent, string guid, BrowserInitializer initializer) : base(parent, guid) { @@ -66,21 +67,22 @@ internal Browser(IChannelOwner parent, string guid, BrowserInitializer initializ public IBrowserType BrowserType => _browserType; [MethodImpl(MethodImplOptions.NoInlining)] - public async Task CloseAsync() + public async Task CloseAsync(BrowserCloseOptions options = default) { + _closeReason = options?.Reason; try { if (ShouldCloseConnectionOnClose) { - Channel.Connection.DoClose(DriverMessages.BrowserClosedExceptionMessage); + Channel.Connection.DoClose(); } else { - await Channel.CloseAsync().ConfigureAwait(false); + await Channel.CloseAsync(options?.Reason).ConfigureAwait(false); } await _closedTcs.Task.ConfigureAwait(false); } - catch (Exception e) when (DriverMessages.IsSafeCloseError(e)) + catch (Exception e) when (DriverMessages.IsTargetClosedError(e)) { // Swallow exception } diff --git a/src/Playwright/Core/BrowserContext.cs b/src/Playwright/Core/BrowserContext.cs index 93b984df76..cc24bbfe7e 100644 --- a/src/Playwright/Core/BrowserContext.cs +++ b/src/Playwright/Core/BrowserContext.cs @@ -51,6 +51,7 @@ internal class BrowserContext : ChannelOwnerBase, IChannelOwner, internal readonly List _pages = new(); private readonly Browser _browser; private bool _closeWasCalled; + private string _closeReason; internal TimeoutSettings _timeoutSettings = new(); @@ -245,12 +246,13 @@ public Task AddInitScriptAsync(string script = null, string scriptPath = null) public Task ClearPermissionsAsync() => Channel.ClearPermissionsAsync(); [MethodImpl(MethodImplOptions.NoInlining)] - public async Task CloseAsync() + public async Task CloseAsync(BrowserContextCloseOptions options = default) { if (_closeWasCalled) { return; } + _closeReason = options?.Reason; _closeWasCalled = true; await WrapApiCallAsync( async () => @@ -274,7 +276,7 @@ await WrapApiCallAsync( } }, true).ConfigureAwait(false); - await Channel.CloseAsync().ConfigureAwait(false); + await Channel.CloseAsync(options?.Reason).ConfigureAwait(false); await _closeTcs.Task.ConfigureAwait(false); } @@ -451,6 +453,11 @@ public Task UnrouteAsync(Func urlFunc, Action handler = de public Task UnrouteAsync(Func urlFunc, Func handler = default) => UnrouteAsync(null, urlFunc, handler); + internal string _effectiveCloseReason() + { + return _closeReason ?? _browser._closeReason; + } + [MethodImpl(MethodImplOptions.NoInlining)] public async Task InnerWaitForEventAsync(PlaywrightEvent playwrightEvent, Func action = default, Func predicate = default, float? timeout = default) { @@ -465,7 +472,7 @@ public async Task InnerWaitForEventAsync(PlaywrightEvent playwrightEven if (playwrightEvent.Name != BrowserContextEvent.Close.Name) { - waiter.RejectOnEvent(this, BrowserContextEvent.Close.Name, new("Context closed")); + waiter.RejectOnEvent(this, BrowserContextEvent.Close.Name, () => new TargetClosedException(_effectiveCloseReason())); } var result = waiter.WaitForEventAsync(this, playwrightEvent.Name, predicate); diff --git a/src/Playwright/Core/BrowserType.cs b/src/Playwright/Core/BrowserType.cs index b42487f183..4040a9ce9d 100644 --- a/src/Playwright/Core/BrowserType.cs +++ b/src/Playwright/Core/BrowserType.cs @@ -178,7 +178,7 @@ void ClosePipe() connection.MarkAsRemote(); connection.Close += (_, _) => ClosePipe(); - string closeError = null; + Exception closeError = null; Browser browser = null; void OnPipeClosed() { @@ -192,7 +192,7 @@ void OnPipeClosed() context.OnClose(); } browser?.DidClose(); - connection.DoClose(closeError ?? DriverMessages.BrowserClosedExceptionMessage); + connection.DoClose(closeError); } pipe.Closed += (_, _) => OnPipeClosed(); connection.OnMessage = async (object message, bool _) => @@ -201,7 +201,7 @@ void OnPipeClosed() { await pipe.SendAsync(message).ConfigureAwait(false); } - catch (Exception e) when (DriverMessages.IsSafeCloseError(e)) + catch (Exception e) when (DriverMessages.IsTargetClosedError(e)) { // swallow exception } @@ -219,7 +219,7 @@ void OnPipeClosed() } catch (Exception ex) { - closeError = ex.ToString(); + closeError = ex; _channel.Connection.TraceMessage("pw:dotnet", $"Dispatching error: {ex.Message}\n{ex.StackTrace}"); ClosePipe(); } diff --git a/src/Playwright/Core/DriverMessages.cs b/src/Playwright/Core/DriverMessages.cs index f1346b971a..9630bcb935 100644 --- a/src/Playwright/Core/DriverMessages.cs +++ b/src/Playwright/Core/DriverMessages.cs @@ -31,17 +31,7 @@ namespace Microsoft.Playwright.Core; /// internal static class DriverMessages { - /// - /// Message used when the browser gets closed. - /// - public const string BrowserClosedExceptionMessage = "Browser has been closed"; + public const string TargetClosedExceptionMessage = "Target page, context or browser has been closed"; - /// - /// Message used when the browser or the context get closed. - /// - public const string BrowserOrContextClosedExceptionMessage = "Target page, context or browser has been closed"; - - public static bool IsSafeCloseError(Exception e) - => e.Message.Contains(BrowserClosedExceptionMessage) || - e.Message.Contains(BrowserOrContextClosedExceptionMessage); + public static bool IsTargetClosedError(Exception e) => e is TargetClosedException; } diff --git a/src/Playwright/Core/Frame.cs b/src/Playwright/Core/Frame.cs index 2b566a7000..f5701eb84a 100644 --- a/src/Playwright/Core/Frame.cs +++ b/src/Playwright/Core/Frame.cs @@ -702,16 +702,16 @@ private Task WaitForURLAsync(string urlString, Regex urlRegex, Func(Page, PageEvent.Close.Name, new("Navigation failed because page was closed!")); - waiter.RejectOnEvent(Page, PageEvent.Crash.Name, new("Navigation failed because page was crashed!")); + waiter.RejectOnEvent(Page, PageEvent.Close.Name, () => ((Page)Page)._closeErrorWithReason()); + waiter.RejectOnEvent(Page, PageEvent.Crash.Name, new PlaywrightException("Navigation failed because page was crashed!")); waiter.RejectOnEvent