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();
}