diff --git a/.github/workflows/playwright.yaml b/.github/workflows/playwright.yaml index 807745ce..a8f221ea 100644 --- a/.github/workflows/playwright.yaml +++ b/.github/workflows/playwright.yaml @@ -7,6 +7,15 @@ on: paths: ['Kiss.Bff.EndToEndTest/**'] workflow_dispatch: +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + jobs: test: timeout-minutes: 60 @@ -31,27 +40,20 @@ jobs: 'TestSettings:TEST_PASSWORD': '${{ secrets.PLAYWRIGHT_PASSWORD }}' 'TestSettings:TEST_TOTP_SECRET': ${{ secrets.PLAYWRIGHT_TOTP_SECRET }} 'TestSettings:TEST_BASE_URL': ${{ secrets.PLAYWRIGHT_BASE_URL }} - - name: Create html file - if: ${{ failure() && steps.e2e.conclusion == 'failure' }} - run: | - cd bin/Debug/net8.0/playwright-traces - my_string=$(echo *.zip) - IFS=' ' read -ra my_array <<< "$my_string" - result='Playwright traces" - echo "$result" - echo "$result" > index.html - - name: Deploy to GitHub Pages - if: ${{ failure() && steps.e2e.conclusion == 'failure' }} - uses: peaceiris/actions-gh-pages@v4 + - name: Upload artifact + if: ${{ always() }} + uses: actions/upload-pages-artifact@v3 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./Kiss.Bff.EndToEndTest/bin/Debug/net8.0/playwright-traces # directory of your reports - publish_branch: gh-pages/e2e # deploying to gh-pages branch \ No newline at end of file + # directory of your reports + path: './Kiss.Bff.EndToEndTest/bin/Debug/net8.0/playwright-traces' + deploy: + if: ${{ always() && github.ref == 'refs/heads/main' && github.event_name != 'pull_request' }} + needs: test + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/Kiss.Bff.EndToEndTest/Helpers/AcceptAllDialogs.cs b/Kiss.Bff.EndToEndTest/Helpers/AcceptAllDialogs.cs new file mode 100644 index 00000000..a421fe72 --- /dev/null +++ b/Kiss.Bff.EndToEndTest/Helpers/AcceptAllDialogs.cs @@ -0,0 +1,19 @@ +namespace Kiss.Bff.EndToEndTest.Helpers +{ + public static class AcceptAllDialogsExtension + { + public static IDisposable AcceptAllDialogs(this IPage page) + { + page.Dialog += Accept; + return new DoOnDispose(() => page.Dialog -= Accept); + } + + private static async void Accept(object? _, IDialog dialog) => await dialog.AcceptAsync(); + + + private sealed class DoOnDispose(Action action) : IDisposable + { + public void Dispose() => action(); + } + } +} diff --git a/Kiss.Bff.EndToEndTest/Helpers/Pagination.cs b/Kiss.Bff.EndToEndTest/Helpers/Pagination.cs new file mode 100644 index 00000000..f2b0c084 --- /dev/null +++ b/Kiss.Bff.EndToEndTest/Helpers/Pagination.cs @@ -0,0 +1,25 @@ +namespace Kiss.Bff.EndToEndTest.Helpers +{ + internal static class Pagination + { + public static ILocator GetCurrentPageLink(this ILocator locator) => locator.Locator("[aria-current=page]").First; + public static ILocator GetNextPageLink(this ILocator locator) => locator.Locator("[rel='next']").First; + public static ILocator GetPreviousPageLink(this ILocator locator) => locator.Locator("[rel='prev']").First; + public static async Task IsDisabledPageLink(this ILocator locator) + { + await locator.WaitForAsync(); + + var classes = await locator.GetAttributeAsync("class"); + if (classes == null) return false; + // when the page is disabled it doesn't get a disabled attribute. + // TODO: research if there is a better pattern for this that is compatible with the den haag pagination component: + // https://nl-design-system.github.io/denhaag/?path=/docs/react-navigation-pagination--docs + // Note that their component renders invalid html: a button with a rel attribute. + // this might serve as a source of inspiration: + // https://design.homeoffice.gov.uk/components?name=Pagination + // https://design-system.w3.org/components/pagination.html + return classes.Contains("denhaag-pagination__link--disabled") + || classes.Contains("denhaag-pagination__link--current"); + } + } +} diff --git a/Kiss.Bff.EndToEndTest/Infrastructure/BaseTestInitializer.cs b/Kiss.Bff.EndToEndTest/Infrastructure/BaseTestInitializer.cs deleted file mode 100644 index 87438247..00000000 --- a/Kiss.Bff.EndToEndTest/Infrastructure/BaseTestInitializer.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Microsoft.Extensions.Configuration; - - -namespace Kiss.Bff.EndToEndTest -{ - [TestClass] - public class BaseTestInitializer : PageTest - { - private const string StoragePath = "./auth.json"; - - private static readonly IConfiguration s_configuration = new ConfigurationBuilder() - .AddUserSecrets() - .AddEnvironmentVariables() - .Build(); - - private static readonly UniqueOtpHelper s_uniqueOtpHelper = new(GetRequiredConfig("TestSettings:TEST_TOTP_SECRET")); - - [TestInitialize] - public virtual async Task TestInitialize() - { - var username = GetRequiredConfig("TestSettings:TEST_USERNAME"); - var password = GetRequiredConfig("TestSettings:TEST_PASSWORD"); - - await Context.Tracing.StartAsync(new() - { - Title = $"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}", - Screenshots = true, - Snapshots = true, - Sources = true - }); - - var loginHelper = new AzureAdLoginHelper(Page, username, password, s_uniqueOtpHelper); - await loginHelper.LoginAsync(); - await Context.StorageStateAsync(new() { Path = StoragePath }); - } - - [TestCleanup] - public async Task TestCleanup() - { - var options = TestContext.CurrentTestOutcome != UnitTestOutcome.Passed - ? new TracingStopOptions - { - Path = Path.Combine( - Environment.CurrentDirectory, - "playwright-traces", - $"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}.zip" - ) - } - : null; - - await Context.Tracing.StopAsync(options); - } - - public override BrowserNewContextOptions ContextOptions() - { - return new(base.ContextOptions()) - { - BaseURL = GetRequiredConfig("TestSettings:TEST_BASE_URL"), - // save auth state so we don't need to log in in every single test - StorageStatePath = File.Exists(StoragePath) ? StoragePath : null, - }; - } - - private static string GetRequiredConfig(string key) - { - var value = s_configuration[key]; - if (string.IsNullOrEmpty(value)) - { - throw new InvalidOperationException($"'{key}' is missing from the configuration"); - } - return value; - } - } -} diff --git a/Kiss.Bff.EndToEndTest/Infrastructure/KissPlaywrightTest.cs b/Kiss.Bff.EndToEndTest/Infrastructure/KissPlaywrightTest.cs new file mode 100644 index 00000000..ce418a07 --- /dev/null +++ b/Kiss.Bff.EndToEndTest/Infrastructure/KissPlaywrightTest.cs @@ -0,0 +1,155 @@ +using System.Collections.Concurrent; +using System.Text.Encodings.Web; +using Microsoft.Extensions.Configuration; + + +namespace Kiss.Bff.EndToEndTest +{ + /// + /// Inherit this class in each test class. This does the following:
+ /// 1. Makes sure the user is logged in before each test starts
+ /// 2. Makes sure Playwright records traces for each test
+ /// 3. Exposes a method to define test steps. These show up in the Playwright traces and in the test report.
+ /// 4. Builds a html test report after all tests in a test class are done. + /// We upload these to github pages + ///
+ [TestClass] + public class KissPlaywrightTest : PageTest + { + private const string StoragePath = "./auth.json"; + + private static readonly IConfiguration s_configuration = new ConfigurationBuilder() + .AddUserSecrets() + .AddEnvironmentVariables() + .Build(); + + private static readonly UniqueOtpHelper s_uniqueOtpHelper = new(GetRequiredConfig("TestSettings:TEST_TOTP_SECRET")); + + // this is used to build a test report for each test + private static readonly ConcurrentDictionary s_testReports = []; + + private readonly List _steps = []; + + /// + /// This is run before each test + /// + /// + [TestInitialize] + public virtual async Task TestInitialize() + { + // log in with azure ad + var username = GetRequiredConfig("TestSettings:TEST_USERNAME"); + var password = GetRequiredConfig("TestSettings:TEST_PASSWORD"); + + var loginHelper = new AzureAdLoginHelper(Page, username, password, s_uniqueOtpHelper); + await loginHelper.LoginAsync(); + // store the cookie so we stay logged in in each test + await Context.StorageStateAsync(new() { Path = StoragePath }); + + // start tracing. we do this AFTER logging in so the password doesn't end up in the tracing + await Context.Tracing.StartAsync(new() + { + Title = $"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}", + Screenshots = true, + Snapshots = true, + Sources = true, + }); + } + + /// + /// Start a test step. This ends up in the test report and as group in the playwright tracing + /// + /// + /// + protected async Task Step(string description) + { + await Context.Tracing.GroupEndAsync(); + await Context.Tracing.GroupAsync(description); + _steps.Add(description); + } + + /// + /// This is run after each test + /// + /// + [TestCleanup] + public async Task TestCleanup() + { + // if we are in a group, end it + await Context.Tracing.GroupEndAsync(); + var fileName = $"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}.zip"; + var fullPath = Path.Combine(Environment.CurrentDirectory, "playwright-traces", fileName); + + // stop tracing and save a zip file in the output directory + await Context.Tracing.StopAsync(new() + { + Path = fullPath + }); + + // build a html report containing the test steps and a link to the playwright traces viewer + var html = $""" +
+

{HtmlEncoder.Default.Encode(TestContext.TestName ?? "")}

+ Playwright tracing +

Steps:

+
    {string.Join("", _steps.Select(step => $""" +
  1. {HtmlEncoder.Default.Encode(step)}
  2. + """))} +
+
+ """; + + s_testReports.TryAdd(TestContext.TestName!, html); + } + + /// + /// This is run after all tests in a test class are done + /// + /// + [ClassCleanup(InheritanceBehavior.BeforeEachDerivedClass)] + public static async Task ClassCleanup() + { + // combine the reports for each test in a single html file + var html = $$""" + + + + + + + Playwright traces + + + + +
+ {{string.Join("", s_testReports.OrderBy(x=> x.Key).Select(x=> x.Value))}} +
+ + """; + + using var writer = File.CreateText(Path.Combine(Environment.CurrentDirectory, "playwright-traces", "index.html")); + await writer.WriteLineAsync(html); + } + + private static string GetRequiredConfig(string key) + { + var value = s_configuration[key]; + if (string.IsNullOrEmpty(value)) + { + throw new InvalidOperationException($"'{key}' is missing from the configuration"); + } + return value; + } + + public override BrowserNewContextOptions ContextOptions() + { + return new(base.ContextOptions()) + { + BaseURL = GetRequiredConfig("TestSettings:TEST_BASE_URL"), + // save auth state so we don't need to log in in every single test + StorageStatePath = File.Exists(StoragePath) ? StoragePath : null, + }; + } + } +} diff --git a/Kiss.Bff.EndToEndTest/Kiss.Bff.EndToEndTest.csproj b/Kiss.Bff.EndToEndTest/Kiss.Bff.EndToEndTest.csproj index ea08e376..5af80391 100644 --- a/Kiss.Bff.EndToEndTest/Kiss.Bff.EndToEndTest.csproj +++ b/Kiss.Bff.EndToEndTest/Kiss.Bff.EndToEndTest.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -12,12 +12,12 @@ - - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Kiss.Bff.EndToEndTest/NieuwsEnWerkInstructies.cs b/Kiss.Bff.EndToEndTest/NieuwsEnWerkInstructies.cs deleted file mode 100644 index d62bd682..00000000 --- a/Kiss.Bff.EndToEndTest/NieuwsEnWerkInstructies.cs +++ /dev/null @@ -1,544 +0,0 @@ -namespace Kiss.Bff.EndToEndTest; - -[TestClass] -public class NieuwsEnWerkInstructies : BaseTestInitializer -{ - //[TestMethod] - //public async Task Als_ik_op_de_paginering_links_klik_navigeer_ik_naar_een_nieuwe_pagina() - //{ - // // Locate the 'Nieuws' section - // await Expect(NieuwsSection).ToBeVisibleAsync(); - - // // Locate the 'Next' page button using the pagination structure - // var nextPageButton = NieuwsSection.Locator("[rel='next']").First; - - // await Expect(nextPageButton).ToBeVisibleAsync(); - - // // Click the 'Next' page button - // await nextPageButton.ClickAsync(); - - // // Wait for the button to ensure the page navigation has started - // await nextPageButton.WaitForAsync(); - - // // Verify that the first page button is still visible after navigation - // var firstPageButton = NieuwsSection.GetByLabel("Pagina 1"); - // // TODO fix the pagination component. numbers should always have an aria label with the number in it - // //await Expect(firstPageButton).ToBeVisibleAsync(); - - // // Verify that the current page button reflects the correct page number - // var currentPageButton = NieuwsSection.Locator("[aria-current=page]"); - // var page2Button = NieuwsSection.GetByLabel("Pagina 2"); - // var page2ButtonWithAriaCurrentPage = currentPageButton.And(page2Button); - - // // Ensure the current page button's aria-label attribute is 'Pagina 2' - // await Expect(page2ButtonWithAriaCurrentPage).ToBeVisibleAsync(); - //} - - - //[TestMethod] - //public async Task Als_ik_skill_filters_selecteer_worden_de_nieuwberichten_hierop_gefilterd() - //{ - // // Example: Test filtering by skill - // var categorieFilterSection = Page.Locator("details").Filter(new() { HasText = "Filter op categorie" }); - // await Expect(categorieFilterSection).ToBeVisibleAsync(); - // await categorieFilterSection.Locator("summary").ClickAsync(); - // var algemeenCheckbox = categorieFilterSection.GetByRole(AriaRole.Checkbox, new() { Name = "Algemeen" }); - // var belastingenCheckbox = categorieFilterSection.GetByRole(AriaRole.Checkbox, new() { Name = "Belastingen" }); - - // await algemeenCheckbox.CheckAsync(); - // await belastingenCheckbox.CheckAsync(); - - // // Verify results are filtered - // var articles = Page.GetByRole(AriaRole.Article); - // await Expect(articles.First).ToBeVisibleAsync(); - - // var resultCount = await articles.CountAsync(); - - // Assert.IsTrue(resultCount > 0, "Expected to find articles after filtering by skills."); - - // // Loop through each article and verify it contains at least one of the selected skills - // for (var i = 0; i < resultCount; i++) - // { - // var article = articles.Nth(i); - // var algemeenSkill = article.Locator("small.category-Algemeen"); - // var belastingenSkill = article.Locator("small.category-Belastingen"); - // await Expect(algemeenSkill.Or(belastingenSkill).First).ToBeVisibleAsync(); - // } - - // // Reset filters - // await algemeenCheckbox.UncheckAsync(); - // await belastingenCheckbox.UncheckAsync(); - //} - - // Dit test Stap 2. 8. 9. 10. 15. - [TestMethod] - public async Task Als_ik_een_oud_bericht_update_komt_deze_bovenaan() - { - try - { - // Check if old test messages exist - var oldTestMessageLocator = Page.Locator("article:has-text('8e600d44-81fb-4302-9675-31b687619026')"); - if (await oldTestMessageLocator.IsVisibleAsync()) - { - await DeleteBericht("8e600d44-81fb-4302-9675-31b687619026"); - await DeleteBericht("724e44a3-6ba1-4e92-85c3-d44e35238f4a"); - await DeleteBericht("5b8277a7-fb1a-4358-8099-24b9487b29bc"); - } - - - // Step 2: Create Message A with the publish date one minute in the past - await CreateBericht("Message A: 8e600d44-81fb-4302-9675-31b687619026", false, "", TimeSpan.FromMinutes(-1)); - - // Create Message B and C with the current publish date - await CreateBericht("Message B: 724e44a3-6ba1-4e92-85c3-d44e35238f4a", false, ""); - await CreateBericht("Important Message C: 5b8277a7-fb1a-4358-8099-24b9487b29bc", true, ""); - - // Go to the page and retrieve the order of articles - await Page.GotoAsync("/"); - - await Page.WaitForSelectorAsync("article:has-text('Message A')"); - await Page.WaitForSelectorAsync("article:has-text('Message B')"); - await Page.WaitForSelectorAsync("article:has-text('Message C')"); - - var allArticles = NieuwsSection.GetByRole(AriaRole.Article); - - // Dictionary to hold article positions - var initialOrderOnPage = new Dictionary(); - - for (var index = 0; index < await allArticles.CountAsync(); index++) - { - var element = allArticles.Nth(index); - var innerHtml = await element.InnerTextAsync(); - - if (innerHtml.Contains("Message A: 8e600d44-81fb-4302-9675-31b687619026")) - { - initialOrderOnPage.Add("Message A", index); - } - if (innerHtml.Contains("Message B: 724e44a3-6ba1-4e92-85c3-d44e35238f4a")) - { - initialOrderOnPage.Add("Message B", index); - } - if (innerHtml.Contains("Message C: 5b8277a7-fb1a-4358-8099-24b9487b29bc")) - { - initialOrderOnPage.Add("Message C", index); - } - } - - var indexVanA = initialOrderOnPage["Message A"]; - var indexVanB = initialOrderOnPage["Message B"]; - var indexVanC = initialOrderOnPage["Message C"]; - - Assert.IsTrue(indexVanC < indexVanB && indexVanB < indexVanA, "Initial order should be C, B, A."); - - // Act: Update message A - await UpdateBericht("Message A: 8e600d44-81fb-4302-9675-31b687619026", "Updated Message A: 8e600d44-81fb-4302-9675-31b687619026"); - - // Refresh page and retrieve articles again - await Page.GotoAsync("/"); - - await Page.WaitForSelectorAsync("article:has-text('Message A')"); - await Page.WaitForSelectorAsync("article:has-text('Message B')"); - await Page.WaitForSelectorAsync("article:has-text('Message C')"); - - allArticles = NieuwsSection.GetByRole(AriaRole.Article); - - // Rebuild the dictionary for updated positions - var orderOnPageAfterMessageUpdate = new Dictionary(); - for (var index = 0; index < await allArticles.CountAsync(); index++) - { - var element = allArticles.Nth(index); - var innerHtml = await element.InnerTextAsync(); - - if (innerHtml.Contains("Updated Message A: 8e600d44-81fb-4302-9675-31b687619026")) - { - orderOnPageAfterMessageUpdate.Add("Message A", index); - } - if (innerHtml.Contains("Message B: 724e44a3-6ba1-4e92-85c3-d44e35238f4a")) - { - orderOnPageAfterMessageUpdate.Add("Message B", index); - } - if (innerHtml.Contains("Message C: 5b8277a7-fb1a-4358-8099-24b9487b29bc")) - { - orderOnPageAfterMessageUpdate.Add("Message C", index); - } - } - - // Assert the updated order: C (highest), B, A (lowest) - indexVanA = orderOnPageAfterMessageUpdate["Message A"]; - indexVanB = orderOnPageAfterMessageUpdate["Message B"]; - indexVanC = orderOnPageAfterMessageUpdate["Message C"]; - - Assert.IsTrue(indexVanC < indexVanB && indexVanB > indexVanA, "Updated order should be C, A, B."); - } - finally - { - // Clean up test messages - await DeleteBericht("8e600d44-81fb-4302-9675-31b687619026"); - await DeleteBericht("724e44a3-6ba1-4e92-85c3-d44e35238f4a"); - await DeleteBericht("5b8277a7-fb1a-4358-8099-24b9487b29bc"); - } - } - - // 9. Publiceer een bericht met markering Belangrijk - [TestMethod] - public async Task Als_ik_een_belangrijk_bericht_publiceer_komt_deze_bovenaan() - { - var titel = $"End to end test {Guid.NewGuid()}"; - // Step 1: Get the initial featured indicator count - var initialFeatureCount = await GetFeaturedCount(); - - // Step 2: Create a new important message - await CreateBericht(titel, true, ""); - - try - { - // Step 3: Go to the page and ensure the news section is visible - await Page.GotoAsync("/"); - - await Expect(NieuwsSection).ToBeVisibleAsync(); - - // Step 4: Check if the newly created important message appears at the top - var firstArticle = NieuwsSection.GetByRole(AriaRole.Article).First; - await Expect(firstArticle).ToContainTextAsync(titel); - var isBelangrijk = await firstArticle.Locator(".featured").IsVisibleAsync(); - - // Ensure the first article contains "Belangrijk" only if it's supposed to - if (isBelangrijk) - { - await Expect(firstArticle.Locator(".featured")).ToContainTextAsync("Belangrijk"); - } - else - { - Console.WriteLine("This article does not contain the 'Belangrijk' tag."); - } - - // Step 5: Get the new featured count - var updatedCount = await GetFeaturedCount(); - Assert.IsTrue(updatedCount >= initialFeatureCount + 1, $"Expected featured count to be at least {initialFeatureCount + 1}, but got {updatedCount}"); - - // Step 6: Mark the article as read - await firstArticle.GetByRole(AriaRole.Button, new() { Name = "Markeer als gelezen" }).ClickAsync(); - - // Step 7: Validate that the featured count is now back to the initial count - var reUpdatedCount = await GetFeaturedCount(); - Assert.IsTrue(reUpdatedCount == initialFeatureCount, $"Expected featured count to be equal to the initial count {initialFeatureCount} again, but instead got {reUpdatedCount}"); - } - finally - { - // Step 8: Clean up by deleting the created message - await DeleteBericht(titel); - } - } - - private async Task GetFeaturedCount() - { - // Declare featuredIndicator outside the try block so it's accessible throughout the method - var featuredIndicator = Page.Locator(".featured-indicator"); - await Page.WaitForResponseAsync(x => x.Url.Contains("featuredcount")); - if (await featuredIndicator.IsVisibleAsync()) - { - var featureText = await featuredIndicator.TextContentAsync(); - return int.Parse(featureText!); - } - return 0; - } - - - // This test covers Step 12. 13. 14. - [TestMethod] - public async Task Als_ik_een_skill_toevoeg_wordt_deze_vermeld_in_de_filter() - { - // Define the new skill name to be added and tested - var newSkill = "Playwright Test Skill"; - - try - { - // Step 1: Navigate to the Skills management page - await NavigateToSkillsBeheer(); - - // Step 2: Add the new skill - await CreateSkill(newSkill); - await Page.GotoAsync("/"); - // Step 3: Open the filter dropdown to verify the skill - await Page.ClickAsync("summary:has-text('Filter op categorie')"); - - // Step 4: Verify the newly added skill appears in the filter list as a checkbox option - var addedSkillCheckbox = Page.GetByRole(AriaRole.Checkbox, new() { Name = newSkill }).First; - await Expect(addedSkillCheckbox).ToBeVisibleAsync(); - - } - finally - { - // clean-up: Remove the skill after test completion - await DeleteSkill(newSkill); - } - } - - //// Made private because the test isn't done yet, this is just a stepping stone made with the playwright editor - //[TestMethod] - //public async Task Als_ik_een_skill_en_nieuws_item_toevoeg_zou_ik_deze_moeten_zien_bij_filteren() - //{ - // var newSkill = "Test Skill"; - // var newsTitle = "Test Nieuws Item"; - // bool isImportant = false; - - // try - // { - // // Step 1: Create a new skill - // await CreateSkill(newSkill); - - // // Step 2: Create a news item with the new skill - // await CreateBericht(newsTitle, isImportant, newSkill); - - // // Step 3: Verify that the news item appears when filtering by the new skill - // await Page.GotoAsync("/"); - - // await Page.ClickAsync("summary:has-text('Filter op categorie')"); // Open the filter dropdown - // var skillCheckbox = Page.GetByRole(AriaRole.Checkbox, new() { Name = newSkill }).First; - // await skillCheckbox.CheckAsync(); // Check the skill in the filter - - // // Step 4: Verify the news item appears - // await Expect(Page.GetByRole(AriaRole.Heading, new() { Name = newsTitle })).ToBeVisibleAsync(); - // } - // finally - // { - // await DeleteBericht(newsTitle); - // await DeleteSkill(newSkill); - // } - //} - - private ILocator NieuwsSection => Page.Locator("section").Filter(new() { HasText = "Nieuws" }); - - private async Task MarkAllNewsItems(bool read) - { - // Locate the 'Nieuws' section - await Expect(NieuwsSection).ToBeVisibleAsync(); - - var firstGelezenButton = NieuwsSection.GetByTitle("Markeer als gelezen").First; - var firstOnGelezenButton = NieuwsSection.GetByTitle("Markeer als ongelezen").First; - - var buttonToClick = read - ? firstGelezenButton - : firstOnGelezenButton; - - var firstPage = NieuwsSection.GetByRole(AriaRole.Link).Filter(new() { HasTextRegex = new("^1$") }); - - if (!await IsDisabledPage(firstPage)) - { - await firstPage.ClickAsync(); - } - - while (true) - { - await Expect(firstOnGelezenButton.Or(firstGelezenButton).First).ToBeVisibleAsync(); - - // Mark all news items as read on the current page - while (await buttonToClick.IsVisibleAsync()) - { - await buttonToClick.ClickAsync(); - } - - var nextPage = NieuwsSection.Locator("[rel='next']").First; - - if (await IsDisabledPage(nextPage)) - { - break; - } - - await nextPage.ClickAsync(); - } - - if (!await IsDisabledPage(firstPage)) - { - await firstPage.ClickAsync(); - } - } - - private async Task NavigateToNieuwsWerkinstructiesBeheer() - { - var beheerlink = Page.GetByRole(AriaRole.Link, new() { Name = "Beheer" }); - var berichtenlink = Page.GetByRole(AriaRole.Link, new() { Name = "Nieuws en werkinstructies" }); - - await Expect(beheerlink.Or(berichtenlink).First).ToBeVisibleAsync(); - - if (await beheerlink.IsVisibleAsync()) - { - await beheerlink.ClickAsync(); - } - - await Expect(beheerlink).ToBeVisibleAsync(new() { Visible = false }); - - if (await berichtenlink.GetAttributeAsync("aria-current") != "page") - { - await berichtenlink.ClickAsync(); - } - } - - private async Task NavigateToSkillsBeheer() - { - var beheerlink = Page.GetByRole(AriaRole.Link, new() { Name = "Beheer" }); - var skillslink = Page.GetByRole(AriaRole.Link, new() { Name = "Skills" }); - - await Expect(beheerlink.Or(skillslink).First).ToBeVisibleAsync(); - - if (await beheerlink.IsVisibleAsync()) - { - await beheerlink.ClickAsync(); - } - - await Expect(beheerlink).ToBeVisibleAsync(new() { Visible = false }); - - if (await skillslink.GetAttributeAsync("aria-current") != "page") - { - await skillslink.ClickAsync(); - } - } - - private async Task CreateBericht(string titel, bool isBelangrijk, string skill, TimeSpan? publishDateOffset = null) - { - await NavigateToNieuwsWerkinstructiesBeheer(); - var toevoegenLink = Page.GetByRole(AriaRole.Link, new() { Name = "Toevoegen" }); - await toevoegenLink.ClickAsync(); - await Page.GetByRole(AriaRole.Radio, new() { Name = "Nieuws" }).CheckAsync(); - - await Page.GetByRole(AriaRole.Textbox, new() { Name = "Titel" }).FillAsync(titel); - - // Fill in the content area - await Page.Locator(".ck-content").WaitForAsync(); - await Page.Locator("textarea").FillAsync(titel); - - if (isBelangrijk) - { - await Page.GetByRole(AriaRole.Checkbox, new() { Name = "Belangrijk" }).CheckAsync(); - } - - if (!string.IsNullOrEmpty(skill)) - { - var skillCheckbox = Page.GetByRole(AriaRole.Checkbox, new() { Name = skill }); - await skillCheckbox.CheckAsync(); // Ensure the skill checkbox is checked - } - - // Use the current time as the base publish date - DateTime publishDate = DateTime.Now; - - // Apply the provided offset to the publish date - if (publishDateOffset.HasValue) - { - publishDate = publishDate.Add(publishDateOffset.Value); - } - - // Set the publish date in the input field - var publishDateInput = Page.Locator("#publicatieDatum"); - await publishDateInput.FillAsync(publishDate.ToString("yyyy-MM-ddTHH:mm")); - - var opslaanKnop = Page.GetByRole(AriaRole.Button, new() { Name = "Opslaan" }); - while (await opslaanKnop.IsVisibleAsync() && await opslaanKnop.IsEnabledAsync()) - { - await opslaanKnop.ClickAsync(); - } - - await Expect(Page.GetByRole(AriaRole.Table)).ToBeVisibleAsync(); - } - - private async Task UpdateBericht(string oldTitle, string newTitle) - { - // Navigate to the news management page - await NavigateToNieuwsWerkinstructiesBeheer(); - - // Find the news item by its old title - var nieuwsRows = Page.GetByRole(AriaRole.Row) - .Filter(new() - { - Has = Page.GetByRole(AriaRole.Cell, new() { Name = oldTitle, Exact = true }) - }); - - // Click the "Details" link for the news item - await nieuwsRows.GetByRole(AriaRole.Link, new() { Name = "Details" }).ClickAsync(); - - // Update the title to the new one - await Page.GetByLabel("Titel").FillAsync(newTitle); - - // Save the changes - await Page.GetByRole(AriaRole.Button, new() { Name = "Opslaan" }).ClickAsync(); - await Expect(Page.GetByRole(AriaRole.Table)).ToBeVisibleAsync(); - } - - - private async Task DeleteBericht(string titel) - { - await NavigateToNieuwsWerkinstructiesBeheer(); - var nieuwsRows = Page.GetByRole(AriaRole.Row) - .Filter(new() - { - Has = Page.GetByRole(AriaRole.Cell, new() { Name = "Nieuws" }).First - }) - .Filter(new() - { - Has = Page.GetByRole(AriaRole.Cell, new() { Name = titel, Exact = false }).First - }); - - var deleteButton = nieuwsRows.GetByTitle("Verwijder").First; - - Page.Dialog += Accept; - await deleteButton.ClickAsync(); - Page.Dialog -= Accept; - await Expect(Page.GetByRole(AriaRole.Table)).ToBeVisibleAsync(); - } - - private async Task CreateSkill(string skillName) - { - // Step 1: Navigate to the "Skills" beheer page - await NavigateToSkillsBeheer(); - - // Step 2: Click on the "Toevoegen" button to add a new skill - var toevoegenLink = Page.GetByRole(AriaRole.Link, new() { Name = "toevoegen" }); - await toevoegenLink.ClickAsync(); - - // Step 3: Fill in the skill name in the input field - await Page.GetByRole(AriaRole.Textbox, new() { Name = "Naam" }).FillAsync(skillName); - - // Step 4: Locate and click the "Opslaan" button to save the new skill - var opslaanKnop = Page.GetByRole(AriaRole.Button, new() { Name = "Opslaan" }); - - // Ensure that the save button is visible and enabled before clicking - while (await opslaanKnop.IsVisibleAsync() && await opslaanKnop.IsEnabledAsync()) - { - await opslaanKnop.ClickAsync(); - } - - // Step 5: Optionally verify that the new skill has been added - await Expect(Page.GetByRole(AriaRole.Heading, new() { Name = "Skills" })).ToBeVisibleAsync(); - } - - private async Task DeleteSkill(string skillName) - { - // Step 1: Navigate to the Skills management page - await NavigateToSkillsBeheer(); - - // Step 2: Locate the skill listitem by its name - var skillLocator = Page.GetByRole(AriaRole.Listitem).Filter(new() { HasText = skillName }); - - // Step 3: Locate the delete button within the listitem - var deleteButton = skillLocator.GetByRole(AriaRole.Button).And(Page.GetByTitle("Verwijderen")); - - // Step 4: Click the delete button and accept the dialog - Page.Dialog += Accept; - await deleteButton.ClickAsync(); - - // Step 5: Verify the skill is no longer present in the list - await Expect(skillLocator).ToBeHiddenAsync(); - } - - static async void Accept(object? _, IDialog dialog) => await dialog.AcceptAsync(); - - private async Task IsDisabledPage(ILocator locator) - { - await Expect(locator).ToBeVisibleAsync(); - - var classes = await locator.GetAttributeAsync("class"); - if (classes == null) return false; - // we always have a next page link, but sometimes it is disabled. TO DO: use disabled attribute so we don't have to rely on classes - return classes.Contains("denhaag-pagination__link--disabled") - || classes.Contains("denhaag-pagination__link--current"); - } -} diff --git a/Kiss.Bff.EndToEndTest/NieuwsEnWerkInstructies/Helpers/CreateBericht.cs b/Kiss.Bff.EndToEndTest/NieuwsEnWerkInstructies/Helpers/CreateBericht.cs new file mode 100644 index 00000000..494e3e5c --- /dev/null +++ b/Kiss.Bff.EndToEndTest/NieuwsEnWerkInstructies/Helpers/CreateBericht.cs @@ -0,0 +1,151 @@ +using Kiss.Bff.EndToEndTest.Helpers; + +namespace Kiss.Bff.EndToEndTest.NieuwsEnWerkInstructies.Helpers +{ + internal static class CreateBerichtExtension + { + public static async Task CreateBerichten(this IPage page, IEnumerable requests) + { + var berichten = new List(); + async ValueTask Dispose() + { + if (berichten.Count == 0) return; + await page.Context.Tracing.GroupEndAsync(); + await page.Context.Tracing.GroupAsync("Cleanup berichten"); + await page.Context.Tracing.GroupAsync("Start cleanup"); + foreach (var item in berichten) + { + try + { + await item.DisposeAsync(); + } + catch (Exception) + { + } + } + await page.Context.Tracing.GroupEndAsync(); + } + try + { + foreach (var item in requests) + { + var bericht = await page.CreateBericht(item); + berichten.Add(bericht); + } + return new DoOnDisposeAsync(Dispose); + } + catch (Exception) + { + await Dispose(); + throw; + } + } + + private class DoOnDisposeAsync(Func thingToDo) : IAsyncDisposable + { + public ValueTask DisposeAsync() => thingToDo(); + } + + public static async Task CreateBericht(this IPage page, CreateBerichtRequest request) + { + request = request with { Body = !string.IsNullOrWhiteSpace(request.Body) ? request.Body : request.Title }; + await page.NavigateToNieuwsWerkinstructiesBeheer(); + var toevoegenLink = page.GetByRole(AriaRole.Link, new() { Name = "Toevoegen" }); + await toevoegenLink.ClickAsync(); + await page.GetByRole(AriaRole.Radio, new() { Name = request.BerichtType.ToString() }).CheckAsync(); + + await page.GetByRole(AriaRole.Textbox, new() { Name = "Titel" }).FillAsync(request.Title); + + await page.GetByRole(AriaRole.Textbox, new() { Name = "Rich Text Editor" }).FillAsync(request.Body); + + if (request.IsImportant) + { + await page.GetByRole(AriaRole.Checkbox, new() { Name = "Belangrijk" }).CheckAsync(); + } + + if (!string.IsNullOrEmpty(request.Skill)) + { + var skillCheckbox = page.GetByRole(AriaRole.Checkbox, new() { Name = request.Skill }); + await skillCheckbox.CheckAsync(); // Ensure the skill checkbox is checked + } + + // Use the current time as the base publish date + var publishDate = DateTime.Now; + + // Apply the provided offset to the publish date + if (request.PublishDateOffset.HasValue) + { + publishDate = publishDate.Add(request.PublishDateOffset.Value); + } + + // Set the publish date in the input field + var publishDateInput = page.GetByLabel("Publicatiedatum"); + await publishDateInput.FillAsync(publishDate.ToString("yyyy-MM-ddTHH:mm")); + + var opslaanKnop = page.GetByRole(AriaRole.Button, new() { Name = "Opslaan" }); + while (await opslaanKnop.IsVisibleAsync() && await opslaanKnop.IsEnabledAsync()) + { + await opslaanKnop.ClickAsync(); + } + + await page.GetByRole(AriaRole.Table).WaitForAsync(); + return new(page) + { + IsImportant = request.IsImportant, + Title = request.Title, + PublishDateOffset = request.PublishDateOffset, + PublicatieDatum = publishDate, + PublicatieEinddatum = publishDate.AddYears(1), + Skill = request.Skill, + Body = request.Body, + BerichtType = request.BerichtType, + }; + } + } + + internal record class Bericht(IPage Page) : CreateBerichtRequest, IAsyncDisposable + { + public required new string Body { get; init; } + public DateTime PublicatieDatum { get; init; } + public DateTime PublicatieEinddatum { get; init; } + public async ValueTask DisposeAsync() + { + await Page.Context.Tracing.GroupEndAsync(); + await Page.Context.Tracing.GroupAsync("Cleanup artikel"); + await Page.NavigateToNieuwsWerkinstructiesBeheer(); + var nieuwsRows = Page.GetByRole(AriaRole.Row) + .Filter(new() + { + Has = Page.GetByRole(AriaRole.Cell, new() { Name = BerichtType.ToString() }).First + }) + .Filter(new() + { + Has = Page.GetByRole(AriaRole.Cell, new() { Name = Title, Exact = true }).First + }); + + var deleteButton = nieuwsRows.GetByTitle("Verwijder").First; + using (var _ = Page.AcceptAllDialogs()) + { + await deleteButton.ClickAsync(); + } + await Page.GetByRole(AriaRole.Table).WaitForAsync(); + } + } + + internal enum BerichtType + { + Nieuws, + Werkinstructie + } + + internal record class CreateBerichtRequest() + { + public required string Title { get; init; } + public virtual string? Body { get; init; } + public bool IsImportant { get; init; } + public string? Skill { get; init; } + public BerichtType BerichtType { get; init; } = BerichtType.Nieuws; + + public TimeSpan? PublishDateOffset { get; init; } + } +} diff --git a/Kiss.Bff.EndToEndTest/NieuwsEnWerkInstructies/Helpers/CreateSkill.cs b/Kiss.Bff.EndToEndTest/NieuwsEnWerkInstructies/Helpers/CreateSkill.cs new file mode 100644 index 00000000..323dbd94 --- /dev/null +++ b/Kiss.Bff.EndToEndTest/NieuwsEnWerkInstructies/Helpers/CreateSkill.cs @@ -0,0 +1,71 @@ +using Kiss.Bff.EndToEndTest.Helpers; + +namespace Kiss.Bff.EndToEndTest.NieuwsEnWerkInstructies.Helpers +{ + internal static class CreateSkillExtension + { + + private class DoOnDisposeAsync(Func thingToDo) : IAsyncDisposable + { + public ValueTask DisposeAsync() => thingToDo(); + } + + public static async Task CreateSkill(this IPage page, string skillName) + { + // Step 1: Navigate to the "Skills" beheer page + await page.NavigateToSkillsBeheer(); + + // Step 2: Click on the "Toevoegen" button to add a new skill + var toevoegenLink = page.GetByRole(AriaRole.Link, new() { Name = "toevoegen" }); + await toevoegenLink.ClickAsync(); + + // Step 3: Fill in the skill name in the input field + await page.GetByRole(AriaRole.Textbox, new() { Name = "Naam" }).FillAsync(skillName); + + // Step 4: Locate and click the "Opslaan" button to save the new skill + var opslaanKnop = page.GetByRole(AriaRole.Button, new() { Name = "Opslaan" }); + + // Ensure that the save button is visible and enabled before clicking + while (await opslaanKnop.IsVisibleAsync() && await opslaanKnop.IsEnabledAsync()) + { + await opslaanKnop.ClickAsync(); + } + + await page.GetByRole(AriaRole.Listitem).Filter(new () { HasText = skillName }).WaitForAsync(); + return new(page) + { + Naam = skillName + }; + } + } + + internal record class Skill(IPage Page) : IAsyncDisposable + { + public required string Naam { get; init; } + + public async ValueTask DisposeAsync() + { + await Page.Context.Tracing.GroupEndAsync(); + await Page.Context.Tracing.GroupAsync("Cleanup skill"); + // Step 1: Navigate to the Skills management page + await Page.NavigateToSkillsBeheer(); + + // Step 2: Locate the skill listitem by its name + var skillLocator = Page.GetByRole(AriaRole.Listitem).Filter(new() { HasText = Naam }); + + // Step 3: Locate the delete button within the listitem + var deleteButton = skillLocator.GetByRole(AriaRole.Button).And(skillLocator.GetByTitle("Verwijderen")); + + // Step 4: Click the delete button and accept the dialog + using (var _ = Page.AcceptAllDialogs()) + { + await deleteButton.ClickAsync(); + } + + // Step 5: Verify the skill is no longer present in the list + await Page.GetByRole(AriaRole.Heading, new () { Name = "Skills"}).WaitForAsync(); + await skillLocator.WaitForAsync(new() { State = WaitForSelectorState.Hidden }); + await Page.Context.Tracing.GroupEndAsync(); + } + } +} diff --git a/Kiss.Bff.EndToEndTest/NieuwsEnWerkInstructies/Helpers/FeaturedCount.cs b/Kiss.Bff.EndToEndTest/NieuwsEnWerkInstructies/Helpers/FeaturedCount.cs new file mode 100644 index 00000000..b39f9952 --- /dev/null +++ b/Kiss.Bff.EndToEndTest/NieuwsEnWerkInstructies/Helpers/FeaturedCount.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Kiss.Bff.EndToEndTest.NieuwsEnWerkInstructies.Helpers +{ + internal static class FeaturedCount + { + public static async Task GetFeaturedCount(this IPage page) + { + // Declare featuredIndicator outside the try block so it's accessible throughout the method + var featuredIndicator = page.Locator(".featured-indicator"); + await page.WaitForResponseAsync(x => x.Url.Contains("featuredcount")); + if (await featuredIndicator.IsVisibleAsync()) + { + var featureText = await featuredIndicator.TextContentAsync(); + return int.Parse(featureText!); + } + return 0; + } + } +} diff --git a/Kiss.Bff.EndToEndTest/NieuwsEnWerkInstructies/Helpers/Locators.cs b/Kiss.Bff.EndToEndTest/NieuwsEnWerkInstructies/Helpers/Locators.cs new file mode 100644 index 00000000..8a06dc8a --- /dev/null +++ b/Kiss.Bff.EndToEndTest/NieuwsEnWerkInstructies/Helpers/Locators.cs @@ -0,0 +1,26 @@ +namespace Kiss.Bff.EndToEndTest.NieuwsEnWerkInstructies.Helpers +{ + internal static class Locators + { + public static ILocator GetNieuwsSection(this IPage page) => page.Locator("section").Filter(new() { Has = page.GetByRole(AriaRole.Heading, new() { Name = "Nieuws" }) }); + public static ILocator GetWerkinstructiesSection(this IPage page) => page.Locator("section").Filter(new() { Has = page.GetByRole(AriaRole.Heading, new() { Name = "Werkinstructies" }) }); + public static ILocator GetBerichtOnHomePage(this IPage page, Bericht bericht) => page.GetByRole(AriaRole.Article).Filter(new() { Has = page.GetByRole(AriaRole.Heading, new() { Name = bericht.Title }) }); + public static ILocator GetSummaryElement(this IPage page) => page.Locator("summary"); + public static ILocator GetSkillsSummaryElement(this IPage page) => page.GetSummaryElement().Filter(new() { HasText = "Filter op categorie" }); + public static ILocator GetSkillsFieldset(this IPage page) => page.GetByRole(AriaRole.Group, new() { Name = "Filter op categorie" }); + public static ILocator GetWerkberichtTypeSelector(this IPage page) => page.Locator($"#werkbericht-type-input"); + public static ILocator GetNieuwsAndWerkinstructiesSearch(this IPage page) => page.Locator($"#search-input"); + + public static ILocator GetSearchResult(this IPage page) => page.Locator("section").Filter(new() { Has = page.GetByRole(AriaRole.Heading, new() { Name = "Zoekresultaten" }) }); + public static ILocator GetSearchResultFilteredByType(this IPage page,string type) => page.GetSearchResult().GetByRole(AriaRole.Article).Filter(new() { Has = page.Locator("small", new() { HasText = type }) }); + + public static ILocator GetBeheerRowByValue(this IPage page, string title) => page.GetByRole(AriaRole.Row) + .Filter(new() + { + Has = page.GetByRole(AriaRole.Cell, new() { Name = title, Exact = true }).First + }); + public static ILocator GetBeheerTableCell(this IPage page, int col, int row) => + page.Locator($"table tbody tr:nth-child({row}) td:nth-child({col})"); + + } +} diff --git a/Kiss.Bff.EndToEndTest/NieuwsEnWerkInstructies/Helpers/Navigation.cs b/Kiss.Bff.EndToEndTest/NieuwsEnWerkInstructies/Helpers/Navigation.cs new file mode 100644 index 00000000..7956b504 --- /dev/null +++ b/Kiss.Bff.EndToEndTest/NieuwsEnWerkInstructies/Helpers/Navigation.cs @@ -0,0 +1,46 @@ +namespace Kiss.Bff.EndToEndTest.NieuwsEnWerkInstructies.Helpers +{ + internal static class Navigation + { + public static async Task NavigateToNieuwsWerkinstructiesBeheer(this IPage page) + { + var beheerlink = page.GetByRole(AriaRole.Link, new() { Name = "Beheer" }); + var berichtenlink = page.GetByRole(AriaRole.Link, new() { Name = "Nieuws en werkinstructies" }); + + await beheerlink.Or(berichtenlink).First.WaitForAsync(); + + if (await beheerlink.IsVisibleAsync()) + { + await beheerlink.ClickAsync(); + } + + await beheerlink.WaitForAsync(new() { State = WaitForSelectorState.Hidden }); + + if (await berichtenlink.GetAttributeAsync("aria-current") != "page") + { + await berichtenlink.ClickAsync(); + await page.GetByRole(AriaRole.Heading, new() { Name = "Berichten" }).WaitForAsync(); + } + } + + public static async Task NavigateToSkillsBeheer(this IPage page) + { + var beheerlink = page.GetByRole(AriaRole.Link, new() { Name = "Beheer" }); + var skillslink = page.GetByRole(AriaRole.Link, new() { Name = "Skills" }); + await beheerlink.Or(skillslink).First.WaitForAsync(); + + if (await beheerlink.IsVisibleAsync()) + { + await beheerlink.ClickAsync(); + } + + await beheerlink.WaitForAsync(new() { State = WaitForSelectorState.Hidden }); + + if (await skillslink.GetAttributeAsync("aria-current") != "page") + { + await skillslink.ClickAsync(); + await page.GetByRole(AriaRole.Heading, new() { Name = "Skills" }).WaitForAsync(); + } + } + } +} diff --git a/Kiss.Bff.EndToEndTest/NieuwsEnWerkInstructies/NieuwsEnWerkInstructiesScenarios.cs b/Kiss.Bff.EndToEndTest/NieuwsEnWerkInstructies/NieuwsEnWerkInstructiesScenarios.cs new file mode 100644 index 00000000..887f76ea --- /dev/null +++ b/Kiss.Bff.EndToEndTest/NieuwsEnWerkInstructies/NieuwsEnWerkInstructiesScenarios.cs @@ -0,0 +1,971 @@ + +using Kiss.Bff.EndToEndTest.Helpers; +using Kiss.Bff.EndToEndTest.NieuwsEnWerkInstructies.Helpers; + +namespace Kiss.Bff.EndToEndTest.NieuwsEnWerkInstructies; + +[TestClass] +public class NieuwsEnWerkInstructiesScenarios : KissPlaywrightTest +{ + [TestMethod] + public async Task Scenario01() + { + await Step("Given there is at least 1 nieuwsbericht"); + await using var news = await Page.CreateBericht(new() { Title = "Playwright test nieuwsbericht", BerichtType = BerichtType.Nieuws }); + + await Step("And there is at least 1 werkinstructie"); + await using var werkbericht = await Page.CreateBericht(new() { Title = "Playwright test werkinstructie", BerichtType = BerichtType.Werkinstructie }); + + await Step("When the user navigates to the HOME Page"); + await Page.GotoAsync("/"); + + await Step("Then nieuwsberichten are displayed"); + await Expect(Page.GetNieuwsSection().GetByRole(AriaRole.Article).First).ToBeVisibleAsync(); + + await Step("And werkinstructies are displayed"); + await Expect(Page.GetWerkinstructiesSection().GetByRole(AriaRole.Article).First).ToBeVisibleAsync(); + } + + [TestMethod] + public async Task Scenario02() + { + await Step("Given there is at least 1 important message"); + await using var testbericht = await Page.CreateBericht(new() { Title = "Playwright test bericht belangrijk", IsImportant = true }); + + await Step("When navigates to the HOME Page"); + await Page.GotoAsync("/"); + + await Step("Then the count of the important messages is displayed in the News and Instructions tabs."); + var count = await Page.GetFeaturedCount(); + Assert.AreNotEqual(0, count); + } + + [TestMethod] + public async Task Scenario03() + { + await Step("Given there is at least 1 nieuwsbericht"); + await using var testbericht = await Page.CreateBericht(new() { Title = "Playwright test bericht", Body = "Inhoud die we gaan verbergen" }); + var article = Page.GetBerichtOnHomePage(testbericht); + var markeerGelezenButton = article.GetByRole(AriaRole.Button).And(article.GetByTitle("Markeer als gelezen")); + var markeerOngelezenButton = article.GetByRole(AriaRole.Button).And(article.GetByTitle("Markeer als ongelezen")); + var body = article.GetByText(testbericht.Body!); + + await Step("When the user navigates to the HOME Page"); + await Page.GotoAsync("/"); + + await Step("And clicks on the book icon within the nieuwsbericht card"); + await markeerGelezenButton.ClickAsync(); + + await Step("Then the button title on hover changes to 'markeer ongelezen'"); + await Expect(markeerGelezenButton).ToBeHiddenAsync(); + await Expect(markeerOngelezenButton).ToBeVisibleAsync(); + + await Step("And the body of the nieuwsbericht is hidden"); + await Expect(body).ToBeHiddenAsync(); + } + + [TestMethod] + public async Task Scenario04() + { + var newsArticles = Page.GetNieuwsSection().GetByRole(AriaRole.Article); + + await Step("Given there are at least 20 nieuwsberichten"); + var berichtRequests = Enumerable.Range(1, 20) + .Select(x => new CreateBerichtRequest + { + Title = "Playwright test bericht" + x + }); + await using var berichten = await Page.CreateBerichten(berichtRequests); + + await Step("When the user navigates to the HOME Page"); + await Page.GotoAsync("/"); + + var initialFirstArticleAriaSnapshot = await newsArticles.First.AriaSnapshotAsync(); + + await Step("And clicks on the \"Next page\" button for the 'Nieuws' section to go to the next page"); + var nextPageButton = Page.GetNieuwsSection().GetNextPageLink(); + await nextPageButton.ClickAsync(); + + await Step("Then the user should see 10 new articles on the next page"); + await Expect(newsArticles).ToHaveCountAsync(10); + await Expect(newsArticles.First).Not.ToMatchAriaSnapshotAsync(initialFirstArticleAriaSnapshot); + + await Step("And the current page number should be 2"); + var currentPageButton = Page.GetNieuwsSection().GetCurrentPageLink(); + var page2Button = Page.GetNieuwsSection().GetByLabel("Pagina 2"); + var aButtonThatIsTheCurrentPageAndHasLabelPagina2 = currentPageButton.And(page2Button); + await Expect(aButtonThatIsTheCurrentPageAndHasLabelPagina2).ToBeVisibleAsync(); + } + + [TestMethod] + public async Task Scenario05() + { + var newSection = Page.GetNieuwsSection(); + var newsArticles = Page.GetNieuwsSection().GetByRole(AriaRole.Article); + + await Step("Given there are at least 20 nieuwsberichten"); + var berichtRequests = Enumerable.Range(1, 20) + .Select(x => new CreateBerichtRequest + { + Title = "Playwright test bericht" + x + }); + await using var berichten = await Page.CreateBerichten(berichtRequests); + + await Step("And the user is on the HOME page"); + await Page.GotoAsync("/"); + await Expect(Page.GetNieuwsSection()).ToBeVisibleAsync(); + + // Locate the 'Next' page button using the pagination structure + await Step("And is on page 2 with 10 articles displayed"); + var nextPageButton = Page.GetNieuwsSection().GetNextPageLink(); + await nextPageButton.ClickAsync(); + await Expect(newsArticles).ToHaveCountAsync(10); + + var intialAriaSnapshot = await newsArticles.First.AriaSnapshotAsync(); + + await Step("When the user clicks on the \"Previous\" button for the 'Nieuws' section to go to the next page"); + var previousPageButton = Page.GetNieuwsSection().GetPreviousPageLink(); + await previousPageButton.ClickAsync(); + + await Step("Then the user should see 10 different articles on the first page"); + await Expect(newsArticles).ToHaveCountAsync(10); + await Expect(newsArticles.First).Not.ToMatchAriaSnapshotAsync(intialAriaSnapshot); + + await Step("And the current page number should be 1"); + var currentPageButton = Page.GetNieuwsSection().GetCurrentPageLink(); + var page1Button = Page.GetNieuwsSection().GetByLabel("Pagina 1"); + var aButtonThatIsTheCurrentPageAndHasLabelPagina1 = currentPageButton.And(page1Button); + await Expect(aButtonThatIsTheCurrentPageAndHasLabelPagina1).ToBeVisibleAsync(); + } + + [TestMethod] + public async Task Scenario06() + { + await Step("Given there are at least 20 werkinstructies"); + var berichtRequests = Enumerable.Range(1, 20) + .Select(x => new CreateBerichtRequest + { + Title = "Playwright test bericht" + x, + BerichtType = BerichtType.Werkinstructie + }); + await using var berichten = await Page.CreateBerichten(berichtRequests); + var articles = Page.GetWerkinstructiesSection().GetByRole(AriaRole.Article); + + await Step("And the user is on the HOME Page"); + await Page.GotoAsync("/"); + await Expect(Page.GetWerkinstructiesSection()).ToBeVisibleAsync(); + + await Step("And is on page 2 with 10 werkinstructies displayed"); + var nextPageButton = Page.GetWerkinstructiesSection().GetNextPageLink(); + await nextPageButton.ClickAsync(); + await Expect(articles).ToHaveCountAsync(10); + + var intialFirstArticleAriaSnapshot = await articles.First.AriaSnapshotAsync(); + + await Step("When the user clicks on the \"Previous\" button for the 'Werkinstructies' section to go to the next page"); + var previousPageButton = Page.GetWerkinstructiesSection().GetPreviousPageLink(); + await previousPageButton.ClickAsync(); + + await Step("Then the user should see 10 different werkinstructies on the first page"); + await Expect(articles).ToHaveCountAsync(10); + await Expect(articles.First).Not.ToMatchAriaSnapshotAsync(intialFirstArticleAriaSnapshot); + + await Step("And the current page number should be 1"); + var currentPageButton = Page.GetWerkinstructiesSection().GetCurrentPageLink(); + var page1Button = Page.GetWerkinstructiesSection().GetByLabel("Pagina 1"); + var aButtonThatIsTheCurrentPageAndHasLabelPagina1 = currentPageButton.And(page1Button); + await Expect(aButtonThatIsTheCurrentPageAndHasLabelPagina1).ToBeVisibleAsync(); + } + + [TestMethod] + public async Task Scenario07() + { + await Step("Given there are at least 20 werkinstructies"); + var berichtRequests = Enumerable.Range(1, 20) + .Select(x => new CreateBerichtRequest + { + Title = "Playwright test bericht" + x, + BerichtType = BerichtType.Werkinstructie + }); + + await using var berichten = await Page.CreateBerichten(berichtRequests); + var articles = Page.GetWerkinstructiesSection().GetByRole(AriaRole.Article); + + await Step("And the user is on the HOME Page"); + await Page.GotoAsync("/"); + await Expect(Page.GetWerkinstructiesSection()).ToBeVisibleAsync(); + + // Locate the 'Next' page button using the pagination structure + var nextPageButton = Page.GetWerkinstructiesSection().GetNextPageLink(); + var werkinstructies = Page.GetWerkinstructiesSection().GetByRole(AriaRole.Article); + + await Step("And is on the last page of werkinstructies"); + + // keep clicking on the next page button until it's disabled + while (!await nextPageButton.IsDisabledPageLink()) + { + await nextPageButton.ClickAsync(); + await werkinstructies.First.WaitForAsync(); + } + + var initialFirstArticleAriaSnapshot = await articles.First.AriaSnapshotAsync(); + + await Step("When the user clicks on the \"Next\" button for the ‘Werkinstructies’ section"); + + await Assert.ThrowsExceptionAsync( + () => nextPageButton.ClickAsync(new() { Timeout = 1000 }), + "Expected the button to not be clickable, but it was"); + + await Step("Then the user remains on the last page"); + + await Step("And no additional werkinstructies are displayed"); + await Expect(articles.First).ToMatchAriaSnapshotAsync(initialFirstArticleAriaSnapshot); + } + + [TestMethod] + public async Task Scenario08() + { + await Step("Given there is a nieuwsbericht that is read"); + await using var bericht = await Page.CreateBericht(new() { Title = "Bericht playwright gelezen/ongelezen", Body = "Text to look for" }); + await Page.GotoAsync("/"); + var article = Page.GetBerichtOnHomePage(bericht); + var articleBody = article.GetByText(bericht.Body); + var markeerGelezenButton = article.GetByRole(AriaRole.Button).And(article.GetByTitle("Markeer als gelezen")); + var markeerOngelezenButton = article.GetByRole(AriaRole.Button).And(article.GetByTitle("Markeer als ongelezen")); + var articleHeading = article.GetByRole(AriaRole.Heading); + await markeerGelezenButton.ClickAsync(); + await Expect(articleBody).ToBeHiddenAsync(); + + await Step("And the user is on the HOME Page"); + await Page.GotoAsync("/"); + + await Step("When the user clicks the 'Markeer als ongelezen' button"); + await markeerOngelezenButton.ClickAsync(); + + await Step("Then content of the nieuwsbericht is visible"); + await Expect(article).ToContainTextAsync(bericht.Body); + } + + [TestMethod] + public async Task Scenario09() + { + await Step("Given there are at least two skills"); + await using var skill1 = await Page.CreateSkill(Guid.NewGuid().ToString()); + await using var skill2 = await Page.CreateSkill(Guid.NewGuid().ToString()); + + await Step("And there is exactly one nieuwsbericht related to the first skill"); + await using var berichtWithSkill1 = await Page.CreateBericht(new CreateBerichtRequest { Title = Guid.NewGuid().ToString(), Skill = skill1.Naam }); + + await Step("And there is exactly one nieuwsbericht related to the second skill"); + await using var berichtWithSkill2 = await Page.CreateBericht(new CreateBerichtRequest { Title = Guid.NewGuid().ToString() }); + + await Step("And there is at least one nieuwsbericht without a relation to any skill"); + await using var berichtWithoutSkill = await Page.CreateBericht(new CreateBerichtRequest { Title = Guid.NewGuid().ToString(), Skill = skill2.Naam }); + + await Step("And the user is on the HOME Page"); + await Page.GotoAsync("/"); + + await Step("When the user selects the first skill from the filter options"); + await Page.GetSkillsSummaryElement().ClickAsync(); + await Page.GetSkillsFieldset().GetByRole(AriaRole.Checkbox, new() { Name = skill1.Naam }).CheckAsync(); + + await Step("Then only the article related to the first skill is visible"); + var articles = Page.GetNieuwsSection().GetByRole(AriaRole.Article); + await Expect(articles).ToHaveCountAsync(1); + await Expect(articles.GetByRole(AriaRole.Heading, new() { Name = berichtWithSkill1.Title })).ToBeVisibleAsync(); + } + + [TestMethod] + public async Task Scenario10() + { + await Step("Given there is a skill that is not linked to any article"); + await using var skill = await Page.CreateSkill(Guid.NewGuid().ToString()); + + await Step("And the user is on the HOME Page"); + await Page.GotoAsync("/"); + + await Step("When the user selects the skill from the filter options"); + await Page.GetSkillsSummaryElement().ClickAsync(); + await Page.GetSkillsFieldset().GetByRole(AriaRole.Checkbox, new() { Name = skill.Naam }).CheckAsync(); + + await Step("Then no articles are visible"); + // wait until the spinner is gone + await Expect(Page.Locator(".spinner")).ToBeHiddenAsync(); + var articles = Page.GetByRole(AriaRole.Article); + await Expect(articles).ToBeHiddenAsync(); + } + + [TestMethod] + public async Task Scenario11() + { + await Step("Given a unique text (uuid)"); + + var uniqueTitle = Guid.NewGuid().ToString(); + + await Step("Given there is exactly 1 werkinstructie with this text in the title"); + + var werkbericht = await Page.CreateBericht(new() { Title = uniqueTitle, BerichtType = BerichtType.Werkinstructie }); + + await Step("And there is exactly 1 nieuwsbericht with this text in the title"); + + var nieuws = await Page.CreateBericht(new() { Title = uniqueTitle, BerichtType = BerichtType.Nieuws }); + + await Step("And the user is on the HOME Page"); + + await Page.GotoAsync("/"); + + await Step("When the user selects 'Nieuws' from the filter dropdown"); + + await Page.GetWerkberichtTypeSelector().SelectOptionAsync("Nieuws"); + + await Step("And searches for the unique text"); + + await Page.GetNieuwsAndWerkinstructiesSearch().FillAsync(uniqueTitle); + await Page.GetNieuwsAndWerkinstructiesSearch().PressAsync("Enter"); + + + await Step("Then exactly one news article should be displayed"); + + await Expect(Page.GetSearchResult().GetByRole(AriaRole.Article)).ToHaveCountAsync(1); + + await Step("And no work instructions should be visible"); + + await Expect(Page.GetWerkinstructiesSection()).ToBeHiddenAsync(); + + + } + + [TestMethod] + public async Task Scenario12() + { + await Step("Given a unique text (uuid)"); + + var uniqueTitle = Guid.NewGuid().ToString(); + + await Step("Given there is exactly 1 werkinstructie with this text in the title"); + + await using var werkbericht = await Page.CreateBericht(new() { Title = uniqueTitle, BerichtType = BerichtType.Werkinstructie }); + + await Step("And there is exactly 1 nieuwsbericht with this text in the title"); + + await using var nieuws = await Page.CreateBericht(new() { Title = uniqueTitle, BerichtType = BerichtType.Nieuws }); + + await Step("And the user is on the HOME Page"); + + await Page.GotoAsync("/"); + + await Step("When the user selects 'Werkinstructie' from the filter dropdown"); + + await Page.GetWerkberichtTypeSelector().SelectOptionAsync("Werkinstructie"); + + await Step("And searches for the unique text"); + + await Page.GetNieuwsAndWerkinstructiesSearch().FillAsync(uniqueTitle); + await Page.GetNieuwsAndWerkinstructiesSearch().PressAsync("Enter"); + + await Step("Then exactly 1 work instruction should be displayed"); + + await Expect(Page.GetSearchResult().GetByRole(AriaRole.Article)).ToHaveCountAsync(1); + + await Step("And no news articles should be visible"); + + await Expect(Page.GetNieuwsSection()).ToBeHiddenAsync(); + + } + + [TestMethod] + public async Task Scenario13() + { + await Step("Given there are at least 3 skills"); + + var skill1 = Guid.NewGuid().ToString(); + var skill2 = Guid.NewGuid().ToString(); + var skill3 = Guid.NewGuid().ToString(); + + await using var skillItem1 = await Page.CreateSkill(skill1); + await using var skillItem2 = await Page.CreateSkill(skill2); + await using var skillItem3 = await Page.CreateSkill(skill3); + + await Step("And there is exactly one nieuwsbericht related to the first skill"); + + string uniqueTitle = Guid.NewGuid().ToString(); + await using var nieuwsWithSkill1 = await Page.CreateBericht(new() { Title = uniqueTitle, BerichtType = BerichtType.Nieuws, Skill = skill1 }); + + + await Step("And there is exactly one werkinstructie related to the first skill"); + + uniqueTitle = Guid.NewGuid().ToString(); + await using var werkberichtWithSkill1 = await Page.CreateBericht(new() { Title = uniqueTitle, BerichtType = BerichtType.Werkinstructie, Skill = skill1 }); + + await Step("And there is exactly one nieuwsbericht related to the second skill"); + + uniqueTitle = Guid.NewGuid().ToString(); + await using var nieuwsWithSkill2 = await Page.CreateBericht(new() { Title = uniqueTitle, BerichtType = BerichtType.Nieuws, Skill = skill2 }); + + await Step("And there is exactly one werkinstructie related to the second skill"); + + uniqueTitle = Guid.NewGuid().ToString(); + await using var werkberichtWithSkill2 = await Page.CreateBericht(new() { Title = uniqueTitle, BerichtType = BerichtType.Werkinstructie, Skill = skill2 }); + + await Step("And there is exactly one nieuwsbericht related to the third skill"); + + uniqueTitle = Guid.NewGuid().ToString(); + await using var nieuwsWithSkill3 = await Page.CreateBericht(new() { Title = uniqueTitle, BerichtType = BerichtType.Nieuws, Skill = skill3 }); + + await Step("And there is exactly one werkinstructie related to the third skill"); + + uniqueTitle = Guid.NewGuid().ToString(); + await using var werkberichtWithSkill3 = await Page.CreateBericht(new() { Title = uniqueTitle, BerichtType = BerichtType.Werkinstructie, Skill = skill3 }); + + await Step("And there is at least one nieuwsbericht without a relation to any skill"); + + uniqueTitle = Guid.NewGuid().ToString(); + await using var nieuwsWithSkill4 = await Page.CreateBericht(new() { Title = uniqueTitle, BerichtType = BerichtType.Nieuws }); + + await Step("And there is at least one werkinstructie without a relation to any skill"); + + uniqueTitle = Guid.NewGuid().ToString(); + await using var werkberichtWithSkill4 = await Page.CreateBericht(new() { Title = uniqueTitle, BerichtType = BerichtType.Werkinstructie }); + + await Step("And the user is on the HOME Page"); + + await Page.GotoAsync("/"); + + await Step("When the user selects the first skill from the filter options"); + + await Page.GetSkillsSummaryElement().ClickAsync(); + await Page.GetSkillsFieldset().GetByRole(AriaRole.Checkbox, new() { Name = skill1 }).CheckAsync(); + + + await Step("And the user selects the second skill from the filter options"); + + await Page.GetSkillsFieldset().GetByRole(AriaRole.Checkbox, new() { Name = skill2 }).CheckAsync(); + + await Step("Then only the two nieuwsberichten and werkinstructies related to the first and second skill are visible"); + + var nieuwsSection = Page.GetNieuwsSection(); + var werkinstructiesSection = Page.GetWerkinstructiesSection(); + + await Expect(nieuwsSection.GetByRole(AriaRole.Article)).ToHaveCountAsync(2); + await Expect(nieuwsSection.GetByRole(AriaRole.Heading, new() { Name = nieuwsWithSkill1.Title })).ToBeVisibleAsync(); + await Expect(nieuwsSection.GetByRole(AriaRole.Heading, new() { Name = nieuwsWithSkill2.Title })).ToBeVisibleAsync(); + await Expect(nieuwsSection.GetByRole(AriaRole.Heading, new() { Name = nieuwsWithSkill3.Title })).ToBeHiddenAsync(); + + await Expect(werkinstructiesSection.GetByRole(AriaRole.Article)).ToHaveCountAsync(2); + await Expect(werkinstructiesSection.GetByRole(AriaRole.Heading, new() { Name = werkberichtWithSkill1.Title })).ToBeVisibleAsync(); + await Expect(werkinstructiesSection.GetByRole(AriaRole.Heading, new() { Name = werkberichtWithSkill2.Title })).ToBeVisibleAsync(); + await Expect(werkinstructiesSection.GetByRole(AriaRole.Heading, new() { Name = werkberichtWithSkill3.Title })).ToBeHiddenAsync(); + + } + + [TestMethod] + public async Task Scenario14() + { + await Step("Given a unique text (uuid)"); + + var uniqueTitle = Guid.NewGuid().ToString(); + + await Step("Given there is exactly one nieuwsbericht with that text as the title"); + + await using var nieuwsbericht = await Page.CreateBericht(new() { Title = uniqueTitle, BerichtType = BerichtType.Nieuws }); + + await Step("And there is exactly one werkinstructie with that text as the title"); + + await using var werkinstructie = await Page.CreateBericht(new() { Title = uniqueTitle, BerichtType = BerichtType.Werkinstructie }); + + await Step("And the user is on the HOME Page"); + + await Page.GotoAsync("/"); + + await Step("When the user selects 'Alle' from the filter dropdown"); + + await Page.GetWerkberichtTypeSelector().SelectOptionAsync("Alle"); + + await Step("And searches for the unique text"); + + await Page.GetNieuwsAndWerkinstructiesSearch().FillAsync(uniqueTitle); + await Page.GetNieuwsAndWerkinstructiesSearch().PressAsync("Enter"); + + await Step("Then exactly one nieuwsbericht and exactly one werkinstructie are visible"); + + await Expect(Page.GetSearchResultFilteredByType("Werkinstructie")).ToHaveCountAsync(1); + await Expect(Page.GetSearchResultFilteredByType("Nieuws")).ToHaveCountAsync(1); + + + } + + [TestMethod] + public async Task Scenario15() + { + await Step("Given a unique text (uuid)"); + + var uniqueTitle = Guid.NewGuid().ToString(); + var otherText = Guid.NewGuid().ToString(); + + await Step("Given there is exactly one nieuwsbericht with that text as the title"); + + await using var nieuws1 = await Page.CreateBericht(new() { Title = uniqueTitle, BerichtType = BerichtType.Nieuws }); + + await Step("And there is exactly one werkinstructie with that text as the title"); + + await using var werkinstructie1 = await Page.CreateBericht(new() { Title = uniqueTitle, BerichtType = BerichtType.Werkinstructie }); + + await Step("And there is at least one nieuwsbericht without that text"); + + await using var nieuws2 = await Page.CreateBericht(new() { Title = otherText, BerichtType = BerichtType.Nieuws }); + + await Step("And there is at least one werkinstructie without that text"); + + await using var werkinstructie2 = await Page.CreateBericht(new() { Title = otherText, BerichtType = BerichtType.Werkinstructie }); + + await Step("And the user is on the HOME Page"); + + await Page.GotoAsync("/"); + + await Step("And has selected 'Alle' from the filter dropdown"); + + await Page.GetWerkberichtTypeSelector().SelectOptionAsync("Alle"); + + await Step("And has searched for the unique text"); + + await Page.GetNieuwsAndWerkinstructiesSearch().FillAsync(uniqueTitle); + + await Step("When the user clicks on the close icon in the search bar"); + + await Page.GetNieuwsAndWerkinstructiesSearch().ClearAsync(); + + await Step("Then at least two werkinstructies should be visible"); + + Assert.IsTrue((await Page.GetWerkinstructiesSection().GetByRole(AriaRole.Article).CountAsync()) >= 2, "at least two werkinstructies should be visible"); + + await Step("And at least two nieuwsberichten should be visible"); + + Assert.IsTrue((await Page.GetNieuwsSection().GetByRole(AriaRole.Article).CountAsync()) >= 2, "at least two nieuwsberichten should be visible"); + + + } + [TestMethod] + public void Scenario16() + { + Assert.Inconclusive($"This scenario seems to be a duplicate of {nameof(Scenario09)}"); + } + + [TestMethod] + public async Task Scenario17() + { + await Step("Given there is at least 1 nieuwsbericht"); + + await using var nieuws = await Page.CreateBericht(new() { Title = Guid.NewGuid().ToString(), BerichtType = BerichtType.Nieuws }); + + await Step("And the user is on the Nieuws and werkinstructiesscreen available under Beheer"); + + await Page.GotoAsync("/"); + + await Step("Then the nieuwsbericht should be displayed in a list"); + + var nieuwsSection = Page.GetNieuwsSection(); + await Expect(nieuwsSection.GetByRole(AriaRole.Heading, new() { Name = nieuws.Title })).ToBeVisibleAsync(); + } + + [TestMethod] + public async Task Scenario18() + { + await Step("Given there is at least 1 nieuwsbericht"); + + var nieuws = await Page.CreateBericht(new() { Title = Guid.NewGuid().ToString(), BerichtType = BerichtType.Nieuws }); + + await Step("And the user is on the Nieuws and werkinstructiesscreen available under Beheer"); + + await Page.NavigateToNieuwsWerkinstructiesBeheer(); + + await Step("When user clicks on the delete icon of the nieuwsbericht in the list"); + + var nieuwsRow = Page.GetBeheerRowByValue(nieuws.Title); + + await Step("And confirms a pop-up window with the message ‘Weet u zeker dat u dit bericht wilt verwijderen?’"); + + var deleteButton = nieuwsRow.GetByTitle("Verwijder").First; + + using (var _ = Page.AcceptAllDialogs()) + { + await deleteButton.ClickAsync(); + } + + await Step("Then the nieuwsbericht is no longer in the list"); + + var deletedRow = Page.GetBeheerRowByValue(nieuws.Title); + + await Expect(deletedRow).ToBeHiddenAsync(); + + + } + + [TestMethod] + public async Task Scenario19() + { + await Step("Given there is at least 1 werkinstructie"); + + var werkinstructie = await Page.CreateBericht(new() { Title = Guid.NewGuid().ToString(), BerichtType = BerichtType.Werkinstructie }); + + await Step("And the user is on the Nieuws and werkinstructiesscreen available under Beheer"); + + await Page.NavigateToNieuwsWerkinstructiesBeheer(); + + await Step("When user clicks on the delete icon of the werkinstructie in the list"); + + var werkinstructieRow = Page.GetBeheerRowByValue(werkinstructie.Title); + + await Step("And confirms a pop-up window with the message ‘Weet u zeker dat u dit bericht wilt verwijderen?’"); + + var deleteButton = werkinstructieRow.GetByTitle("Verwijder").First; + + using (var _ = Page.AcceptAllDialogs()) + { + await deleteButton.ClickAsync(); + } + + await Step("Then the werkinstructie is no longer in the list"); + + await Expect(Page.GetBeheerRowByValue(werkinstructie.Title)).ToBeHiddenAsync(); + + } + + [TestMethod] + public async Task Scenario20() + { + await Step("Given there is at least 1 nieuwsbericht"); + + await using var skill = await Page.CreateSkill(Guid.NewGuid().ToString()); + await using var nieuw = await Page.CreateBericht(new() { Title = Guid.NewGuid().ToString(), BerichtType = BerichtType.Nieuws, Skill=skill.Naam , Body= Guid.NewGuid().ToString()}); + + await Step("And the user is on the Nieuws and werkinstructiesscreen available under Beheer"); + + await Page.NavigateToNieuwsWerkinstructiesBeheer(); + + await Step("When the user clicks on the arrow button of the nieuwsbericht"); + + await Page.GetBeheerRowByValue(nieuw.Title).GetByRole(AriaRole.Link).ClickAsync(); + + await Step("Then the Type, Titel, Inhoud, Publicatiedatum, Publicatie-einddatum and Skills of the nieuwsbericht are visible in a details screen"); + + await Expect(Page.Locator("#titel")).ToHaveValueAsync(nieuw.Title); + await Expect(Page.GetByText("Nieuws", new() { Exact = true })).ToBeCheckedAsync(); + await Expect(Page.Locator("label:text('Inhoud') + div")).ToContainTextAsync(nieuw.Body); + await Expect(Page.Locator("#publicatieDatum")).ToHaveValueAsync(nieuw.PublicatieDatum.ToString("yyyy-MM-ddTHH:mm")); + await Expect(Page.GetByLabel("Publicatie-einddatum")).ToHaveValueAsync(nieuw.PublicatieEinddatum.ToString("yyyy-MM-ddTHH:mm")); + await Expect(Page.GetByRole(AriaRole.Checkbox, new() { Name = skill.Naam })).ToBeCheckedAsync(); + } + + [TestMethod] + public async Task Scenario21() + { + await Step("Given there is at least 1 nieuwsbericht"); + + await using var skill = await Page.CreateSkill(Guid.NewGuid().ToString()); + var nieuw = await Page.CreateBericht(new() { Title = Guid.NewGuid().ToString(), BerichtType = BerichtType.Nieuws, Skill = skill.Naam }); + + await Step("And the user is on the Nieuws and werkinstructiesscreen available under Beheer"); + + await Page.NavigateToNieuwsWerkinstructiesBeheer(); + + await Step("And the user has clicked on the arrow button of the nieuwsbericht"); + + await Page.GetBeheerRowByValue(nieuw.Title).GetByRole(AriaRole.Link).ClickAsync(); + + await Step("And the news detail screen is displayed"); + + await Expect(Page.Locator("#titel")).ToHaveValueAsync(nieuw.Title); + await Expect(Page.GetByText("Nieuws", new() { Exact = true })).ToBeCheckedAsync(); + await Expect(Page.GetByRole(AriaRole.Checkbox, new() { Name = skill.Naam })).ToBeCheckedAsync(); + + await Step("When the user updates the title section of news"); + + var updatedTitle = Guid.NewGuid().ToString(); + await Page.GetByLabel("Titel").FillAsync(updatedTitle); + + await Step("And clicks on the submit button"); + + var opslaanKnop = Page.GetByRole(AriaRole.Button, new() { Name = "Opslaan" }); + while (await opslaanKnop.IsVisibleAsync() && await opslaanKnop.IsEnabledAsync()) + { + await opslaanKnop.ClickAsync(); + } + + await Step("Then the updated news title is displayed in Berichten screen"); + + + await Expect(Page.GetBeheerTableCell(1,1)).ToHaveTextAsync(updatedTitle); + + await Step("And the “Gewijzigd op” field gets updated with the latest time"); + + await Expect(Page.GetBeheerTableCell(5, 1)).ToHaveTextAsync(DateTime.Now.ToString("dd-MM-yyyy, HH:mm")); + } + + [TestMethod] + public async Task Scenario22() + { + await Step("Given there is at least 1 nieuwsbericht"); + + await using var skill = await Page.CreateSkill(Guid.NewGuid().ToString()); + await using var nieuw = await Page.CreateBericht(new() { Title = Guid.NewGuid().ToString(), BerichtType = BerichtType.Nieuws, Skill = skill.Naam }); + + + await Step("And the user is on the Nieuws and werkinstructiesscreen available under Beheer"); + + await Page.NavigateToNieuwsWerkinstructiesBeheer(); + + await Step("And the user has clicked on the arrow button of the nieuwsbericht"); + + await Page.GetBeheerRowByValue(nieuw.Title).GetByRole(AriaRole.Link).ClickAsync(); + + await Step("And the news detail screen is displayed"); + + await Expect(Page.Locator("#titel")).ToHaveValueAsync(nieuw.Title); + await Expect(Page.GetByText("Nieuws", new() { Exact = true })).ToBeCheckedAsync(); + await Expect(Page.GetByRole(AriaRole.Checkbox, new() { Name = skill.Naam })).ToBeCheckedAsync(); + + await Step("When the user updates the Publicatiedatum section of the nieuwsbericht to a future date"); + + var updatedPublicatieDatum = nieuw.PublicatieDatum.AddDays(30); + + await Page.GetByLabel("Publicatiedatum").FillAsync(updatedPublicatieDatum.ToString("yyyy-MM-ddTHH:mm")); + + await Step("And clicks on the submit button"); + var opslaanKnop = Page.GetByRole(AriaRole.Button, new() { Name = "Opslaan" }); + while (await opslaanKnop.IsVisibleAsync() && await opslaanKnop.IsEnabledAsync()) + { + await opslaanKnop.ClickAsync(); + } + await Step("Then the nieuwsbericht with the updated Publicatiedatum is displayed in the Berichten screen"); + + await Expect(Page.GetBeheerTableCell(3, 1)).ToHaveTextAsync(updatedPublicatieDatum.ToString("dd-MM-yyyy, HH:mm")); + + } + + [TestMethod] + public async Task Scenario23() + { + await Step("Given there is at least 1 nieuwsbericht"); + + await using var skill = await Page.CreateSkill(Guid.NewGuid().ToString()); + await using var nieuws = await Page.CreateBericht(new() { Title = Guid.NewGuid().ToString(), BerichtType = BerichtType.Nieuws, Skill = skill.Naam }); + + await Step("And the user is on the Nieuws and werkinstructiesscreen available under Beheer"); + + await Page.NavigateToNieuwsWerkinstructiesBeheer(); + + await Step("And the user has clicked on the arrow button of the nieuwsbericht"); + + await Page.GetBeheerRowByValue(nieuw.Title).GetByRole(AriaRole.Link).ClickAsync(); + + await Step("And the news detail screen is displayed"); + + await Expect(Page.Locator("#titel")).ToHaveValueAsync(nieuw.Title); + await Expect(Page.GetByText("Nieuws", new() { Exact = true })).ToBeCheckedAsync(); + await Expect(Page.GetByRole(AriaRole.Checkbox, new() { Name = skill.Naam })).ToBeCheckedAsync(); + + + await Step("When the user checks the ‘belangrijk’ checkbox"); + + await Page.GetByRole(AriaRole.Checkbox, new() { Name = "Belangrijk" }).CheckAsync(); + + + await Step("And clicks on the submit button"); + + var opslaanKnop = Page.GetByRole(AriaRole.Button, new() { Name = "Opslaan" }); + while (await opslaanKnop.IsVisibleAsync() && await opslaanKnop.IsEnabledAsync()) + { + await opslaanKnop.ClickAsync(); + } + + await Step("And navigates to the home screen of the KISS environment"); + + await Page.GotoAsync("/"); + + await Step("And navigates to the page containing the nieuwsbericht selected earlier"); + await Page.GetNieuwsAndWerkinstructiesSearch().FillAsync(nieuw.Title); + await Page.GetNieuwsAndWerkinstructiesSearch().PressAsync("Enter"); + + await Step("Then the nieuwsbericht should be displayed with the ‘belangrijk’ flag"); + + await Expect(Page.GetSearchResultFilteredByType("Nieuws")).ToHaveCountAsync(1); + await Expect(Page.GetSearchResultFilteredByType("Nieuws").GetByText("Belangrijk")).ToBeVisibleAsync(); + + } + + [TestMethod] + public async Task Scenario24() + { + await Step("Given the user is on the Nieuws and werkinstructiesscreen available under Beheer"); + + await Step("When the user clicks on the “Toevoegen” button"); + + await Step("And selects ‘Nieuws’ as ‘Type’"); + + await Step("And fills in the ‘Titel’ and ‘Inhoud’ fields"); + + await Step("And clicks on the submit button"); + + await Step("Then the nieuwsbericht is displayed in Berichten"); + + Assert.Inconclusive("Not implemented yet"); + } + + [TestMethod] + public async Task Scenario25() + { + await Step("Given the user is on the Nieuws and werkinstructiesscreen available under Beheer"); + + await Step("When the user clicks on the “Toevoegen” button"); + + await Step("And selects ‘Nieuws’ as ‘Type’"); + + await Step("And fills in the ‘Titel’ and ‘Inhoud’ fields"); + + await Step("And clicks on the submit button"); + + await Step("And navigates to the page containing the nieuwsbericht created earlier "); + + await Step("Then the nieuwsbericht should be displayed"); + + Assert.Inconclusive("Not implemented yet"); + } + + [TestMethod] + public async Task Scenario26() + { + await Step("Given the user is on the Nieuws and werkinstructiesscreen available under Beheer"); + + await Step("When the user clicks on the “Toevoegen” button"); + + await Step("And selects ‘Werkinstructie’ as ‘Type’"); + + await Step("And fills in the ‘Titel’ and ‘Inhoud’ fields"); + + await Step("And clicks on the submit button"); + + await Step("Then the werkinstructie is displayed in Berichten"); + + Assert.Inconclusive("Not implemented yet"); + } + + [TestMethod] + public async Task Scenario27() + { + await Step("Given the user is on the Nieuws and werkinstructiesscreen available under Beheer"); + + await Step("When the user clicks on the “Toevoegen” button"); + + await Step("And selects Werkinstructieas ‘Type’"); + + await Step("And fills in the ‘Titel’ and ‘Inhoud’ fields"); + + await Step("And clicks on the submit button"); + + await Step("And navigates to the page containing the werkinstructie created earlier "); + + await Step("Then the werkinstructie should be displayed"); + + Assert.Inconclusive("Not implemented yet"); + } + + [TestMethod] + public async Task Scenario28() + { + await Step("Given there is at least 1 nieuwsbericht"); + + await Step("When the user navigates to the Nieuws and werkinstructiesscreen available under Beheer"); + + await Step("Then there is a table titled ‘Berichten’ with rows named as “Titel”, “Type”,”publicatiedatum”, “Aangemaakt op” and “ Gewijzigd op”"); + + Assert.Inconclusive("Not implemented yet"); + } + + [TestMethod] + public void Scenario29() + { + Assert.Inconclusive("This scenario in the document is still unclear"); + } + + [TestMethod] + public async Task Scenario30() + { + await Step("Given a nieuwsbericht for with a publicatiedatum in the future"); + + await Step("When the user navigates to the HOME Page"); + + await Step("And browses through all pages of the Nieuws section"); + + await Step("Then the nieuwsbericht should not be visible"); + + Assert.Inconclusive("Not implemented yet"); + } + + [TestMethod] + public async Task Scenario31() + { + await Step("Given a nieuwsbericht for a publicatiedatum in the past"); + + await Step("When the user navigates to the HOME PageAnd the user browses through all pages of the Nieuws section"); + + await Step("Then the nieuwsbericht should be visible"); + + Assert.Inconclusive("Not implemented yet"); + } + + [TestMethod] + public async Task Scenario32() + { + await Step("Given a nieuwsbericht for a publicatie-einddatum in the past"); + + await Step("When the user navigates to the HOME Page"); + + await Step("And browses through all pages of the Nieuws section"); + + await Step("Then the nieuwsbericht should not be visible"); + + Assert.Inconclusive("Not implemented yet"); + } + + [TestMethod] + public async Task Scenario33() + { + await Step("Given a nieuwsbericht for a publicatie-einddatum in the future"); + + await Step("When the user navigates to the HOME Page"); + + await Step("And browses through all pages of the Nieuws section"); + + await Step("Then the nieuwsbericht should be visible"); + + Assert.Inconclusive("Not implemented yet"); + } + + [TestMethod] + public async Task Scenario34() + { + await Step("Given a nieuwsbericht with multiple skills"); + + await Step("When the user navigates to the HOME Page"); + + await Step("And browses through all pages of the Nieuws section"); + + await Step("Then the nieuwsbericht should be displayed with the corresponding skills as labels"); + + Assert.Inconclusive("Not implemented yet"); + } + + [TestMethod] + public async Task Scenario35() + { + await Step("Given a werkinstructie with multiple skills"); + + await Step("When the user navigates to the HOME Page"); + + await Step("And browses through all pages of the Nieuws section"); + + await Step("Then the werkinstructie should be displayed with the corresponding skills as labels"); + + Assert.Inconclusive("Not implemented yet"); + } +} diff --git a/Kiss.Bff.EndToEndTest/README.md b/Kiss.Bff.EndToEndTest/README.md new file mode 100644 index 00000000..ed01dae5 --- /dev/null +++ b/Kiss.Bff.EndToEndTest/README.md @@ -0,0 +1,16 @@ +# End to end tests +These tests are scheduled to run on Github Actions. [An html report](https://klantinteractie-servicesysteem.github.io/KISS-frontend/) is generated on Github Pages. + +## Run/debug the tests locally +1. In Visual Studio, right click the `Kiss.Bff.EndToEndTest` project and select `Manage user secrets` +1. Fill in the following values: +```jsonc +{ + "TestSettings": { + "TEST_BASE_URL": "", // a valid base url for an environment where an instance of kiss is running + "TEST_USERNAME": "", // a valid username to login to Azure Entra Id + "TEST_PASSWORD": "", // a valid password to login to Azure Entra Id + "TEST_TOTP_SECRET": "" // a secret to generate 2 Factor Authentication codes for Azure Entra Id + } +} +``` \ No newline at end of file