diff --git a/CSF.Screenplay.Selenium/Builders/QueryPredicatePrototypeBuilder.cs b/CSF.Screenplay.Selenium/Builders/QueryPredicatePrototypeBuilder.cs index b280d4af..168ae25c 100644 --- a/CSF.Screenplay.Selenium/Builders/QueryPredicatePrototypeBuilder.cs +++ b/CSF.Screenplay.Selenium/Builders/QueryPredicatePrototypeBuilder.cs @@ -230,6 +230,20 @@ public QueryPredicatePrototype Size(Size value) /// /// Creates a query predicate based on the element's text content. /// + /// + /// + /// When reading text from the web browser, this predicate will trim leading/trailing whitespace from that text before comparing it. + /// This is because some browsers (Safari) + /// include whitespace at the beginning/end of text read from the browser, which isn't visible to the end user. This is typically the + /// space which is inherent in the markup, but which browsers ignore when actually displaying content. + /// + /// + /// Trimming it by default ensures that Screenplay reproduces functionality reliably cross-browser. + /// If this causes an issue and you would like the leading/trailing whitespace included the use + /// instead. + /// Note that you may see different results in browsers which include leading/trailing whitespace anyway. + /// + /// /// The predicate to apply to the element's text. /// A , which may be converted to a full predicate. public QueryPredicatePrototype Text(Func predicate) @@ -238,11 +252,55 @@ public QueryPredicatePrototype Text(Func predicate) /// /// Creates a query predicate based on the element's text content. /// + /// + /// + /// When reading text from the web browser, this predicate will trim leading/trailing whitespace from that text before comparing it. + /// This is because some browsers (Safari) + /// include whitespace at the beginning/end of text read from the browser, which isn't visible to the end user. This is typically the + /// space which is inherent in the markup, but which browsers ignore when actually displaying content. + /// + /// + /// Trimming it by default ensures that Screenplay reproduces functionality reliably cross-browser. + /// If this causes an issue and you would like the leading/trailing whitespace included the use + /// instead. + /// Note that you may see different results in browsers which include leading/trailing whitespace anyway. + /// + /// /// The value to compare against the element's text. /// A , which may be converted to a full predicate. public QueryPredicatePrototype Text(string value) => CreatePrototype(new TextQuery(), Spec.Func(x => x == value), t => $"{t.Name} has text equal to '{value}'"); + /// + /// Creates a query predicate based on the element's text content. + /// + /// + /// + /// When reading text from the web browser, this predicate will leave any leading/trailing whitespace in the text + /// without trimming it. + /// Note that you may see different results in browsers which include leading/trailing whitespace anyway. + /// + /// + /// The predicate to apply to the element's text. + /// A , which may be converted to a full predicate. + public QueryPredicatePrototype TextWithoutTrimmingWhitespace(Func predicate) + => CreatePrototype(new TextQuery(false), Spec.Func(predicate), t => $"{t.Name} has text matching a predicate"); + + /// + /// Creates a query predicate based on the element's text content, without trimming leading/trailing whitespace. + /// + /// + /// + /// When reading text from the web browser, this predicate will leave any leading/trailing whitespace in the text + /// without trimming it. + /// Note that you may see different results in browsers which include leading/trailing whitespace anyway. + /// + /// + /// The value to compare against the element's text. + /// A , which may be converted to a full predicate. + public QueryPredicatePrototype TextWithoutTrimmingWhitespace(string value) + => CreatePrototype(new TextQuery(false), Spec.Func(x => x == value), t => $"{t.Name} has text equal to '{value}'"); + /// /// Creates a query predicate based on the element's DOM value. /// diff --git a/CSF.Screenplay.Selenium/Builders/TextMultiQueryBuilder.cs b/CSF.Screenplay.Selenium/Builders/TextMultiQueryBuilder.cs new file mode 100644 index 00000000..2de1a328 --- /dev/null +++ b/CSF.Screenplay.Selenium/Builders/TextMultiQueryBuilder.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using CSF.Screenplay.Performables; +using CSF.Screenplay.Selenium.Elements; +using CSF.Screenplay.Selenium.Queries; +using CSF.Screenplay.Selenium.Questions; + +namespace CSF.Screenplay.Selenium.Builders +{ + /// + /// Builder type for a performable that gets a collection of text values. + /// + /// + /// + /// The purpose of this builder is to enable or disable the trimming of whitespace characters at the beginning or end of the returned text. + /// + /// + public class TextMultiQueryBuilder : IGetsPerformableWithResult> + { + readonly ITarget elements; + bool trimWhitespace = true; + + /// + /// Configures the performable to disable the trimming of whitespace characters at the beginning or end of the returned text. + /// + /// + /// + /// Trimming of whitespace at the beginning/end of text is enabled by default. This is because some browsers (Safari) + /// include whitespace at the beginning/end of text read from the browser, which isn't visible to the end user. This is typically the + /// space which is inherent in the markup, but which browsers ignore when actually displaying content. + /// + /// + /// Trimming it by default ensures that Screenplay reproduces functionality reliably cross-browser. + /// If this causes an issue and you would like the leading/trailing whitespace included in the result then use this method. + /// Note that you may see different results in browsers which include leading/trailing whitespace anyway. + /// + /// + /// The current instance of the builder, so calls may be chained. + public TextMultiQueryBuilder WithoutTrimmingWhitespace() + { + trimWhitespace = false; + return this; + } + + IPerformableWithResult> IGetsPerformableWithResult>.GetPerformable() + => ElementCollectionPerformableWithResultAdapter.From(ElementCollectionQuery.From(new TextQuery(trimWhitespace)), elements); + + /// + /// Initializes a new instance of the class. + /// + /// The target elements to query. + public TextMultiQueryBuilder(ITarget elements) + { + this.elements = elements ?? throw new System.ArgumentNullException(nameof(elements)); + } + } +} \ No newline at end of file diff --git a/CSF.Screenplay.Selenium/Builders/TextQueryBuilder.cs b/CSF.Screenplay.Selenium/Builders/TextQueryBuilder.cs new file mode 100644 index 00000000..38b14966 --- /dev/null +++ b/CSF.Screenplay.Selenium/Builders/TextQueryBuilder.cs @@ -0,0 +1,55 @@ +using CSF.Screenplay.Performables; +using CSF.Screenplay.Selenium.Elements; +using CSF.Screenplay.Selenium.Queries; +using CSF.Screenplay.Selenium.Questions; + +namespace CSF.Screenplay.Selenium.Builders +{ + /// + /// Builder type for a performable that gets a single text value. + /// + /// + /// + /// The purpose of this builder is to enable or disable the trimming of whitespace characters at the beginning or end of the returned text. + /// + /// + public class TextQueryBuilder : IGetsPerformableWithResult + { + readonly ITarget element; + bool trimWhitespace = true; + + /// + /// Configures the performable to disable the trimming of whitespace characters at the beginning or end of the returned text. + /// + /// + /// + /// Trimming of whitespace at the beginning/end of text is enabled by default. This is because some browsers (Safari) + /// include whitespace at the beginning/end of text read from the browser, which isn't visible to the end user. This is typically the + /// space which is inherent in the markup, but which browsers ignore when actually displaying content. + /// + /// + /// Trimming it by default ensures that Screenplay reproduces functionality reliably cross-browser. + /// If this causes an issue and you would like the leading/trailing whitespace included in the result then use this method. + /// Note that you may see different results in browsers which include leading/trailing whitespace anyway. + /// + /// + /// The current instance of the builder, so calls may be chained. + public TextQueryBuilder WithoutTrimmingWhitespace() + { + trimWhitespace = false; + return this; + } + + IPerformableWithResult IGetsPerformableWithResult.GetPerformable() + => SingleElementPerformableWithResultAdapter.From(SingleElementQuery.From(new TextQuery(trimWhitespace)), element); + + /// + /// Initializes a new instance of the class. + /// + /// The target element to query. + public TextQueryBuilder(ITarget element) + { + this.element = element ?? throw new System.ArgumentNullException(nameof(element)); + } + } +} \ No newline at end of file diff --git a/CSF.Screenplay.Selenium/Queries/TextQuery.cs b/CSF.Screenplay.Selenium/Queries/TextQuery.cs index 23e0d623..b5294497 100644 --- a/CSF.Screenplay.Selenium/Queries/TextQuery.cs +++ b/CSF.Screenplay.Selenium/Queries/TextQuery.cs @@ -7,10 +7,26 @@ namespace CSF.Screenplay.Selenium.Queries /// public class TextQuery : IQuery { + readonly bool trimWhitespace; + /// public string Name => $"the contained text"; /// - public string GetValue(SeleniumElement element) => element.WebElement.Text; + public string GetValue(SeleniumElement element) + { + var text = element.WebElement.Text; + return trimWhitespace ? text?.Trim() : text; + } + + /// + /// Initializes a new instance of the class. + /// + /// If , leading & trailing whitespace will be trimmed from + /// the returned text content; if it will not. + public TextQuery(bool trimWhitespace = true) + { + this.trimWhitespace = trimWhitespace; + } } } diff --git a/Tests/CSF.Screenplay.Selenium.Tests/Builders/TextMultiQueryBuilderTests.cs b/Tests/CSF.Screenplay.Selenium.Tests/Builders/TextMultiQueryBuilderTests.cs new file mode 100644 index 00000000..6509664f --- /dev/null +++ b/Tests/CSF.Screenplay.Selenium.Tests/Builders/TextMultiQueryBuilderTests.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using CSF.Extensions.WebDriver; +using CSF.Extensions.WebDriver.Factories; +using CSF.Screenplay.Performables; +using CSF.Screenplay.Selenium.Elements; +using Moq; +using OpenQA.Selenium; + +namespace CSF.Screenplay.Selenium.Builders; + +[TestFixture, Parallelizable] +public class TextMultiQueryBuilderTests +{ + [Test, AutoMoqData] + public async Task WithoutTrimmingWhitespaceShouldBuildAQueryWhichDoesNotTrimWhitespace([Frozen] ITarget target, + TextMultiQueryBuilder sut, + Actor actor, + SeleniumElement element1, + SeleniumElement element2, + IWebDriver driver) + { + actor.IsAbleTo(new BrowseTheWeb(Mock.Of(f => f.GetDefaultWebDriver(null) == new WebDriverAndOptions(driver, Mock.Of())))); + Mock.Get(target).Setup(x => x.GetElements(driver)).Returns(new SeleniumElementCollection([element1, element2], "the elements")); + Mock.Get(element1.WebElement).SetupGet(x => x.Text).Returns(" foo bar "); + Mock.Get(element2.WebElement).SetupGet(x => x.Text).Returns(" baz bob "); + sut.WithoutTrimmingWhitespace(); + var performable = ((IGetsPerformableWithResult>) sut).GetPerformable(); + var result = await performable.PerformAsAsync(actor); + Assert.That(result, Is.EqualTo(new [] {" foo bar ", " baz bob "})); + } + + [Test, AutoMoqData] + public async Task ShouldBuildAQueryWhichTrimsWhitespaceByDefault([Frozen] ITarget target, + TextMultiQueryBuilder sut, + Actor actor, + SeleniumElement element1, + SeleniumElement element2, + IWebDriver driver) + { + actor.IsAbleTo(new BrowseTheWeb(Mock.Of(f => f.GetDefaultWebDriver(null) == new WebDriverAndOptions(driver, Mock.Of())))); + Mock.Get(target).Setup(x => x.GetElements(driver)).Returns(new SeleniumElementCollection([element1, element2], "the elements")); + Mock.Get(element1.WebElement).SetupGet(x => x.Text).Returns(" foo bar "); + Mock.Get(element2.WebElement).SetupGet(x => x.Text).Returns(" baz bob "); + var performable = ((IGetsPerformableWithResult>) sut).GetPerformable(); + var result = await performable.PerformAsAsync(actor); + Assert.That(result, Is.EqualTo(new [] {"foo bar", "baz bob"})); + } +} \ No newline at end of file diff --git a/Tests/CSF.Screenplay.Selenium.Tests/Builders/TextQueryBuilderTests.cs b/Tests/CSF.Screenplay.Selenium.Tests/Builders/TextQueryBuilderTests.cs new file mode 100644 index 00000000..ae1b068f --- /dev/null +++ b/Tests/CSF.Screenplay.Selenium.Tests/Builders/TextQueryBuilderTests.cs @@ -0,0 +1,43 @@ +using CSF.Extensions.WebDriver; +using CSF.Extensions.WebDriver.Factories; +using CSF.Screenplay.Performables; +using CSF.Screenplay.Selenium.Elements; +using Moq; +using OpenQA.Selenium; + +namespace CSF.Screenplay.Selenium.Builders; + +[TestFixture, Parallelizable] +public class TextQueryBuilderTests +{ + [Test, AutoMoqData] + public async Task WithoutTrimmingWhitespaceShouldBuildAQueryWhichDoesNotTrimWhitespace([Frozen] ITarget target, + TextQueryBuilder sut, + Actor actor, + SeleniumElement element, + IWebDriver driver) + { + actor.IsAbleTo(new BrowseTheWeb(Mock.Of(f => f.GetDefaultWebDriver(null) == new WebDriverAndOptions(driver, Mock.Of())))); + Mock.Get(target).Setup(x => x.GetElement(driver)).Returns(element); + Mock.Get(element.WebElement).SetupGet(x => x.Text).Returns(" foo bar "); + sut.WithoutTrimmingWhitespace(); + var performable = ((IGetsPerformableWithResult) sut).GetPerformable(); + var result = await performable.PerformAsAsync(actor); + Assert.That(result, Is.EqualTo(" foo bar ")); + } + + [Test, AutoMoqData] + public async Task ShouldBuildAQueryWhichTrimsWhitespaceByDefault([Frozen] ITarget target, + TextQueryBuilder sut, + Actor actor, + SeleniumElement element, + IWebDriver driver) + { + actor.IsAbleTo(new BrowseTheWeb(Mock.Of(f => f.GetDefaultWebDriver(null) == new WebDriverAndOptions(driver, Mock.Of())))); + Mock.Get(target).Setup(x => x.GetElement(driver)).Returns(element); + Mock.Get(element.WebElement).SetupGet(x => x.Text).Returns(" foo bar "); + var performable = ((IGetsPerformableWithResult) sut).GetPerformable(); + var result = await performable.PerformAsAsync(actor); + Assert.That(result, Is.EqualTo("foo bar")); + } +}