Skip to content

Commit

Permalink
Add WithSeleniumReporting extension method (#33)
Browse files Browse the repository at this point in the history
Closes #32
  • Loading branch information
Galad committed May 9, 2018
1 parent 5eb25ff commit 414580b
Show file tree
Hide file tree
Showing 6 changed files with 366 additions and 28 deletions.
32 changes: 11 additions & 21 deletions src/ToDoList.Specifications/ToDoListSteps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,14 @@
using Tranquire;
using Tranquire.Reporting;
using Tranquire.Selenium;
using Tranquire.Selenium.Extensions;

namespace ToDoList.Specifications
{
[Binding]
public class ToDoListSteps : StepsBase
{
private readonly StringBuilder _reportingStringBuilder;
private IObserver<ScreenshotInfo> _observer;


public ToDoListSteps(ScenarioContext context) : base(context)
{
_reportingStringBuilder = new StringBuilder();
Expand All @@ -44,25 +42,17 @@ public void Before()
if (IsLiveUnitTesting)
{
delay = TimeSpan.Zero;
}
var debugObserver = new DebugObserver();
var xmlDocumentReporting = new XmlDocumentObserver();
var reportingObserver = new CompositeObserver<ActionNotification>(
xmlDocumentReporting,
new RenderedReportingObserver(debugObserver, RenderedReportingObserver.DefaultRenderer)
);
Context.Set(xmlDocumentReporting);
_observer = new CompositeObserver<ScreenshotInfo>(
new SaveScreenshotsToFileOnComplete(Path.Combine(GetTestDirectory(), "Screenshots")),
new ScreenshotInfoToActionAttachmentObserverAdapter(xmlDocumentReporting),
new RenderedScreenshotInfoObserver(debugObserver)
);
}
var actor = new Actor("John")
.WithReporting(reportingObserver)
.TakeScreenshots(screenshotName, _observer)
.WithSeleniumReporting(
Path.Combine(GetTestDirectory(), "Screenshots"),
screenshotName,
out var seleniumReporter,
new DebugObserver())
.HighlightTargets()
.SlowSelenium(delay)
.CanUse(WebBrowser.With(driver));
Context.Set(seleniumReporter);
Context.Set(actor);
Context.Set(driver);
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);
Expand All @@ -81,11 +71,11 @@ private static string GetTestDirectory()
[AfterScenario]
public void After()
{
_observer.OnCompleted();
Context.Get<ChromeDriver>().Dispose();
Debug.WriteLine(_reportingStringBuilder.ToString());
var xmlDocument = Context.Get<XmlDocumentObserver>();
Debug.WriteLine(xmlDocument.GetXmlDocument().ToString());
var seleniumReporter = Context.Get<ISeleniumReporter>();
seleniumReporter.SaveScreenshots();
Debug.WriteLine(seleniumReporter.GetXmlDocument().ToString());
}

[Given(@"I have an empty to-do list")]
Expand Down
107 changes: 102 additions & 5 deletions src/Tranquire.Selenium.Tests/ActorExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using FluentAssertions;
using AutoFixture.Idioms;
using AutoFixture.Idioms;
using AutoFixture.Xunit2;
using Tranquire.Tests;
using Xunit;
using System.Linq;
using FluentAssertions;
using System;
using System.Linq;
using Tranquire.Reporting;
using Tranquire.Selenium.Extensions;
using Tranquire.Tests;
using Xunit;

namespace Tranquire.Selenium.Tests
{
Expand Down Expand Up @@ -142,5 +143,101 @@ public void Sut_VerifyActorDecoratorBehavior(ActorDecoratorExtensionAssertion as
//assert
actual.Should().BeOfType<SlowSelenium>().Which.Should().BeEquivalentTo(expected);
}

[Theory, DomainAutoData]
public void WithSeleniumReporting_ShouldReturnCorrectValue(
[Modest]Actor actor,
IActor iactor,
string screenshotDirectory,
string screenshotName,
IObserver<string> observers)
{
TestWithSeleniumReporting(actor, iactor, screenshotDirectory, screenshotName, observers);
}

[Theory, DomainAutoData]
public void WithSeleniumReporting_WithCanNotify_ShouldReturnCorrectValue(
[Modest]Actor actor,
IActor iactor,
string screenshotDirectory,
string screenshotName,
IObserver<string> observers,
ICanNotify canNotify)
{
TestWithSeleniumReporting(actor, iactor, screenshotDirectory, screenshotName, observers, canNotify);
}

private static void TestWithSeleniumReporting(
Actor actor,
IActor iactor,
string screenshotDirectory,
string screenshotName,
IObserver<string> observers,
ICanNotify canNotify = null)
{
// arrange
// act
Actor actual;
ISeleniumReporter actualSeleniumReporter;
if (canNotify == null)
{
actual = ActorExtensions.WithSeleniumReporting(
actor,
screenshotDirectory,
screenshotName,
out actualSeleniumReporter,
observers
);
canNotify = new CompositeCanNotify();
}
else
{
actual = ActorExtensions.WithSeleniumReporting(
actor,
screenshotDirectory,
screenshotName,
canNotify,
out actualSeleniumReporter,
observers
);
}
// assert
var xmlDocumentObserver = new XmlDocumentObserver();
var takeScreenshot = actual.InnerActorBuilder(iactor).Should().BeOfType<TakeScreenshot>().Which;
var expectedTakeScreenshot = ActorExtensions.TakeScreenshots(
actor,
screenshotName,
new CompositeObserver<ScreenshotInfo>(
new ScreenshotInfoToActionAttachmentObserverAdapter(xmlDocumentObserver),
new RenderedScreenshotInfoObserver(new CompositeObserver<string>(observers)),
new SaveScreenshotsToFileOnComplete(screenshotDirectory)
)
)
.InnerActorBuilder(iactor) as TakeScreenshot;
takeScreenshot.Should().BeEquivalentTo(expectedTakeScreenshot, o => o.Excluding(a => a.Actor)
.Excluding(a => a.NextScreenshotName)
.RespectingRuntimeTypes());
var actualScreenshotNames = Enumerable.Range(0, 10).Select(_ => takeScreenshot.NextScreenshotName());
var expectedScreenshotNames = Enumerable.Range(0, 10).Select(_ => expectedTakeScreenshot.NextScreenshotName());
actualScreenshotNames.Should().BeEquivalentTo(expectedScreenshotNames);

var reportingActor = takeScreenshot.Actor.Should().BeOfType<ReportingActor>().Which;
var expectedReportingActor = actor.WithReporting(
new CompositeObserver<ActionNotification>(
xmlDocumentObserver,
new RenderedReportingObserver(
new CompositeObserver<string>(observers),
RenderedReportingObserver.DefaultRenderer
)
),
canNotify
)
.InnerActorBuilder(iactor) as ReportingActor;
reportingActor.Should().BeEquivalentTo(expectedReportingActor, o => o.Excluding(a => a.Actor)
.RespectingRuntimeTypes());

var expectedSeleniumReporter = new SeleniumReporter(xmlDocumentObserver, new SaveScreenshotsToFileOnComplete(screenshotDirectory));
actualSeleniumReporter.Should().BeEquivalentTo(expectedSeleniumReporter);
}
}
}
82 changes: 82 additions & 0 deletions src/Tranquire.Selenium.Tests/Extensions/SeleniumReporterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using AutoFixture.Idioms;
using FluentAssertions;
using Moq;
using System;
using Tranquire.Reporting;
using Tranquire.Selenium.Extensions;
using Tranquire.Tests;
using Xunit;

namespace Tranquire.Selenium.Tests.Extensions
{
public class SeleniumReporterTests
{
[Theory, DomainAutoData]
public void Sut_VerifyGuardClause(GuardClauseAssertion assertion)
{
assertion.Verify(typeof(SeleniumReporter));
}

[Theory, DomainAutoData]
public void Sut_VerifyConstructorInitializedMembers(ConstructorInitializedMemberAssertion assertion)
{
assertion.Verify<SeleniumReporter>(r => r.ScreenshotInfoObserver);
assertion.Verify<SeleniumReporter>(r => r.XmlDocumentObserver);
}

[Theory, DomainAutoData]
public void Sut_IsSeleniumReporter(SeleniumReporter sut)
{
sut.Should().BeAssignableTo<ISeleniumReporter>();
}

[Theory, DomainAutoData]
public void GetXmlDocument_ShouldReturnCorrectValue(
SeleniumReporter sut,
XmlDocumentObserver expectedDocumentObserver,
INamed named)
{
// arrange
SetupObserver(expectedDocumentObserver, named);
SetupObserver(sut.XmlDocumentObserver, named);
var expected = expectedDocumentObserver.GetXmlDocument();
// act
var actual = sut.GetXmlDocument();
// assert
actual.ToString().Should().Be(expected.ToString());
}

[Theory, DomainAutoData]
public void GetHtmlDocument_ShouldReturnCorrectValue(
SeleniumReporter sut,
XmlDocumentObserver expectedDocumentObserver,
INamed named)
{
// arrange
SetupObserver(expectedDocumentObserver, named);
SetupObserver(sut.XmlDocumentObserver, named);
var expected = expectedDocumentObserver.GetHtmlDocument();
// act
var actual = sut.GetHtmlDocument();
// assert
actual.Should().Be(expected);
}

private static void SetupObserver(XmlDocumentObserver expectedDocumentObserver, INamed named)
{
expectedDocumentObserver.OnNext(new ActionNotification(named, 0, new BeforeActionNotificationContent(DateTimeOffset.MinValue, CommandType.Action)));
expectedDocumentObserver.OnNext(new ActionNotification(named, 0, new AfterActionNotificationContent(TimeSpan.FromSeconds(1))));
}

[Theory, DomainAutoData]
public void SaveScreenshots_ShouldCallOnCompleted(
SeleniumReporter sut)
{
// arrange
// act
sut.SaveScreenshots();
// assert
Mock.Get(sut.ScreenshotInfoObserver).Verify(s => s.OnCompleted());
}
}
}
98 changes: 96 additions & 2 deletions src/Tranquire.Selenium/ActorExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Threading;
using Tranquire.Reporting;
using Tranquire.Selenium.Extensions;

namespace Tranquire.Selenium
Expand Down Expand Up @@ -89,6 +89,100 @@ string nextScreenshotName()
return string.Format(CultureInfo.InvariantCulture, screenshotFormat, Interlocked.Increment(ref id));
}
return new Actor(actor.Name, actor.Abilities, a => new TakeScreenshot(actor.InnerActorBuilder(a), nextScreenshotName, screenshotInfoObserver));
}
}

/// <summary>
/// Configure the actor for Selenium reporting and returns an object that allows to retrieve the report. This is similar to using:
/// - <see cref="Tranquire.ActorExtensions.WithReporting(Actor, IObserver{Reporting.ActionNotification})"/>
/// - <see cref="TakeScreenshots(Actor, string, IObserver{ScreenshotInfo})"/>
/// </summary>
/// <param name="actor">The actor</param>
/// <param name="screenshotDirectory">The directory where the screenshots are saved</param>
/// <param name="screenshotNameOrFormat">A string containing a format for 0 or 1 format item, used to generate the screenshot names. If no format item is provided, the default format is "<paramref name="screenshotNameOrFormat"/>_{0:00}".</param>
/// <param name="seleniumReporter">A <see cref="ISeleniumReporter"/> object that can be used to save the screenshots and retrieve the report at the end of the run</param>
/// <param name="textOutputObservers">Additional observer that can be used to display the text report</param>
/// <returns></returns>
public static Actor WithSeleniumReporting(
this Actor actor,
string screenshotDirectory,
string screenshotNameOrFormat,
out ISeleniumReporter seleniumReporter,
params IObserver<string>[] textOutputObservers)
{
return WithSeleniumReportingInternal(
actor,
screenshotDirectory,
screenshotNameOrFormat,
out seleniumReporter,
textOutputObservers,
(a, o) => a.WithReporting(o));
}

/// <summary>
/// Configure the actor for Selenium reporting and returns an object that allows to retrieve the report. This is similar to using:
/// - <see cref="Tranquire.ActorExtensions.WithReporting(Actor, IObserver{Reporting.ActionNotification}, ICanNotify)"/>
/// - <see cref="TakeScreenshots(Actor, string, IObserver{ScreenshotInfo})"/>
/// </summary>
/// <param name="actor">The actor</param>
/// <param name="screenshotDirectory">The directory where the screenshots are saved</param>
/// <param name="screenshotNameOrFormat">A string containing a format for 0 or 1 format item, used to generate the screenshot names. If no format item is provided, the default format is "<paramref name="screenshotNameOrFormat"/>_{0:00}".</param>
/// <param name="canNotify">A <see cref="ICanNotify"/> instance that allows to control what actions and questions can send a notification</param>
/// <param name="seleniumReporter">A <see cref="ISeleniumReporter"/> object that can be used to save the screenshots and retrieve the report at the end of the run</param>
/// <param name="textOutputObservers">Additional observer that can be used to display the text report</param>
/// <returns></returns>
public static Actor WithSeleniumReporting(
this Actor actor,
string screenshotDirectory,
string screenshotNameOrFormat,
ICanNotify canNotify,
out ISeleniumReporter seleniumReporter,
params IObserver<string>[] textOutputObservers)
{
return WithSeleniumReportingInternal(
actor,
screenshotDirectory,
screenshotNameOrFormat,
out seleniumReporter,
textOutputObservers,
(a, o) => a.WithReporting(o, canNotify));
}

private static Actor WithSeleniumReportingInternal(
Actor actor,
string screenshotDirectory,
string screenshotNameOrFormat,
out ISeleniumReporter seleniumReporter,
IObserver<string>[] textOutputObservers,
Func<Actor, IObserver<ActionNotification>, Actor> applyWithReporting)
{
if (string.IsNullOrEmpty(screenshotDirectory))
{
throw new ArgumentNullException(nameof(screenshotDirectory));
}
if (string.IsNullOrEmpty(screenshotNameOrFormat))
{
throw new ArgumentNullException(nameof(screenshotNameOrFormat));
}
if (textOutputObservers == null)
{
throw new ArgumentNullException(nameof(textOutputObservers));
}

var xmlDocumentObserver = new XmlDocumentObserver();
var textObservers = new CompositeObserver<string>(textOutputObservers);
var reportingObserver = new CompositeObserver<ActionNotification>(
xmlDocumentObserver,
new RenderedReportingObserver(textObservers, RenderedReportingObserver.DefaultRenderer)
);
var saveScreenshotObserver = new SaveScreenshotsToFileOnComplete(screenshotDirectory);
var screenshotObserver = new CompositeObserver<ScreenshotInfo>(
saveScreenshotObserver,
new ScreenshotInfoToActionAttachmentObserverAdapter(xmlDocumentObserver),
new RenderedScreenshotInfoObserver(textObservers)
);
seleniumReporter = new SeleniumReporter(xmlDocumentObserver, saveScreenshotObserver);
return applyWithReporting(actor, reportingObserver)
.TakeScreenshots(screenshotNameOrFormat, screenshotObserver);
}
}
}
Loading

0 comments on commit 414580b

Please sign in to comment.