diff --git a/src/ScreenshotCreator.Logic/IPlaywrightFacade.cs b/src/ScreenshotCreator.Logic/IPlaywrightFacade.cs new file mode 100644 index 0000000..d1c2fb0 --- /dev/null +++ b/src/ScreenshotCreator.Logic/IPlaywrightFacade.cs @@ -0,0 +1,8 @@ +using Microsoft.Playwright; + +namespace ScreenshotCreator.Logic; + +public interface IPlaywrightFacade : IAsyncDisposable +{ + ValueTask GetPlaywrightPageAsync(); +} \ No newline at end of file diff --git a/src/ScreenshotCreator.Logic/IPlaywrightHelper.cs b/src/ScreenshotCreator.Logic/IPlaywrightHelper.cs index 9e8c5bc..20cd511 100644 --- a/src/ScreenshotCreator.Logic/IPlaywrightHelper.cs +++ b/src/ScreenshotCreator.Logic/IPlaywrightHelper.cs @@ -1,10 +1,8 @@ -using Microsoft.Playwright; - -namespace ScreenshotCreator.Logic; +namespace ScreenshotCreator.Logic; public interface IPlaywrightHelper { - ValueTask InitializePlaywrightAsync(); + IPlaywrightFacade CreatePlaywrightFacade(); Task WaitAsync(); } \ No newline at end of file diff --git a/src/ScreenshotCreator.Logic/PlaywrightFacade.cs b/src/ScreenshotCreator.Logic/PlaywrightFacade.cs new file mode 100644 index 0000000..9a13754 --- /dev/null +++ b/src/ScreenshotCreator.Logic/PlaywrightFacade.cs @@ -0,0 +1,30 @@ +using Microsoft.Playwright; + +namespace ScreenshotCreator.Logic; + +internal class PlaywrightFacade : IPlaywrightFacade +{ + private IBrowser? _browser; + private bool _disposed; + private IPage? _page; + private IPlaywright? _playwright; + + public async ValueTask DisposeAsync() + { + if (_disposed) return; + + if (_browser != null) await _browser.DisposeAsync(); + _playwright?.Dispose(); + + _disposed = true; + } + + public async ValueTask GetPlaywrightPageAsync() + { + _playwright = await Playwright.CreateAsync(); + _browser = await _playwright.Chromium.LaunchAsync(); + _page = await _browser.NewPageAsync(new BrowserNewPageOptions { TimezoneId = Environment.GetEnvironmentVariable("TZ") }); + + return _page; + } +} \ No newline at end of file diff --git a/src/ScreenshotCreator.Logic/PlaywrightHelper.cs b/src/ScreenshotCreator.Logic/PlaywrightHelper.cs index c097116..93356e1 100644 --- a/src/ScreenshotCreator.Logic/PlaywrightHelper.cs +++ b/src/ScreenshotCreator.Logic/PlaywrightHelper.cs @@ -1,46 +1,13 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Microsoft.Playwright; +using Microsoft.Extensions.Options; namespace ScreenshotCreator.Logic; -[ExcludeFromCodeCoverage] -public sealed class PlaywrightHelper(IOptions options, ILogger logger) : IPlaywrightHelper, IAsyncDisposable +public class PlaywrightHelper(IOptions options) : IPlaywrightHelper { private readonly ScreenshotOptions _screenshotOptions = options.Value; - private IPlaywright? _playwright; - private IBrowser? _browser; - private IPage? _page; - private bool _disposed; - - public async ValueTask DisposeAsync() - { - if (_disposed) return; - - if (_browser != null) await _browser.DisposeAsync(); - _playwright?.Dispose(); - - _disposed = true; - } /// - public async ValueTask InitializePlaywrightAsync() - { - if (!_screenshotOptions.DoNotReusePlaywrightPage && _page != null) - { - logger.ReusingPlaywrightPage(); - return _page; - } - - _playwright = await Playwright.CreateAsync(); - _browser = await _playwright.Chromium.LaunchAsync(); - _page = await _browser.NewPageAsync(new BrowserNewPageOptions { TimezoneId = Environment.GetEnvironmentVariable("TZ") }); - - logger.PlaywrightInitialized(); - - return _page; - } + public IPlaywrightFacade CreatePlaywrightFacade() => new PlaywrightFacade(); /// public async Task WaitAsync() => await Task.Delay(TimeSpan.FromSeconds(_screenshotOptions.TimeBetweenHttpCallsInSeconds)); diff --git a/src/ScreenshotCreator.Logic/ScreenshotCreator.Logic.csproj b/src/ScreenshotCreator.Logic/ScreenshotCreator.Logic.csproj index cb333a2..78862cf 100644 --- a/src/ScreenshotCreator.Logic/ScreenshotCreator.Logic.csproj +++ b/src/ScreenshotCreator.Logic/ScreenshotCreator.Logic.csproj @@ -10,4 +10,7 @@ + + + \ No newline at end of file diff --git a/src/ScreenshotCreator.Logic/ScreenshotCreator.cs b/src/ScreenshotCreator.Logic/ScreenshotCreator.cs index ed3d5ac..d2e8a1c 100644 --- a/src/ScreenshotCreator.Logic/ScreenshotCreator.cs +++ b/src/ScreenshotCreator.Logic/ScreenshotCreator.cs @@ -11,7 +11,8 @@ public sealed class ScreenshotCreator(IPlaywrightHelper playwrightHelper, IOptio public async Task CreateScreenshotAsync(uint width, uint height) { - var page = await playwrightHelper.InitializePlaywrightAsync(); + await using var playwrightFacade = playwrightHelper.CreatePlaywrightFacade(); + var page = await playwrightFacade.GetPlaywrightPageAsync(); await page.SetViewportSizeAsync((int)width, (int)height); if (await NeedsLoginAsync(page)) { await LoginAsync(page); } diff --git a/src/ScreenshotCreator.Logic/ScreenshotOptions.cs b/src/ScreenshotCreator.Logic/ScreenshotOptions.cs index 74ff221..1cbcb92 100644 --- a/src/ScreenshotCreator.Logic/ScreenshotOptions.cs +++ b/src/ScreenshotCreator.Logic/ScreenshotOptions.cs @@ -38,9 +38,7 @@ public class ScreenshotOptions [Required] public bool BackgroundProcessingEnabled { get; set; } - public bool BackgroundProcessingWithTryCatch { get; set; } = false; - - public bool DoNotReusePlaywrightPage { get; set; } = false; + public bool BackgroundProcessingWithTryCatch { get; set; } public Activity? Activity { get; set; } diff --git a/tests/Tests/Integration/Api/ProgramTests.cs b/tests/Tests/Integration/Api/ProgramTests.cs index 96339e0..6d98d81 100644 --- a/tests/Tests/Integration/Api/ProgramTests.cs +++ b/tests/Tests/Integration/Api/ProgramTests.cs @@ -44,7 +44,7 @@ public async Task CreateImageNowForOpenHab() result.Should().HaveStatusCode(HttpStatusCode.OK); result.Content.Headers.ContentType.Should().NotBeNull(); result.Content.Headers.ContentType!.MediaType.Should().Be("image/png"); - (await result.Content.ReadAsByteArrayAsync()).Length.Should().BeInRange(8000, 15000); + (await result.Content.ReadAsByteArrayAsync()).Length.Should().BeInRange(7000, 15000); } [Test] diff --git a/tests/Tests/Integration/Logic/PlaywrightFacadeTests.cs b/tests/Tests/Integration/Logic/PlaywrightFacadeTests.cs new file mode 100644 index 0000000..f5e764b --- /dev/null +++ b/tests/Tests/Integration/Logic/PlaywrightFacadeTests.cs @@ -0,0 +1,37 @@ +using FluentAssertions; +using ScreenshotCreator.Logic; + +namespace Tests.Integration.Logic; + +[TestFixture] +[Category("Integration")] +public class PlaywrightFacadeTests : PlaywrightTests +{ + [Test] + public async Task GetPlaywrightPage_ShouldInitializePlaywrightAndCreateNewPage() + { + // Arrange + var testee = new PlaywrightFacade(); + + // Act + var result = await testee.GetPlaywrightPageAsync(); + + // Assert + result.Context.Browser.Should().NotBeNull(); + } + + [Test] + public async Task Dispose_ShouldDisposeUnderlyingPlaywrightSession() + { + // Arrange + var testee = new PlaywrightFacade(); + var result = await testee.GetPlaywrightPageAsync(); + + // Act & Assert + await testee.DisposeAsync(); + + // Assert + var createScreenShotAsync = async () => await result.ScreenshotAsync(); + await createScreenShotAsync.Should().ThrowAsync().WithMessage("*disposed*"); + } +} \ No newline at end of file diff --git a/tests/Tests/Integration/Logic/PlaywrightHelperTests.cs b/tests/Tests/Integration/Logic/PlaywrightHelperTests.cs new file mode 100644 index 0000000..0ac062d --- /dev/null +++ b/tests/Tests/Integration/Logic/PlaywrightHelperTests.cs @@ -0,0 +1,40 @@ +using System.Diagnostics; +using FluentAssertions; +using Microsoft.Extensions.Options; +using ScreenshotCreator.Logic; + +namespace Tests.Integration.Logic; + +[TestFixture] +[Category("Integration")] +public class PlaywrightHelperTests : PlaywrightTests +{ + [Test] + public void CreatePlaywrightFacade_ShouldCreateNewObjectEveryTime() + { + // Arrange + var testee = new PlaywrightHelper(Options.Create(new ScreenshotOptions())); + + // Act + var result1 = testee.CreatePlaywrightFacade(); + var result2 = testee.CreatePlaywrightFacade(); + + // Assert + result1.Should().NotBeSameAs(result2); + } + + [Test] + public async Task Wait_ShouldWaitTheConfiguredAmountOfSeconds() + { + // Arrange + var testee = new PlaywrightHelper(Options.Create(new ScreenshotOptions { TimeBetweenHttpCallsInSeconds = 1 })); + + // Act + var stopwatch = Stopwatch.StartNew(); + await testee.WaitAsync(); + stopwatch.Stop(); + + // Assert + stopwatch.Elapsed.Should().BeCloseTo(TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(100)); + } +} \ No newline at end of file diff --git a/tests/Tests/Unit/Logic/ScreenshotCreatorTests.cs b/tests/Tests/Unit/Logic/ScreenshotCreatorTests.cs index 12f79be..a7c3c97 100644 --- a/tests/Tests/Unit/Logic/ScreenshotCreatorTests.cs +++ b/tests/Tests/Unit/Logic/ScreenshotCreatorTests.cs @@ -17,9 +17,11 @@ public async Task CreateScreenshotWithoutLoginByConfig(string subresource) { // Arrange var screenshotOptions = new ScreenshotOptions { Url = $"https://www.mysite.com{subresource}", UrlType = UrlType.Any }; - var playwrightHelperMock = Substitute.For(); var pageMock = Substitute.For(); - playwrightHelperMock.InitializePlaywrightAsync().Returns(pageMock); + var playwrightFacadeMock = Substitute.For(); + playwrightFacadeMock.GetPlaywrightPageAsync().Returns(pageMock); + var playwrightHelperMock = Substitute.For(); + playwrightHelperMock.CreatePlaywrightFacade().Returns(playwrightFacadeMock); var testee = new ScreenshotCreator.Logic.ScreenshotCreator(playwrightHelperMock, Options.Create(screenshotOptions), NullLogger.Instance); @@ -33,8 +35,9 @@ public async Task CreateScreenshotWithoutLoginByConfig(string subresource) await pageMock.Received(1) .ScreenshotAsync(Arg.Is(options => options.Path == screenshotOptions.ScreenshotFile && options.Type == ScreenshotType.Png)); - await playwrightHelperMock.Received(1).InitializePlaywrightAsync(); + playwrightHelperMock.Received(1).CreatePlaywrightFacade(); await playwrightHelperMock.Received(1).WaitAsync(); + await playwrightFacadeMock.Received(1).GetPlaywrightPageAsync(); } [TestCase("")] @@ -43,10 +46,12 @@ public async Task CreateScreenshotWithoutLoginByPageContent(string subresource) { // Arrange var screenshotOptions = new ScreenshotOptions { Url = $"https://www.mysite.com{subresource}", UrlType = UrlType.OpenHab }; - var playwrightHelperMock = Substitute.For(); var pageMock = Substitute.For(); pageMock.GetByText("You are not allowed to view this page because of visibility restrictions.").CountAsync().Returns(0); - playwrightHelperMock.InitializePlaywrightAsync().Returns(pageMock); + var playwrightFacadeMock = Substitute.For(); + playwrightFacadeMock.GetPlaywrightPageAsync().Returns(pageMock); + var playwrightHelperMock = Substitute.For(); + playwrightHelperMock.CreatePlaywrightFacade().Returns(playwrightFacadeMock); var testee = new ScreenshotCreator.Logic.ScreenshotCreator(playwrightHelperMock, Options.Create(screenshotOptions), NullLogger.Instance); @@ -60,8 +65,9 @@ public async Task CreateScreenshotWithoutLoginByPageContent(string subresource) await pageMock.Received(1) .ScreenshotAsync(Arg.Is(options => options.Path == screenshotOptions.ScreenshotFile && options.Type == ScreenshotType.Png)); - await playwrightHelperMock.Received(1).InitializePlaywrightAsync(); + playwrightHelperMock.Received(1).CreatePlaywrightFacade(); await playwrightHelperMock.Received(2).WaitAsync(); + await playwrightFacadeMock.Received(1).GetPlaywrightPageAsync(); } [TestCase("", 3)] @@ -70,11 +76,13 @@ public async Task CreateScreenshotWithLogin(string subresource, int expectedCall { // Arrange var screenshotOptions = new ScreenshotOptions { Url = $"https://www.mysite.com{subresource}", UrlType = UrlType.OpenHab }; - var playwrightHelperMock = Substitute.For(); var pageMock = Substitute.For(); pageMock.GetByText("You are not allowed to view this page because of visibility restrictions.").CountAsync().Returns(1); pageMock.GetByText("lock_shield_fill").IsVisibleAsync().Returns(true); - playwrightHelperMock.InitializePlaywrightAsync().Returns(pageMock); + var playwrightFacadeMock = Substitute.For(); + playwrightFacadeMock.GetPlaywrightPageAsync().Returns(pageMock); + var playwrightHelperMock = Substitute.For(); + playwrightHelperMock.CreatePlaywrightFacade().Returns(playwrightFacadeMock); var testee = new ScreenshotCreator.Logic.ScreenshotCreator(playwrightHelperMock, Options.Create(screenshotOptions), Substitute.For>()); @@ -94,8 +102,9 @@ await pageMock.Received(1) .ScreenshotAsync(Arg.Is(options => options.Path == screenshotOptions.ScreenshotFile && options.Type == ScreenshotType.Png)); await pageMock.Received(2).GetByText("You are not allowed to view this page because of visibility restrictions.").CountAsync(); - await playwrightHelperMock.Received(1).InitializePlaywrightAsync(); + playwrightHelperMock.Received(1).CreatePlaywrightFacade(); await playwrightHelperMock.Received(4).WaitAsync(); + await playwrightFacadeMock.Received(1).GetPlaywrightPageAsync(); } [Test] @@ -103,12 +112,14 @@ public async Task CreateScreenshotWithLogin_ShouldOpenMenuAndClickLogin_IfLoginP { // Arrange var screenshotOptions = new ScreenshotOptions { Url = "https://www.mysite.com", UrlType = UrlType.OpenHab }; - var playwrightHelperMock = Substitute.For(); var pageMock = Substitute.For(); pageMock.GetByPlaceholder("User Name").IsVisibleAsync().Returns(false); pageMock.GetByText("You are not allowed to view this page because of visibility restrictions.").CountAsync().Returns(1); pageMock.GetByText("lock_shield_fill").IsVisibleAsync().Returns(false); - playwrightHelperMock.InitializePlaywrightAsync().Returns(pageMock); + var playwrightFacadeMock = Substitute.For(); + playwrightFacadeMock.GetPlaywrightPageAsync().Returns(pageMock); + var playwrightHelperMock = Substitute.For(); + playwrightHelperMock.CreatePlaywrightFacade().Returns(playwrightFacadeMock); var testee = new ScreenshotCreator.Logic.ScreenshotCreator(playwrightHelperMock, Options.Create(screenshotOptions), Substitute.For>()); @@ -130,12 +141,14 @@ public async Task CreateScreenshotWithLogin_ShouldNotOpenMenuAndLogin_IfLoginPag { // Arrange var screenshotOptions = new ScreenshotOptions { Url = "https://www.mysite.com", UrlType = UrlType.OpenHab }; - var playwrightHelperMock = Substitute.For(); var pageMock = Substitute.For(); pageMock.GetByPlaceholder("User Name").IsVisibleAsync().Returns(true); pageMock.GetByText("You are not allowed to view this page because of visibility restrictions.").CountAsync().Returns(1); pageMock.GetByText("lock_shield_fill").IsVisibleAsync().Returns(false); - playwrightHelperMock.InitializePlaywrightAsync().Returns(pageMock); + var playwrightFacadeMock = Substitute.For(); + playwrightFacadeMock.GetPlaywrightPageAsync().Returns(pageMock); + var playwrightHelperMock = Substitute.For(); + playwrightHelperMock.CreatePlaywrightFacade().Returns(playwrightFacadeMock); var testee = new ScreenshotCreator.Logic.ScreenshotCreator(playwrightHelperMock, Options.Create(screenshotOptions), Substitute.For>()); @@ -156,12 +169,14 @@ public async Task CreateScreenshotWithLogin_ShouldNotCreateScreenshot_IfSiteDoes { // Arrange var screenshotOptions = new ScreenshotOptions { Url = "https://www.mysite.com", UrlType = UrlType.OpenHab, AvailabilityIndicator = "Success" }; - var playwrightHelperMock = Substitute.For(); var pageMock = Substitute.For(); pageMock.GetByText("You are not allowed to view this page because of visibility restrictions.").CountAsync().Returns(1); pageMock.GetByText(screenshotOptions.AvailabilityIndicator).CountAsync().Returns(0); pageMock.GetByText("lock_shield_fill").IsVisibleAsync().Returns(true); - playwrightHelperMock.InitializePlaywrightAsync().Returns(pageMock); + var playwrightFacadeMock = Substitute.For(); + playwrightFacadeMock.GetPlaywrightPageAsync().Returns(pageMock); + var playwrightHelperMock = Substitute.For(); + playwrightHelperMock.CreatePlaywrightFacade().Returns(playwrightFacadeMock); var testee = new ScreenshotCreator.Logic.ScreenshotCreator(playwrightHelperMock, Options.Create(screenshotOptions), Substitute.For>()); @@ -180,12 +195,14 @@ public async Task CreateScreenshotWithLogin_ShouldCreateScreenshot_IfSiteIndicat { // Arrange var screenshotOptions = new ScreenshotOptions { Url = "https://www.mysite.com", UrlType = UrlType.OpenHab, AvailabilityIndicator = "Success" }; - var playwrightHelperMock = Substitute.For(); var pageMock = Substitute.For(); pageMock.GetByText("You are not allowed to view this page because of visibility restrictions.").CountAsync().Returns(1); pageMock.GetByText(screenshotOptions.AvailabilityIndicator).CountAsync().Returns(1); pageMock.GetByText("lock_shield_fill").IsVisibleAsync().Returns(true); - playwrightHelperMock.InitializePlaywrightAsync().Returns(pageMock); + var playwrightFacadeMock = Substitute.For(); + playwrightFacadeMock.GetPlaywrightPageAsync().Returns(pageMock); + var playwrightHelperMock = Substitute.For(); + playwrightHelperMock.CreatePlaywrightFacade().Returns(playwrightFacadeMock); var testee = new ScreenshotCreator.Logic.ScreenshotCreator(playwrightHelperMock, Options.Create(screenshotOptions), Substitute.For>());