diff --git a/Lombiq.Tests.UI.Samples/Tests/BasicOrchardFeaturesTests.cs b/Lombiq.Tests.UI.Samples/Tests/BasicOrchardFeaturesTests.cs index d72f425df..cdb974378 100644 --- a/Lombiq.Tests.UI.Samples/Tests/BasicOrchardFeaturesTests.cs +++ b/Lombiq.Tests.UI.Samples/Tests/BasicOrchardFeaturesTests.cs @@ -1,4 +1,4 @@ -using Lombiq.Tests.UI.Extensions; +using Lombiq.Tests.UI.BasicOrchardFeaturesTesting; using Lombiq.Tests.UI.Samples.Constants; using System.Threading.Tasks; using Xunit; diff --git a/Lombiq.Tests.UI.Samples/Tests/DatabaseSnapshotTests.cs b/Lombiq.Tests.UI.Samples/Tests/DatabaseSnapshotTests.cs index 7134de3f5..a57dffc69 100644 --- a/Lombiq.Tests.UI.Samples/Tests/DatabaseSnapshotTests.cs +++ b/Lombiq.Tests.UI.Samples/Tests/DatabaseSnapshotTests.cs @@ -1,3 +1,4 @@ +using Lombiq.Tests.UI.BasicOrchardFeaturesTesting; using Lombiq.Tests.UI.Extensions; using Lombiq.Tests.UI.Samples.Constants; using System.IO; diff --git a/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/AuditTrailFeatureTestingUITestContextExtensions.cs b/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/AuditTrailFeatureTestingUITestContextExtensions.cs new file mode 100644 index 000000000..340b96c9d --- /dev/null +++ b/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/AuditTrailFeatureTestingUITestContextExtensions.cs @@ -0,0 +1,50 @@ +using Atata; +using Lombiq.Tests.UI.Extensions; +using Lombiq.Tests.UI.Services; +using OpenQA.Selenium; +using System.Threading.Tasks; + +namespace Lombiq.Tests.UI.BasicOrchardFeaturesTesting; + +/// +/// Provides a set of extension methods for Orchard Core Audit Trail feature testing. +/// +public static class AuditTrailFeatureTestingUITestContextExtensions +{ + public static Task TestAuditTrailAsync(this UITestContext context) => + context.ExecuteTestAsync( + "Test Audit Trail", + async () => + { + var auditTrailPath = "/AuditTrail"; + var auditTrailTestPageTitle = "Audit Trail Test Page"; + + await context.EnableFeatureDirectlyAsync("OrchardCore.AuditTrail"); + await context.GoToAdminRelativeUrlAsync("/Settings" + auditTrailPath); + + await context.GoToEditorTabAsync("Content"); + + await context.SetCheckboxValueAsync(By.XPath("//input[@value='Page']")); + + await context.ClickReliablyOnSubmitAsync(); + + var contentItemsPage = await context.GoToContentItemsPageAsync(); + context.RefreshCurrentAtataContext(); + contentItemsPage + .CreateNewPage() + .Title.Set(auditTrailTestPageTitle) + .Publish.ClickAndGo() + .AlertMessages.Should.Contain(message => message.IsSuccess); + + await context.GoToAdminRelativeUrlAsync(auditTrailPath); + + var auditTrailTestPageSuccessXpath = "//div[contains(@class, eventdata)]/small[contains(., 'was published')" + // #spell-check-ignore-line + $" and contains(., 'of the Page')]/a[text()='{auditTrailTestPageTitle}']"; + + context.Exists(By.XPath(auditTrailTestPageSuccessXpath)); + + auditTrailTestPageSuccessXpath = auditTrailTestPageSuccessXpath.Replace("published", "created"); + + context.Exists(By.XPath(auditTrailTestPageSuccessXpath)); + }); +} diff --git a/Lombiq.Tests.UI/Extensions/BasicOrchardFeaturesTestingUITestContextExtensions.cs b/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/BasicFeaturesTestingUITestContextExtensions.cs similarity index 86% rename from Lombiq.Tests.UI/Extensions/BasicOrchardFeaturesTestingUITestContextExtensions.cs rename to Lombiq.Tests.UI/BasicOrchardFeaturesTesting/BasicFeaturesTestingUITestContextExtensions.cs index d601038be..bba29c32c 100644 --- a/Lombiq.Tests.UI/Extensions/BasicOrchardFeaturesTestingUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/BasicFeaturesTestingUITestContextExtensions.cs @@ -1,20 +1,19 @@ using Atata; using Lombiq.Tests.UI.Constants; -using Lombiq.Tests.UI.Helpers; +using Lombiq.Tests.UI.Extensions; using Lombiq.Tests.UI.Models; using Lombiq.Tests.UI.Pages; using Lombiq.Tests.UI.Services; -using OpenQA.Selenium; using Shouldly; using System; using System.Threading.Tasks; -namespace Lombiq.Tests.UI.Extensions; +namespace Lombiq.Tests.UI.BasicOrchardFeaturesTesting; /// /// Provides a set of extension methods for basic Orchard features testing. /// -public static class BasicOrchardFeaturesTestingUITestContextExtensions +public static class BasicFeaturesTestingUITestContextExtensions { /// /// @@ -141,6 +140,8 @@ public static async Task TestBasicOrchardFeaturesExceptSetupAsync( await context.TestContentOperationsAsync(customPageHeaderCheckAsync: customPageHeaderCheckAsync); await context.TestTurningFeatureOnAndOffAsync(); await context.TestMediaOperationsAsync(); + await context.TestAuditTrailAsync(); + await context.TestWorkflowsAsync(); await context.TestLogoutAsync(); } @@ -168,6 +169,8 @@ public static async Task TestBasicOrchardFeaturesExceptSetupAndRegistrationAsync await context.TestLoginAsync(); await context.TestContentOperationsAsync(dontCheckFrontend, customPageHeaderCheckAsync: customPageHeaderCheckAsync); await context.TestTurningFeatureOnAndOffAsync(); + await context.TestAuditTrailAsync(); + await context.TestWorkflowsAsync(); await context.TestLogoutAsync(); } @@ -559,97 +562,6 @@ public static Task TestTurningFeatureOnAndOffAsync( .SearchForFeature(featureName).IsEnabled.Should.Equal(originalEnabledState)); }); - public static Task TestMediaOperationsAsync(this UITestContext context) => - context.ExecuteTestAsync( - "Test media operations", - async () => - { - const string mediaPath = "/Media"; - var imageName = FileUploadHelper.SamplePngFileName; - var documentName = FileUploadHelper.SamplePdfFileName; - - await context.GoToAdminRelativeUrlAsync(mediaPath); - - context.UploadSamplePngByIdOfAnyVisibility("fileupload"); // #spell-check-ignore-line - - // Workaround for pending uploads, until you make an action the page is stuck on "Uploads Pending". - context.WaitForPageLoad(); - await context.ClickReliablyOnAsync(By.CssSelector("body")); - - context.Exists(By.XPath($"//span[contains(text(), '{imageName}')]")); - - await context - .Get(By.CssSelector($"a[href=\"/media/{imageName}\"]").OfAnyVisibility()) - .ClickReliablyAsync(context); - context.SwitchToFirstWindow(); - - context.WaitForPageLoad(); - await context.GoToAdminRelativeUrlAsync(mediaPath); - - context.UploadSamplePdfByIdOfAnyVisibility("fileupload"); // #spell-check-ignore-line - - // Workaround for pending uploads, until you make an action the page is stuck on "Uploads Pending". - context.WaitForPageLoad(); - await context.ClickReliablyOnAsync(By.CssSelector("body")); - - context.Exists(By.XPath($"//span[contains(text(), '{documentName}')]")); - - await context - .Get(By.XPath($"//span[contains(text(), '{documentName}')]/ancestor::tr").OfAnyVisibility()) - .ClickReliablyAsync(context); - - await context - .Get(By.CssSelector($"a[href=\"/media/{documentName}\"]")) - .ClickReliablyAsync(context); - context.SwitchToFirstWindow(); - - context.WaitForPageLoad(); - await context.GoToAdminRelativeUrlAsync(mediaPath); - - await context - .Get(By.CssSelector("#folder-tree .treeroot .folder-actions")) // #spell-check-ignore-line - .ClickReliablyAsync(context); - - context.Get(By.Id("create-folder-name")).SendKeys("Example Folder"); - - await context.ClickReliablyOnAsync(By.Id("modalFooterOk")); - - // Wait until new folder is created. - context.Exists( - By.XPath("//div[contains(@class, 'alert-info') and contains(.,'This folder is empty')]")); - - context.UploadSamplePngByIdOfAnyVisibility("fileupload"); // #spell-check-ignore-line - context.UploadSamplePdfByIdOfAnyVisibility("fileupload"); // #spell-check-ignore-line - context.WaitForPageLoad(); - - var image = context.Get(By.XPath($"//span[contains(text(), '{imageName}')]")); - - context.Exists(By.XPath($"//span[contains(text(), '{documentName}')]")); - - await image.ClickReliablyAsync(context); - - await context - .Get(By.XPath($"//span[contains(text(), '{imageName}')]/ancestor::tr")) - .Get(By.CssSelector("a.btn.btn-link.btn-sm.delete-button")) - .ClickReliablyAsync(context); - - await context.ClickModalOkAsync(); - context.WaitForPageLoad(); - await context.GoToAdminRelativeUrlAsync(mediaPath); - - context.Missing(By.XPath("//span[text()=' Image.png ' and @class='break-word']")); - - var deleteFolderButton = - context.Get(By.CssSelector("#folder-tree li.selected div.btn-group.folder-actions .svg-inline--fa.fa-trash")); - await deleteFolderButton.ClickReliablyAsync(context); - - await context.ClickModalOkAsync(); - context.WaitForPageLoad(); - await context.GoToAdminRelativeUrlAsync(mediaPath); - - context.Missing(By.XPath("//div[text()='Example Folder' and @class='folder-name ms-2']")); - }); - /// /// Executes the with the specified . /// diff --git a/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/MediaOperationsTestingUITestContextExtensions.cs b/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/MediaOperationsTestingUITestContextExtensions.cs new file mode 100644 index 000000000..ad1ab5648 --- /dev/null +++ b/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/MediaOperationsTestingUITestContextExtensions.cs @@ -0,0 +1,105 @@ +using Atata; +using Lombiq.Tests.UI.Extensions; +using Lombiq.Tests.UI.Helpers; +using Lombiq.Tests.UI.Services; +using OpenQA.Selenium; +using System.Threading.Tasks; + +namespace Lombiq.Tests.UI.BasicOrchardFeaturesTesting; + +/// +/// Provides a set of extension methods for testing Orchard Core media operations. +/// +public static class MediaOperationsTestingUITestContextExtensions +{ + public static Task TestMediaOperationsAsync(this UITestContext context) => + context.ExecuteTestAsync( + "Test media operations", + async () => + { + const string mediaPath = "/Media"; + var imageName = FileUploadHelper.SamplePngFileName; + var documentName = FileUploadHelper.SamplePdfFileName; + + await context.GoToAdminRelativeUrlAsync(mediaPath); + + context.UploadSamplePngByIdOfAnyVisibility("fileupload"); // #spell-check-ignore-line + + // Workaround for pending uploads, until you make an action the page is stuck on "Uploads Pending". + context.WaitForPageLoad(); + await context.ClickReliablyOnAsync(By.CssSelector("body")); + + context.Exists(By.XPath($"//span[contains(text(), '{imageName}')]")); + + await context + .Get(By.CssSelector($"a[href=\"/media/{imageName}\"]").OfAnyVisibility()) + .ClickReliablyAsync(context); + context.SwitchToFirstWindow(); + + context.WaitForPageLoad(); + await context.GoToAdminRelativeUrlAsync(mediaPath); + + context.UploadSamplePdfByIdOfAnyVisibility("fileupload"); // #spell-check-ignore-line + + // Workaround for pending uploads, until you make an action the page is stuck on "Uploads Pending". + context.WaitForPageLoad(); + await context.ClickReliablyOnAsync(By.CssSelector("body")); + + context.Exists(By.XPath($"//span[contains(text(), '{documentName}')]")); + + await context + .Get(By.XPath($"//span[contains(text(), '{documentName}')]/ancestor::tr").OfAnyVisibility()) + .ClickReliablyAsync(context); + + await context + .Get(By.CssSelector($"a[href=\"/media/{documentName}\"]")) + .ClickReliablyAsync(context); + context.SwitchToFirstWindow(); + + context.WaitForPageLoad(); + await context.GoToAdminRelativeUrlAsync(mediaPath); + + await context + .Get(By.CssSelector("#folder-tree .treeroot .folder-actions")) // #spell-check-ignore-line + .ClickReliablyAsync(context); + + context.Get(By.Id("create-folder-name")).SendKeys("Example Folder"); + + await context.ClickReliablyOnAsync(By.Id("modalFooterOk")); + + // Wait until new folder is created. + context.Exists( + By.XPath("//div[contains(@class, 'alert-info') and contains(.,'This folder is empty')]")); + + context.UploadSamplePngByIdOfAnyVisibility("fileupload"); // #spell-check-ignore-line + context.UploadSamplePdfByIdOfAnyVisibility("fileupload"); // #spell-check-ignore-line + context.WaitForPageLoad(); + + var image = context.Get(By.XPath($"//span[contains(text(), '{imageName}')]")); + + context.Exists(By.XPath($"//span[contains(text(), '{documentName}')]")); + + await image.ClickReliablyAsync(context); + + await context + .Get(By.XPath($"//span[contains(text(), '{imageName}')]/ancestor::tr")) + .Get(By.CssSelector("a.btn.btn-link.btn-sm.delete-button")) + .ClickReliablyAsync(context); + + await context.ClickModalOkAsync(); + context.WaitForPageLoad(); + await context.GoToAdminRelativeUrlAsync(mediaPath); + + context.Missing(By.XPath("//span[text()=' Image.png ' and @class='break-word']")); + + var deleteFolderButton = + context.Get(By.CssSelector("#folder-tree li.selected div.btn-group.folder-actions .svg-inline--fa.fa-trash")); + await deleteFolderButton.ClickReliablyAsync(context); + + await context.ClickModalOkAsync(); + context.WaitForPageLoad(); + await context.GoToAdminRelativeUrlAsync(mediaPath); + + context.Missing(By.XPath("//div[text()='Example Folder' and @class='folder-name ms-2']")); + }); +} diff --git a/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/WorkflowsFeatureTestingUITestContextExtensions.cs b/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/WorkflowsFeatureTestingUITestContextExtensions.cs new file mode 100644 index 000000000..433cef42f --- /dev/null +++ b/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/WorkflowsFeatureTestingUITestContextExtensions.cs @@ -0,0 +1,75 @@ +using Atata; +using Lombiq.Tests.UI.Extensions; +using Lombiq.Tests.UI.Services; +using OpenQA.Selenium; +using System.Threading.Tasks; + +namespace Lombiq.Tests.UI.BasicOrchardFeaturesTesting; + +/// +/// Provides a set of extension methods for Orchard Core Workflows feature testing. +/// +public static class WorkflowsFeatureTestingUITestContextExtensions +{ + public static Task TestWorkflowsAsync(this UITestContext context) => + context.ExecuteTestAsync( + "Test Workflows", + async () => + { + var workflowsPath = "/Workflows/Types"; + var contentItemPublishTestSuccessMessage = "The content item was published."; + + await context.EnableFeatureDirectlyAsync("OrchardCore.Workflows"); + await context.GoToAdminRelativeUrlAsync(workflowsPath + "/EditProperties"); + + await context.ClickAndFillInWithRetriesAsync(By.Id("Name"), "Test workflow"); + await context.ClickReliablyOnSubmitAsync(); + + await context.ClickReliablyOnAsync(By.XPath("//button[@data-activity-type='Event']")); + await context.ClickReliablyOnAsync(By.XPath("//a[contains(@href, 'ContentPublishedEvent')]")); + + await context.ClickAndFillInWithRetriesAsync(By.Id("IActivity_Title"), "Content Published Trigger"); + await context.SetCheckboxValueAsync(By.XPath("//input[@value='Page']")); + await context.ClickReliablyOnSubmitAsync(); + + await context.ClickReliablyOnAsync(By.XPath("//button[@data-activity-type='Task']")); + await context.ClickReliablyOnAsync(By.XPath("//a[contains(@href, 'NotifyTask')]")); + + await context.ClickAndFillInWithRetriesAsync(By.Id("IActivity_Title"), "Content Published Notification"); + await context.ClickAndFillInWithRetriesAsync(By.Id("NotifyTask_Message"), contentItemPublishTestSuccessMessage); + await context.ClickReliablyOnSubmitAsync(); + + var taskXPath = "//div[contains(@class, 'activity-task')]"; + + context.DragAndDropToOffset(By.XPath(taskXPath), 400, 0); + + context.DragAndDrop( + By.XPath("//div[@class = 'jtk-endpoint jtk-endpoint-anchor jtk-draggable jtk-droppable']"), // #spell-check-ignore-line + By.XPath(taskXPath)); + + // We need to save the workflow early, because sometimes the editor, thus the startup task button can be + // buggy during UI testing (it won't be clicked, even if we check for its existence). This way it's + // always clicked. + await context.ClickReliablyOnSubmitAsync(); + await context.ClickReliablyOnAsync(By.XPath("//div[contains(@class, 'activity-event')]")); + + await context.ClickReliablyOnAsync(By.XPath("//a[@title='Startup task']")); + await context.ClickReliablyOnSubmitAsync(); + + context.ShouldBeSuccess("Workflow has been saved."); + + var contentItemsPage = await context.GoToContentItemsPageAsync(); + context.RefreshCurrentAtataContext(); + contentItemsPage + .CreateNewPage() + .Title.Set("Workflows Test Page") + .Publish.ClickAndGo(); + + context.ShouldBeSuccess(contentItemPublishTestSuccessMessage); + + // Checking if the workflow run was logged. + await context.GoToAdminRelativeUrlAsync(workflowsPath); + await context.ClickReliablyOnAsync(By.XPath("//a[text()='Test workflow']/following-sibling::a[contains(@href, 'Instances')]")); + context.Exists(By.XPath("//span[@class = 'badge text-bg-success']")); + }); +} diff --git a/Lombiq.Tests.UI/Extensions/FormUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/FormUITestContextExtensions.cs index 11e4bf1c7..2bdf1c4d7 100644 --- a/Lombiq.Tests.UI/Extensions/FormUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/FormUITestContextExtensions.cs @@ -232,7 +232,7 @@ public static Task FillInCodeMirrorEditorWithRetriesAsync( public static bool IsElementChecked(this UITestContext context, By by) => context.Get(by.OfAnyVisibility()).GetDomProperty("checked") == bool.TrueString; - public static async Task SetCheckboxValueAsync(this UITestContext context, By by, bool isChecked) + public static async Task SetCheckboxValueAsync(this UITestContext context, By by, bool isChecked = true) { var element = context.Get(by.OfAnyVisibility()); var currentValue = element.GetDomProperty("checked") == bool.TrueString; diff --git a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs index 0eebf4ba3..373a7a0cf 100644 --- a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs @@ -5,6 +5,7 @@ using Lombiq.Tests.UI.Services; using Newtonsoft.Json; using OpenQA.Selenium; +using OpenQA.Selenium.Interactions; using OpenQA.Selenium.Support.UI; using OrchardCore.ContentFields.ViewModels; using System; @@ -385,4 +386,25 @@ public static bool IsSetupPage(this UITestContext context) => public static Task GoToContentItemByIdAsync(this UITestContext context, string contentItemId) => context.GoToRelativeUrlAsync("/Contents/ContentItems/" + contentItemId); + + /// + /// A method to perform a drag and drop action from a source element to a destination element. + /// + /// The source element, that should be dragged and dropped. + /// The destination element, where the source element should be dropped. + public static void DragAndDrop(this UITestContext context, By sourceElementBy, By destinationBy) => + new Actions(context.Driver).DragAndDrop(context.Get(sourceElementBy), context.Get(destinationBy)) + .Build() + .Perform(); + + /// + /// A method to perform a drag and drop action from a source element to an offset. + /// + /// The source element, that should be dragged and dropped. + /// The x offset in pixels. + /// The y offset in pixels. + public static void DragAndDropToOffset(this UITestContext context, By sourceElementBy, int offsetX, int offsetY) => + new Actions(context.Driver).DragAndDropToOffset(context.Get(sourceElementBy), offsetX, offsetY) + .Build() + .Perform(); }