Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions CSF.Screenplay.Selenium/Builders/QueryPredicatePrototypeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,20 @@ public QueryPredicatePrototype<Size> Size(Size value)
/// <summary>
/// Creates a query predicate based on the element's text content.
/// </summary>
/// <remarks>
/// <para>
/// 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.
/// </para>
/// <para>
/// 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
/// <see cref="TextWithoutTrimmingWhitespace(string)"/> instead.
/// Note that you may see different results in browsers which include leading/trailing whitespace anyway.
/// </para>
/// </remarks>
/// <param name="predicate">The predicate to apply to the element's text.</param>
/// <returns>A <see cref="QueryPredicatePrototype{String}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<string> Text(Func<string,bool> predicate)
Expand All @@ -238,11 +252,55 @@ public QueryPredicatePrototype<string> Text(Func<string,bool> predicate)
/// <summary>
/// Creates a query predicate based on the element's text content.
/// </summary>
/// <remarks>
/// <para>
/// 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.
/// </para>
/// <para>
/// 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
/// <see cref="TextWithoutTrimmingWhitespace(string)"/> instead.
/// Note that you may see different results in browsers which include leading/trailing whitespace anyway.
/// </para>
/// </remarks>
/// <param name="value">The value to compare against the element's text.</param>
/// <returns>A <see cref="QueryPredicatePrototype{String}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<string> Text(string value)
=> CreatePrototype(new TextQuery(), Spec.Func<string>(x => x == value), t => $"{t.Name} has text equal to '{value}'");

/// <summary>
/// Creates a query predicate based on the element's text content.
/// </summary>
/// <remarks>
/// <para>
/// 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.
/// </para>
/// </remarks>
/// <param name="predicate">The predicate to apply to the element's text.</param>
/// <returns>A <see cref="QueryPredicatePrototype{String}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<string> TextWithoutTrimmingWhitespace(Func<string,bool> predicate)
=> CreatePrototype(new TextQuery(false), Spec.Func(predicate), t => $"{t.Name} has text matching a predicate");

/// <summary>
/// Creates a query predicate based on the element's text content, without trimming leading/trailing whitespace.
/// </summary>
/// <remarks>
/// <para>
/// 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.
/// </para>
/// </remarks>
/// <param name="value">The value to compare against the element's text.</param>
/// <returns>A <see cref="QueryPredicatePrototype{String}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<string> TextWithoutTrimmingWhitespace(string value)
=> CreatePrototype(new TextQuery(false), Spec.Func<string>(x => x == value), t => $"{t.Name} has text equal to '{value}'");

/// <summary>
/// Creates a query predicate based on the element's DOM <c>value</c>.
/// </summary>
Expand Down
56 changes: 56 additions & 0 deletions CSF.Screenplay.Selenium/Builders/TextMultiQueryBuilder.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Builder type for a performable that gets a collection of text values.
/// </summary>
/// <remarks>
/// <para>
/// The purpose of this builder is to enable or disable the trimming of whitespace characters at the beginning or end of the returned text.
/// </para>
/// </remarks>
public class TextMultiQueryBuilder : IGetsPerformableWithResult<IReadOnlyList<string>>
{
readonly ITarget elements;
bool trimWhitespace = true;

/// <summary>
/// Configures the performable to disable the trimming of whitespace characters at the beginning or end of the returned text.
/// </summary>
/// <remarks>
/// <para>
/// 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.
/// </para>
/// <para>
/// 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.
/// </para>
/// </remarks>
/// <returns>The current instance of the builder, so calls may be chained.</returns>
public TextMultiQueryBuilder WithoutTrimmingWhitespace()
{
trimWhitespace = false;
return this;
}

IPerformableWithResult<IReadOnlyList<string>> IGetsPerformableWithResult<IReadOnlyList<string>>.GetPerformable()
=> ElementCollectionPerformableWithResultAdapter.From(ElementCollectionQuery.From(new TextQuery(trimWhitespace)), elements);

/// <summary>
/// Initializes a new instance of the <see cref="TextMultiQueryBuilder"/> class.
/// </summary>
/// <param name="elements">The target elements to query.</param>
public TextMultiQueryBuilder(ITarget elements)
{
this.elements = elements ?? throw new System.ArgumentNullException(nameof(elements));
}
}
}
55 changes: 55 additions & 0 deletions CSF.Screenplay.Selenium/Builders/TextQueryBuilder.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Builder type for a performable that gets a single text value.
/// </summary>
/// <remarks>
/// <para>
/// The purpose of this builder is to enable or disable the trimming of whitespace characters at the beginning or end of the returned text.
/// </para>
/// </remarks>
public class TextQueryBuilder : IGetsPerformableWithResult<string>
{
readonly ITarget element;
bool trimWhitespace = true;

/// <summary>
/// Configures the performable to disable the trimming of whitespace characters at the beginning or end of the returned text.
/// </summary>
/// <remarks>
/// <para>
/// 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.
/// </para>
/// <para>
/// 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.
/// </para>
/// </remarks>
/// <returns>The current instance of the builder, so calls may be chained.</returns>
public TextQueryBuilder WithoutTrimmingWhitespace()
{
trimWhitespace = false;
return this;
}

IPerformableWithResult<string> IGetsPerformableWithResult<string>.GetPerformable()
=> SingleElementPerformableWithResultAdapter.From(SingleElementQuery.From(new TextQuery(trimWhitespace)), element);

/// <summary>
/// Initializes a new instance of the <see cref="TextQueryBuilder"/> class.
/// </summary>
/// <param name="element">The target element to query.</param>
public TextQueryBuilder(ITarget element)
{
this.element = element ?? throw new System.ArgumentNullException(nameof(element));
}
}
}
18 changes: 17 additions & 1 deletion CSF.Screenplay.Selenium/Queries/TextQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,26 @@ namespace CSF.Screenplay.Selenium.Queries
/// </summary>
public class TextQuery : IQuery<string>
{
readonly bool trimWhitespace;

/// <inheritdoc/>
public string Name => $"the contained text";

/// <inheritdoc/>
public string GetValue(SeleniumElement element) => element.WebElement.Text;
public string GetValue(SeleniumElement element)
{
var text = element.WebElement.Text;
return trimWhitespace ? text?.Trim() : text;
}

/// <summary>
/// Initializes a new instance of the <see cref="TextQuery"/> class.
/// </summary>
/// <param name="trimWhitespace">If <see langword="true"/>, leading &amp; trailing whitespace will be trimmed from
/// the returned text content; if <see langword="true"/> it will not.</param>
public TextQuery(bool trimWhitespace = true)
{
this.trimWhitespace = trimWhitespace;
}
}
}
Original file line number Diff line number Diff line change
@@ -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<IGetsWebDriver>(f => f.GetDefaultWebDriver(null) == new WebDriverAndOptions(driver, Mock.Of<DriverOptions>()))));
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<IReadOnlyList<string>>) 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<IGetsWebDriver>(f => f.GetDefaultWebDriver(null) == new WebDriverAndOptions(driver, Mock.Of<DriverOptions>()))));
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<IReadOnlyList<string>>) sut).GetPerformable();
var result = await performable.PerformAsAsync(actor);
Assert.That(result, Is.EqualTo(new [] {"foo bar", "baz bob"}));
}
}
Original file line number Diff line number Diff line change
@@ -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<IGetsWebDriver>(f => f.GetDefaultWebDriver(null) == new WebDriverAndOptions(driver, Mock.Of<DriverOptions>()))));
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<string>) 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<IGetsWebDriver>(f => f.GetDefaultWebDriver(null) == new WebDriverAndOptions(driver, Mock.Of<DriverOptions>()))));
Mock.Get(target).Setup(x => x.GetElement(driver)).Returns(element);
Mock.Get(element.WebElement).SetupGet(x => x.Text).Returns(" foo bar ");
var performable = ((IGetsPerformableWithResult<string>) sut).GetPerformable();
var result = await performable.PerformAsAsync(actor);
Assert.That(result, Is.EqualTo("foo bar"));
}
}
Loading