diff --git a/src/Playwright.MSTest/PlaywrightTest.cs b/src/Playwright.MSTest/PlaywrightTest.cs index e60c5cbd8..b0a7f59d7 100644 --- a/src/Playwright.MSTest/PlaywrightTest.cs +++ b/src/Playwright.MSTest/PlaywrightTest.cs @@ -53,4 +53,10 @@ public async Task PlaywrightSetup() public IPageAssertions Expect(IPage page) => Assertions.Expect(page); public IAPIResponseAssertions Expect(IAPIResponse response) => Assertions.Expect(response); + + public ILocatorAssertions Expect(ILocator locator, string message) => Assertions.Expect(locator, message); + + public IPageAssertions Expect(IPage page, string message) => Assertions.Expect(page, message); + + public IAPIResponseAssertions Expect(IAPIResponse response, string message) => Assertions.Expect(response, message); } diff --git a/src/Playwright.NUnit/PlaywrightTest.cs b/src/Playwright.NUnit/PlaywrightTest.cs index 94526676f..15cf59a48 100644 --- a/src/Playwright.NUnit/PlaywrightTest.cs +++ b/src/Playwright.NUnit/PlaywrightTest.cs @@ -53,4 +53,10 @@ public async Task PlaywrightSetup() public IPageAssertions Expect(IPage page) => Assertions.Expect(page); public IAPIResponseAssertions Expect(IAPIResponse response) => Assertions.Expect(response); + + public ILocatorAssertions Expect(ILocator locator, string message) => Assertions.Expect(locator, message); + + public IPageAssertions Expect(IPage page, string message) => Assertions.Expect(page, message); + + public IAPIResponseAssertions Expect(IAPIResponse response, string message) => Assertions.Expect(response, message); } diff --git a/src/Playwright.Tests/Assertions/LocatorAssertionsTests.cs b/src/Playwright.Tests/Assertions/LocatorAssertionsTests.cs index 97eea0aff..b84b58df6 100644 --- a/src/Playwright.Tests/Assertions/LocatorAssertionsTests.cs +++ b/src/Playwright.Tests/Assertions/LocatorAssertionsTests.cs @@ -73,6 +73,24 @@ public async Task FailWithIndeterminateTrue() StringAssert.Contains("Expect \"ToBeCheckedAsync\" with timeout 1000ms", exception.Message); } + [PlaywrightTest("playwright-test/playwright.expect.spec.ts", "should support a custom expect message")] + public async Task ShouldSupportCustomExpectMessage() + { + await Page.SetContentAsync("
"); + var locator = Page.Locator("input"); + var exception = await PlaywrightAssert.ThrowsAsync( + () => Expect(locator, "should be logged in").ToBeVisibleAsync(new() { Timeout = 1000 })); + StringAssert.StartsWith("should be logged in", exception.Message); + StringAssert.Contains("Locator expected to be visible", exception.Message); + + // Custom message also propagates through Not. + await Page.SetContentAsync(""); + var present = Page.Locator("input"); + var notException = await PlaywrightAssert.ThrowsAsync( + () => Expect(present, "should not be present").Not.ToBeVisibleAsync(new() { Timeout = 1000 })); + StringAssert.StartsWith("should not be present", notException.Message); + } + [PlaywrightTest("playwright-test/playwright.expect.spec.ts", "should be able to set default timeout")] public async Task ShouldBeAbleToSetDefaultTimeout() { diff --git a/src/Playwright.Xunit.v3/PlaywrightTest.cs b/src/Playwright.Xunit.v3/PlaywrightTest.cs index 0c99831ca..532f79245 100644 --- a/src/Playwright.Xunit.v3/PlaywrightTest.cs +++ b/src/Playwright.Xunit.v3/PlaywrightTest.cs @@ -52,4 +52,10 @@ public override async ValueTask InitializeAsync() public IPageAssertions Expect(IPage page) => Assertions.Expect(page); public IAPIResponseAssertions Expect(IAPIResponse response) => Assertions.Expect(response); + + public ILocatorAssertions Expect(ILocator locator, string message) => Assertions.Expect(locator, message); + + public IPageAssertions Expect(IPage page, string message) => Assertions.Expect(page, message); + + public IAPIResponseAssertions Expect(IAPIResponse response, string message) => Assertions.Expect(response, message); } diff --git a/src/Playwright.Xunit/PlaywrightTest.cs b/src/Playwright.Xunit/PlaywrightTest.cs index e4b8ae5b1..08967132f 100644 --- a/src/Playwright.Xunit/PlaywrightTest.cs +++ b/src/Playwright.Xunit/PlaywrightTest.cs @@ -52,4 +52,10 @@ public override async Task InitializeAsync() public IPageAssertions Expect(IPage page) => Assertions.Expect(page); public IAPIResponseAssertions Expect(IAPIResponse response) => Assertions.Expect(response); + + public ILocatorAssertions Expect(ILocator locator, string message) => Assertions.Expect(locator, message); + + public IPageAssertions Expect(IPage page, string message) => Assertions.Expect(page, message); + + public IAPIResponseAssertions Expect(IAPIResponse response, string message) => Assertions.Expect(response, message); } diff --git a/src/Playwright/Assertions.cs b/src/Playwright/Assertions.cs index 9fa016b3f..12c51c9ad 100644 --- a/src/Playwright/Assertions.cs +++ b/src/Playwright/Assertions.cs @@ -44,4 +44,23 @@ public static class Assertions public static IPageAssertions Expect(IPage page) => new PageAssertions(page, false); public static IAPIResponseAssertions Expect(IAPIResponse response) => new APIResponseAssertions(response, false); + + /// + /// Creates assertions that prefix any failure message with , providing + /// extra context in test reports. + /// + /// The locator to assert against. + /// Message to prepend to any assertion failure. + /// Assertions for the given locator. + public static ILocatorAssertions Expect(ILocator locator, string message) => new LocatorAssertions(locator, false, message); + + /// + /// The page to assert against. + /// Message to prepend to any assertion failure. + public static IPageAssertions Expect(IPage page, string message) => new PageAssertions(page, false, message); + + /// + /// The API response to assert against. + /// Message to prepend to any assertion failure. + public static IAPIResponseAssertions Expect(IAPIResponse response, string message) => new APIResponseAssertions(response, false, message); } diff --git a/src/Playwright/Core/APIResponseAssertions.cs b/src/Playwright/Core/APIResponseAssertions.cs index e12998000..01c324c43 100644 --- a/src/Playwright/Core/APIResponseAssertions.cs +++ b/src/Playwright/Core/APIResponseAssertions.cs @@ -33,8 +33,9 @@ internal class APIResponseAssertions : IAPIResponseAssertions { private readonly APIResponse _actual; private readonly bool _isNot; + private readonly string? _customMessage; - public APIResponseAssertions(IAPIResponse response, bool isNot) + public APIResponseAssertions(IAPIResponse response, bool isNot, string? customMessage = null) { if (response == null) { @@ -42,9 +43,10 @@ public APIResponseAssertions(IAPIResponse response, bool isNot) } _actual = (APIResponse)response; _isNot = isNot; + _customMessage = customMessage; } - public IAPIResponseAssertions Not => new APIResponseAssertions(_actual, !_isNot); + public IAPIResponseAssertions Not => new APIResponseAssertions(_actual, !_isNot, _customMessage); public async Task ToBeOKAsync() { @@ -72,7 +74,8 @@ public async Task ToBeOKAsync() responseText = $"\nResponse text:\n{trimmedText}"; } } - throw new PlaywrightException(message + responseText); + var prefix = _customMessage != null ? _customMessage + "\n\n" : string.Empty; + throw new PlaywrightException(prefix + message + responseText); } private static bool IsTextualMimeType(string contentType) diff --git a/src/Playwright/Core/AssertionsBase.cs b/src/Playwright/Core/AssertionsBase.cs index bfb1a2b9e..c6527835b 100644 --- a/src/Playwright/Core/AssertionsBase.cs +++ b/src/Playwright/Core/AssertionsBase.cs @@ -44,13 +44,16 @@ internal abstract class AssertionsBase { private static float _defaultTimeout = 5_000; - public AssertionsBase(bool isNot) + public AssertionsBase(bool isNot, string? customMessage = null) { IsNot = isNot; + CustomMessage = customMessage; } protected bool IsNot { get; } + protected string? CustomMessage { get; } + protected abstract Task CallExpectAsync(string expression, FrameExpectOptions expectOptions, string title); protected async Task ExpectImplAsync(string expression, ExpectedTextValue textValue, object expected, string message, string title, FrameExpectOptions options) @@ -85,11 +88,12 @@ protected async Task ExpectImplAsync(string expression, FrameExpectOptions expec { message += "\n" + result.ErrorMessage; } + var prefix = CustomMessage != null ? CustomMessage + "\n\n" : string.Empty; if (expected == null) { - throw new PlaywrightException($"{message} {log}"); + throw new PlaywrightException($"{prefix}{message} {log}"); } - throw new PlaywrightException($"{message} '{FormatValue(expected)}'\nBut was: '{FormatValue(actual)}' {log}"); + throw new PlaywrightException($"{prefix}{message} '{FormatValue(expected)}'\nBut was: '{FormatValue(actual)}' {log}"); } } diff --git a/src/Playwright/Core/LocatorAssertions.cs b/src/Playwright/Core/LocatorAssertions.cs index 9ebc9beae..f1aa62c0c 100644 --- a/src/Playwright/Core/LocatorAssertions.cs +++ b/src/Playwright/Core/LocatorAssertions.cs @@ -32,14 +32,14 @@ namespace Microsoft.Playwright.Core; internal class LocatorAssertions : AssertionsBase, ILocatorAssertions { - public LocatorAssertions(ILocator locator, bool isNot) : base(isNot) + public LocatorAssertions(ILocator locator, bool isNot, string? customMessage = null) : base(isNot, customMessage) { ActualLocator = (Locator)locator; } private Locator ActualLocator { get; } - public ILocatorAssertions Not => new LocatorAssertions(ActualLocator, !IsNot); + public ILocatorAssertions Not => new LocatorAssertions(ActualLocator, !IsNot, CustomMessage); protected override Task CallExpectAsync(string expression, FrameExpectOptions expectOptions, string title) { diff --git a/src/Playwright/Core/PageAssertions.cs b/src/Playwright/Core/PageAssertions.cs index a20c1e30e..8d283fca9 100644 --- a/src/Playwright/Core/PageAssertions.cs +++ b/src/Playwright/Core/PageAssertions.cs @@ -33,12 +33,12 @@ internal class PageAssertions : AssertionsBase, IPageAssertions { private readonly Page _page; - public PageAssertions(IPage page, bool isNot) : base(isNot) + public PageAssertions(IPage page, bool isNot, string? customMessage = null) : base(isNot, customMessage) { _page = (Page)page; } - public IPageAssertions Not => new PageAssertions(_page, !IsNot); + public IPageAssertions Not => new PageAssertions(_page, !IsNot, CustomMessage); protected override Task CallExpectAsync(string expression, FrameExpectOptions expectOptions, string title) {