Skip to content

Commit c801103

Browse files
authored
Merge pull request #283 from csf-dev/craigfowler/issue277
Resolve #277 - OpenUrl reports
2 parents 39d9b94 + 6c7c992 commit c801103

7 files changed

Lines changed: 217 additions & 9 deletions

File tree

CSF.Screenplay.Selenium/Actions/OpenUrl.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ public class OpenUrl : IPerformable, ICanReport
1515
public ValueTask PerformAsAsync(ICanPerform actor, CancellationToken cancellationToken = default)
1616
{
1717
var ability = actor.GetAbility<BrowseTheWeb>();
18-
ability.WebDriver.Url = uri.Uri.AbsoluteUri;
18+
ability.WebDriver.Url = uri.Uri.ToString();
1919
return default;
2020
}
2121

2222
/// <inheritdoc/>
2323
public ReportFragment GetReportFragment(Actor actor, IFormatsReportFragment formatter)
24-
=> formatter.Format("{Actor} opens their browser at {UriName}: {Uri}", actor.Name, uri.Name, uri.Uri.AbsoluteUri);
24+
=> formatter.Format("{Actor} opens their browser at {UriName}: {Uri}", actor.Name, uri.Name, uri.Uri.ToString());
2525

2626
/// <summary>
2727
/// Initializes a new instance of the <see cref="OpenUrl"/> class with the specified URL.

CSF.Screenplay.Selenium/NamedUri.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,32 @@ public sealed class NamedUri : IHasName
2727
/// </summary>
2828
public Uri Uri { get; }
2929

30+
/// <summary>
31+
/// Gets a copy of the current named URI, except 'rebased' using the specified base URI.
32+
/// </summary>
33+
/// <remarks>
34+
/// <para>
35+
/// If the current <see cref="Uri"/> is <see cref="UriKind.Absolute"/> then this method has not effect and the named URI which is
36+
/// returned is the unmodified current instance.
37+
/// </para>
38+
/// <para>
39+
/// If the current Uri is not absolute, then the specified base URI is prepended to the current URI, serving as a base.
40+
/// The new URI is then returned from this method. Note that this method will never result in the current instance being
41+
/// mutated, at most it will only return a copy of the current instance, which has the newly-rebased URI.
42+
/// </para>
43+
/// </remarks>
44+
/// <param name="baseUri">A new base URI</param>
45+
/// <returns>A URI which might have been rebased onto the new base URI</returns>
46+
public NamedUri RebaseTo(Uri baseUri)
47+
{
48+
if(baseUri == null) throw new ArgumentNullException(nameof(baseUri));
49+
50+
if (Uri.IsAbsoluteUri) return this;
51+
52+
var rebased = new Uri(baseUri, Uri);
53+
return new NamedUri(rebased, Name);
54+
}
55+
3056
/// <summary>
3157
/// Initializes a new instance of the <see cref="NamedUri"/> class.
3258
/// </summary>
@@ -63,6 +89,6 @@ public NamedUri(string uri, string name = null)
6389
/// </summary>
6490
/// <param name="uri">The URI to convert.</param>
6591
/// <returns>A new <see cref="NamedUri"/> instance.</returns>
66-
public static implicit operator NamedUri(string uri) => uri is null ? null : new NamedUri(new Uri(uri, UriKind.RelativeOrAbsolute));
92+
public static implicit operator NamedUri(string uri) => uri is null ? null : new NamedUri(uri);
6793
}
6894
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
using System;
3+
4+
namespace CSF.Screenplay.Selenium
5+
{
6+
/// <summary>
7+
/// Extension methods for <see cref="NamedUri"/>.
8+
/// </summary>
9+
public static class NamedUriExtensions
10+
{
11+
/// <summary>
12+
/// Gets a copy of the current named URI, except 'rebased' using the specified base URI.
13+
/// </summary>
14+
/// <remarks>
15+
/// <para>
16+
/// If the current <see cref="Uri"/> is <see cref="UriKind.Absolute"/> then this method has not effect and the named URI which is
17+
/// returned is the unmodified current instance.
18+
/// </para>
19+
/// <para>
20+
/// If the current Uri is not absolute, then the specified base URI is prepended to the current URI, serving as a base.
21+
/// The new URI is then returned from this method. Note that this method will never result in the current instance being
22+
/// mutated, at most it will only return a copy of the current instance, which has the newly-rebased URI.
23+
/// </para>
24+
/// </remarks>
25+
/// <param name="namedUri">The named URI to rebase</param>
26+
/// <param name="baseUri">A new base URI</param>
27+
/// <returns>A URI which might have been rebased onto the new base URI</returns>
28+
public static NamedUri RebaseTo(this NamedUri namedUri, string baseUri)
29+
{
30+
if(namedUri == null) throw new ArgumentNullException(nameof(namedUri));
31+
if(baseUri == null) throw new ArgumentNullException(nameof(baseUri));
32+
return namedUri.RebaseTo(new Uri(baseUri, UriKind.Absolute));
33+
}
34+
}
35+
36+
}

CSF.Screenplay.Selenium/Tasks/OpenUrlRespectingBase.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,11 @@ public class OpenUrlRespectingBase : IPerformable, ICanReport
2323
/// <inheritdoc/>
2424
public ValueTask PerformAsAsync(ICanPerform actor, CancellationToken cancellationToken = default)
2525
{
26-
if(uri.Uri.IsAbsoluteUri)
27-
return actor.PerformAsync(new OpenUrl(uri.Uri), cancellationToken);
28-
2926
if(!actor.TryGetAbility<UseABaseUri>(out var ability))
30-
return actor.PerformAsync(new OpenUrl(uri.Uri), cancellationToken);
27+
return actor.PerformAsync(new OpenUrl(uri), cancellationToken);
3128

32-
var absoluteUri = new Uri(ability.BaseUri, uri.Uri);
33-
return actor.PerformAsync(new OpenUrl(absoluteUri), cancellationToken);
29+
var rebased = uri.RebaseTo(ability.BaseUri);
30+
return actor.PerformAsync(new OpenUrl(rebased), cancellationToken);
3431
}
3532

3633
/// <inheritdoc/>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System;
2+
using CSF.Screenplay.Selenium;
3+
4+
namespace CSF.Screenplay.Selenium;
5+
6+
[TestFixture, Parallelizable]
7+
public class NamedUriTests
8+
{
9+
[Test]
10+
public void RebaseToShouldReturnARebasedUriIfItIsNotAbsolute()
11+
{
12+
var sut = new NamedUri("test.html", "name");
13+
var rebased = sut.RebaseTo("https://example.com");
14+
Assert.That(rebased.Uri.ToString(), Is.EqualTo("https://example.com/test.html"));
15+
}
16+
17+
[Test]
18+
public void RebaseToShouldNotAlterTheUriIfItIsAbsolute()
19+
{
20+
var sut = new NamedUri("https://foobar.example.com/test.html", "name");
21+
var rebased = sut.RebaseTo("https://example.com");
22+
Assert.That(rebased.Uri.ToString(), Is.EqualTo("https://foobar.example.com/test.html"));
23+
}
24+
25+
[Test]
26+
public void RebaseToShouldReturnAUriWithTheSameName()
27+
{
28+
var sut = new NamedUri("test.html", "name");
29+
var rebased = sut.RebaseTo("https://example.com");
30+
Assert.That(rebased.Name, Is.EqualTo("name"));
31+
}
32+
33+
[Test]
34+
public void RebaseToShouldThrowIfUriIsNull()
35+
{
36+
var sut = new NamedUri("test.html", "name");
37+
Assert.That(() => sut.RebaseTo(null), Throws.ArgumentNullException);
38+
}
39+
40+
[Test]
41+
public void RebaseToShouldThrowIfUriIsNullString()
42+
{
43+
var sut = new NamedUri("test.html", "name");
44+
Assert.That(() => sut.RebaseTo((string?) null), Throws.ArgumentNullException);
45+
}
46+
47+
[Test]
48+
public void ImplicitCastFromUriShouldCreateANamedUri()
49+
{
50+
NamedUri uri = new Uri("https://example.com/foo.html");
51+
Assert.That(uri.Uri.ToString(), Is.EqualTo("https://example.com/foo.html"));
52+
}
53+
54+
[Test]
55+
public void ImplicitCastFromStringShouldCreateANamedUri()
56+
{
57+
NamedUri uri = "https://example.com/foo.html";
58+
Assert.That(uri.Uri.ToString(), Is.EqualTo("https://example.com/foo.html"));
59+
}
60+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using System;
2+
using CSF.Extensions.WebDriver;
3+
using CSF.Extensions.WebDriver.Factories;
4+
using CSF.Screenplay.Actors;
5+
using CSF.Screenplay.Reporting;
6+
using CSF.Screenplay.Selenium.Actions;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using Moq;
9+
using OpenQA.Selenium;
10+
11+
namespace CSF.Screenplay.Selenium.Tasks;
12+
13+
[TestFixture, Parallelizable]
14+
public class OpenUrlRespectingBaseTests
15+
{
16+
[Test, AutoMoqData]
17+
public async Task TheActionCreatedByThisTaskShouldContainTheCorrectReport(IWebDriver driver, DriverOptions options)
18+
{
19+
var actor = new Actor("Anthony", Guid.NewGuid());
20+
IPerformable? performable = null;
21+
22+
void OnPerform(object? sender, PerformableEventArgs ev) => performable = (IPerformable)ev.Performable;
23+
24+
var namedUri = new NamedUri("test.html", "the test page");
25+
var baseUri = "https://example.com";
26+
actor.IsAbleTo(new UseABaseUri(new Uri(baseUri, UriKind.Absolute)));
27+
actor.IsAbleTo(new BrowseTheWeb(Mock.Of<IGetsWebDriver>(x => x.GetDefaultWebDriver(It.IsAny<Action<DriverOptions>>()) == new WebDriverAndOptions(driver, options))));
28+
var sut = new OpenUrlRespectingBase(namedUri);
29+
var valueFormatterProvider = new ValueFormatterProvider(new ServiceCollection().AddTransient<ToStringFormatter>().BuildServiceProvider(),
30+
new ValueFormatterRegistry { typeof(ToStringFormatter) });
31+
32+
var formatter = new ReportFragmentFormatter(new ReportFormatCreator(), valueFormatterProvider);
33+
34+
actor.EndPerformable += OnPerform;
35+
try
36+
{
37+
await sut.PerformAsAsync(actor);
38+
39+
Assert.Multiple(() =>
40+
{
41+
Assert.That(performable, Is.InstanceOf<OpenUrl>(), "Performable is correct type");
42+
Assert.That(((OpenUrl) performable!).GetReportFragment(actor, formatter).ToString(),
43+
Is.EqualTo("Anthony opens their browser at the test page: https://example.com/test.html"),
44+
"The report is correct");
45+
});
46+
}
47+
finally
48+
{
49+
actor.EndPerformable -= OnPerform;
50+
}
51+
}
52+
53+
[Test, AutoMoqData]
54+
public async Task TheActionCreatedByThisTaskShouldContainTheCorrectReportWhenTheActorDoesNotHaveABaseUrl(IWebDriver driver, DriverOptions options)
55+
{
56+
var actor = new Actor("Anthony", Guid.NewGuid());
57+
IPerformable? performable = null;
58+
59+
void OnPerform(object? sender, PerformableEventArgs ev) => performable = (IPerformable)ev.Performable;
60+
61+
var namedUri = new NamedUri("test.html", "the test page");
62+
actor.IsAbleTo(new BrowseTheWeb(Mock.Of<IGetsWebDriver>(x => x.GetDefaultWebDriver(It.IsAny<Action<DriverOptions>>()) == new WebDriverAndOptions(driver, options))));
63+
var sut = new OpenUrlRespectingBase(namedUri);
64+
var valueFormatterProvider = new ValueFormatterProvider(new ServiceCollection().AddTransient<ToStringFormatter>().BuildServiceProvider(),
65+
new ValueFormatterRegistry { typeof(ToStringFormatter) });
66+
67+
var formatter = new ReportFragmentFormatter(new ReportFormatCreator(), valueFormatterProvider);
68+
69+
actor.EndPerformable += OnPerform;
70+
try
71+
{
72+
await sut.PerformAsAsync(actor);
73+
74+
Assert.Multiple(() =>
75+
{
76+
Assert.That(performable, Is.InstanceOf<OpenUrl>(), "Performable is correct type");
77+
Assert.That(((OpenUrl) performable!).GetReportFragment(actor, formatter).ToString(),
78+
Is.EqualTo("Anthony opens their browser at the test page: test.html"),
79+
"The report is correct");
80+
});
81+
}
82+
finally
83+
{
84+
actor.EndPerformable -= OnPerform;
85+
}
86+
}
87+
}

Tests/CSF.Screenplay.Tests/Reporting/ReportFormatCreatorTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public class ReportFormatCreatorTests
1515
[TestCase("}", "}")]
1616
[TestCase("{foo} {bar", "{0} {bar")]
1717
[TestCase("{{{{foo}}}}", "{{{{foo}}}}")]
18+
[TestCase("{Actor} opens their browser at {UriName}: {Uri}", "{0} opens their browser at {1}: {2}")]
1819
public void GetReportFormatShouldReturnTheCorrectFormattedTemplate(string original, string expected)
1920
{
2021
var sut = new ReportFormatCreator();
@@ -37,6 +38,7 @@ public void GetReportFormatShouldReturnTheCorrectFormattedTemplate(string origin
3738
[TestCase("}", "", "", "")]
3839
[TestCase("{foo} {bar", "foo", "", "")]
3940
[TestCase("{{{{foo}}}}", "", "", "")]
41+
[TestCase("{Actor} opens their browser at {UriName}: {Uri}", "Actor", "UriName", "Uri")]
4042
public void GetReportFormatShouldReturnTheCorrectObjectNames(string template, string name1, string name2, string name3)
4143
{
4244
var expected = new[] { name1, name2, name3 }.Where(x => x.Length > 0).ToList();

0 commit comments

Comments
 (0)