diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj
index 8cf7b1060..1197c424e 100644
--- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj
+++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj
@@ -33,8 +33,8 @@
-
-
+
+
diff --git a/Lombiq.Tests.UI.Samples/Tests/BasicTests.cs b/Lombiq.Tests.UI.Samples/Tests/BasicTests.cs
index 1b58f4c1a..c68601944 100644
--- a/Lombiq.Tests.UI.Samples/Tests/BasicTests.cs
+++ b/Lombiq.Tests.UI.Samples/Tests/BasicTests.cs
@@ -50,8 +50,8 @@ public Task LoginShouldWork() =>
// Let's fill out the login form. In UI tests, nothing is certain. If you fill out a form it's not
// actually sure that the values are indeed there! To make things more reliable, we've added a lot of
// useful methods like FillInWithRetriesAsync().
- await context.FillInWithRetriesAsync(By.Id("UserName"), DefaultUser.UserName);
- await context.FillInWithRetriesAsync(By.Id("Password"), DefaultUser.Password);
+ await context.FillInWithRetriesAsync(By.Id("LoginForm_UserName"), DefaultUser.UserName);
+ await context.FillInWithRetriesAsync(By.Id("LoginForm_Password"), DefaultUser.Password);
// Even clicking can be unreliable thus we have a helper for that too.
await context.ClickReliablyOnSubmitAsync();
diff --git a/Lombiq.Tests.UI.Samples/Tests/BasicVisualVerificationTests_VerifyNavbar_By_ClassName[Contains]_-navbar-brand_Win32NT_msedge.png b/Lombiq.Tests.UI.Samples/Tests/BasicVisualVerificationTests_VerifyNavbar_By_ClassName[Contains]_-navbar-brand_Win32NT_MicrosoftEdge.png
similarity index 100%
rename from Lombiq.Tests.UI.Samples/Tests/BasicVisualVerificationTests_VerifyNavbar_By_ClassName[Contains]_-navbar-brand_Win32NT_msedge.png
rename to Lombiq.Tests.UI.Samples/Tests/BasicVisualVerificationTests_VerifyNavbar_By_ClassName[Contains]_-navbar-brand_Win32NT_MicrosoftEdge.png
diff --git a/Lombiq.Tests.UI.Samples/Tests/EmailTests.cs b/Lombiq.Tests.UI.Samples/Tests/EmailTests.cs
index e29877117..928170eb6 100644
--- a/Lombiq.Tests.UI.Samples/Tests/EmailTests.cs
+++ b/Lombiq.Tests.UI.Samples/Tests/EmailTests.cs
@@ -1,7 +1,5 @@
-using Atata;
using Lombiq.Tests.UI.Extensions;
using Lombiq.Tests.UI.Helpers;
-using OpenQA.Selenium;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
@@ -25,31 +23,13 @@ public Task SendingTestEmailShouldWork() =>
// A shortcut to sign in without going through (and thus testing) the login screen.
await context.SignInDirectlyAsync();
- // Let's go to the "Test settings" option of the e-mail admin page. The default sender is configured in
- // the test recipe so we can use the test feature.
- await context.GoToAdminRelativeUrlAsync("/Email/Index");
-
- // Let's send a basic e-mail.
- await context.FillInWithRetriesAsync(By.Id("To"), "recipient@example.com");
- await context.FillInWithRetriesAsync(By.Id("Subject"), "Test message");
- await context.FillInWithRetriesAsync(By.Id("Body"), "Hi, this is a test.");
-
- // With the button being under the fold in the configured screen size, we need to make sure it's
- // actually clicked. Scrolling there first doesn't work for some reason.
- await ReliabilityHelper.DoWithRetriesOrFailAsync(
- async () =>
- {
- try
- {
- await context.ClickReliablyOnAsync(By.Id("emailtestsend")); // #spell-check-ignore-line
- return true;
- }
- catch (WebDriverException ex) when (ex.Message.Contains("move target out of bounds"))
- {
- return false;
- }
- });
+ // Set up the SMTP port. This is a dynamic value unique to each UI test so it can't come from a recipe.
+ await context.ConfigureSmtpPortAsync();
+ // Let's go to the "Test settings" option of the e-mail admin page and send a basic e-mail. The default
+ // sender is configured in the test recipe so we can use the test feature.
+ await context.GoToEmailTestAsync();
+ await context.FillEmailTestFormAsync("Test message");
context.ShouldBeSuccess();
// The SMTP service running behind the scenes also has a web UI that we can access to see all outgoing
diff --git a/Lombiq.Tests.UI.Samples/Tests/MonkeyTests.cs b/Lombiq.Tests.UI.Samples/Tests/MonkeyTests.cs
index 363c31c2a..5e0362711 100644
--- a/Lombiq.Tests.UI.Samples/Tests/MonkeyTests.cs
+++ b/Lombiq.Tests.UI.Samples/Tests/MonkeyTests.cs
@@ -90,7 +90,8 @@ public Task TestAdminBackgroundTasksAsMonkeyRecursivelyShouldWorkWithAdminUser()
// You could also configure the same thing with regex:
////_monkeyTestingOptions.UrlFilters.Add(new MatchesRegexMonkeyTestingUrlFilter(@"\/Admin\/BackgroundTasks"));
- await context.SignInDirectlyAndGoToRelativeUrlAsync("/Admin/BackgroundTasks");
+ await context.SignInDirectlyAsync();
+ await context.GoToAdminRelativeUrlAsync("/BackgroundTasks");
await context.TestCurrentPageAsMonkeyRecursivelyAsync(monkeyTestingOptions);
},
configuration => configuration.AssertBrowserLog = (logEntries) => logEntries
diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj
index 571b9d3ce..3b7f8662f 100644
--- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj
+++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj
@@ -34,15 +34,15 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/Lombiq.Tests.UI.Shortcuts/Services/ApplicationInfoInjectingFilter.cs b/Lombiq.Tests.UI.Shortcuts/Services/ApplicationInfoInjectingFilter.cs
index 3822b2768..c24598d49 100644
--- a/Lombiq.Tests.UI.Shortcuts/Services/ApplicationInfoInjectingFilter.cs
+++ b/Lombiq.Tests.UI.Shortcuts/Services/ApplicationInfoInjectingFilter.cs
@@ -1,16 +1,18 @@
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
-using Newtonsoft.Json;
using OrchardCore.Modules;
using OrchardCore.ResourceManagement;
using System;
+using System.Text.Json;
using System.Threading.Tasks;
namespace Lombiq.Tests.UI.Shortcuts.Services;
public class ApplicationInfoInjectingFilter : IAsyncResultFilter
{
+ private static readonly JsonSerializerOptions _indentedJsonSerializerOptions = new() { WriteIndented = true };
+
private readonly IResourceManager _resourceManager;
private readonly IConfiguration _shellConfiguration;
private readonly IApplicationContext _applicationContext;
@@ -35,7 +37,7 @@ public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultE
_resourceManager.RegisterHeadScript(new HtmlString(
$""));
diff --git a/Lombiq.Tests.UI.Shortcuts/Startup.cs b/Lombiq.Tests.UI.Shortcuts/Startup.cs
index 537724e21..4da8574a8 100644
--- a/Lombiq.Tests.UI.Shortcuts/Startup.cs
+++ b/Lombiq.Tests.UI.Shortcuts/Startup.cs
@@ -1,6 +1,7 @@
using Lombiq.HelpfulLibraries.AspNetCore.Extensions;
using Lombiq.Tests.UI.Shortcuts.Services;
using Microsoft.Extensions.DependencyInjection;
+using OrchardCore.Data.YesSql;
using OrchardCore.Modules;
namespace Lombiq.Tests.UI.Shortcuts;
@@ -11,5 +12,8 @@ public override void ConfigureServices(IServiceCollection services)
{
services.AddSingleton();
services.AddAsyncResultFilter();
+
+ // To ensure we don't encounter any concurrency issue, enable EnableThreadSafetyChecks for all tests.
+ services.Configure(options => options.EnableThreadSafetyChecks = true);
}
}
diff --git a/Lombiq.Tests.UI.Shortcuts/Views/_ViewImports.cshtml b/Lombiq.Tests.UI.Shortcuts/Views/_ViewImports.cshtml
index be43650df..000b84fbc 100644
--- a/Lombiq.Tests.UI.Shortcuts/Views/_ViewImports.cshtml
+++ b/Lombiq.Tests.UI.Shortcuts/Views/_ViewImports.cshtml
@@ -7,6 +7,6 @@
@using Lombiq.Tests.UI.Shortcuts.Controllers
@using Microsoft.AspNetCore.Mvc.Localization
-@using Newtonsoft.Json
-@using Newtonsoft.Json.Serialization
+@using System.Text.Json
+@using System.Text.Json.Serialization
@using OrchardCore.Mvc.Core.Utilities
diff --git a/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/BasicFeaturesTestingUITestContextExtensions.cs b/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/BasicFeaturesTestingUITestContextExtensions.cs
index bba29c32c..ae7446aab 100644
--- a/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/BasicFeaturesTestingUITestContextExtensions.cs
+++ b/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/BasicFeaturesTestingUITestContextExtensions.cs
@@ -4,6 +4,7 @@
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;
@@ -416,8 +417,8 @@ public static Task TestRegistrationWithInvalidDataAsync(
async () =>
{
var registrationPage = await context.GoToRegistrationPageAsync();
- registrationPage = await registrationPage.RegisterWithAsync(context, parameters);
- registrationPage.ShouldStayOnRegistrationPage().ValidationMessages.Should.Not.BeEmpty();
+ await registrationPage.RegisterWithAsync(context, parameters);
+ context.Exists(By.XPath("//div[contains(concat(' ', normalize-space(@class), ' '), ' validation-summary-errors ')]//li"));
});
}
@@ -446,11 +447,13 @@ public static Task TestRegistrationWithAlreadyRegisteredEmailAsync(
async () =>
{
var registrationPage = await context.GoToRegistrationPageAsync();
- registrationPage = await registrationPage.RegisterWithAsync(context, parameters);
+ await registrationPage.RegisterWithAsync(context, parameters);
context.RefreshCurrentAtataContext();
- registrationPage
- .ShouldStayOnRegistrationPage()
- .ValidationMessages[page => page.Email].Should.BeVisible();
+
+ context
+ .Get(By.CssSelector(".text-danger.field-validation-error"))
+ .Text
+ .ShouldContain("A user with the same username already exists.");
});
}
@@ -544,22 +547,23 @@ public static Task TestTurningFeatureOnAndOffAsync(
context.RefreshCurrentAtataContext();
+ featuresPage.SearchForFeature(featureName).IsEnabled.Get(out var originalEnabledState);
+ featuresPage.Features[featureName].CheckBox.Check();
+ featuresPage.BulkActions.Toggle.Click();
+
+ featuresPage
+ .AggregateAssert(page => page
+ .ShouldContainSuccessAlertMessage(TermMatch.Contains, featureName)
+ .AdminMenu.FindMenuItem(featureName).IsPresent.Should.Equal(!originalEnabledState)
+ .SearchForFeature(featureName).IsEnabled.Should.Equal(!originalEnabledState));
+ featuresPage.Features[featureName].CheckBox.Check();
+ featuresPage.BulkActions.Toggle.Click();
+
featuresPage
- .SearchForFeature(featureName).IsEnabled.Get(out bool originalEnabledState)
- .Features[featureName].CheckBox.Check()
- .BulkActions.Toggle.Click()
-
- .AggregateAssert(page => page
- .ShouldContainSuccessAlertMessage(TermMatch.Contains, featureName)
- .AdminMenu.FindMenuItem(featureName).IsPresent.Should.Equal(!originalEnabledState)
- .SearchForFeature(featureName).IsEnabled.Should.Equal(!originalEnabledState))
- .Features[featureName].CheckBox.Check()
- .BulkActions.Toggle.Click()
-
- .AggregateAssert(page => page
- .ShouldContainSuccessAlertMessage(TermMatch.Contains, featureName)
- .AdminMenu.FindMenuItem(featureName).IsPresent.Should.Equal(originalEnabledState)
- .SearchForFeature(featureName).IsEnabled.Should.Equal(originalEnabledState));
+ .AggregateAssert(page => page
+ .ShouldContainSuccessAlertMessage(TermMatch.Contains, featureName)
+ .AdminMenu.FindMenuItem(featureName).IsPresent.Should.Equal(originalEnabledState)
+ .SearchForFeature(featureName).IsEnabled.Should.Equal(originalEnabledState));
});
///
diff --git a/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/MediaOperationsTestingUITestContextExtensions.cs b/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/MediaOperationsTestingUITestContextExtensions.cs
index ad1ab5648..827a3f85c 100644
--- a/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/MediaOperationsTestingUITestContextExtensions.cs
+++ b/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/MediaOperationsTestingUITestContextExtensions.cs
@@ -32,7 +32,7 @@ public static Task TestMediaOperationsAsync(this UITestContext context) =>
context.Exists(By.XPath($"//span[contains(text(), '{imageName}')]"));
await context
- .Get(By.CssSelector($"a[href=\"/media/{imageName}\"]").OfAnyVisibility())
+ .Get(By.CssSelector($"a[href^=\"/media/{imageName}\"]").OfAnyVisibility())
.ClickReliablyAsync(context);
context.SwitchToFirstWindow();
@@ -52,7 +52,7 @@ await context
.ClickReliablyAsync(context);
await context
- .Get(By.CssSelector($"a[href=\"/media/{documentName}\"]"))
+ .Get(By.CssSelector($"a.btn-link[href^=\"/media/{documentName}\"]"))
.ClickReliablyAsync(context);
context.SwitchToFirstWindow();
diff --git a/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/WorkflowsFeatureTestingUITestContextExtensions.cs b/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/WorkflowsFeatureTestingUITestContextExtensions.cs
index 433cef42f..88c708184 100644
--- a/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/WorkflowsFeatureTestingUITestContextExtensions.cs
+++ b/Lombiq.Tests.UI/BasicOrchardFeaturesTesting/WorkflowsFeatureTestingUITestContextExtensions.cs
@@ -28,14 +28,14 @@ public static Task TestWorkflowsAsync(this UITestContext context) =>
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.ClickAndFillInWithRetriesAsync(By.Id("IActivity_ActivityMetadata_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("IActivity_ActivityMetadata_Title"), "Content Published Notification");
await context.ClickAndFillInWithRetriesAsync(By.Id("NotifyTask_Message"), contentItemPublishTestSuccessMessage);
await context.ClickReliablyOnSubmitAsync();
diff --git a/Lombiq.Tests.UI/Exceptions/VisualVerificationBaselineImageNotFoundException.cs b/Lombiq.Tests.UI/Exceptions/VisualVerificationBaselineImageNotFoundException.cs
index 6020c70a6..d8a5ef5d1 100644
--- a/Lombiq.Tests.UI/Exceptions/VisualVerificationBaselineImageNotFoundException.cs
+++ b/Lombiq.Tests.UI/Exceptions/VisualVerificationBaselineImageNotFoundException.cs
@@ -1,3 +1,4 @@
+using Lombiq.Tests.UI.Services.GitHub;
using System;
namespace Lombiq.Tests.UI.Exceptions;
@@ -16,11 +17,11 @@ public VisualVerificationBaselineImageNotFoundException(
}
private static string GetExceptionMessage(string path, int maxRetryCount) =>
- maxRetryCount == 0 ? $"Baseline image file not found, thus it was created automatically under the path {path}."
- + " Please set its \"Build action\" to \"Embedded resource\" if you want to deploy a self-contained"
- + " (like a NuGet package) UI testing assembly. If you run the test again, this newly created verification"
- + " file will be asserted against and the assertion will pass (unless the display of the app changed in the"
- + " meantime)."
- : $"Baseline image file was not found under the path {path} and maxRetryCount is set to "
- + $"{maxRetryCount.ToTechnicalString()}, so it won't be generated. Set maxRetryCount to 0 to generate images.";
+ maxRetryCount == 0 || GitHubHelper.IsGitHubEnvironment
+ ? $"Baseline image file not found, thus it was created automatically under the path {path}. Please set " +
+ $"its \"Build action\" to \"Embedded resource\" if you want to deploy a self-contained (like a NuGet " +
+ $"package) UI testing assembly. If you run the test again, this newly created verification file will " +
+ $"be asserted against and the assertion will pass (unless the display of the app changed in the meantime)."
+ : $"Baseline image file was not found under the path {path} and maxRetryCount is set to "
+ + $"{maxRetryCount.ToTechnicalString()}, so it won't be generated. Set maxRetryCount to 0 to generate images.";
}
diff --git a/Lombiq.Tests.UI/Extensions/ElementRetrievalUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/ElementRetrievalUITestContextExtensions.cs
index c202be07e..fd3f8038e 100644
--- a/Lombiq.Tests.UI/Extensions/ElementRetrievalUITestContextExtensions.cs
+++ b/Lombiq.Tests.UI/Extensions/ElementRetrievalUITestContextExtensions.cs
@@ -1,6 +1,7 @@
using Atata;
using Lombiq.HelpfulLibraries.Common.Utilities;
using Lombiq.Tests.UI.Services;
+using Microsoft.IdentityModel.Tokens;
using OpenQA.Selenium;
using Shouldly;
using System;
@@ -120,7 +121,8 @@ public static void ErrorMessageExists(this UITestContext context, string errorMe
///
public static void VerifyElementTexts(this UITestContext context, By by, params object[] toMatch)
{
- context.Exists(by); // Ensure content is loaded first.
+ // Ensure content is loaded first.
+ context.DoWithRetriesOrFail(() => !context.Get(by).Text.Trim().IsNullOrEmpty());
var dontCare = toMatch
.Select((item, index) => item == null ? index : -1)
diff --git a/Lombiq.Tests.UI/Extensions/EmailUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/EmailUITestContextExtensions.cs
index 2e70f4c74..bfe9909d5 100644
--- a/Lombiq.Tests.UI/Extensions/EmailUITestContextExtensions.cs
+++ b/Lombiq.Tests.UI/Extensions/EmailUITestContextExtensions.cs
@@ -53,4 +53,65 @@ public static async Task FindSpecificEmailInInboxAsync(
return currentlySelectedEmail;
}
+
+ ///
+ /// Navigates to the /Admin/Settings/email page.
+ ///
+ public static Task GoToEmailSettingsAsync(this UITestContext context) =>
+ context.GoToAdminRelativeUrlAsync("/Settings/email");
+
+ ///
+ /// Navigates to the /Admin/Email/Test page.
+ ///
+ public static Task GoToEmailTestAsync(this UITestContext context) =>
+ context.GoToAdminRelativeUrlAsync("/Email/Test");
+
+ ///
+ /// Fills out the form on the email test page by specifying the recipient address, subject and message body. If the
+ /// is , it also clicks on the send button.
+ ///
+ public static async Task FillEmailTestFormAsync(
+ this UITestContext context,
+ string to,
+ string subject,
+ string body,
+ bool submit = true)
+ {
+ await context.FillInWithRetriesAsync(By.Id("To"), to);
+ await context.FillInWithRetriesAsync(By.Id("Subject"), subject);
+ await context.FillInWithRetriesAsync(By.Id("Body"), body);
+
+ if (submit) await context.ClickReliablyOnSubmitAsync();
+ }
+
+ ///
+ /// A simplified version of where the
+ /// sender if "recipient@example.com" and the message body is "Hi, this is a test.".
+ ///
+ public static Task FillEmailTestFormAsync(this UITestContext context, string subject) =>
+ context.FillEmailTestFormAsync("recipient@example.com", subject, "Hi, this is a test.");
+
+ ///
+ /// Goes to the Email settings and sets the SMTP port to the value of . If it's then the value in the current configuration (in ) is used instead.
+ /// The OrchardCore.Email.Smtp feature must be enabled, but if the SMTP provider is not turned on, this will
+ /// automatically do it as well.
+ ///
+ public static async Task ConfigureSmtpPortAsync(this UITestContext context, int? port = null, bool publish = true)
+ {
+ await context.GoToEmailSettingsAsync();
+ await context.ClickReliablyOnAsync(By.CssSelector("a[href='#tab-s-m-t-p']"));
+
+ var byIsEnabled = By.Id("ISite_SmtpSettings_IsEnabled").OfAnyVisibility();
+ if (context.Get(byIsEnabled).GetAttribute("checked") == null)
+ {
+ await context.SetCheckboxValueAsync(byIsEnabled, isChecked: true);
+ }
+
+ var smtpPort = (port ?? context.Configuration.SmtpServiceConfiguration.Context.Port).ToTechnicalString();
+ await context.ClickAndFillInWithRetriesAsync(By.Id("ISite_SmtpSettings_Port"), smtpPort);
+
+ if (publish) await context.ClickReliablyOnAsync(By.ClassName("save"));
+ }
}
diff --git a/Lombiq.Tests.UI/Extensions/FormUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/FormUITestContextExtensions.cs
index 2bdf1c4d7..2d1938451 100644
--- a/Lombiq.Tests.UI/Extensions/FormUITestContextExtensions.cs
+++ b/Lombiq.Tests.UI/Extensions/FormUITestContextExtensions.cs
@@ -2,11 +2,11 @@
using Atata;
using Lombiq.HelpfulLibraries.Common.Utilities;
using Lombiq.Tests.UI.Services;
-using Newtonsoft.Json;
using OpenQA.Selenium;
using System;
using System.Globalization;
using System.Linq;
+using System.Text.Json;
using System.Threading.Tasks;
// Using the Atata namespace because that'll surely be among the using declarations of the test. OpenQA.Selenium not
@@ -95,7 +95,7 @@ public static void SetMarkdownEasyMdeWysiwygEditor(this UITestContext context, s
autoDownloadFontAwesome: false,
}});
/* Finally set the value programmatically. */
- mde.codemirror.setValue({JsonConvert.SerializeObject(text)});";
+ mde.codemirror.setValue({JsonSerializer.Serialize(text)});";
context.ExecuteScript(script);
}
@@ -110,7 +110,7 @@ public static void FillInMonacoEditor(
{
var script = $@"
monaco.editor.getEditors().find((element) =>
- element.getContainerDomNode().id == {JsonConvert.SerializeObject(editorId)}).setValue({JsonConvert.SerializeObject(text)});";
+ element.getContainerDomNode().id == {JsonSerializer.Serialize(editorId)}).setValue({JsonSerializer.Serialize(text)});";
context.ExecuteScript(script);
}
@@ -124,7 +124,7 @@ public static string GetMonacoEditorText(
{
var script = $@"
return monaco.editor.getEditors().find((element) =>
- element.getContainerDomNode().id == {JsonConvert.SerializeObject(editorId)}).getValue();";
+ element.getContainerDomNode().id == {JsonSerializer.Serialize(editorId)}).getValue();";
return context.ExecuteScript(script) as string;
}
diff --git a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs
index 373a7a0cf..1862f6de7 100644
--- a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs
+++ b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs
@@ -3,7 +3,6 @@
using Lombiq.Tests.UI.Helpers;
using Lombiq.Tests.UI.Pages;
using Lombiq.Tests.UI.Services;
-using Newtonsoft.Json;
using OpenQA.Selenium;
using OpenQA.Selenium.Interactions;
using OpenQA.Selenium.Support.UI;
@@ -13,6 +12,7 @@
using System.Linq;
using System.Net;
using System.Net.Http;
+using System.Text.Json;
using System.Threading.Tasks;
namespace Lombiq.Tests.UI.Extensions;
@@ -255,7 +255,7 @@ public static async Task SetContentPickerByDisplayTextAsync(this UITestContext c
async response =>
{
var json = await response.Content.ReadAsStringAsync();
- var result = JsonConvert.DeserializeObject>(json);
+ var result = JsonSerializer.Deserialize>(json, JOptions.Default);
return result.IndexOf(result.First(item => item.DisplayText == text));
});
diff --git a/Lombiq.Tests.UI/Extensions/ScriptingUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/ScriptingUITestContextExtensions.cs
index 69252faf4..af9b295f6 100644
--- a/Lombiq.Tests.UI/Extensions/ScriptingUITestContextExtensions.cs
+++ b/Lombiq.Tests.UI/Extensions/ScriptingUITestContextExtensions.cs
@@ -1,6 +1,6 @@
using Lombiq.Tests.UI.Services;
-using Newtonsoft.Json;
using OpenQA.Selenium;
+using System.Text.Json;
namespace Lombiq.Tests.UI.Extensions;
@@ -18,7 +18,7 @@ public static object ExecuteAsyncScript(this UITestContext context, string scrip
public static void SetValueWithScript(this UITestContext context, string id, object value) =>
ExecuteScript(
context,
- $"document.getElementById({JsonConvert.SerializeObject(id)}).value = {JsonConvert.SerializeObject(value)};");
+ $"document.getElementById({JsonSerializer.Serialize(id)}).value = {JsonSerializer.Serialize(value)};");
///
/// Uses JavaScript to set textarea values that are hard or impossible by normal means.
@@ -26,5 +26,5 @@ public static void SetValueWithScript(this UITestContext context, string id, obj
public static void SetTextContentWithScript(this UITestContext context, string textareaId, object value) =>
ExecuteScript(
context,
- $"document.getElementById({JsonConvert.SerializeObject(textareaId)}).textContent = {JsonConvert.SerializeObject(value)};");
+ $"document.getElementById({JsonSerializer.Serialize(textareaId)}).textContent = {JsonSerializer.Serialize(value)};");
}
diff --git a/Lombiq.Tests.UI/Extensions/ShortcutsUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/ShortcutsUITestContextExtensions.cs
index e99b64b9e..908265f44 100644
--- a/Lombiq.Tests.UI/Extensions/ShortcutsUITestContextExtensions.cs
+++ b/Lombiq.Tests.UI/Extensions/ShortcutsUITestContextExtensions.cs
@@ -1,5 +1,4 @@
using Lombiq.HelpfulLibraries.OrchardCore.Mvc;
-using Lombiq.HelpfulLibraries.Refit.Helpers;
using Lombiq.Tests.UI.Constants;
using Lombiq.Tests.UI.Exceptions;
using Lombiq.Tests.UI.Pages;
@@ -42,6 +41,7 @@
using System.Linq;
using System.Net.Http;
using System.Text;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -419,7 +419,7 @@ private static IShortcutsApi GetApi(this UITestContext context) =>
BaseAddress = context.Scope.BaseUri,
};
- return RefitHelper.WithNewtonsoftJson(httpClient);
+ return RestService.For(httpClient);
});
///
@@ -666,4 +666,34 @@ private static Task UsingScopeAsync(
string tenant,
bool activateShell) =>
context.Application.UsingScopeAsync(execute, tenant ?? context.TenantName, activateShell);
+
+ ///
+ /// Places the provided into a recipe and executes it with JSON Import.
+ ///
+ public static async Task ExecuteJsonRecipeAsync(this UITestContext context, params object[] steps)
+ {
+ await context.GoToAdminRelativeUrlAsync("/DeploymentPlan/Import/Json");
+
+ var json = JsonSerializer.Serialize(new { steps });
+ await context.FillInCodeMirrorEditorWithRetriesAsync(By.ClassName("CodeMirror"), json);
+ await context.ClickReliablyOnAsync(By.CssSelector(".ta-content button[type='submit']"));
+ context.ShouldBeSuccess();
+ }
+
+ ///
+ /// Executes JSON Import in the admin menu with a single settings step containing the provided which may include multiple named site settings.
+ ///
+ public static Task ExecuteJsonRecipeSiteSettingsAsync(this UITestContext context, IDictionary settingsContent)
+ {
+ settingsContent["name"] = "settings";
+ return context.ExecuteJsonRecipeAsync(settingsContent);
+ }
+
+ ///
+ /// Executes JSON Import in the admin menu with a single settings step containing the provided .
+ ///
+ public static Task ExecuteJsonRecipeSiteSettingAsync(this UITestContext context, T setting) =>
+ context.ExecuteJsonRecipeSiteSettingsAsync(new Dictionary { [typeof(T).Name] = setting });
}
diff --git a/Lombiq.Tests.UI/Extensions/ShouldlyExtensions.cs b/Lombiq.Tests.UI/Extensions/ShouldlyExtensions.cs
index 1d200966c..edaca482c 100644
--- a/Lombiq.Tests.UI/Extensions/ShouldlyExtensions.cs
+++ b/Lombiq.Tests.UI/Extensions/ShouldlyExtensions.cs
@@ -8,6 +8,8 @@ namespace Shouldly;
public static class ShouldlyExtensions
{
+ private static readonly char[] _newlineCharacters = ['\n', '\r'];
+
///
/// Calls on both and (unless
/// they are ) and checks if they are the same.
@@ -58,4 +60,13 @@ public static void ShouldBeEmptyWhen(
: JsonSerializer.Serialize(results.Select(messageTransform), jsonSerializerOptions);
results.ShouldBeEmpty(message); // This will always throw at this point.
}
+
+ ///
+ /// Compares the with the string by splitting both by lines
+ /// (ignoring the empty lines) and comparing the resulting arrays.
+ ///
+ public static void ShouldBeByLine(this string actual, string expected) =>
+ actual
+ .Split(_newlineCharacters, StringSplitOptions.RemoveEmptyEntries)
+ .ShouldBe(expected.Split(_newlineCharacters, StringSplitOptions.RemoveEmptyEntries));
}
diff --git a/Lombiq.Tests.UI/Extensions/VisualVerificationUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/VisualVerificationUITestContextExtensions.cs
index bd4852947..df8601d5b 100644
--- a/Lombiq.Tests.UI/Extensions/VisualVerificationUITestContextExtensions.cs
+++ b/Lombiq.Tests.UI/Extensions/VisualVerificationUITestContextExtensions.cs
@@ -5,6 +5,7 @@
using Lombiq.Tests.UI.Exceptions;
using Lombiq.Tests.UI.Models;
using Lombiq.Tests.UI.Services;
+using Lombiq.Tests.UI.Services.GitHub;
using OpenQA.Selenium;
using Shouldly;
using SixLabors.ImageSharp;
@@ -389,7 +390,7 @@ private static void AssertVisualVerificationApproved(
if (!File.Exists(approvedContext.BaselineImagePath))
{
- if (context.Configuration.MaxRetryCount == 0)
+ if (context.Configuration.MaxRetryCount == 0 || GitHubHelper.IsGitHubEnvironment)
{
context.SaveSuggestedImage(
element,
diff --git a/Lombiq.Tests.UI/Helpers/ByHelper.cs b/Lombiq.Tests.UI/Helpers/ByHelper.cs
index d9685fd7b..f315cfb72 100644
--- a/Lombiq.Tests.UI/Helpers/ByHelper.cs
+++ b/Lombiq.Tests.UI/Helpers/ByHelper.cs
@@ -1,9 +1,9 @@
using Atata;
-using Newtonsoft.Json;
using OpenQA.Selenium;
using System;
using System.Globalization;
using System.Runtime.CompilerServices;
+using System.Text.Json;
namespace Lombiq.Tests.UI.Helpers;
@@ -17,7 +17,7 @@ public static class ByHelper
///
public static By SmtpInboxRow(string text) =>
By
- .XPath($"//tr[contains(@class,'el-table__row')]//div[contains(@class,'cell')][contains(text(), {JsonConvert.SerializeObject(text)})]")
+ .XPath($"//tr[contains(@class,'el-table__row')]//div[contains(@class,'cell')][contains(text(), {JsonSerializer.Serialize(text)})]")
.Within(TimeSpan.FromMinutes(2));
///
@@ -25,14 +25,14 @@ public static By SmtpInboxRow(string text) =>
/// element name restriction.
///
public static By Text(string innerText, string element = "*") =>
- By.XPath($"//{element}[. = {JsonConvert.SerializeObject(innerText)}]");
+ By.XPath($"//{element}[. = {JsonSerializer.Serialize(innerText)}]");
///
/// Returns an XPath selector that looks up elements whose text contains with optional
/// element name restriction.
///
public static By TextContains(string innerText, string element = "*") =>
- By.XPath($"//{element}[contains(., {JsonConvert.SerializeObject(innerText)})]");
+ By.XPath($"//{element}[contains(., {JsonSerializer.Serialize(innerText)})]");
///
/// Creates a from an interpolated string with the invariant culture. This prevents culture-
diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj
index ba2500bf7..1e263752c 100644
--- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj
+++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj
@@ -72,10 +72,10 @@
-
-
-
-
+
+
+
+
diff --git a/Lombiq.Tests.UI/Models/DockerConfiguration.cs b/Lombiq.Tests.UI/Models/DockerConfiguration.cs
index bee60652d..d8ac516e2 100644
--- a/Lombiq.Tests.UI/Models/DockerConfiguration.cs
+++ b/Lombiq.Tests.UI/Models/DockerConfiguration.cs
@@ -1,4 +1,4 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Lombiq.Tests.UI.Models;
@@ -6,6 +6,6 @@ public class DockerConfiguration
{
public string ContainerName { get; set; }
- [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string ContainerSnapshotPath { get; set; } = "/data/Snapshots";
}
diff --git a/Lombiq.Tests.UI/Pages/OrchardCoreFeaturesPage.cs b/Lombiq.Tests.UI/Pages/OrchardCoreFeaturesPage.cs
index e01d5658a..8349b8c62 100644
--- a/Lombiq.Tests.UI/Pages/OrchardCoreFeaturesPage.cs
+++ b/Lombiq.Tests.UI/Pages/OrchardCoreFeaturesPage.cs
@@ -1,6 +1,8 @@
using Atata;
using Atata.Bootstrap;
using Lombiq.Tests.UI.Components;
+using System;
+using System.Linq;
namespace Lombiq.Tests.UI.Pages;
@@ -32,14 +34,16 @@ public sealed class BulkActionsDropdown : BSDropdownToggle<_>
public Link<_> Toggle { get; private set; }
}
- [ControlDefinition("li[not(contains(@class, 'd-none'))]", ContainingClass = "list-group-item", ComponentTypeName = "feature")]
+ [ControlDefinition(
+ "li[contains(@class, 'list-group-item') and not(contains(@class, 'd-none')) and .//label[contains(@class, 'form-check-label')]]",
+ ComponentTypeName = "feature")]
public sealed class FeatureItem : Control<_>
{
[FindFirst(Visibility = Visibility.Any)]
[ClicksUsingActions]
public CheckBox<_> CheckBox { get; private set; }
- [FindByXPath("h6", "label")]
+ [FindByXPath("label")]
public Text<_> Name { get; private set; }
[FindById(TermMatch.StartsWith, "btn-enable")]
@@ -59,6 +63,6 @@ public _ DisableWithConfirmation() =>
public sealed class FeatureItemList : ControlList
{
public FeatureItem this[string featureName] =>
- GetItem(featureName, item => item.Name == featureName);
+ GetAll().First(item => item.Name.Content.Value.ContainsOrdinalIgnoreCase(featureName));
}
}
diff --git a/Lombiq.Tests.UI/Pages/OrchardCoreLoginPage.cs b/Lombiq.Tests.UI/Pages/OrchardCoreLoginPage.cs
index d307a8ff3..c6fce42fc 100644
--- a/Lombiq.Tests.UI/Pages/OrchardCoreLoginPage.cs
+++ b/Lombiq.Tests.UI/Pages/OrchardCoreLoginPage.cs
@@ -17,10 +17,10 @@ public class OrchardCoreLoginPage : Page<_>
{
private const string DefaultUrl = "Login";
- [FindById]
+ [FindById("LoginForm_UserName", nameof(UserName))]
public TextInput<_> UserName { get; private set; }
- [FindById]
+ [FindById("LoginForm_Password", nameof(Password))]
public PasswordInput<_> Password { get; private set; }
[FindByAttribute("type", "submit")]
diff --git a/Lombiq.Tests.UI/Pages/OrchardCoreRegistrationPage.cs b/Lombiq.Tests.UI/Pages/OrchardCoreRegistrationPage.cs
index 5f698b6c0..65903c7e9 100644
--- a/Lombiq.Tests.UI/Pages/OrchardCoreRegistrationPage.cs
+++ b/Lombiq.Tests.UI/Pages/OrchardCoreRegistrationPage.cs
@@ -19,20 +19,20 @@ public class OrchardCoreRegistrationPage : Page<_>
{
public const string DefaultUrl = "Register";
- [FindByName]
+ [FindById("RegisterUserForm_UserName")]
public TextInput<_> UserName { get; private set; }
- [FindByName]
+ [FindById("RegisterUserForm_Email")]
[SetsValueReliably]
public TextInput<_> Email { get; private set; }
- [FindByName]
+ [FindById("RegisterUserForm_Password")]
public PasswordInput<_> Password { get; private set; }
- [FindByName]
+ [FindById("RegisterUserForm_ConfirmPassword")]
public PasswordInput<_> ConfirmPassword { get; private set; }
- [FindByName("RegistrationCheckbox")]
+ [FindById("RegisterUserForm_RegistrationCheckbox")]
public CheckBox<_> PrivacyPolicyAgreement { get; private set; }
[FindByAttribute("type", "submit")]
diff --git a/Lombiq.Tests.UI/Services/OrchardCoreHosting/FakeStore.cs b/Lombiq.Tests.UI/Services/OrchardCoreHosting/FakeStore.cs
index 894d6d2f3..b4cad4e1c 100644
--- a/Lombiq.Tests.UI/Services/OrchardCoreHosting/FakeStore.cs
+++ b/Lombiq.Tests.UI/Services/OrchardCoreHosting/FakeStore.cs
@@ -18,7 +18,7 @@ public sealed class FakeStore : IStore
public ITypeService TypeNames => _store.TypeNames;
- public ISession CreateSession()
+ public ISession CreateSession(bool withTracking = true)
{
var session = _store.CreateSession();
_createdSessions.Add(session);
diff --git a/Lombiq.Tests.UI/Services/SmtpService.cs b/Lombiq.Tests.UI/Services/SmtpService.cs
index 28b4d8a8a..68111bea2 100644
--- a/Lombiq.Tests.UI/Services/SmtpService.cs
+++ b/Lombiq.Tests.UI/Services/SmtpService.cs
@@ -1,8 +1,8 @@
using CliWrap;
using Lombiq.HelpfulLibraries.Cli;
-using Newtonsoft.Json.Linq;
using System;
using System.IO;
+using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
@@ -10,6 +10,7 @@ namespace Lombiq.Tests.UI.Services;
public class SmtpServiceConfiguration
{
+ public SmtpServiceRunningContext Context { get; set; }
}
public class SmtpServiceRunningContext
@@ -64,10 +65,10 @@ public async Task StartAsync()
_cancellationTokenSource = new CancellationTokenSource();
var token = _cancellationTokenSource.Token;
- var manifest = JObject.Parse(await File.ReadAllTextAsync(dotnetToolsConfigFilePath, token));
+ var manifest = JsonNode.Parse(await File.ReadAllTextAsync(dotnetToolsConfigFilePath, token));
// Verify that an smtp4dev configuration is in place.
- if ((manifest["tools"] as JObject)?["rnwood.smtp4dev"] == null)
+ if (manifest?["tools"]?["rnwood.smtp4dev"] == null)
{
throw new InvalidOperationException("There was no smtp4dev configuration in the .NET CLI local tool manifest file.");
}
diff --git a/Lombiq.Tests.UI/Services/UITestExecutionSession.cs b/Lombiq.Tests.UI/Services/UITestExecutionSession.cs
index 2149641a7..219748eef 100644
--- a/Lombiq.Tests.UI/Services/UITestExecutionSession.cs
+++ b/Lombiq.Tests.UI/Services/UITestExecutionSession.cs
@@ -10,13 +10,13 @@
using Lombiq.Tests.UI.Services.GitHub;
using Microsoft.VisualBasic.FileIO;
using Mono.Unix;
-using Newtonsoft.Json.Linq;
using Selenium.Axe;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Xunit.Abstractions;
using Xunit.Sdk;
@@ -711,7 +711,7 @@ async Task SqlServerManagerBeforeAppStartHandlerAsync(string contentRootPath, In
" wasn't found. This most possibly means that the tenant's setup failed.");
}
- var appSettings = JObject.Parse(await File.ReadAllTextAsync(appSettingsPath));
+ var appSettings = JsonNode.Parse(await File.ReadAllTextAsync(appSettingsPath))!;
appSettings[nameof(sqlServerContext.ConnectionString)] = sqlServerContext.ConnectionString;
await File.WriteAllTextAsync(appSettingsPath, appSettings.ToString());
}
@@ -764,6 +764,7 @@ private async Task StartSmtpServiceAsync()
{
_smtpService = new SmtpService(_configuration.SmtpServiceConfiguration);
var smtpContext = await _smtpService.StartAsync();
+ _configuration.SmtpServiceConfiguration.Context = smtpContext;
Task SmtpServiceBeforeAppStartHandlerAsync(string contentRootPath, InstanceCommandLineArgumentsBuilder arguments)
{