diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a98dd77..d998146 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -25,7 +25,7 @@ jobs: run: 'src/ScreenshotCreator.Api/bin/Debug/net7.0/playwright.ps1 install chromium' shell: pwsh - name: Test and collect coverage - run: dotnet test --no-build /p:CollectCoverage=true /p:CoverletOutputFormat=opencover + run: dotnet test --no-build /p:CollectCoverage=true /p:CoverletOutputFormat=opencover '/p:ExcludeByFile=\"**/Log.Prefix.cs\"' - name: End Sonar scan run: dotnet sonarscanner end /d:sonar.login=${{ secrets.SONAR_TOKEN }} - name: Set up QEMU diff --git a/src/ScreenshotCreator.Api/HeaderDictionaryExtensions.cs b/src/ScreenshotCreator.Api/HeaderDictionaryExtensions.cs index ec080f8..356791f 100644 --- a/src/ScreenshotCreator.Api/HeaderDictionaryExtensions.cs +++ b/src/ScreenshotCreator.Api/HeaderDictionaryExtensions.cs @@ -13,7 +13,7 @@ public static void AddWaveshareInstructions(this IHeaderDictionary headers, headers.Add("waveshare-last-modified-local-time", GetLastModifiedAsLocalTime(screenshotFile, getLastWriteTimeUtc ?? File.GetLastWriteTimeUtc, localTimeZoneId ?? TimeZoneInfo.Local.Id)); headers.Add("waveshare-sleep-between-updates", screenshotOptions.CalculateSleepBetweenUpdates()); - headers.Add("waveshare-update-screen", screenshotOptions.Activity.DisplayShouldBeActive() ? true.ToString() : false.ToString()); + headers.Add("waveshare-update-screen", screenshotOptions.Activity.DisplayShouldBeActive().ToString()); } private static string GetLastModifiedAsLocalTime(string file, Func getLastWriteTimeUtc, string localTimeZoneId) => diff --git a/src/ScreenshotCreator.Api/Log.Prefix.cs b/src/ScreenshotCreator.Api/Log.Prefix.cs new file mode 100644 index 0000000..1416155 --- /dev/null +++ b/src/ScreenshotCreator.Api/Log.Prefix.cs @@ -0,0 +1,6 @@ +namespace ScreenshotCreator.Api; + +public static partial class Log +{ + private const string Prefix = nameof(Api); +} \ No newline at end of file diff --git a/src/ScreenshotCreator.Api/Log.cs b/src/ScreenshotCreator.Api/Log.cs index 840c5b8..2013b91 100644 --- a/src/ScreenshotCreator.Api/Log.cs +++ b/src/ScreenshotCreator.Api/Log.cs @@ -2,8 +2,6 @@ public static partial class Log { - private const string Prefix = nameof(Api); - [LoggerMessage(EventId = 0, EventName = Prefix + nameof(BackgroundServiceTriggered), Level = LogLevel.Information, diff --git a/src/ScreenshotCreator.Api/Program.cs b/src/ScreenshotCreator.Api/Program.cs index bc695a5..5de60bd 100644 --- a/src/ScreenshotCreator.Api/Program.cs +++ b/src/ScreenshotCreator.Api/Program.cs @@ -15,6 +15,7 @@ .ValidateDataAnnotations() .ValidateOnStart(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHostedService(); diff --git a/src/ScreenshotCreator.Logic/IPlaywrightHelper.cs b/src/ScreenshotCreator.Logic/IPlaywrightHelper.cs new file mode 100644 index 0000000..9e8c5bc --- /dev/null +++ b/src/ScreenshotCreator.Logic/IPlaywrightHelper.cs @@ -0,0 +1,10 @@ +using Microsoft.Playwright; + +namespace ScreenshotCreator.Logic; + +public interface IPlaywrightHelper +{ + ValueTask InitializePlaywrightAsync(); + + Task WaitAsync(); +} \ No newline at end of file diff --git a/src/ScreenshotCreator.Logic/IScreenshotCreator.cs b/src/ScreenshotCreator.Logic/IScreenshotCreator.cs index 74770f2..e307256 100644 --- a/src/ScreenshotCreator.Logic/IScreenshotCreator.cs +++ b/src/ScreenshotCreator.Logic/IScreenshotCreator.cs @@ -1,7 +1,6 @@ namespace ScreenshotCreator.Logic; public interface IScreenshotCreator - : IAsyncDisposable { Task CreateScreenshotAsync(uint width, uint height); } \ No newline at end of file diff --git a/src/ScreenshotCreator.Logic/ImageProcessor.cs b/src/ScreenshotCreator.Logic/ImageProcessor.cs index 91b7344..90e0d35 100644 --- a/src/ScreenshotCreator.Logic/ImageProcessor.cs +++ b/src/ScreenshotCreator.Logic/ImageProcessor.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Diagnostics.CodeAnalysis; using System.Net.Mime; using ImageMagick; using Microsoft.Extensions.Logging; @@ -25,11 +26,14 @@ public async Task ProcessAsync(string screenshotFile, bool bla var bytes = asWaveshareBytes ? ToWaveshareBytes(image) : image.ToByteArray(); - var contentType = asWaveshareBytes ? MediaTypeNames.Application.Octet : image.FormatInfo?.MimeType; + var contentType = asWaveshareBytes ? MediaTypeNames.Application.Octet : GetImageMimeType(image); return new ProcessingResult(bytes, contentType); } + [ExcludeFromCodeCoverage(Justification = "Didn't find a of setting FormatInfo to null")] + private static string GetImageMimeType(MagickImage image) => image.FormatInfo?.MimeType ?? "NoClue"; + private byte[] ToWaveshareBytes(MagickImage image) { if (image.Width != 800 || image.Height != 480) diff --git a/src/ScreenshotCreator.Logic/Log.Prefix.cs b/src/ScreenshotCreator.Logic/Log.Prefix.cs new file mode 100644 index 0000000..fe5c681 --- /dev/null +++ b/src/ScreenshotCreator.Logic/Log.Prefix.cs @@ -0,0 +1,6 @@ +namespace ScreenshotCreator.Logic; + +public static partial class Log +{ + private const string Prefix = nameof(Logic); +} \ No newline at end of file diff --git a/src/ScreenshotCreator.Logic/Log.cs b/src/ScreenshotCreator.Logic/Log.cs index 97b7525..cb78f5d 100644 --- a/src/ScreenshotCreator.Logic/Log.cs +++ b/src/ScreenshotCreator.Logic/Log.cs @@ -4,8 +4,6 @@ namespace ScreenshotCreator.Logic; public static partial class Log { - private const string Prefix = nameof(Logic); - [LoggerMessage(EventId = 0, EventName = Prefix + nameof(PlaywrightInitialized), Level = LogLevel.Information, diff --git a/src/ScreenshotCreator.Logic/PlaywrightHelper.cs b/src/ScreenshotCreator.Logic/PlaywrightHelper.cs new file mode 100644 index 0000000..ef9679d --- /dev/null +++ b/src/ScreenshotCreator.Logic/PlaywrightHelper.cs @@ -0,0 +1,54 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.Playwright; + +namespace ScreenshotCreator.Logic; + +[ExcludeFromCodeCoverage] +public sealed class PlaywrightHelper : IPlaywrightHelper, IAsyncDisposable +{ + private readonly ILogger _logger; + private readonly ScreenshotOptions _screenshotOptions; + private IPlaywright? _playwright; + private IBrowser? _browser; + private IPage? _page; + private bool _disposed; + + public PlaywrightHelper(IOptions options, ILogger logger) + { + _logger = logger; + _screenshotOptions = options.Value; + } + + public async ValueTask DisposeAsync() + { + if (_disposed) return; + + if (_browser != null) await _browser.DisposeAsync(); + _playwright?.Dispose(); + + _disposed = true; + } + + /// + public async ValueTask InitializePlaywrightAsync() + { + if (_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 async Task WaitAsync() => await Task.Delay(TimeSpan.FromSeconds(_screenshotOptions.TimeBetweenHttpCallsInSeconds)); +} \ No newline at end of file diff --git a/src/ScreenshotCreator.Logic/ScreenshotCreator.cs b/src/ScreenshotCreator.Logic/ScreenshotCreator.cs index 64576f8..af02bd8 100644 --- a/src/ScreenshotCreator.Logic/ScreenshotCreator.cs +++ b/src/ScreenshotCreator.Logic/ScreenshotCreator.cs @@ -1,5 +1,4 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Playwright; @@ -7,52 +6,35 @@ namespace ScreenshotCreator.Logic; public sealed class ScreenshotCreator : IScreenshotCreator { + private readonly IPlaywrightHelper _playwrightHelper; private readonly ILogger _logger; private readonly ScreenshotOptions _screenshotOptions; - private IPage? _page; - private bool _disposed; - private IPlaywright? _playwright; - private IBrowser? _browser; - public ScreenshotCreator(IOptions options, ILogger logger) + public ScreenshotCreator(IPlaywrightHelper playwrightHelper, IOptions options, ILogger logger) { + _playwrightHelper = playwrightHelper; _logger = logger; _screenshotOptions = options.Value; } public async Task CreateScreenshotAsync(uint width, uint height) { - if (_page == null) - _page = await InitializePlaywrightAsync(); - else - _logger.ReusingPlaywrightPage(); + var page = await _playwrightHelper.InitializePlaywrightAsync(); - await _page.SetViewportSizeAsync((int)width, (int)height); - if (await NeedsLoginAsync(_page)) { await LoginAsync(_page); } + await page.SetViewportSizeAsync((int)width, (int)height); + if (await NeedsLoginAsync(page)) await LoginAsync(page); - await NavigateToUrlAsync(_page); + await NavigateToUrlAsync(page); - await _page.ScreenshotAsync(new PageScreenshotOptions { Path = _screenshotOptions.ScreenshotFileName, Type = ScreenshotType.Png }); + await page.ScreenshotAsync(new PageScreenshotOptions { Path = _screenshotOptions.ScreenshotFileName, Type = ScreenshotType.Png }); _logger.ScreenshotCreated(); } - /// - [ExcludeFromCodeCoverage(Justification = "Default Dispose pattern")] - public async ValueTask DisposeAsync() - { - if (_disposed) return; - - if (_browser != null) await _browser.DisposeAsync(); - _playwright?.Dispose(); - - _disposed = true; - } - private async Task NavigateToUrlAsync(IPage page) { await page.GotoAsync(_screenshotOptions.Url); - await WaitAsync(); + await _playwrightHelper.WaitAsync(); } private async Task LoginAsync(IPage page) @@ -60,11 +42,11 @@ private async Task LoginAsync(IPage page) _logger.LoggingIn(); await page.GotoAsync(GetBaseUrl()); - await WaitAsync(); + await _playwrightHelper.WaitAsync(); await page.GetByPlaceholder("User Name").FillAsync(_screenshotOptions.Username); await page.GetByPlaceholder("Password", new PageGetByPlaceholderOptions { Exact = true }).FillAsync(_screenshotOptions.Password); await page.GetByRole(AriaRole.Button).ClickAsync(); - await WaitAsync(); + await _playwrightHelper.WaitAsync(); } private async Task NeedsLoginAsync(IPage page) @@ -83,18 +65,5 @@ private async Task NeedsLoginAsync(IPage page) return needsLogin; } - private async Task InitializePlaywrightAsync() - { - _playwright = await Playwright.CreateAsync(); - _browser = await _playwright.Chromium.LaunchAsync(); - var page = await _browser.NewPageAsync(new BrowserNewPageOptions { TimezoneId = Environment.GetEnvironmentVariable("TZ") }); - - _logger.PlaywrightInitialized(); - - return page; - } - - private async Task WaitAsync() => await Task.Delay(TimeSpan.FromSeconds(_screenshotOptions.TimeBetweenHttpCallsInSeconds)); - private string GetBaseUrl() => new Uri(_screenshotOptions.Url).GetLeftPart(UriPartial.Authority); } \ No newline at end of file diff --git a/stryker-config.json b/stryker-config.json index aa3a38a..9eb3b8c 100644 --- a/stryker-config.json +++ b/stryker-config.json @@ -10,6 +10,7 @@ "project-info": { "name": "github.com/mu88/ScreenshotCreator" }, + "test-case-filter": "TestCategory=Unit", "ignore-methods": [ "_logger.*" ] diff --git a/tests/Tests/Performance/Logic/ImageProcessorTests.cs b/tests/Tests/Performance/Logic/ImageProcessorTests.cs index e2707c1..1b184d9 100644 --- a/tests/Tests/Performance/Logic/ImageProcessorTests.cs +++ b/tests/Tests/Performance/Logic/ImageProcessorTests.cs @@ -28,5 +28,5 @@ public void ProcessImage_ShouldNotConsumeTooMuchMemory_WhenCreatingBlackWhiteIma public class ImageProcessorBenchmarks { [Benchmark] - public async Task ProcessAsync() => await new ImageProcessor(new Mock>().Object).ProcessAsync("testData/Screenshot.png", true, true); + public static async Task ProcessAsync() => await new ImageProcessor(new Mock>().Object).ProcessAsync("testData/Screenshot.png", true, true); } \ No newline at end of file diff --git a/tests/Tests/Unit/Api/HeaderDictionaryExtensionsTests.cs b/tests/Tests/Unit/Api/HeaderDictionaryExtensionsTests.cs index c9a33ee..46e4d25 100644 --- a/tests/Tests/Unit/Api/HeaderDictionaryExtensionsTests.cs +++ b/tests/Tests/Unit/Api/HeaderDictionaryExtensionsTests.cs @@ -1,6 +1,7 @@ using FluentAssertions; using FluentAssertions.Extensions; using Microsoft.AspNetCore.Http; +using Moq; using ScreenshotCreator.Api; using ScreenshotCreator.Logic; @@ -10,21 +11,40 @@ namespace Tests.Unit.Api; [Category("Unit")] public class HeaderDictionaryExtensionsTests { - [Test] - public void AddWaveshareInstructions() + [TestCase(true, + "", + "", + "15:00", + true, + "1953")] + [TestCase(false, + "16:00", + "13:00", + "15:00", + false, + "15")] + public void AddWaveshareInstructions(bool isNull, + string activeFrom, + string activeTo, + string now, + bool expectedDisplayState, + string expectedSleep) { // Arrange Environment.SetEnvironmentVariable("TZ", null); + var timeProviderMock = new Mock(); + timeProviderMock.Setup(provider => provider.GetUtcNow()).Returns(12.April(1953).Add(TimeOnly.Parse(now).ToTimeSpan())); + var activity = isNull ? null : new Activity(TimeOnly.Parse(activeFrom), TimeOnly.Parse(activeTo), 15u); var getLastWriteTimeUtc = (string file) => 12.April(2023).At(19, 53).AsUtc(); - var screenshotOptions = new ScreenshotOptions { RefreshIntervalInSeconds = 1953 }; + var screenshotOptions = new ScreenshotOptions { RefreshIntervalInSeconds = 1953, Activity = activity }; var testee = new HeaderDictionary(); // Act testee.AddWaveshareInstructions(screenshotOptions, "testData/Screenshot.png", getLastWriteTimeUtc, "Europe/Berlin"); // Assert - testee.Should().Contain(header => header.Key == "waveshare-update-screen" && header.Value.Single() == "True"); - testee.Should().Contain(header => header.Key == "waveshare-sleep-between-updates" && header.Value.Single() == "1953"); + testee.Should().Contain(header => header.Key == "waveshare-update-screen" && header.Value.Single() == expectedDisplayState.ToString()); + testee.Should().Contain(header => header.Key == "waveshare-sleep-between-updates" && header.Value.Single() == expectedSleep); testee.Should().Contain(header => header.Key == "waveshare-last-modified-local-time"); TimeOnly.Parse(testee["waveshare-last-modified-local-time"].Single()!) .Should() diff --git a/tests/Tests/Unit/Logic/ActivityExtensionsTests.cs b/tests/Tests/Unit/Logic/ActivityExtensionsTests.cs index 6a8a228..46ee91e 100644 --- a/tests/Tests/Unit/Logic/ActivityExtensionsTests.cs +++ b/tests/Tests/Unit/Logic/ActivityExtensionsTests.cs @@ -11,12 +11,16 @@ public class ActivityExtensionsTests { [TestCase(true, "", "", "15:00", true)] [TestCase(false, "13:00", "16:00", "15:00", true)] + [TestCase(false, "15:00", "16:00", "15:00", true)] + [TestCase(false, "14:00", "15:00", "15:00", true)] [TestCase(false, "16:00", "13:00", "15:00", false)] + [TestCase(false, "14:00", "14:30", "15:00", false)] public void DisplayShouldBeActive(bool isNull, string activeFrom, string activeTo, string now, bool expectedResult) { // Arrange + Environment.SetEnvironmentVariable("TZ", null); var timeProviderMock = new Mock(); - timeProviderMock.Setup(provider => provider.GetUtcNow()).Returns(12.April(1953).Add(TimeOnly.Parse(now).ToTimeSpan())); + timeProviderMock.Setup(provider => provider.GetUtcNow()).Returns(12.April(2023).Add(TimeOnly.Parse(now).ToTimeSpan())); var testee = isNull ? null : new Activity(TimeOnly.Parse(activeFrom), TimeOnly.Parse(activeTo), 0u); // Act diff --git a/tests/Tests/Unit/Logic/ImageProcessorTests.cs b/tests/Tests/Unit/Logic/ImageProcessorTests.cs index 13abb5e..9ce2dd4 100644 --- a/tests/Tests/Unit/Logic/ImageProcessorTests.cs +++ b/tests/Tests/Unit/Logic/ImageProcessorTests.cs @@ -55,14 +55,16 @@ public async Task ProcessImage_ShouldCreateBlackWhiteImageInWaveshareFormat() result.MediaType.Should().Be(MediaTypeNames.Application.Octet); } - [Test] - public async Task ProcessImage_ShouldReturnEmptyIfImageHasInvalidDimensions() + [TestCase("testData/Screenshot_invalidByInvalid.png")] + [TestCase("testData/Screenshot_invalidBy480.png")] + [TestCase("testData/Screenshot_800ByInvalid.png")] + public async Task ProcessImage_ShouldReturnEmptyIfImageHasInvalidDimensions(string fileName) { // Arrange var testee = new ImageProcessor(new Mock>().Object); // Act - var result = await testee.ProcessAsync("testData/Screenshot_invalid_dimensions.png", true, true); + var result = await testee.ProcessAsync(fileName, true, true); // Assert result.Data.Should().HaveCount(0); diff --git a/tests/Tests/Unit/Logic/ScreenshotCreatorTests.cs b/tests/Tests/Unit/Logic/ScreenshotCreatorTests.cs index 7faf005..6b55888 100644 --- a/tests/Tests/Unit/Logic/ScreenshotCreatorTests.cs +++ b/tests/Tests/Unit/Logic/ScreenshotCreatorTests.cs @@ -1,7 +1,10 @@ using FluentAssertions; using ImageMagick; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; +using Microsoft.Playwright; +using Moq; using ScreenshotCreator.Logic; namespace Tests.Unit.Logic; @@ -10,21 +13,105 @@ namespace Tests.Unit.Logic; [Category("Unit")] public class ScreenshotCreatorTests { - [Test] - public async Task CreateScreenshotWithoutLogin() + [TestCase("")] + [TestCase("/custom")] + public async Task CreateScreenshotWithoutLoginByConfig(string subresource) { // Arrange - var screenshotOptions = new ScreenshotOptions { Url = "https://www.dynamo-dresden.de", UrlType = UrlType.Any }; - var testee = new ScreenshotCreator.Logic.ScreenshotCreator(Options.Create(screenshotOptions), + var screenshotOptions = new ScreenshotOptions { Url = $"https://www.mysite.com{subresource}", UrlType = UrlType.Any }; + var playwrightHelperMock = new Mock(); + var pageMock = new Mock(); + pageMock + .Setup(page => page.ScreenshotAsync(It.Is(options => options.Path == screenshotOptions.ScreenshotFileName && + options.Type == ScreenshotType.Png))) + .ReturnsAsync(await File.ReadAllBytesAsync("testData/Screenshot.png")); + playwrightHelperMock.Setup(helper => helper.InitializePlaywrightAsync()).ReturnsAsync(pageMock.Object); + var testee = new ScreenshotCreator.Logic.ScreenshotCreator(playwrightHelperMock.Object, + Options.Create(screenshotOptions), NullLogger.Instance); // Act - await testee.CreateScreenshotAsync(800, 600); + await testee.CreateScreenshotAsync(800, 480); // Assert - var screenshot = new MagickImage(await File.ReadAllBytesAsync("Screenshot.png")); + var screenshot = new MagickImage(await File.ReadAllBytesAsync("testData/Screenshot.png")); screenshot.ColorSpace.Should().Be(ColorSpace.sRGB); screenshot.Width.Should().Be(800); - screenshot.Height.Should().Be(600); + screenshot.Height.Should().Be(480); + pageMock.Verify(page => page.SetViewportSizeAsync(800, 480), Times.Once); + pageMock.Verify(page => page.GotoAsync(screenshotOptions.Url, null), Times.Once); + playwrightHelperMock.Verify(helper => helper.InitializePlaywrightAsync(), Times.Once); + playwrightHelperMock.Verify(helper => helper.WaitAsync(), Times.Once); + } + + [TestCase("")] + [TestCase("/custom")] + public async Task CreateScreenshotWithoutLoginByPageContent(string subresource) + { + // Arrange + var screenshotOptions = new ScreenshotOptions { Url = $"https://www.mysite.com{subresource}", UrlType = UrlType.OpenHab }; + var playwrightHelperMock = new Mock(); + var pageMock = new Mock(); + pageMock + .Setup(page => page.ScreenshotAsync(It.Is(options => options.Path == screenshotOptions.ScreenshotFileName && + options.Type == ScreenshotType.Png))) + .ReturnsAsync(await File.ReadAllBytesAsync("testData/Screenshot.png")); + pageMock.Setup(page => page.GetByText("You are not allowed to view this page because of visibility restrictions.", null).CountAsync()).ReturnsAsync(0); + playwrightHelperMock.Setup(helper => helper.InitializePlaywrightAsync()).ReturnsAsync(pageMock.Object); + var testee = new ScreenshotCreator.Logic.ScreenshotCreator(playwrightHelperMock.Object, + Options.Create(screenshotOptions), + NullLogger.Instance); + + // Act + await testee.CreateScreenshotAsync(800, 480); + + // Assert + var screenshot = new MagickImage(await File.ReadAllBytesAsync("testData/Screenshot.png")); + screenshot.ColorSpace.Should().Be(ColorSpace.sRGB); + screenshot.Width.Should().Be(800); + screenshot.Height.Should().Be(480); + pageMock.Verify(page => page.SetViewportSizeAsync(800, 480), Times.Once); + pageMock.Verify(page => page.GotoAsync(screenshotOptions.Url, null), Times.Exactly(2)); + playwrightHelperMock.Verify(helper => helper.InitializePlaywrightAsync(), Times.Once); + playwrightHelperMock.Verify(helper => helper.WaitAsync(), Times.Exactly(2)); + } + + [TestCase("", 3)] + [TestCase("/custom", 2)] + public async Task CreateScreenshotWithLogin(string subresource, int expectedCallsOfGoto) + { + // Arrange + var screenshotOptions = new ScreenshotOptions { Url = $"https://www.mysite.com{subresource}", UrlType = UrlType.OpenHab }; + var playwrightHelperMock = new Mock(); + var pageMock = new Mock(); + pageMock.Setup(page => page.GetByPlaceholder("User Name", null).FillAsync(screenshotOptions.Username, null)); + pageMock.Setup(page => page + .GetByPlaceholder("Password", It.Is(options => options.Exact == true)) + .FillAsync(screenshotOptions.Password, null)); + pageMock.Setup(page => page.GetByRole(AriaRole.Button, null).ClickAsync(null)); + pageMock.Setup(page => page.GetByText("You are not allowed to view this page because of visibility restrictions.", null).CountAsync()).ReturnsAsync(1); + playwrightHelperMock.Setup(helper => helper.InitializePlaywrightAsync()).ReturnsAsync(pageMock.Object); + var testee = new ScreenshotCreator.Logic.ScreenshotCreator(playwrightHelperMock.Object, + Options.Create(screenshotOptions), + new Mock>().Object); + + // Act + await testee.CreateScreenshotAsync(800, 480); + + // Assert + pageMock.Verify(page => page.GetByPlaceholder("User Name", null).FillAsync(screenshotOptions.Username, null), Times.Once); + pageMock.Verify(page => page + .GetByPlaceholder("Password", It.Is(options => options.Exact == true)) + .FillAsync(screenshotOptions.Password, null), + Times.Once); + pageMock.Verify(page => page.GetByRole(AriaRole.Button, null).ClickAsync(null), Times.Once); + pageMock.Verify(page => page.SetViewportSizeAsync(800, 480), Times.Once); + pageMock.Verify(page => page.GotoAsync(screenshotOptions.Url, null), Times.Exactly(expectedCallsOfGoto)); + pageMock.Verify(page => page.ScreenshotAsync(It.Is(options => options.Path == screenshotOptions.ScreenshotFileName && + options.Type == ScreenshotType.Png)), + Times.Once); + pageMock.Verify(page => page.GetByText("You are not allowed to view this page because of visibility restrictions.", null).CountAsync(), Times.Once); + playwrightHelperMock.Verify(helper => helper.InitializePlaywrightAsync(), Times.Once); + playwrightHelperMock.Verify(helper => helper.WaitAsync(), Times.Exactly(4)); } } \ No newline at end of file diff --git a/tests/Tests/testData/Screenshot_800ByInvalid.png b/tests/Tests/testData/Screenshot_800ByInvalid.png new file mode 100644 index 0000000..80f394f Binary files /dev/null and b/tests/Tests/testData/Screenshot_800ByInvalid.png differ diff --git a/tests/Tests/testData/Screenshot_invalidBy480.png b/tests/Tests/testData/Screenshot_invalidBy480.png new file mode 100644 index 0000000..4233537 Binary files /dev/null and b/tests/Tests/testData/Screenshot_invalidBy480.png differ diff --git a/tests/Tests/testData/Screenshot_invalid_dimensions.png b/tests/Tests/testData/Screenshot_invalidByInvalid.png similarity index 100% rename from tests/Tests/testData/Screenshot_invalid_dimensions.png rename to tests/Tests/testData/Screenshot_invalidByInvalid.png