From d206057006ea9790b1513c614d749f74eef4a4ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Wed, 7 Jan 2026 23:54:55 +0100 Subject: [PATCH 001/132] Update OC versions to preview. --- .../Lombiq.Tests.UI.AppExtensions.csproj | 8 +++---- .../Lombiq.Tests.UI.Samples.csproj | 2 +- .../Lombiq.Tests.UI.Shortcuts.csproj | 22 +++++++++---------- .../Lombiq.Tests.UI.Tests.UI.csproj | 2 +- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 10 ++++----- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index 5740b73ad..01ad41349 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 false $(DefaultItemExcludes);.git* @@ -28,9 +28,9 @@ - - - + + + diff --git a/Lombiq.Tests.UI.Samples/Lombiq.Tests.UI.Samples.csproj b/Lombiq.Tests.UI.Samples/Lombiq.Tests.UI.Samples.csproj index af8528794..149aaf3c0 100644 --- a/Lombiq.Tests.UI.Samples/Lombiq.Tests.UI.Samples.csproj +++ b/Lombiq.Tests.UI.Samples/Lombiq.Tests.UI.Samples.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 false Exe diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index 50719dba6..58707c0f8 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 false true $(DefaultItemExcludes);.git* @@ -29,16 +29,16 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/Lombiq.Tests.UI.Tests.UI/Lombiq.Tests.UI.Tests.UI.csproj b/Lombiq.Tests.UI.Tests.UI/Lombiq.Tests.UI.Tests.UI.csproj index 36cbc2eed..a06fa1d34 100644 --- a/Lombiq.Tests.UI.Tests.UI/Lombiq.Tests.UI.Tests.UI.csproj +++ b/Lombiq.Tests.UI.Tests.UI/Lombiq.Tests.UI.Tests.UI.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index b3271fef3..71abf7d4c 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 false $(DefaultItemExcludes);.git* From aef8ec9e34dbe46de541995141c81fde283ef7ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Thu, 8 Jan 2026 11:58:57 +0100 Subject: [PATCH 002/132] Update "net8.0" references in UITT. --- Lombiq.Tests.UI/Docs/Configuration.md | 2 +- Lombiq.Tests.UI/Docs/Troubleshooting.md | 2 +- Lombiq.Tests.UI/Helpers/WebAppConfigHelper.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lombiq.Tests.UI/Docs/Configuration.md b/Lombiq.Tests.UI/Docs/Configuration.md index 5369cbe7e..454306648 100644 --- a/Lombiq.Tests.UI/Docs/Configuration.md +++ b/Lombiq.Tests.UI/Docs/Configuration.md @@ -24,7 +24,7 @@ Note also that some projects' _xunit.runner.json_ files may include the flag [`s Certain test execution parameters can be configured externally too, the ones retrieved via the `TestConfigurationManager` class. All configuration options are basic key-value pairs and can be provided in one of the two ways: -- Key-value pairs in a _TestConfiguration.json_ file. Note that this file needs to be in the folder where the UI tests execute. By default this is the build output folder of the given test project, i.e. where the projects's DLL is generated (e.g. _bin/Debug/net6.0_). +- Key-value pairs in a _TestConfiguration.json_ file. Note that this file needs to be in the folder where the UI tests execute. By default this is the build output folder of the given test project, i.e. where the projects's DLL is generated (e.g. _bin/Debug/net10.0_). - Environment variables: Their names should be prefixed with `Lombiq_Tests_UI`, followed by the config with a `__` as it is with [(ASP).NET configuration](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/), e.g. `Lombiq_Tests_UI__OrchardCoreUITestExecutorConfiguration__MaxRetryCount` (instead of the double underscore you can also use a `:` on certain platforms like Windows). Keep in mind that you can set these just for the current session too. Configuration in environment variables will take precedence over the _TestConfiguration.json_ file. When you're setting environment variables while trying out test execution keep in mind that you'll have to restart the app after changing any environment variable. Here's a full _TestConfiguration.json_ file example, something appropriate during development when you have a fast machine (probably faster then the one used to execute these tests) and want tests to fail fast instead of being reliable: diff --git a/Lombiq.Tests.UI/Docs/Troubleshooting.md b/Lombiq.Tests.UI/Docs/Troubleshooting.md index c097708c9..b6695eeb4 100644 --- a/Lombiq.Tests.UI/Docs/Troubleshooting.md +++ b/Lombiq.Tests.UI/Docs/Troubleshooting.md @@ -2,7 +2,7 @@ ## General tips -- When a test fails it'll create a dump in the test execution's folder (usually something like _bin/Debug/net8.0_ under your test project), in a new _TestDumps_ folder. (Though succeeding tests may also provide some output files there.) This should help you pinpoint where the issue is even if the test was run in a CI environment, and you can't reproduce it locally. The dump contains the following: +- When a test fails it'll create a dump in the test execution's folder (usually something like _bin/Debug/net10.0_ under your test project), in a new _TestDumps_ folder. (Though succeeding tests may also provide some output files there.) This should help you pinpoint where the issue is even if the test was run in a CI environment, and you can't reproduce it locally. The dump contains the following: - The Orchard application's folder, including settings files, the SQLite or SQL Server DB, logs, etc. that you can utilize to see log entries and to run the app from that state. - Browser logs, i.e. the developer console output. - Screenshots of each page in order the test visited them, as well as when the test failed (Windows Photo Viewer won't be able to open it though, use something else like the Windows 10 Photos app). diff --git a/Lombiq.Tests.UI/Helpers/WebAppConfigHelper.cs b/Lombiq.Tests.UI/Helpers/WebAppConfigHelper.cs index b48bb5601..643969eb3 100644 --- a/Lombiq.Tests.UI/Helpers/WebAppConfigHelper.cs +++ b/Lombiq.Tests.UI/Helpers/WebAppConfigHelper.cs @@ -12,10 +12,10 @@ public static class WebAppConfigHelper /// /// The web app's project name. /// - /// The name of the folder that corresponds to the .NET version in the build output folder (e.g. "net8.0"). + /// The name of the folder that corresponds to the .NET version in the build output folder (e.g. "net10.0"). /// /// The absolute path to the assembly (DLL) of the application being tested. - public static string GetAbsoluteApplicationAssemblyPath(string webAppName, string frameworkFolderName = "net8.0") + public static string GetAbsoluteApplicationAssemblyPath(string webAppName, string frameworkFolderName = "net10.0") { string baseDirectory; From 46dea81374b086045c287f24bf77355e21e509d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Thu, 8 Jan 2026 12:50:16 +0100 Subject: [PATCH 003/132] Fix HL warnings. --- Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs b/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs index 641ecc6db..37606d20f 100644 --- a/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs +++ b/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs @@ -17,7 +17,7 @@ public record ElasticsearchRunningContext(Guid Id, string Prefix) { /// /// Gets the expression that refers to all indexes that start with . This should only be used - /// with , because the OrchardCore-specific services automatically apply the prefix from + /// with , because the OrchardCore-specific services automatically apply the prefix from /// configuration so it would result in double prefixing. /// private IndexName LowLevelIndexName => Indices.Index($"{Prefix}_*"); From 0ba8718adf6f1be1e4594122ebbce76ca86c5f73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 9 Jan 2026 01:30:21 +0100 Subject: [PATCH 004/132] Fix various errors. --- .../Models/ElasticsearchRunningContext.cs | 47 +++++++++---------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs b/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs index 37606d20f..76d9a793f 100644 --- a/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs +++ b/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs @@ -1,9 +1,11 @@ -using Elasticsearch.Net; +using Elastic.Clients.Elasticsearch; +using Elastic.Clients.Elasticsearch.Core; +using Elastic.Clients.Elasticsearch.IndexManagement; using Lombiq.Tests.UI.Extensions; using Lombiq.Tests.UI.Services; using Microsoft.Extensions.DependencyInjection; -using Nest; using OrchardCore.Indexing; +using OrchardCore.Search.Elasticsearch.Core.Models; using OrchardCore.Search.Elasticsearch.Core.Services; using System; using System.Diagnostics.CodeAnalysis; @@ -29,19 +31,14 @@ public Task BeforeTestAsync(UITestContext context) => { var index = LowLevelIndexName; var testCancellationToken = context.Configuration.TestCancellationToken; + var client = GetClient(provider, index); - if (GetClient(provider) is not { } client) - { - throw new InvalidOperationException( - $"Couldn't resolve {nameof(IElasticClient)} while waiting for \"{index}\"."); - } - - (await client.Indices.FlushAsync(index, ct: testCancellationToken)).ThrowIfFailed($"flush index \"{index}\""); - (await client.Indices.RefreshAsync(index, ct: testCancellationToken)).ThrowIfFailed($"refresh index \"{index}\""); + (await client.Indices.FlushAsync(index, cancellationToken: testCancellationToken)).ThrowIfFailed($"flush index \"{index}\""); + (await client.Indices.RefreshAsync(index, cancellationToken: testCancellationToken)).ThrowIfFailed($"refresh index \"{index}\""); - var settingsService = provider.GetRequiredService(); - var indexSettings = await settingsService.GetSettingsAsync(); - var exactIndexName = indexSettings.FirstOrDefault()?.IndexName; + var exactIndexName = (await provider.GetRequiredService().GetSettingsAsync()) + .FirstOrDefault() + ?.IndexName; if (exactIndexName == null) return; @@ -52,7 +49,7 @@ public Task BeforeTestAsync(UITestContext context) => .CreateLinkedTokenSource(testCancellationToken, timeoutCancellationTokenSource.Token); var jointCancellationToken = jointCancellationTokenSource.Token; - var elasticIndexManager = provider.GetRequiredService(); + var elasticIndexManager = provider.GetRequiredService(); long? lastFinishedTaskId = null; @@ -99,7 +96,7 @@ private static async Task GetLastTaskIdAsync(IIndexingTaskManager indexing #pragma warning disable S1994 // "for" loop increment clauses should modify the loops' counters for (var startIndex = 0; hasTask; startIndex += batchSize) { - var lastTask = (await indexingTaskManager.GetIndexingTasksAsync(startIndex, batchSize)) + var lastTask = (await indexingTaskManager.GetIndexingTasksAsync(startIndex, batchSize, category: null)) .LastOrDefault(); hasTask = lastTask != null; @@ -118,7 +115,7 @@ private static async Task GetLastTaskIdAsync(IIndexingTaskManager indexing /// Asking for the last task ID can throw an exception if the underlying value is not initialized yet. This method /// catches the exception and returns null instead so it can be safely retried. /// - private static async Task TryGetLastFinishedTaskIdAsync(ElasticIndexManager elasticIndexManager, string indexName) + private static async Task TryGetLastFinishedTaskIdAsync(ElasticsearchIndexManager elasticIndexManager, string indexName) { try { @@ -152,18 +149,14 @@ private static async Task WithPrefixElasticsearchIndexCleanupFinallyAsync( UITestContext context, IndexName index) { - static async Task CheckIfIndexExistsAsync(IElasticClient client, IndexName index) + static async Task CheckIfIndexExistsAsync(ElasticsearchClient client, IndexName index) { - var indices = (await client.Indices.GetAsync(index, ct: CancellationToken.None)) + var indices = (await client.Indices.GetAsync(index, cancellationToken: CancellationToken.None)) .ThrowIfFailed($"query index \"{index}\""); return indices.Indices.Count > 0; } - if (GetClient(provider) is not { } client) - { - throw new InvalidOperationException( - $"Couldn't resolve {nameof(IElasticClient)} while attempting to clean up \"{index}\"."); - } + var client = GetClient(provider, index); if (!await CheckIfIndexExistsAsync(client, index)) { @@ -180,6 +173,10 @@ static async Task CheckIfIndexExistsAsync(IElasticClient client, IndexName } } - private static IElasticClient GetClient(IServiceProvider provider) => - provider.GetService() ?? provider.GetService(); + private static ElasticsearchClient GetClient(IServiceProvider provider, IndexName index) => + (provider.GetService() is { } factory + ? factory.Create(new ElasticsearchConnectionOptions()) + : provider.GetService()) ?? + throw new InvalidOperationException( + $"Couldn't resolve {nameof(ElasticsearchClient)} while waiting for \"{index}\"."); } From b0b3dd4ca2c2c791cce90d94a64d67834f3d887f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 9 Jan 2026 12:52:25 +0100 Subject: [PATCH 005/132] Fix remaining build errors. --- .../ShortcutsUITestContextExtensions.cs | 6 ++-- ...ngScopeWebApplicationInstanceExtensions.cs | 2 +- .../Models/ElasticsearchRunningContext.cs | 29 ++++++++++++------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Lombiq.Tests.UI/Extensions/ShortcutsUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/ShortcutsUITestContextExtensions.cs index 2225a4348..d6708c6ae 100644 --- a/Lombiq.Tests.UI/Extensions/ShortcutsUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/ShortcutsUITestContextExtensions.cs @@ -500,7 +500,7 @@ public static async Task CreateAndSwitchToTenantAsync( ? DatabaseProviderValue.SqlConnection : setupParameters.DatabaseProvider.ToString(); - await context.Application.UsingScopeAsync( + await context.Application.UsingScopeServiceProviderAsync( async serviceProvider => { var shellHost = serviceProvider.GetRequiredService(); @@ -523,7 +523,7 @@ await context.Application.UsingScopeAsync( await shellHost.UpdateShellSettingsAsync(shellSettings); }); - await context.Application.UsingScopeAsync( + await context.Application.UsingScopeServiceProviderAsync( async serviceProvider => { var setupService = serviceProvider.GetRequiredService(); @@ -702,7 +702,7 @@ private static Task UsingScopeAsync( tenant ??= context.TenantName; if (tenant.StartsWith('!')) tenant = ShellSettings.DefaultShellName; - return context.Application.UsingScopeAsync(execute, tenant, activateShell); + return context.Application.UsingScopeServiceProviderAsync(execute, tenant, activateShell); } /// diff --git a/Lombiq.Tests.UI/Extensions/UsingScopeWebApplicationInstanceExtensions.cs b/Lombiq.Tests.UI/Extensions/UsingScopeWebApplicationInstanceExtensions.cs index 00cbad113..a2f9d7eb2 100644 --- a/Lombiq.Tests.UI/Extensions/UsingScopeWebApplicationInstanceExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/UsingScopeWebApplicationInstanceExtensions.cs @@ -15,7 +15,7 @@ public static class UsingScopeWebApplicationInstanceExtensions /// Executes a delegate using the shell scope given by in an isolated async flow, while /// managing the shell state and invoking tenant events. /// - public static Task UsingScopeAsync( + public static Task UsingScopeServiceProviderAsync( this IWebApplicationInstance instance, Func execute, string tenant = "Default", diff --git a/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs b/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs index 76d9a793f..d32cd46df 100644 --- a/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs +++ b/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs @@ -27,7 +27,7 @@ public record ElasticsearchRunningContext(Guid Id, string Prefix) // Elasticsearch indexing sometimes takes longer, and the testing starts before indexing finishes. To prevent that, // we are checking if all indexing tasks are finished. public Task BeforeTestAsync(UITestContext context) => - context.Application.UsingScopeAsync(async provider => + context.Application.UsingScopeServiceProviderAsync(async provider => { var index = LowLevelIndexName; var testCancellationToken = context.Configuration.TestCancellationToken; @@ -36,9 +36,9 @@ public Task BeforeTestAsync(UITestContext context) => (await client.Indices.FlushAsync(index, cancellationToken: testCancellationToken)).ThrowIfFailed($"flush index \"{index}\""); (await client.Indices.RefreshAsync(index, cancellationToken: testCancellationToken)).ThrowIfFailed($"refresh index \"{index}\""); - var exactIndexName = (await provider.GetRequiredService().GetSettingsAsync()) - .FirstOrDefault() - ?.IndexName; + var indexProfileStore = provider.GetRequiredService(); + var indexSettings = await indexProfileStore.GetAllElasticsearchIndexesAsync(); + var exactIndexName = indexSettings.FirstOrDefault()?.IndexName; if (exactIndexName == null) return; @@ -49,7 +49,7 @@ public Task BeforeTestAsync(UITestContext context) => .CreateLinkedTokenSource(testCancellationToken, timeoutCancellationTokenSource.Token); var jointCancellationToken = jointCancellationTokenSource.Token; - var elasticIndexManager = provider.GetRequiredService(); + var elasticsearchDocumentIndexManager = provider.GetRequiredService(); long? lastFinishedTaskId = null; @@ -63,7 +63,10 @@ public Task BeforeTestAsync(UITestContext context) => { jointCancellationToken.ThrowIfCancellationRequested(); - lastFinishedTaskId = await TryGetLastFinishedTaskIdAsync(elasticIndexManager, exactIndexName); + lastFinishedTaskId = await TryGetLastFinishedTaskIdAsync( + elasticsearchDocumentIndexManager, + indexProfileStore, + exactIndexName); // The indexing takes a couple of seconds, so there is no need to check them so fast: we are adding // a delay. @@ -115,11 +118,15 @@ private static async Task GetLastTaskIdAsync(IIndexingTaskManager indexing /// Asking for the last task ID can throw an exception if the underlying value is not initialized yet. This method /// catches the exception and returns null instead so it can be safely retried. /// - private static async Task TryGetLastFinishedTaskIdAsync(ElasticsearchIndexManager elasticIndexManager, string indexName) + private static async Task TryGetLastFinishedTaskIdAsync( + ElasticsearchDocumentIndexManager elasticsearchDocumentIndexManager, + IIndexProfileStore indexProfileStore, + string indexName) { try { - return await elasticIndexManager.GetLastTaskId(indexName); + var indexProfile = await indexProfileStore.FindByNameAsync(indexName); + return await elasticsearchDocumentIndexManager.GetLastTaskIdAsync(indexProfile); } catch (InvalidOperationException) { @@ -131,7 +138,7 @@ private async Task AfterTestInnerAsync(UITestContext context) { try { - await context.Application.UsingScopeAsync(provider => + await context.Application.UsingScopeServiceProviderAsync(provider => WithPrefixElasticsearchIndexCleanupFinallyAsync(provider, context, LowLevelIndexName)); } catch (Exception inner) @@ -164,12 +171,12 @@ static async Task CheckIfIndexExistsAsync(ElasticsearchClient client, Inde return; } - var deleteRequest = new DeleteIndexRequest(index) { ExpandWildcards = ExpandWildcards.All }; + var deleteRequest = new DeleteIndexRequest(index) { ExpandWildcards = [ExpandWildcard.All] }; (await client.Indices.DeleteAsync(deleteRequest)).ThrowIfFailed($"delete index \"{index}\""); if (await CheckIfIndexExistsAsync(client, index)) { - throw new InvalidOperationException($"Couldn't delete indexes for \"{index.Name}\"."); + throw new InvalidOperationException($"Couldn't delete indexes for \"{index}\"."); } } From 2fb9e7fa5efb205a5dcdffa0a9e159b314f12d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 9 Jan 2026 14:31:45 +0100 Subject: [PATCH 006/132] Fix miscileneous warnings. --- .../Extensions/OrchardCoreConfigurationExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Tests.UI/Extensions/OrchardCoreConfigurationExtensions.cs b/Lombiq.Tests.UI/Extensions/OrchardCoreConfigurationExtensions.cs index 50042180b..a2290ba98 100644 --- a/Lombiq.Tests.UI/Extensions/OrchardCoreConfigurationExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/OrchardCoreConfigurationExtensions.cs @@ -16,7 +16,7 @@ public static void ConfigureElasticSearchPrefix(this OrchardCoreConfiguration co /// /// Configure the app settings to use the provided in the Elasticsearch indexes created by - /// the . + /// the . /// public static void ConfigureElasticsearchPrefix(this OrchardCoreConfiguration configuration, string prefix) => configuration.BeforeAppStart += (_, arguments) => From 393342c45acdf48569fde466920f002de0c5f9ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 9 Jan 2026 18:43:52 +0100 Subject: [PATCH 007/132] Remove System.Linq.Async package references, since that's now part of .NET. --- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 71abf7d4c..e2bbab455 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -90,7 +90,6 @@ renovate.json5 for details. --> - From 08475ae68395befd8666d194dd17ce0ef400a216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 9 Jan 2026 18:57:50 +0100 Subject: [PATCH 008/132] In UITT mark NotMediaCacheEntries as Obsolete and make sure it's not used. It's no longer needed since https://github.com/OrchardCMS/OrchardCore/pull/18341 was merged. --- Lombiq.Tests.UI/Helpers/AppLogAssertionHelper.cs | 2 ++ .../OrchardCoreUITestExecutorConfigurationExtensions.cs | 2 -- .../Services/OrchardCoreUITestExecutorConfiguration.cs | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Lombiq.Tests.UI/Helpers/AppLogAssertionHelper.cs b/Lombiq.Tests.UI/Helpers/AppLogAssertionHelper.cs index 5ffc5b682..5986496bc 100644 --- a/Lombiq.Tests.UI/Helpers/AppLogAssertionHelper.cs +++ b/Lombiq.Tests.UI/Helpers/AppLogAssertionHelper.cs @@ -9,6 +9,7 @@ public static class AppLogAssertionHelper /// /// An wrapping . /// + [Obsolete("This is no longer necessary after https://github.com/OrchardCMS/OrchardCore/pull/18341.")] public static readonly Expression> NotMediaCacheEntriesPredicate = logEntry => NotMediaCacheEntries(logEntry); @@ -17,6 +18,7 @@ public static class AppLogAssertionHelper /// DefaultMediaFileStoreCacheFileProvider. These errors frequently happen during UI testing when using Azure /// Blob Storage for media storage. They're harmless, though. /// + [Obsolete("This is no longer necessary after https://github.com/OrchardCMS/OrchardCore/pull/18341.")] public static bool NotMediaCacheEntries(IApplicationLogEntry logEntry) => logEntry.Category != "OrchardCore.Media.Core.DefaultMediaFileStoreCacheFileProvider" || !logEntry.Message.StartsWithOrdinalIgnoreCase("Error deleting cache folder"); diff --git a/Lombiq.Tests.UI/SecurityScanning/OrchardCoreUITestExecutorConfigurationExtensions.cs b/Lombiq.Tests.UI/SecurityScanning/OrchardCoreUITestExecutorConfigurationExtensions.cs index 34b5dbf61..0fe4f506d 100644 --- a/Lombiq.Tests.UI/SecurityScanning/OrchardCoreUITestExecutorConfigurationExtensions.cs +++ b/Lombiq.Tests.UI/SecurityScanning/OrchardCoreUITestExecutorConfigurationExtensions.cs @@ -1,5 +1,4 @@ using Lombiq.Tests.UI.Extensions; -using Lombiq.Tests.UI.Helpers; using Lombiq.Tests.UI.Services; using Lombiq.Tests.UI.Shortcuts.Controllers; using Microsoft.Extensions.Logging; @@ -73,7 +72,6 @@ public static Func CreateAppLogAssertionForSecuri app.LogsShouldNotContainAsync( logEntry => logEntry.Level >= LogLevel.Error && - AppLogAssertionHelper.NotMediaCacheEntries(logEntry) && !permittedErrorLinePatterns.Any(pattern => Regex.IsMatch(logEntry.ToString(), pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled)), TestContext.Current.CancellationToken); diff --git a/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs b/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs index ffe26f01b..a5ea2a966 100644 --- a/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs +++ b/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs @@ -30,9 +30,10 @@ public enum Browser public class OrchardCoreUITestExecutorConfiguration { - public static readonly Func AssertAppLogsAreEmptyAsync = app => - app.LogsShouldBeEmptyAsync(TestContext.Current.CancellationToken); + public static readonly Func AssertAppLogsAreEmptyAsync = + app => app.LogsShouldBeEmptyAsync(TestContext.Current.CancellationToken); + [Obsolete("This is no longer necessary after https://github.com/OrchardCMS/OrchardCore/pull/18341.")] public static readonly Func AssertAppLogsCanContainCacheFolderErrorsAsync = app => app.LogsShouldNotContainAsync(AppLogAssertionHelper.NotMediaCacheEntriesPredicate, TestContext.Current.CancellationToken); @@ -85,7 +86,7 @@ public class OrchardCoreUITestExecutorConfiguration $"{nameof(OrchardCoreUITestExecutorConfiguration)}:RetryIntervalSeconds", 0)); - public Func AssertAppLogsAsync { get; set; } = AssertAppLogsCanContainCacheFolderErrorsAsync; + public Func AssertAppLogsAsync { get; set; } = AssertAppLogsAreEmptyAsync; /// /// Gets a collection of delegate that selects which response data get saved to Date: Sun, 11 Jan 2026 00:45:12 +0100 Subject: [PATCH 009/132] Update all Microsoft packages. --- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index e2bbab455..109eb8797 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -74,10 +74,10 @@ - + - - + + From cd84c8db4b463230ca1ffeced2bf89a997971b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sun, 11 Jan 2026 01:59:49 +0100 Subject: [PATCH 010/132] Exclude error log "Skipping feature 'OrchardCore.Tenants' as it is allowed on the default tenant only.". --- .../Services/OrchardCoreUITestExecutorConfiguration.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs b/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs index a5ea2a966..90472fc61 100644 --- a/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs +++ b/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs @@ -33,6 +33,11 @@ public class OrchardCoreUITestExecutorConfiguration public static readonly Func AssertAppLogsAreEmptyAsync = app => app.LogsShouldBeEmptyAsync(TestContext.Current.CancellationToken); + public static readonly Func AssertAppLogsCanContainFeatureSkipAsync = + app => app.LogsShouldNotContainAsync( + entry => entry.Message != "Skipping feature 'OrchardCore.Tenants' as it is allowed on the default tenant only.", + TestContext.Current.CancellationToken); + [Obsolete("This is no longer necessary after https://github.com/OrchardCMS/OrchardCore/pull/18341.")] public static readonly Func AssertAppLogsCanContainCacheFolderErrorsAsync = app => app.LogsShouldNotContainAsync(AppLogAssertionHelper.NotMediaCacheEntriesPredicate, TestContext.Current.CancellationToken); @@ -86,7 +91,7 @@ public class OrchardCoreUITestExecutorConfiguration $"{nameof(OrchardCoreUITestExecutorConfiguration)}:RetryIntervalSeconds", 0)); - public Func AssertAppLogsAsync { get; set; } = AssertAppLogsAreEmptyAsync; + public Func AssertAppLogsAsync { get; set; } = AssertAppLogsCanContainFeatureSkipAsync; /// /// Gets a collection of delegate that selects which response data get saved to Date: Sun, 11 Jan 2026 15:38:12 +0100 Subject: [PATCH 011/132] Fix ShuttingDownIdleTenantsShouldWork --- .../Services/OrchardCoreUITestExecutorConfiguration.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs b/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs index 90472fc61..ca0e72a05 100644 --- a/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs +++ b/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs @@ -2,6 +2,7 @@ using Lombiq.Tests.UI.Helpers; using Lombiq.Tests.UI.SecurityScanning; using Lombiq.Tests.UI.Services.GitHub; +using Microsoft.Extensions.Logging; using OpenQA.Selenium.BiDi.Log; using OpenQA.Selenium.BiDi.Network; using Shouldly; @@ -35,7 +36,9 @@ public class OrchardCoreUITestExecutorConfiguration public static readonly Func AssertAppLogsCanContainFeatureSkipAsync = app => app.LogsShouldNotContainAsync( - entry => entry.Message != "Skipping feature 'OrchardCore.Tenants' as it is allowed on the default tenant only.", + entry => + entry.Level >= LogLevel.Warning && + entry.Message != "Skipping feature 'OrchardCore.Tenants' as it is allowed on the default tenant only.", TestContext.Current.CancellationToken); [Obsolete("This is no longer necessary after https://github.com/OrchardCMS/OrchardCore/pull/18341.")] From d1c6efad66092b15f1272cff242ac99e44bc5071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sun, 11 Jan 2026 20:10:00 +0100 Subject: [PATCH 012/132] Fix TimeShouldUpdate. --- Lombiq.Tests.UI.Samples/Tests/ShiftTimeTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lombiq.Tests.UI.Samples/Tests/ShiftTimeTests.cs b/Lombiq.Tests.UI.Samples/Tests/ShiftTimeTests.cs index 8282b8d94..160c923e1 100644 --- a/Lombiq.Tests.UI.Samples/Tests/ShiftTimeTests.cs +++ b/Lombiq.Tests.UI.Samples/Tests/ShiftTimeTests.cs @@ -35,10 +35,14 @@ public Task TimeShouldUpdate() => await context.GoToAdminRelativeUrlAsync( "/Contents/ContentTypes/LiquidWidget/Create?returnUrl=%2FAdmin%2FLayers&" + "LayerMetadata.Zone=Content&LayerMetadata.Position=1"); + await context.FillInWithRetriesAsync(By.Id("LayerMetadata_Title"), "Current Time Widget"); + await context.SetDropdownByValueAsync(By.Id("LayerMetadata_LayerMetadata_Layer"), "Always"); await context.FillInCodeMirrorEditorWithRetriesAsync( By.CssSelector(".CodeMirror.cm-s-default"), "
{{ \"now\" | utc | date: \"%Y-%m-%d %H:%M\" }}
"); await context.ClickReliablyOnAsync(By.ClassName("publish")); + context.ShouldBeSuccess(); + context.Missing(By.CssSelector(".validation-summary-errors ul li")); var now = await GetNowAsync(context); From 047096b9a63da4d6f1e76c11a8c8334fab0ba0b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sun, 11 Jan 2026 22:20:17 +0100 Subject: [PATCH 013/132] Remove OrchardCore.Tenants from the recipes. --- Lombiq.Tests.UI.Samples/Tests/TenantTests.cs | 1 + Lombiq.Tests.UI.Shortcuts/Manifest.cs | 1 - .../Extensions/ShortcutsUITestContextExtensions.cs | 6 ++++++ Lombiq.Tests.UI/Services/UITestContext.cs | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Lombiq.Tests.UI.Samples/Tests/TenantTests.cs b/Lombiq.Tests.UI.Samples/Tests/TenantTests.cs index 459a4ac2b..63ea9ce35 100644 --- a/Lombiq.Tests.UI.Samples/Tests/TenantTests.cs +++ b/Lombiq.Tests.UI.Samples/Tests/TenantTests.cs @@ -31,6 +31,7 @@ public Task CreatingTenantShouldWork() => await context.SignInDirectlyAsync(); // Create the tenant with a custom admin user. + await context.EnableTenantsFeatureAsync(); await context.CreateAndSwitchToTenantAsync( TestTenantName, TestTenantUrlPrefix, diff --git a/Lombiq.Tests.UI.Shortcuts/Manifest.cs b/Lombiq.Tests.UI.Shortcuts/Manifest.cs index 17843c5e2..706a3a091 100644 --- a/Lombiq.Tests.UI.Shortcuts/Manifest.cs +++ b/Lombiq.Tests.UI.Shortcuts/Manifest.cs @@ -19,7 +19,6 @@ "OrchardCore.ContentTypes", "OrchardCore.DisplayManagement", "OrchardCore.Roles", - "OrchardCore.Tenants", "OrchardCore.Users", ] )] diff --git a/Lombiq.Tests.UI/Extensions/ShortcutsUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/ShortcutsUITestContextExtensions.cs index d6708c6ae..04937f7b4 100644 --- a/Lombiq.Tests.UI/Extensions/ShortcutsUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/ShortcutsUITestContextExtensions.cs @@ -268,6 +268,12 @@ public static Task EnableFeatureDirectlyAsync( tenant, activateShell); + /// + /// Enables the Tenants feature for the default tenant. + /// + public static Task EnableTenantsFeatureAsync(this UITestContext context) => + context.EnableFeatureDirectlyAsync("OrchardCore.Tenants", ShellSettings.DefaultShellName); + /// /// Disables the feature with the given directly. /// diff --git a/Lombiq.Tests.UI/Services/UITestContext.cs b/Lombiq.Tests.UI/Services/UITestContext.cs index 2ddbfb25d..deac2ed45 100644 --- a/Lombiq.Tests.UI/Services/UITestContext.cs +++ b/Lombiq.Tests.UI/Services/UITestContext.cs @@ -152,7 +152,7 @@ public sealed class UITestContext : IAsyncDisposable /// Gets the current tenant name. When testing sites with multi-tenancy use /// . ///
- public string TenantName { get; private set; } = "Default"; + public string TenantName { get; private set; } = ShellSettings.DefaultShellName; /// /// Gets or sets the prefix used for all relative URLs. It should neither start nor end with a slash. From aa1861930cf095426a53ad653619fbe7a3dab468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Mon, 12 Jan 2026 00:05:16 +0100 Subject: [PATCH 014/132] Fix BehaviorFeaturesGuardTests. --- .../Extensions/TenantsUITestContextExtensions.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Lombiq.Tests.UI/Extensions/TenantsUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/TenantsUITestContextExtensions.cs index f769b9a41..c60c7b72f 100644 --- a/Lombiq.Tests.UI/Extensions/TenantsUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/TenantsUITestContextExtensions.cs @@ -13,7 +13,8 @@ public static async Task CreateAndSwitchToTenantManuallyAsync( string urlPrefix = "", string urlHost = "", string featureProfile = "", - bool navigate = true) + bool navigate = true, + bool enableFeature = true) { await context.CreateTenantManuallyAsync(name, urlPrefix, urlHost, featureProfile, navigate); @@ -28,8 +29,15 @@ public static async Task CreateTenantManuallyAsync( string urlPrefix = "", string urlHost = "", string featureProfile = "", - bool navigate = true) + bool navigate = true, + bool enableFeature = true) { + if (enableFeature) + { + await context.EnableTenantsFeatureAsync(); + await context.EnableFeatureDirectlyAsync("OrchardCore.Tenants.FeatureProfiles"); + } + if (navigate) { await context.GoToAdminRelativeUrlAsync("/Tenants"); From 449c8c63569a34e6135e347624465f52aea8114d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Mon, 12 Jan 2026 00:16:18 +0100 Subject: [PATCH 015/132] Fix BehaviorMediaThemeTests. --- .../Extensions/ShortcutsUITestContextExtensions.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Lombiq.Tests.UI/Extensions/ShortcutsUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/ShortcutsUITestContextExtensions.cs index 04937f7b4..59eec9b88 100644 --- a/Lombiq.Tests.UI/Extensions/ShortcutsUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/ShortcutsUITestContextExtensions.cs @@ -499,8 +499,14 @@ public static async Task CreateAndSwitchToTenantAsync( string name, string urlPrefix, OrchardCoreSetupParameters setupParameters, - string featureProfile = null) + string featureProfile = null, + bool enableFeature = true) { + if (enableFeature) + { + await context.EnableTenantsFeatureAsync(); + } + setupParameters ??= new OrchardCoreSetupParameters(context); var databaseProvider = setupParameters.DatabaseProvider == OrchardCoreSetupParameters.DatabaseType.SqlConnection ? DatabaseProviderValue.SqlConnection From 1a999792584fa16b0f1860a1294884715801c35e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Mon, 12 Jan 2026 11:22:19 +0100 Subject: [PATCH 016/132] Temporarily disable ElasticsearchShouldWork. --- Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs b/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs index 99df4b025..e58c161c7 100644 --- a/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs +++ b/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs @@ -2,6 +2,7 @@ using Lombiq.Tests.UI.Samples.Helpers; using OpenQA.Selenium; using System; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Xunit; @@ -16,6 +17,7 @@ namespace Lombiq.Tests.UI.Samples.Tests; // If you use the Build and Test Orchard Core workflow of Lombiq GitHub Actions for CI builds (see // https://github.com/Lombiq/GitHub-Actions/blob/dev/Docs/Workflows/BuildDotNetCoreOrchardCore/BuildAndTestOrchardCoreSolution.md), // then you can also utilize Elasticsearch in CI test runs, without needing to do any setup on your own. +[SuppressMessage("Usage", "xUnit1004:Test methods should not be skipped", Justification = "Temporarily disabled.")] public class ElasticsearchTests : UITestBase { public ElasticsearchTests(ITestOutputHelper testOutputHelper) @@ -23,7 +25,7 @@ public ElasticsearchTests(ITestOutputHelper testOutputHelper) { } - [Fact] + [Fact(Skip = "Temporarily disabled.")] public Task ElasticsearchShouldWork() => ExecuteTestAsync( async context => From ef6423f9159208e2d55468fd1a19a892be9a12e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 13 Jan 2026 23:36:58 +0100 Subject: [PATCH 017/132] Re-enable ElasticsearchTests. --- Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs b/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs index e58c161c7..85fe5b625 100644 --- a/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs +++ b/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs @@ -1,8 +1,6 @@ using Lombiq.Tests.UI.Extensions; using Lombiq.Tests.UI.Samples.Helpers; using OpenQA.Selenium; -using System; -using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Xunit; @@ -17,7 +15,6 @@ namespace Lombiq.Tests.UI.Samples.Tests; // If you use the Build and Test Orchard Core workflow of Lombiq GitHub Actions for CI builds (see // https://github.com/Lombiq/GitHub-Actions/blob/dev/Docs/Workflows/BuildDotNetCoreOrchardCore/BuildAndTestOrchardCoreSolution.md), // then you can also utilize Elasticsearch in CI test runs, without needing to do any setup on your own. -[SuppressMessage("Usage", "xUnit1004:Test methods should not be skipped", Justification = "Temporarily disabled.")] public class ElasticsearchTests : UITestBase { public ElasticsearchTests(ITestOutputHelper testOutputHelper) @@ -25,7 +22,7 @@ public ElasticsearchTests(ITestOutputHelper testOutputHelper) { } - [Fact(Skip = "Temporarily disabled.")] + [Fact] public Task ElasticsearchShouldWork() => ExecuteTestAsync( async context => From d6f3e24679ab0f6443d25a4bbbb260315ff9ebbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Wed, 14 Jan 2026 17:28:50 +0100 Subject: [PATCH 018/132] Upgrade OC preview. --- .../Lombiq.Tests.UI.AppExtensions.csproj | 6 +++--- .../Lombiq.Tests.UI.Shortcuts.csproj | 20 +++++++++---------- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 8 ++++---- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index 01ad41349..4fada9a53 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -28,9 +28,9 @@ - - - + + + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index b86783ad9..40ed35e30 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -29,16 +29,16 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 109eb8797..f6cea4fe3 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -81,10 +81,10 @@ - - - - + + + + From 9360e795df0c2557ec08a8af49186fa9a691dcfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Wed, 14 Jan 2026 22:15:46 +0100 Subject: [PATCH 019/132] Reference search by name. --- Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs b/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs index 85fe5b625..4e5b58dbd 100644 --- a/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs +++ b/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs @@ -29,7 +29,7 @@ public Task ElasticsearchShouldWork() => { // Going to the built-in search feature. By default, it's not accessible for anonymous users, so we need // to log in. - await context.SignInDirectlyAndGoToRelativeUrlAsync("/search"); + await context.SignInDirectlyAndGoToRelativeUrlAsync("/search/elasticsearchshouldwork"); // Filling out the search form, looking for the sample blog post coming from the built-in Blog recipe. await context.ClickAndFillInWithRetriesAsync(By.Name("Terms"), "exploration"); From fd71d24fbf9bee300a9d0ae73dccb03287b95b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Wed, 14 Jan 2026 22:34:54 +0100 Subject: [PATCH 020/132] Temporarily disabled. --- Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs b/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs index 4e5b58dbd..cb6c11fe0 100644 --- a/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs +++ b/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs @@ -22,7 +22,9 @@ public ElasticsearchTests(ITestOutputHelper testOutputHelper) { } - [Fact] +#pragma warning disable xUnit1004 + [Fact(Skip = "Temporarily disabled.")] +#pragma warning restore xUnit1004 public Task ElasticsearchShouldWork() => ExecuteTestAsync( async context => From f6252b766b47cc32d251564577e19fed90e998a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 16 Jan 2026 15:32:05 +0100 Subject: [PATCH 021/132] Switch from Swagger to Microsoft.AspNetCore.OpenApi. --- .../Lombiq.Tests.UI.Shortcuts.csproj | 1 + Lombiq.Tests.UI.Shortcuts/Manifest.cs | 6 +++--- Lombiq.Tests.UI.Shortcuts/ShortcutsFeatureIds.cs | 2 +- Lombiq.Tests.UI.Shortcuts/Startup.cs | 9 ++++----- .../SecurityScanningUITestContextExtensions.cs | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index 40ed35e30..030c7c66a 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -29,6 +29,7 @@ + diff --git a/Lombiq.Tests.UI.Shortcuts/Manifest.cs b/Lombiq.Tests.UI.Shortcuts/Manifest.cs index 706a3a091..ac2c43828 100644 --- a/Lombiq.Tests.UI.Shortcuts/Manifest.cs +++ b/Lombiq.Tests.UI.Shortcuts/Manifest.cs @@ -53,10 +53,10 @@ )] [assembly: Feature( - Id = Swagger, - Name = "Swagger - Shortcuts - Lombiq UI Testing Toolbox", + Id = OpenApi, + Name = "OpenAPI - Shortcuts - Lombiq UI Testing Toolbox", Category = "Development", - Description = DescriptionUiTestWarning + "Provides a Swagger endpoint to generate a JSON OpenAPI definition for " + + Description = DescriptionUiTestWarning + "Provides an endpoint to generate a JSON OpenAPI definition for " + "the web APIs available in the app. Used in security scanning." )] diff --git a/Lombiq.Tests.UI.Shortcuts/ShortcutsFeatureIds.cs b/Lombiq.Tests.UI.Shortcuts/ShortcutsFeatureIds.cs index 1f7532430..0915a78ec 100644 --- a/Lombiq.Tests.UI.Shortcuts/ShortcutsFeatureIds.cs +++ b/Lombiq.Tests.UI.Shortcuts/ShortcutsFeatureIds.cs @@ -10,6 +10,6 @@ public static class ShortcutsFeatureIds public const string FeatureToggleTestBench = $"{Default}.{nameof(FeatureToggleTestBench)}"; public const string MediaCachePurge = $"{Default}.{nameof(MediaCachePurge)}"; public const string ShiftTime = $"{Default}.{nameof(ShiftTime)}"; - public const string Swagger = $"{Default}.{nameof(Swagger)}"; + public const string OpenApi = $"{Default}.{nameof(OpenApi)}"; public const string Workflows = $"{Default}.{nameof(Workflows)}"; } diff --git a/Lombiq.Tests.UI.Shortcuts/Startup.cs b/Lombiq.Tests.UI.Shortcuts/Startup.cs index 4b75d20ef..708418947 100644 --- a/Lombiq.Tests.UI.Shortcuts/Startup.cs +++ b/Lombiq.Tests.UI.Shortcuts/Startup.cs @@ -27,15 +27,14 @@ public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder ro app.UseMiddleware(); } -[Feature(ShortcutsFeatureIds.Swagger)] -public sealed class SwaggerStartup : StartupBase +[Feature(ShortcutsFeatureIds.OpenApi)] +public sealed class OpenApiSetup : StartupBase { public override void ConfigureServices(IServiceCollection services) => - services.AddSwaggerGen(swaggerGenOptions => - swaggerGenOptions.SwaggerDoc("v1", new OpenApiInfo { Title = "Orchard Core API", Version = "v1" })); + services.AddOpenApi(options => options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_1); public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider) => - app.UseSwagger(); + routes.MapOpenApi(); } [Feature(ShortcutsFeatureIds.ShiftTime)] diff --git a/Lombiq.Tests.UI/SecurityScanning/SecurityScanningUITestContextExtensions.cs b/Lombiq.Tests.UI/SecurityScanning/SecurityScanningUITestContextExtensions.cs index 947686bcf..1496d97d6 100644 --- a/Lombiq.Tests.UI/SecurityScanning/SecurityScanningUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/SecurityScanning/SecurityScanningUITestContextExtensions.cs @@ -123,7 +123,7 @@ public static Task RunAndAssertGraphQLSecurityScanAsync( /// /// /// The of the JSON OpenAPI definition for the API to scan. If then the API - /// of the app will automatically be discovered with Swagger. + /// of the app will automatically be discovered with OpenAPI. /// /// A delegate to configure the security scan in detail. /// @@ -137,7 +137,7 @@ public static async Task RunAndAssertOpenApiSecurityScanAsync( { if (apiDefinitionUri == null) { - await context.EnableFeatureDirectlyAsync(Shortcuts.ShortcutsFeatureIds.Swagger); + await context.EnableFeatureDirectlyAsync(Shortcuts.ShortcutsFeatureIds.OpenApi); } await context.RunAndAssertSecurityScanAsync( @@ -152,7 +152,7 @@ await context.RunAndAssertSecurityScanAsync( "No job named \"openapi\" found in the Automation Framework Plan. We can only run the " + "OpenAPI scan if the job exists."); - apiDefinitionUri ??= context.GetAbsoluteUri("/swagger/v1/swagger.json"); + apiDefinitionUri ??= context.GetAbsoluteUri("/openapi/v1.json"); openApiJob.GetOrCreateParameters().SetMappingChild("apiUrl", apiDefinitionUri.ToString()); }); From adea444fd8f3b3cc0da28977af07ef330c71f933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 16 Jan 2026 20:31:49 +0100 Subject: [PATCH 022/132] Simplify GetClient. --- .../Models/ElasticsearchRunningContext.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs b/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs index d32cd46df..917b779c3 100644 --- a/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs +++ b/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs @@ -31,7 +31,7 @@ public Task BeforeTestAsync(UITestContext context) => { var index = LowLevelIndexName; var testCancellationToken = context.Configuration.TestCancellationToken; - var client = GetClient(provider, index); + var client = GetClient(provider); (await client.Indices.FlushAsync(index, cancellationToken: testCancellationToken)).ThrowIfFailed($"flush index \"{index}\""); (await client.Indices.RefreshAsync(index, cancellationToken: testCancellationToken)).ThrowIfFailed($"refresh index \"{index}\""); @@ -163,7 +163,7 @@ static async Task CheckIfIndexExistsAsync(ElasticsearchClient client, Inde return indices.Indices.Count > 0; } - var client = GetClient(provider, index); + var client = GetClient(provider); if (!await CheckIfIndexExistsAsync(client, index)) { @@ -180,10 +180,20 @@ static async Task CheckIfIndexExistsAsync(ElasticsearchClient client, Inde } } - private static ElasticsearchClient GetClient(IServiceProvider provider, IndexName index) => - (provider.GetService() is { } factory - ? factory.Create(new ElasticsearchConnectionOptions()) - : provider.GetService()) ?? + private static ElasticsearchClient GetClient(IServiceProvider provider) + { + if (provider.GetService() is { } existingClient) + { + return existingClient; + } + + if (provider.GetService() is { } factory && + factory.Create(new ElasticsearchConnectionOptions()) is { } factoryClient) + { + return factoryClient; + } + throw new InvalidOperationException( - $"Couldn't resolve {nameof(ElasticsearchClient)} while waiting for \"{index}\"."); + $"Couldn't resolve {nameof(ElasticsearchClient)}."); + } } From 1acc3978b74c0252342e8b3ac75652f3b97cff0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sat, 17 Jan 2026 14:22:52 +0100 Subject: [PATCH 023/132] Fix category. --- Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs b/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs index 917b779c3..94c36750e 100644 --- a/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs +++ b/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs @@ -5,6 +5,7 @@ using Lombiq.Tests.UI.Services; using Microsoft.Extensions.DependencyInjection; using OrchardCore.Indexing; +using OrchardCore.Indexing.Core; using OrchardCore.Search.Elasticsearch.Core.Models; using OrchardCore.Search.Elasticsearch.Core.Services; using System; @@ -99,7 +100,7 @@ private static async Task GetLastTaskIdAsync(IIndexingTaskManager indexing #pragma warning disable S1994 // "for" loop increment clauses should modify the loops' counters for (var startIndex = 0; hasTask; startIndex += batchSize) { - var lastTask = (await indexingTaskManager.GetIndexingTasksAsync(startIndex, batchSize, category: null)) + var lastTask = (await indexingTaskManager.GetIndexingTasksAsync(startIndex, batchSize, IndexingConstants.ContentsIndexSource)) .LastOrDefault(); hasTask = lastTask != null; From ce18ef37aabb64c05a1253815be1f4564f73a1ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sat, 17 Jan 2026 19:44:57 +0100 Subject: [PATCH 024/132] Refactor ElasticsearchRunningContext. --- .../Tests/ElasticsearchTests.cs | 4 +- .../Extensions/TestContextExtensions.cs | 16 +- .../Models/ElasticsearchRunningContext.cs | 137 +++++------------- .../Services/UITestExecutionSession.cs | 15 +- 4 files changed, 48 insertions(+), 124 deletions(-) diff --git a/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs b/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs index cb6c11fe0..4e5b58dbd 100644 --- a/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs +++ b/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs @@ -22,9 +22,7 @@ public ElasticsearchTests(ITestOutputHelper testOutputHelper) { } -#pragma warning disable xUnit1004 - [Fact(Skip = "Temporarily disabled.")] -#pragma warning restore xUnit1004 + [Fact] public Task ElasticsearchShouldWork() => ExecuteTestAsync( async context => diff --git a/Lombiq.Tests.UI/Extensions/TestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/TestContextExtensions.cs index 4bf7b4220..638569f5a 100644 --- a/Lombiq.Tests.UI/Extensions/TestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/TestContextExtensions.cs @@ -7,16 +7,12 @@ public static class TestContextExtensions { [Obsolete("Use GetElasticsearchSafeIndexName instead. This method will be removed in a future version.")] public static string GetElasticserachSafeIndexName(this ITestContext context, Guid id) => - context.GetElasticsearchSafeIndexName(id); + context.GetElasticsearchSafeIndexName(); /// - /// Gets a which is safe to use as an Elasticsearch index. + /// Generates a safe Elasticsearch index name from the test identifiers in . /// - /// - /// A unique identifier that stays the same between setup and test. This ensures that leftover data in the test - /// won't be confused with previous runs. - /// - public static string GetElasticsearchSafeIndexName(this ITestContext context, Guid id) + public static string GetElasticsearchSafeIndexName(this ITestContext context) { // Elasticsearch indexes are lowercase only. #pragma warning disable CA1308 // Normalize strings to uppercase @@ -28,12 +24,14 @@ public static string GetElasticsearchSafeIndexName(this ITestContext context, Gu .Trim('-'); #pragma warning restore CA1308 // Normalize strings to uppercase - if (string.IsNullOrWhiteSpace(name)) return id.ToString("N"); + var id = context?.Test?.UniqueID?.NullIfWhiteSpace() ?? Guid.NewGuid().ToString("N"); + + if (string.IsNullOrWhiteSpace(name)) return id; // An Elasticsearch index can't be longer than 255 character, but that includes the test name, tenant name, GUID // and relative index name. So altogether 100 characters is a reasonable limit for the test name prefix. if (name.Length > 100) name = name[..100]; - return $"{name}-{id:N}"; + return $"{name}-{id}"; } } diff --git a/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs b/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs index 94c36750e..bc72ae4d8 100644 --- a/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs +++ b/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs @@ -6,17 +6,21 @@ using Microsoft.Extensions.DependencyInjection; using OrchardCore.Indexing; using OrchardCore.Indexing.Core; +using OrchardCore.Recipes.Models; +using OrchardCore.Search.Elasticsearch.Core.Deployment; using OrchardCore.Search.Elasticsearch.Core.Models; +using OrchardCore.Search.Elasticsearch.Core.Recipes; using OrchardCore.Search.Elasticsearch.Core.Services; using System; using System.Diagnostics.CodeAnalysis; -using System.Linq; +using System.Text.Json; +using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; namespace Lombiq.Tests.UI.Models; -public record ElasticsearchRunningContext(Guid Id, string Prefix) +public record ElasticsearchRunningContext(string Prefix) { /// /// Gets the expression that refers to all indexes that start with . This should only be used @@ -30,121 +34,30 @@ public record ElasticsearchRunningContext(Guid Id, string Prefix) public Task BeforeTestAsync(UITestContext context) => context.Application.UsingScopeServiceProviderAsync(async provider => { - var index = LowLevelIndexName; - var testCancellationToken = context.Configuration.TestCancellationToken; - var client = GetClient(provider); - - (await client.Indices.FlushAsync(index, cancellationToken: testCancellationToken)).ThrowIfFailed($"flush index \"{index}\""); - (await client.Indices.RefreshAsync(index, cancellationToken: testCancellationToken)).ThrowIfFailed($"refresh index \"{index}\""); - - var indexProfileStore = provider.GetRequiredService(); - var indexSettings = await indexProfileStore.GetAllElasticsearchIndexesAsync(); - var exactIndexName = indexSettings.FirstOrDefault()?.IndexName; - - if (exactIndexName == null) return; - - var timeout = TimeSpan.FromSeconds(60); - - using var timeoutCancellationTokenSource = new CancellationTokenSource(timeout); - using var jointCancellationTokenSource = CancellationTokenSource - .CreateLinkedTokenSource(testCancellationToken, timeoutCancellationTokenSource.Token); - var jointCancellationToken = jointCancellationTokenSource.Token; - - var elasticsearchDocumentIndexManager = provider.GetRequiredService(); - - long? lastFinishedTaskId = null; - - try + if (provider.GetService() is { } indexProfileManager) { - var lastTaskId = await GetLastTaskIdAsync(provider.GetRequiredService()); - - // We have the ID of the last indexing task that should happen, so we are waiting here for that task to - // complete, since "GetLastTaskId()" returns only completed tasks. - while (lastFinishedTaskId < lastTaskId || lastFinishedTaskId == null) - { - jointCancellationToken.ThrowIfCancellationRequested(); - - lastFinishedTaskId = await TryGetLastFinishedTaskIdAsync( - elasticsearchDocumentIndexManager, - indexProfileStore, - exactIndexName); - - // The indexing takes a couple of seconds, so there is no need to check them so fast: we are adding - // a delay. - await Task.Delay(500, jointCancellationToken); - } + await RebuildAllIndexesAsync(indexProfileManager, provider); } - catch (TaskCanceledException ex) + + if (provider.GetService() is { } indexingService) { - throw new TaskCanceledException( - "Elasticsearch indexing wasn't finished due to " + - $"{(testCancellationToken.IsCancellationRequested ? "the test being canceled" : $"it not completing within {timeout}")}.", - ex, - jointCancellationToken); + await indexingService.ProcessRecordsForAllIndexesAsync(); } }); - public Task AfterTestAsync(UITestContext context) => - context?.Application?.Services is { } ? AfterTestInnerAsync(context) : Task.CompletedTask; - - private static async Task GetLastTaskIdAsync(IIndexingTaskManager indexingTaskManager) + public async Task AfterTestAsync(UITestContext context) { - const int batchSize = 1000; - long lastTaskId = 0; - bool hasTask = true; - - // We are getting the last indexing task (regardless of the state). This function works like a cursor, so - // there is no way to get the last task in the list directly. Since we have to provide a "count" parameter, - // we are retrieving the indexing tasks by batches of 1000. Then if there are no more, we get the last one. - // We want to set "hasTask" inside the loop. -#pragma warning disable S1994 // "for" loop increment clauses should modify the loops' counters - for (var startIndex = 0; hasTask; startIndex += batchSize) + try { - var lastTask = (await indexingTaskManager.GetIndexingTasksAsync(startIndex, batchSize, IndexingConstants.ContentsIndexSource)) - .LastOrDefault(); - - hasTask = lastTask != null; - - if (hasTask) + if (context?.Application?.Services != null) { - lastTaskId = lastTask.Id; + await context.Application.UsingScopeServiceProviderAsync(provider => + WithPrefixElasticsearchIndexCleanupFinallyAsync(provider, context, LowLevelIndexName)); } } -#pragma warning restore S1994 // "for" loop increment clauses should modify the loops' counters - - return lastTaskId; - } - - /// - /// Asking for the last task ID can throw an exception if the underlying value is not initialized yet. This method - /// catches the exception and returns null instead so it can be safely retried. - /// - private static async Task TryGetLastFinishedTaskIdAsync( - ElasticsearchDocumentIndexManager elasticsearchDocumentIndexManager, - IIndexProfileStore indexProfileStore, - string indexName) - { - try - { - var indexProfile = await indexProfileStore.FindByNameAsync(indexName); - return await elasticsearchDocumentIndexManager.GetLastTaskIdAsync(indexProfile); - } - catch (InvalidOperationException) - { - return null; - } - } - - private async Task AfterTestInnerAsync(UITestContext context) - { - try - { - await context.Application.UsingScopeServiceProviderAsync(provider => - WithPrefixElasticsearchIndexCleanupFinallyAsync(provider, context, LowLevelIndexName)); - } catch (Exception inner) { - context.Scope?.AtataContext?.Log?.Error(inner.ToString()); + context?.Scope?.AtataContext?.Log?.Error(inner.ToString()); } } @@ -197,4 +110,20 @@ private static ElasticsearchClient GetClient(IServiceProvider provider) throw new InvalidOperationException( $"Couldn't resolve {nameof(ElasticsearchClient)}."); } + + private static Task RebuildAllIndexesAsync( + IIndexProfileManager indexProfileManager, + IServiceProvider serviceProvider) + { + var step = new ElasticsearchIndexRebuildStep(indexProfileManager, serviceProvider); + var model = new ElasticsearchIndexRebuildDeploymentStep { IncludeAll = true }; + var context = new RecipeExecutionContext + { + ExecutionId = Guid.NewGuid().ToString(), + Name = "elastic-index-rebuild", + Step = (JsonObject)JsonSerializer.SerializeToNode(model), + }; + + return step.ExecuteAsync(context); + } } diff --git a/Lombiq.Tests.UI/Services/UITestExecutionSession.cs b/Lombiq.Tests.UI/Services/UITestExecutionSession.cs index 62a03b8f8..dae60e4c6 100644 --- a/Lombiq.Tests.UI/Services/UITestExecutionSession.cs +++ b/Lombiq.Tests.UI/Services/UITestExecutionSession.cs @@ -217,6 +217,11 @@ private async ValueTask ShutdownAsync() _configuration.Events.AfterClick -= TakeScreenshotIfEnabledAsync; } + if (_context?.ElasticsearchRunningContext is { } elasticsearchRunningContext) + { + await elasticsearchRunningContext.AfterTestAsync(_context); + } + if (_applicationInstance != null) await _applicationInstance.DisposeAsync(); string contextId = null; @@ -257,11 +262,6 @@ private async ValueTask ShutdownAsync() } } - if (_context?.ElasticsearchRunningContext is { } elasticsearchRunningContext) - { - await elasticsearchRunningContext.AfterTestAsync(_context); - } - _screenshotCount = 0; _context = null; @@ -901,12 +901,11 @@ Task SmtpServiceBeforeAppStartHandlerAsync(OrchardCoreAppStartContext context, I private ElasticsearchRunningContext SetUpElasticsearch() { - var id = Guid.NewGuid(); - var prefix = TestContext.Current.GetElasticsearchSafeIndexName(id); + var prefix = TestContext.Current.GetElasticsearchSafeIndexName(); _configuration.OrchardCoreConfiguration.ConfigureElasticsearchPrefix(prefix); - return new(id, prefix); + return new(prefix); } private async Task CaptureBrowserUsingDumpsAsync(string debugInformationPath) From 134898bd6e7db9a27b17bcf2006dbb9453bbd42c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sat, 17 Jan 2026 20:41:27 +0100 Subject: [PATCH 025/132] Update elasticsearchshouldwork recipe. --- ...mbiq.OSOCE.Tests.Elasticsearch.recipe.json | 120 ++++++++++++++++-- .../Tests/ElasticsearchTests.cs | 2 +- 2 files changed, 108 insertions(+), 14 deletions(-) diff --git a/Lombiq.Tests.UI.Samples/Recipes/Lombiq.OSOCE.Tests.Elasticsearch.recipe.json b/Lombiq.Tests.UI.Samples/Recipes/Lombiq.OSOCE.Tests.Elasticsearch.recipe.json index 193f705d5..2d2c97ad6 100644 --- a/Lombiq.Tests.UI.Samples/Recipes/Lombiq.OSOCE.Tests.Elasticsearch.recipe.json +++ b/Lombiq.Tests.UI.Samples/Recipes/Lombiq.OSOCE.Tests.Elasticsearch.recipe.json @@ -29,25 +29,119 @@ "Indices": [ { "elasticsearchshouldwork": { - "AnalyzerName": "standard", - "IndexLatest": false, - "IndexedContentTypes": [ - "BlogPost" - ], - "Culture": "any", - "StoreSourceData": true + "Id": "elasticsearchshouldwork000", + "ProviderName": "Elasticsearch", + "Type": "Content", + "CreatedUtc": "2026-01-17T19:21:17Z", + "Properties": { + "ContentIndexMetadata": { + "IndexLatest": false, + "IndexedContentTypes": [ + "BlogPost" + ], + "Culture": "any" + }, + "ElasticsearchIndexMetadata": { + "StoreSourceData": true, + "AnalyzerName": "standard", + "IndexMappings": { + "KeyFieldName": "ContentItemId", + "Mapping": { + "dynamic_templates": [ + { + "*.Inherited": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "*.Inherited" + } + }, + { + "*.Ids": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "*.Ids" + } + }, + { + "*.Location": { + "mapping": { + "type": "geo_point" + }, + "match_mapping_type": "object", + "path_match": "*.Location" + } + } + ], + "properties": { + "ContentItemId": { + "type": "keyword" + }, + "ContentItemVersionId": { + "type": "keyword" + }, + "Content.ContentItem.Owner": { + "type": "keyword" + }, + "Content.ContentItem.FullText": { + "type": "text" + }, + "Content.ContentItem.ContainedPart": { + "properties": { + "Ids": { + "type": "keyword" + }, + "Order": { + "type": "float" + } + }, + "type": "object" + }, + "Content.ContentItem.DisplayText": { + "properties": { + "Analyzed": { + "type": "text" + }, + "Normalized": { + "type": "keyword" + }, + "Keyword": { + "type": "keyword" + } + }, + "type": "object" + }, + "Content.ContentItem.ContentType": { + "type": "keyword" + } + }, + "_source": { + "enabled": true, + "excludes": [ + "Content.ContentItem.DisplayText.Analyzed" + ] + } + } + } + }, + "ElasticsearchDefaultQueryMetadata": { + "QueryAnalyzerName": "standard", + "DefaultSearchFields": [ + "Content.ContentItem.FullText" + ] + } + } } } ] }, { "name": "Settings", - "ElasticSettings": { - "SearchIndex": "elasticsearchshouldwork", - "DefaultSearchFields": [ - "Content.ContentItem.FullText" - ], - "AllowElasticQueryStringQueryInSearch": false + "SearchSettings": { + "DefaultIndexProfileName": "elasticsearchshouldwork" } }, { diff --git a/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs b/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs index 4e5b58dbd..85fe5b625 100644 --- a/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs +++ b/Lombiq.Tests.UI.Samples/Tests/ElasticsearchTests.cs @@ -29,7 +29,7 @@ public Task ElasticsearchShouldWork() => { // Going to the built-in search feature. By default, it's not accessible for anonymous users, so we need // to log in. - await context.SignInDirectlyAndGoToRelativeUrlAsync("/search/elasticsearchshouldwork"); + await context.SignInDirectlyAndGoToRelativeUrlAsync("/search"); // Filling out the search form, looking for the sample blog post coming from the built-in Blog recipe. await context.ClickAndFillInWithRetriesAsync(By.Name("Terms"), "exploration"); From 5e362ec13b029f5ffccd471c2d894ab16d5a2415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sun, 18 Jan 2026 23:58:33 +0100 Subject: [PATCH 026/132] Use Lock type. --- .../Services/OrchardCoreHosting/OrchardApplicationFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Tests.UI/Services/OrchardCoreHosting/OrchardApplicationFactory.cs b/Lombiq.Tests.UI/Services/OrchardCoreHosting/OrchardApplicationFactory.cs index 4bd0c39b4..c6141ddda 100644 --- a/Lombiq.Tests.UI/Services/OrchardCoreHosting/OrchardApplicationFactory.cs +++ b/Lombiq.Tests.UI/Services/OrchardCoreHosting/OrchardApplicationFactory.cs @@ -122,5 +122,5 @@ public override async ValueTask DisposeAsync() internal static class OrchardApplicationFactoryCounter { - public static object CreateHostLock { get; } = new(); + public static Lock CreateHostLock { get; } = new(); } From 5cfbc847e4d5b1c4521a2d9adf914553b8a4bd35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Mon, 19 Jan 2026 01:20:52 +0100 Subject: [PATCH 027/132] Fix delete wildcard error. --- Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs b/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs index bc72ae4d8..158cb7b3d 100644 --- a/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs +++ b/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs @@ -13,6 +13,7 @@ using OrchardCore.Search.Elasticsearch.Core.Services; using System; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Text.Json; using System.Text.Json.Nodes; using System.Threading; @@ -85,8 +86,11 @@ static async Task CheckIfIndexExistsAsync(ElasticsearchClient client, Inde return; } - var deleteRequest = new DeleteIndexRequest(index) { ExpandWildcards = [ExpandWildcard.All] }; - (await client.Indices.DeleteAsync(deleteRequest)).ThrowIfFailed($"delete index \"{index}\""); + var getRequest = new GetIndexRequest(index) { ExpandWildcards = [ExpandWildcard.All], AllowNoIndices = true }; + var getResponse = (await client.Indices.GetAsync(getRequest)).ThrowIfFailed($"get index \"{index}\""); + + if (getResponse.Indices.Count == 0) return; + (await client.Indices.DeleteAsync(getResponse.Indices.Keys.ToArray())).ThrowIfFailed($"delete index \"{index}\""); if (await CheckIfIndexExistsAsync(client, index)) { From 1d37bc3f71388545dda12832fd576c23a3f63a4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 20 Jan 2026 00:17:19 +0100 Subject: [PATCH 028/132] Update branch selector. --- .github/workflows/publish-cloudsmith.yml | 2 +- .github/workflows/publish-nuget.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-cloudsmith.yml b/.github/workflows/publish-cloudsmith.yml index c92bc5923..e6203dced 100644 --- a/.github/workflows/publish-cloudsmith.yml +++ b/.github/workflows/publish-cloudsmith.yml @@ -8,7 +8,7 @@ on: jobs: publish-nuget: name: Publish to Cloudsmith - uses: Lombiq/GitHub-Actions/.github/workflows/publish-nuget.yml@dev + uses: Lombiq/GitHub-Actions/.github/workflows/publish-nuget.yml@issue/OSOE-925 with: source: https://nuget.cloudsmith.io/lombiq/open-source-orchard-core-extensions/v3/index.json secrets: diff --git a/.github/workflows/publish-nuget.yml b/.github/workflows/publish-nuget.yml index 44a74afd4..2b2a5e738 100644 --- a/.github/workflows/publish-nuget.yml +++ b/.github/workflows/publish-nuget.yml @@ -9,6 +9,6 @@ jobs: publish-nuget: name: Publish to NuGet if: ${{ !contains(github.ref_name, '-preview.') }} - uses: Lombiq/GitHub-Actions/.github/workflows/publish-nuget.yml@dev + uses: Lombiq/GitHub-Actions/.github/workflows/publish-nuget.yml@issue/OSOE-925 secrets: API_KEY: ${{ secrets.DEFAULT_NUGET_PUBLISH_API_KEY }} From f32c02c632d02e4fddbaa8b50d5a4bc91114a23e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 20 Jan 2026 00:22:16 +0100 Subject: [PATCH 029/132] Update branch selectors. --- .github/workflows/validate-nuget-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate-nuget-publish.yml b/.github/workflows/validate-nuget-publish.yml index 9f8979c5d..f0fd6be1e 100644 --- a/.github/workflows/validate-nuget-publish.yml +++ b/.github/workflows/validate-nuget-publish.yml @@ -9,4 +9,4 @@ on: jobs: validate-nuget-publish: name: Validate NuGet Publish - uses: Lombiq/GitHub-Actions/.github/workflows/validate-nuget-publish.yml@dev + uses: Lombiq/GitHub-Actions/.github/workflows/validate-nuget-publish.yml@issue/OSOE-925 From f41f0288b6d3a6e127aa8a45606bccc4681f890d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 20 Jan 2026 01:29:48 +0100 Subject: [PATCH 030/132] Update "Lombiq.Tests" nuget. --- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 7776ad06f..d0c8c752e 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -108,7 +108,7 @@ - + From 9b0115f1ee3769908948f06de52af7fc70960353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 20 Jan 2026 02:32:26 +0100 Subject: [PATCH 031/132] Update HL --- .../Lombiq.Tests.UI.AppExtensions.csproj | 2 +- Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj | 2 +- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index 4fada9a53..b82adcaf8 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -39,6 +39,6 @@ - + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index 030c7c66a..436561469 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -48,7 +48,7 @@ - + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index d0c8c752e..5637c6d5a 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -109,9 +109,9 @@ - - - + + + + + + PKV006 + net8.0 + + \ No newline at end of file From c3a390134deee724a2df5b95205331b7d02d73f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 20 Jan 2026 13:23:06 +0100 Subject: [PATCH 033/132] dummy From 5dc69767ec223bfc1f92ad4e676269f925195ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 20 Jan 2026 14:30:58 +0100 Subject: [PATCH 034/132] Delete all CompatibilitySuppressions so we may start from scratch. --- CompatibilitySuppressions.xml | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 CompatibilitySuppressions.xml diff --git a/CompatibilitySuppressions.xml b/CompatibilitySuppressions.xml deleted file mode 100644 index 8af156c87..000000000 --- a/CompatibilitySuppressions.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PKV006 - net8.0 - - \ No newline at end of file From da679d31ad56d29c6869aa1ed44d5514e6b655e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 20 Jan 2026 15:20:43 +0100 Subject: [PATCH 035/132] CompatibilitySuppressions.xml --- .../CompatibilitySuppressions.xml | 8 ++++++++ Lombiq.Tests.UI.Shortcuts/CompatibilitySuppressions.xml | 8 ++++++++ Lombiq.Tests.UI/CompatibilitySuppressions.xml | 8 ++++++++ 3 files changed, 24 insertions(+) create mode 100644 Lombiq.Tests.UI.AppExtensions/CompatibilitySuppressions.xml create mode 100644 Lombiq.Tests.UI.Shortcuts/CompatibilitySuppressions.xml create mode 100644 Lombiq.Tests.UI/CompatibilitySuppressions.xml diff --git a/Lombiq.Tests.UI.AppExtensions/CompatibilitySuppressions.xml b/Lombiq.Tests.UI.AppExtensions/CompatibilitySuppressions.xml new file mode 100644 index 000000000..8af156c87 --- /dev/null +++ b/Lombiq.Tests.UI.AppExtensions/CompatibilitySuppressions.xml @@ -0,0 +1,8 @@ + + + + + PKV006 + net8.0 + + \ No newline at end of file diff --git a/Lombiq.Tests.UI.Shortcuts/CompatibilitySuppressions.xml b/Lombiq.Tests.UI.Shortcuts/CompatibilitySuppressions.xml new file mode 100644 index 000000000..8af156c87 --- /dev/null +++ b/Lombiq.Tests.UI.Shortcuts/CompatibilitySuppressions.xml @@ -0,0 +1,8 @@ + + + + + PKV006 + net8.0 + + \ No newline at end of file diff --git a/Lombiq.Tests.UI/CompatibilitySuppressions.xml b/Lombiq.Tests.UI/CompatibilitySuppressions.xml new file mode 100644 index 000000000..8af156c87 --- /dev/null +++ b/Lombiq.Tests.UI/CompatibilitySuppressions.xml @@ -0,0 +1,8 @@ + + + + + PKV006 + net8.0 + + \ No newline at end of file From d448bb15124f826c7468ff82272cd234120771cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Thu, 22 Jan 2026 14:25:07 +0100 Subject: [PATCH 036/132] Add and use HtmlValidationFilters. --- .../HtmlValidationUITestContextExtensions.cs | 43 ++++++++++++- .../Services/HtmlValidationConfiguration.cs | 60 ++++++++++++++++++- 2 files changed, 98 insertions(+), 5 deletions(-) diff --git a/Lombiq.Tests.UI/Extensions/HtmlValidationUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/HtmlValidationUITestContextExtensions.cs index 02c9abca4..3ac4835e9 100644 --- a/Lombiq.Tests.UI/Extensions/HtmlValidationUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/HtmlValidationUITestContextExtensions.cs @@ -1,8 +1,12 @@ using Atata.Cli; using Atata.HtmlValidation; using Lombiq.Tests.UI.Exceptions; +using Lombiq.Tests.UI.Models; using Lombiq.Tests.UI.Services; +using Shouldly; using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; namespace Lombiq.Tests.UI.Extensions; @@ -30,9 +34,25 @@ public static async Task AssertHtmlValidityAsync( try { - var assertTask = (assertHtmlValidationResultAsync ?? validationConfiguration.AssertHtmlValidationResultAsync)? - .Invoke(validationResult); - await (assertTask ?? Task.CompletedTask); + if (assertHtmlValidationResultAsync == null && + validationConfiguration.HtmlValidationFilters is { Count: > 0 } filters) + { + var errors = validationResult.GetParsedErrors()?.AsList() ?? []; + if (errors.Count > 0) AssertHtmlValidityWithFilters(errors, filters); + } + else + { + // This is the only place where AssertHtmlValidationResultAsync should be used. When it's removed in the + // future, replace "validationConfiguration.AssertHtmlValidationResultAsync" below with + // "HtmlValidationConfiguration.AssertHtmlValidationOutputIsEmptyAsync" to maintain default behavior. +#pragma warning disable CS0618 // Type or member is obsolete. + if ((assertHtmlValidationResultAsync ?? validationConfiguration.AssertHtmlValidationResultAsync) is { } assert && + assert(validationResult) is { } assertTask) + { + await assertTask; + } +#pragma warning restore CS0618 // Type or member is obsolete + } } catch (Exception exception) { @@ -40,6 +60,23 @@ public static async Task AssertHtmlValidityAsync( } } + private static void AssertHtmlValidityWithFilters( + IList errors, + IDictionary> filters) + { + foreach (var filter in filters.Values.Where(filter => filter != null)) + { + errors.RemoveAll(error => !filter(error)); + if (errors.Count == 0) return; + } + + var humanReadableErrors = HtmlValidationResultExtensions.GetParsedErrorMessageString(errors); + var filtersUsedMessage = $"The following {nameof(HtmlValidationConfiguration.HtmlValidationFilters)} were " + + $"used: {string.Join(", ", filters.Keys)}"; + + errors.ShouldBeEmpty($"{humanReadableErrors}\n\n{filtersUsedMessage}"); + } + /// /// Runs an HTML markup validation with the html-validate library. Note that you need to run this after every page /// load, it won't accumulate during a session. diff --git a/Lombiq.Tests.UI/Services/HtmlValidationConfiguration.cs b/Lombiq.Tests.UI/Services/HtmlValidationConfiguration.cs index 5aa04d86c..6558079cc 100644 --- a/Lombiq.Tests.UI/Services/HtmlValidationConfiguration.cs +++ b/Lombiq.Tests.UI/Services/HtmlValidationConfiguration.cs @@ -2,8 +2,10 @@ using Atata.HtmlValidation; using Lombiq.Tests.UI.Extensions; using Lombiq.Tests.UI.Helpers; +using Lombiq.Tests.UI.Models; using Shouldly; using System; +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; @@ -17,6 +19,8 @@ namespace Lombiq.Tests.UI.Services; /// public class HtmlValidationConfiguration { + private Func _assertHtmlValidationResultAsync = AssertHtmlValidationOutputIsEmptyAsync; + /// /// Gets or sets a value indicating whether to create an HTML validation report if the given test fails HTML /// validation. @@ -52,11 +56,42 @@ public class HtmlValidationConfiguration /// public Action HtmlValidationOptionsAdjuster { get; set; } + /// + /// Gets a dictionary of filters. If not empty, it's used in instead of . + /// + public IDictionary> HtmlValidationFilters { get; } = + new Dictionary>(); + /// /// Gets or sets a delegate to run assertions on the when HTML validation /// happens. Defaults to . /// - public Func AssertHtmlValidationResultAsync { get; set; } = AssertHtmlValidationOutputIsEmptyAsync; + [Obsolete($"Use {nameof(HtmlValidationFilters)} instead. If it's populated this will throw a runtime exception.")] + public Func AssertHtmlValidationResultAsync + { + get + { + if (HtmlValidationFilters.Count > 0) + { + throw new InvalidOperationException($"{nameof(HtmlValidationFilters)} is already configured, so the " + + $"value of {nameof(AssertHtmlValidationResultAsync)} will be ignored."); + } + + return _assertHtmlValidationResultAsync; + } + set + { + if (HtmlValidationFilters.Count > 0) + { + throw new InvalidOperationException($"{nameof(HtmlValidationFilters)} is already configured, so the " + + $"value of {nameof(AssertHtmlValidationResultAsync)} will be ignored."); + } + + _assertHtmlValidationResultAsync = value; + } + } /// /// Gets or sets a value indicating whether to automatically run HTML validation every time a page changes (either @@ -88,6 +123,27 @@ public HtmlValidationConfiguration WithRelativeConfigPath(params string[] pathSe return this; } + /// + /// Updates the . + /// + public HtmlValidationConfiguration WithFilters(string name, Func filter) + { + HtmlValidationFilters[name] = filter; + + return this; + } + + /// + /// Updates the with the OC-15222 key to handle a specific bug. + /// + /// + /// Rule exclusions due to https://github.com/OrchardCMS/OrchardCore/issues/15222, usages can be removed once it is + /// resolved. + /// + public HtmlValidationConfiguration WithOC15222Filter() => + WithFilters("OC-15222", error => + error.RuleId is not ("prefer-native-element" or "text-content" or "no-redundant-role")); + public static readonly Func AssertHtmlValidationOutputIsEmptyAsync = validationResult => { @@ -95,7 +151,7 @@ public HtmlValidationConfiguration WithRelativeConfigPath(params string[] pathSe if (validationResult.Output.Trim().StartsWith('[') || validationResult.Output.Trim().StartsWith('{')) { - var errors = validationResult.GetParsedErrors(); + var errors = validationResult.GetParsedErrors()?.AsList() ?? []; errors.ShouldBeEmpty(HtmlValidationResultExtensions.GetParsedErrorMessageString(errors)); } else From c0ad9c8080e4ea1405b0a1e546f537a9318ac7c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Thu, 22 Jan 2026 16:50:43 +0100 Subject: [PATCH 037/132] Upgrade OC preview. --- .../Lombiq.Tests.UI.AppExtensions.csproj | 6 +++--- .../Lombiq.Tests.UI.Shortcuts.csproj | 20 +++++++++---------- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 8 ++++---- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index b82adcaf8..6726efdd8 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -28,9 +28,9 @@ - - - + + + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index 436561469..29ef33735 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -30,16 +30,16 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 5637c6d5a..e37f6f70a 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -81,10 +81,10 @@ - - - - + + + + From 28cd4ec33ee724d95b5448699a66fc3540022ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Thu, 22 Jan 2026 16:54:30 +0100 Subject: [PATCH 038/132] Fix consolidation. --- .../Lombiq.Tests.UI.AppExtensions.csproj | 6 +++--- .../Lombiq.Tests.UI.Shortcuts.csproj | 20 +++++++++---------- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 8 ++++---- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index b82adcaf8..6726efdd8 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -28,9 +28,9 @@ - - - + + + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index 436561469..29ef33735 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -30,16 +30,16 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 5637c6d5a..e37f6f70a 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -81,10 +81,10 @@ - - - - + + + + From a009f101b9a45f5d591a5a204f9ace6bf63c5ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Thu, 22 Jan 2026 19:05:18 +0100 Subject: [PATCH 039/132] Use the new DeleteAllIndexesAsync extension method. --- Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs b/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs index 158cb7b3d..c2d4571ba 100644 --- a/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs +++ b/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs @@ -1,6 +1,5 @@ using Elastic.Clients.Elasticsearch; using Elastic.Clients.Elasticsearch.Core; -using Elastic.Clients.Elasticsearch.IndexManagement; using Lombiq.Tests.UI.Extensions; using Lombiq.Tests.UI.Services; using Microsoft.Extensions.DependencyInjection; @@ -13,7 +12,6 @@ using OrchardCore.Search.Elasticsearch.Core.Services; using System; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Text.Json; using System.Text.Json.Nodes; using System.Threading; @@ -66,7 +64,7 @@ await context.Application.UsingScopeServiceProviderAsync(provider => "Usage", "MA0040:Forward the CancellationToken parameter to methods that take one", Justification = "Cleanup code has no viable cancellation token because even failed tests should be cleaned up.")] - private static async Task WithPrefixElasticsearchIndexCleanupFinallyAsync( + private async Task WithPrefixElasticsearchIndexCleanupFinallyAsync( IServiceProvider provider, UITestContext context, IndexName index) @@ -86,12 +84,7 @@ static async Task CheckIfIndexExistsAsync(ElasticsearchClient client, Inde return; } - var getRequest = new GetIndexRequest(index) { ExpandWildcards = [ExpandWildcard.All], AllowNoIndices = true }; - var getResponse = (await client.Indices.GetAsync(getRequest)).ThrowIfFailed($"get index \"{index}\""); - - if (getResponse.Indices.Count == 0) return; - (await client.Indices.DeleteAsync(getResponse.Indices.Keys.ToArray())).ThrowIfFailed($"delete index \"{index}\""); - + await client.DeleteAllIndexesAsync(Prefix); if (await CheckIfIndexExistsAsync(client, index)) { throw new InvalidOperationException($"Couldn't delete indexes for \"{index}\"."); From 0f6e4f65f1c7836f367b7a4124ba4719cf4d331f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Thu, 22 Jan 2026 19:40:27 +0100 Subject: [PATCH 040/132] HL nuget --- .../Lombiq.Tests.UI.AppExtensions.csproj | 2 +- Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj | 2 +- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index 6726efdd8..f29164b27 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -39,6 +39,6 @@ - + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index 29ef33735..207c8c5d4 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -48,7 +48,7 @@ - + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index e37f6f70a..25ec53af4 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -109,9 +109,9 @@ - - - + + + From fac58b7eeff1fb68b5bb395ff076f35b2a90d805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 23 Jan 2026 17:01:50 +0100 Subject: [PATCH 045/132] Update HL nuget. --- .../Lombiq.Tests.UI.AppExtensions.csproj | 2 +- Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj | 2 +- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index c27f9d0d1..152c58db9 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -39,6 +39,6 @@ - + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index 32fbfabdc..ba5d2cadf 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -48,7 +48,7 @@ - + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 9d049cd42..15d5aadad 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -109,9 +109,9 @@ - - - + + + From 0ba3e39f6622bd359c1f789cd663fb3987992c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Wed, 28 Jan 2026 16:45:56 +0100 Subject: [PATCH 048/132] Introducing TempDirectoryPath. --- Lombiq.Tests.UI/Constants/DirectoryPaths.cs | 13 +++---- .../FakeBrowserVideoSourceExtensions.cs | 10 +++-- Lombiq.Tests.UI/OrchardCoreUITestBase.cs | 3 +- .../Services/BrowserConfiguration.cs | 6 +++ .../Services/OrchardCoreInstance.cs | 39 +++++++++---------- .../OrchardCoreUITestExecutorConfiguration.cs | 29 ++++++++++++++ Lombiq.Tests.UI/Services/UITestContext.cs | 20 ++++++++-- .../Services/UITestExecutionSession.cs | 19 ++++----- Lombiq.Tests.UI/Services/WebDriverFactory.cs | 7 +++- 9 files changed, 97 insertions(+), 49 deletions(-) diff --git a/Lombiq.Tests.UI/Constants/DirectoryPaths.cs b/Lombiq.Tests.UI/Constants/DirectoryPaths.cs index aa8d0cf99..62c077d0e 100644 --- a/Lombiq.Tests.UI/Constants/DirectoryPaths.cs +++ b/Lombiq.Tests.UI/Constants/DirectoryPaths.cs @@ -11,14 +11,13 @@ public static class DirectoryPaths public const string Screenshots = nameof(Screenshots); public const string Downloads = nameof(Downloads); + [Obsolete($"Use {nameof(UITestContext.TempDirectoryPath)} or {nameof(UITestContext.GetTempSubDirectoryPath)}() " + + $"in {nameof(UITestContext)} instead.")] public static string GetTempDirectoryPath(params string[] subDirectoryNames) => Path.Combine([Environment.CurrentDirectory, Temp, .. subDirectoryNames]); - [Obsolete($"Use {nameof(UITestContext)}.{nameof(UITestContext.GetTempSubDirectoryPath)}() instead.")] - public static string GetTempSubDirectoryPath(string contextId, params string[] subDirectoryNames) => - GetTempDirectoryPath([contextId, .. subDirectoryNames]); - - [Obsolete($"Use {nameof(UITestContext)}.{nameof(UITestContext.ScreenshotsDirectoryPath)} instead.")] - public static string GetScreenshotsDirectoryPath(string contextId) => - GetTempSubDirectoryPath(contextId, Screenshots); + internal static string GetTempDirectoryPathWithFallback(string path) => + string.IsNullOrEmpty(path) + ? Path.Combine(Environment.CurrentDirectory, Temp) + : path; } diff --git a/Lombiq.Tests.UI/Extensions/FakeBrowserVideoSourceExtensions.cs b/Lombiq.Tests.UI/Extensions/FakeBrowserVideoSourceExtensions.cs index 829a16195..d1caeba64 100644 --- a/Lombiq.Tests.UI/Extensions/FakeBrowserVideoSourceExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/FakeBrowserVideoSourceExtensions.cs @@ -1,5 +1,5 @@ -using Lombiq.Tests.UI.Constants; using Lombiq.Tests.UI.Models; +using Lombiq.Tests.UI.Services; using System; using System.IO; @@ -7,11 +7,15 @@ namespace Lombiq.Tests.UI.Extensions; public static class FakeBrowserVideoSourceExtensions { - public static string SaveVideoToTempFolder(this FakeBrowserVideoSource source) + [Obsolete($"Use the overload that specifies the Temp directory path instead.")] + public static string SaveVideoToTempFolder(this FakeBrowserVideoSource source) => + source.SaveVideoToTempFolder(tempDirectoryPath: null); + + public static string SaveVideoToTempFolder(this FakeBrowserVideoSource source, string tempDirectoryPath) { using var fakeCameraSource = source.StreamProvider(); var fakeCameraSourcePath = Path.ChangeExtension( - DirectoryPaths.GetTempDirectoryPath(Guid.NewGuid().ToString()), + OrchardCoreUITestExecutorConfiguration.GetTempDirectoryPathWithFallback(tempDirectoryPath, Guid.NewGuid().ToString()), GetExtension(source.Format)); using var fakeCameraSourceFile = new FileStream(fakeCameraSourcePath, FileMode.CreateNew, FileAccess.Write); diff --git a/Lombiq.Tests.UI/OrchardCoreUITestBase.cs b/Lombiq.Tests.UI/OrchardCoreUITestBase.cs index f8e394ef4..d90449d83 100644 --- a/Lombiq.Tests.UI/OrchardCoreUITestBase.cs +++ b/Lombiq.Tests.UI/OrchardCoreUITestBase.cs @@ -404,8 +404,7 @@ protected virtual async Task ExecuteTestAsync( await changeConfigurationAsync.InvokeFuncAsync(configuration); await ExecuteOrchardCoreTestAsync( - (configuration, contextId) => - new OrchardCoreInstance(configuration.OrchardCoreConfiguration, contextId, configuration.TestOutputHelper), + (configuration, contextId) => new OrchardCoreInstance(configuration, contextId), testManifest, configuration); } diff --git a/Lombiq.Tests.UI/Services/BrowserConfiguration.cs b/Lombiq.Tests.UI/Services/BrowserConfiguration.cs index d8a2c5ba3..b7a2ee199 100644 --- a/Lombiq.Tests.UI/Services/BrowserConfiguration.cs +++ b/Lombiq.Tests.UI/Services/BrowserConfiguration.cs @@ -55,4 +55,10 @@ public class BrowserConfiguration /// exist yet. /// internal string UITestContextId { get; set; } + + /// + /// Gets or sets the , to be used during + /// driver creation when the does not exist yet. + /// + internal string TempDirectoryPath { get; set; } } diff --git a/Lombiq.Tests.UI/Services/OrchardCoreInstance.cs b/Lombiq.Tests.UI/Services/OrchardCoreInstance.cs index 121ff2fa9..783c7d1dc 100644 --- a/Lombiq.Tests.UI/Services/OrchardCoreInstance.cs +++ b/Lombiq.Tests.UI/Services/OrchardCoreInstance.cs @@ -1,6 +1,5 @@ using Lombiq.HelpfulLibraries.Common.Utilities; using Lombiq.Tests.Integration.Services; -using Lombiq.Tests.UI.Constants; using Lombiq.Tests.UI.Helpers; using Lombiq.Tests.UI.Models; using Lombiq.Tests.UI.Services.OrchardCoreHosting; @@ -56,9 +55,8 @@ static OrchardCoreInstanceCounter() public sealed class OrchardCoreInstance : IWebApplicationInstance where TEntryPoint : class { - private readonly OrchardCoreConfiguration _configuration; + private readonly OrchardCoreUITestExecutorConfiguration _configuration; private readonly string _contextId; - private readonly ITestOutputHelper _testOutputHelper; private readonly CancellationTokenSource _cancellationTokenSource = new(); private string _contentRootPath; @@ -68,24 +66,25 @@ public sealed class OrchardCoreInstance : IWebApplicationInstance private TestReverseProxy _reverseProxy; public IServiceProvider Services => _orchardApplication?.Services; + public OrchardCoreConfiguration OrchardCoreConfiguration => _configuration.OrchardCoreConfiguration; + public ITestOutputHelper TestOutputHelper => _configuration.TestOutputHelper; - public OrchardCoreInstance(OrchardCoreConfiguration configuration, string contextId, ITestOutputHelper testOutputHelper) + public OrchardCoreInstance(OrchardCoreUITestExecutorConfiguration configuration, string contextId) { _configuration = configuration; _contextId = contextId; - _testOutputHelper = testOutputHelper; } public async Task StartUpAsync() { _url = await OrchardCoreInstanceCounter.GetNewUriAsync(); - _testOutputHelper.WriteLineTimestampedAndDebug("The generated URL for the Orchard Core instance is \"{0}\".", _url.AbsoluteUri); + TestOutputHelper.WriteLineTimestampedAndDebug("The generated URL for the Orchard Core instance is \"{0}\".", _url.AbsoluteUri); CreateContentRootFolder(); - if (!string.IsNullOrEmpty(_configuration.SnapshotDirectoryPath) && Directory.Exists(_configuration.SnapshotDirectoryPath)) + if (!string.IsNullOrEmpty(OrchardCoreConfiguration.SnapshotDirectoryPath) && Directory.Exists(OrchardCoreConfiguration.SnapshotDirectoryPath)) { - FileSystem.CopyDirectory(_configuration.SnapshotDirectoryPath, _contentRootPath, overwrite: true); + FileSystem.CopyDirectory(OrchardCoreConfiguration.SnapshotDirectoryPath, _contentRootPath, overwrite: true); } else { @@ -102,7 +101,7 @@ public async Task StartUpAsync() await _reverseProxy.StartAsync(); - _configuration.StartCount++; + OrchardCoreConfiguration.StartCount++; await StartOrchardAppAsync(); return _url; @@ -156,18 +155,18 @@ public async ValueTask DisposeAsync() private void CreateContentRootFolder() { - _contentRootPath = DirectoryPaths.GetTempDirectoryPath(_contextId, "App"); + _contentRootPath = _configuration.GetTempDirectoryPathWithFallback(_contextId, "App"); Directory.CreateDirectory(_contentRootPath); - _testOutputHelper.WriteLineTimestampedAndDebug("Content root path was created: {0}", _contentRootPath); + TestOutputHelper.WriteLineTimestampedAndDebug("Content root path was created: {0}", _contentRootPath); } private async Task StartOrchardAppAsync() { - _testOutputHelper.WriteLineTimestampedAndDebug("Attempting to start the Orchard Core instance."); + TestOutputHelper.WriteLineTimestampedAndDebug("Attempting to start the Orchard Core instance."); var arguments = new InstanceCommandLineArgumentsBuilder(); - await _configuration.BeforeAppStart + await OrchardCoreConfiguration.BeforeAppStart .InvokeAsync(handler => handler(CreateAppStartContext(), arguments)); // This is to avoid adding Razor runtime view compilation. @@ -187,9 +186,9 @@ await DirectoryHelper.SafelyDeleteDirectoryIfExistsAsync( options.FilteredLevels.Add(LogLevel.Error); options.FilteredLevels.Add(LogLevel.Critical); options.OutputFormatter = FakeLoggerApplicationLogEntry.FormatLogRecord; - options.OutputSink += message => _testOutputHelper.WriteLine(message); + options.OutputSink += message => TestOutputHelper.WriteLine(message); - _configuration.AfterFakeLoggingConfiguration?.Invoke(CreateAppStartContext(), options); + OrchardCoreConfiguration.AfterFakeLoggingConfiguration?.Invoke(CreateAppStartContext(), options); })), (configuration, orchardBuilder) => orchardBuilder .ConfigureUITesting(configuration, enableShortcutsDuringUITesting: true), @@ -199,7 +198,7 @@ await DirectoryHelper.SafelyDeleteDirectoryIfExistsAsync( _orchardApplication.ClientOptions.BaseAddress = new Uri(_reverseProxy.RootUrl); _reverseProxy.AttachConnectionProvider(_orchardApplication); - _testOutputHelper.WriteLineTimestampedAndDebug("The Orchard Core instance was started."); + TestOutputHelper.WriteLineTimestampedAndDebug("The Orchard Core instance was started."); } private async Task StopOrchardAppAsync() @@ -208,15 +207,15 @@ private async Task StopOrchardAppAsync() if (_orchardApplication == null) return; - _testOutputHelper.WriteLineTimestampedAndDebug("Attempting to stop the Orchard Core instance."); + TestOutputHelper.WriteLineTimestampedAndDebug("Attempting to stop the Orchard Core instance."); await _orchardApplication.DisposeAsync(); _orchardApplication = null; await OrchardCoreInstanceCounter.PortLeases.StopLeaseAsync(_url.Port, CancellationToken.None); - _testOutputHelper.WriteLineTimestampedAndDebug("The Orchard Core instance was stopped."); + TestOutputHelper.WriteLineTimestampedAndDebug("The Orchard Core instance was stopped."); - await _configuration.AfterAppStop + await OrchardCoreConfiguration.AfterAppStop .InvokeAsync(handler => handler(CreateAppStartContext())); } @@ -228,7 +227,7 @@ private async Task TakeSnapshotInnerAsync(string snapshotDirectoryPath) Directory.CreateDirectory(snapshotDirectoryPath); - await _configuration.BeforeTakeSnapshot + await OrchardCoreConfiguration.BeforeTakeSnapshot .InvokeAsync(handler => handler(CreateAppStartContext(), snapshotDirectoryPath)); FileSystem.CopyDirectory(_contentRootPath, snapshotDirectoryPath, overwrite: true); diff --git a/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs b/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs index ca0e72a05..3ce5afaee 100644 --- a/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs +++ b/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs @@ -1,3 +1,4 @@ +using Lombiq.Tests.UI.Constants; using Lombiq.Tests.UI.Extensions; using Lombiq.Tests.UI.Helpers; using Lombiq.Tests.UI.SecurityScanning; @@ -9,6 +10,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Threading; using System.Threading.Tasks; using Xunit; @@ -220,4 +222,31 @@ public CancellationToken TestCancellationToken get => _testCancellationToken == default ? TestContext.Current.CancellationToken : _testCancellationToken; set => _testCancellationToken = value; } + + /// + /// Gets or sets the value which will be copied into upon creation. + /// + public string TempDirectoryPath { get; set; } + + /// + /// Gets if it's not , otherwise gets the path of the in the current directory. + /// + public string GetTempDirectoryPathWithFallback(params string[] subDirectories) => + GetTempDirectoryPathWithFallback(TempDirectoryPath, subDirectories); + + /// + /// Gets if it's not , otherwise gets the path of the + /// in the current directory. + /// + public static string GetTempDirectoryPathWithFallback(string tempDirectoryPath, params string[] subDirectories) + { + var path = string.IsNullOrEmpty(tempDirectoryPath) + ? Path.Combine(Environment.CurrentDirectory, DirectoryPaths.Temp) + : tempDirectoryPath; + + if (subDirectories.Length > 0) path = Path.Combine([path, .. subDirectories]); + + return path; + } } diff --git a/Lombiq.Tests.UI/Services/UITestContext.cs b/Lombiq.Tests.UI/Services/UITestContext.cs index deac2ed45..e04e61526 100644 --- a/Lombiq.Tests.UI/Services/UITestContext.cs +++ b/Lombiq.Tests.UI/Services/UITestContext.cs @@ -1,4 +1,5 @@ using Atata; +using Lombiq.HelpfulLibraries.Common.Utilities; using Lombiq.Tests.UI.Constants; using Lombiq.Tests.UI.Exceptions; using Lombiq.Tests.UI.Extensions; @@ -12,6 +13,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -166,6 +168,12 @@ public sealed class UITestContext : IAsyncDisposable /// public string AdminUrlPrefix { get; set; } = "/Admin"; + /// + /// Gets the path of the directory where running instances are stored. If + /// the internal value is set to (which is the default), then ./Temp/ is used. + /// + public string TempDirectoryPath { get; } + /// /// Gets the absolute path of the subdirectory inside the current test /// instance's directory. @@ -206,6 +214,8 @@ private UITestContext( AzureBlobStorageRunningContext, ElasticsearchRunningContext ) = runningContextContainer; + + TempDirectoryPath = configuration.GetTempDirectoryPathWithFallback(); } /// @@ -324,6 +334,8 @@ public static async Task CreateAsync( runningContextContainer, zapManager); + FileSystemHelper.EnsureDirectoryExists(context.TempDirectoryPath); + if (context.IsBrowserConfigured) { context._biDirectionalDriver = await scope.Driver.AsBiDiAsync(); @@ -356,18 +368,18 @@ await context._biDirectionalDriver.Network.OnResponseCompletedAsync(responseComp } /// - /// Returns the subdirectory described by inside the current test instance's - /// directory. + /// Returns the subdirectory described by the sub-path inside the / directory. /// public string GetTempSubDirectoryPath(params string[] subDirectoryNames) => - DirectoryPaths.GetTempDirectoryPath([Id, .. subDirectoryNames]); + Path.Combine([TempDirectoryPath, Id, .. subDirectoryNames]); /// /// Returns a path in the subdirectory inside the current test instance's /// directory. /// public string GetDownloadFilePath(params string[] subDirectoryNames) => - DirectoryPaths.GetTempDirectoryPath([Id, DirectoryPaths.Downloads, .. subDirectoryNames]); + Path.Combine([TempDirectoryPath, Id, DirectoryPaths.Downloads, .. subDirectoryNames]); private bool IsAlert() { diff --git a/Lombiq.Tests.UI/Services/UITestExecutionSession.cs b/Lombiq.Tests.UI/Services/UITestExecutionSession.cs index dae60e4c6..4964034d3 100644 --- a/Lombiq.Tests.UI/Services/UITestExecutionSession.cs +++ b/Lombiq.Tests.UI/Services/UITestExecutionSession.cs @@ -201,6 +201,8 @@ await CreateTestDumpAsync( private async ValueTask ShutdownAsync() { + var tempDirectoryPath = _context?.TempDirectoryPath; + _testOutputHelper.WriteLineTimestampedAndDebug("Shutting down the test execution session."); if (_configuration.RunAssertLogsOnAllPageChanges) @@ -224,13 +226,7 @@ private async ValueTask ShutdownAsync() if (_applicationInstance != null) await _applicationInstance.DisposeAsync(); - string contextId = null; - - if (_context != null) - { - contextId = _context.Id; - await _context.DisposeAsync(); - } + if (_context != null) await _context.DisposeAsync(); if (_sqlServerManager is not null) { @@ -245,12 +241,14 @@ private async ValueTask ShutdownAsync() // handles to the temp folder, that can be cleaned up too. No need to do it on ephemeral GitHub runners, though, // also because the ZAP report's folder (like "2025-01-22-ZAP-Report-localhost") will remain unwritable (see the // comment in ZapManager). - if (!string.IsNullOrEmpty(contextId) && !GitHubHelper.IsGitHubEnvironment) + if (!string.IsNullOrEmpty(tempDirectoryPath) && + Directory.Exists(tempDirectoryPath) && + !GitHubHelper.IsGitHubEnvironment) { try { // This is a clean-up method, no need to forward a CancellationToken. - await DirectoryHelper.SafelyDeleteDirectoryIfExistsAsync(DirectoryPaths.GetTempDirectoryPath(contextId), CancellationToken.None); + await DirectoryHelper.SafelyDeleteDirectoryIfExistsAsync(tempDirectoryPath, CancellationToken.None); } catch (Exception ex) when (GitHubHelper.IsGitHubEnvironment) { @@ -689,8 +687,7 @@ private async Task CreateContextAsync(Uri testStartRelativeUri) { var contextId = Guid.NewGuid().ToString(); _configuration.BrowserConfiguration.UITestContextId = contextId; - - FileSystemHelper.EnsureDirectoryExists(DirectoryPaths.GetTempDirectoryPath(contextId)); + _configuration.BrowserConfiguration.TempDirectoryPath = _configuration.TempDirectoryPath; var sqlServerContext = _configuration.UseSqlServer ? await SetUpSqlServerAsync() : null; var azureBlobStorageContext = _configuration.UseAzureBlobStorage ? await SetUpAzureBlobStorageAsync() : null; diff --git a/Lombiq.Tests.UI/Services/WebDriverFactory.cs b/Lombiq.Tests.UI/Services/WebDriverFactory.cs index 5fe0b11dc..dd2bf001c 100644 --- a/Lombiq.Tests.UI/Services/WebDriverFactory.cs +++ b/Lombiq.Tests.UI/Services/WebDriverFactory.cs @@ -329,7 +329,7 @@ private static TDriverOptions SetCommonChromiumOptions( if (configuration.FakeVideoSource is not null) { - var fakeCameraSourceFilePath = configuration.FakeVideoSource.SaveVideoToTempFolder(); + var fakeCameraSourceFilePath = configuration.FakeVideoSource.SaveVideoToTempFolder(configuration.TempDirectoryPath); // In some cases the video would not start automatically. To avoid this scenario we are adding the // "disable-gesture-requirement-for-media-playback" flag. @@ -389,7 +389,10 @@ private static async Task> CreateDriverAsync(Func Date: Wed, 28 Jan 2026 22:59:45 +0100 Subject: [PATCH 049/132] Rename JsonHtmlValidationError to HtmlValidationError. --- ...xtensions.cs => HtmlValidationErrorExtensions.cs} | 12 ++++++------ .../Extensions/HtmlValidationResultExtensions.cs | 8 ++++---- .../HtmlValidationUITestContextExtensions.cs | 4 ++-- ...HtmlValidationError.cs => HtmlValidationError.cs} | 2 +- .../Services/HtmlValidationConfiguration.cs | 8 ++++---- 5 files changed, 17 insertions(+), 17 deletions(-) rename Lombiq.Tests.UI/Extensions/{JsonHtmlValidationErrorExtensions.cs => HtmlValidationErrorExtensions.cs} (69%) rename Lombiq.Tests.UI/Models/{JsonHtmlValidationError.cs => HtmlValidationError.cs} (95%) diff --git a/Lombiq.Tests.UI/Extensions/JsonHtmlValidationErrorExtensions.cs b/Lombiq.Tests.UI/Extensions/HtmlValidationErrorExtensions.cs similarity index 69% rename from Lombiq.Tests.UI/Extensions/JsonHtmlValidationErrorExtensions.cs rename to Lombiq.Tests.UI/Extensions/HtmlValidationErrorExtensions.cs index 7ebf524b2..df47dd415 100644 --- a/Lombiq.Tests.UI/Extensions/JsonHtmlValidationErrorExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/HtmlValidationErrorExtensions.cs @@ -5,15 +5,15 @@ namespace Lombiq.Tests.UI.Models; -public static class JsonHtmlValidationErrorExtensions +public static class HtmlValidationErrorExtensions { /// /// Remove entries from if they return when passed into any of the /// . /// - internal static IList RemoveIfFalse( - this IList errors, - IEnumerable> filters) + internal static IList RemoveIfFalse( + this IList errors, + IEnumerable> filters) { foreach (var filter in filters.Where(filter => filter != null)) { @@ -27,8 +27,8 @@ internal static IList RemoveIfFalse( /// /// Return a new list that filters out items using . /// - public static IList FilterWithConfiguration( - this IEnumerable errors, + public static IList FilterWithConfiguration( + this IEnumerable errors, HtmlValidationConfiguration configuration) => errors.ToList().RemoveIfFalse(configuration.HtmlValidationFilters.Values); } diff --git a/Lombiq.Tests.UI/Extensions/HtmlValidationResultExtensions.cs b/Lombiq.Tests.UI/Extensions/HtmlValidationResultExtensions.cs index 0ad3f56cb..aa2c4aef5 100644 --- a/Lombiq.Tests.UI/Extensions/HtmlValidationResultExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/HtmlValidationResultExtensions.cs @@ -31,9 +31,9 @@ public static async Task> GetErrorsAsync(this HtmlValidation /// Gets the parsed errors from the HTML validation result. /// Can only be used if the output formatter is set to JSON. /// - public static IEnumerable GetParsedErrors(this HtmlValidationResult result) => ParseOutput(result.Output); + public static IEnumerable GetParsedErrors(this HtmlValidationResult result) => ParseOutput(result.Output); - public static string GetParsedErrorMessageString(IEnumerable errors) => + public static string GetParsedErrorMessageString(IEnumerable errors) => string.Join( '\n', errors.Select(error => @@ -41,7 +41,7 @@ public static string GetParsedErrorMessageString(IEnumerable ParseOutput(string output) + private static IEnumerable ParseOutput(string output) { try { @@ -60,7 +60,7 @@ private static IEnumerable ParseOutput(string output) .Select(message => { var rawMessageText = message.GetRawText(); - return JsonSerializer.Deserialize(rawMessageText); + return JsonSerializer.Deserialize(rawMessageText); }); } catch (JsonException exception) diff --git a/Lombiq.Tests.UI/Extensions/HtmlValidationUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/HtmlValidationUITestContextExtensions.cs index 5e9a93d87..02ebd3e54 100644 --- a/Lombiq.Tests.UI/Extensions/HtmlValidationUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/HtmlValidationUITestContextExtensions.cs @@ -52,8 +52,8 @@ public static async Task AssertHtmlValidityAsync( } private static void AssertHtmlValidityWithFilters( - IList errors, - IDictionary> filters) + IList errors, + IDictionary> filters) { errors.RemoveIfFalse(filters.Values); if (errors.Count == 0) return; diff --git a/Lombiq.Tests.UI/Models/JsonHtmlValidationError.cs b/Lombiq.Tests.UI/Models/HtmlValidationError.cs similarity index 95% rename from Lombiq.Tests.UI/Models/JsonHtmlValidationError.cs rename to Lombiq.Tests.UI/Models/HtmlValidationError.cs index f5892f8c6..618d4d988 100644 --- a/Lombiq.Tests.UI/Models/JsonHtmlValidationError.cs +++ b/Lombiq.Tests.UI/Models/HtmlValidationError.cs @@ -3,7 +3,7 @@ namespace Lombiq.Tests.UI.Models; -public class JsonHtmlValidationError +public class HtmlValidationError { [JsonPropertyName("ruleId")] public string RuleId { get; set; } diff --git a/Lombiq.Tests.UI/Services/HtmlValidationConfiguration.cs b/Lombiq.Tests.UI/Services/HtmlValidationConfiguration.cs index 8eadbea70..3726adcac 100644 --- a/Lombiq.Tests.UI/Services/HtmlValidationConfiguration.cs +++ b/Lombiq.Tests.UI/Services/HtmlValidationConfiguration.cs @@ -58,14 +58,14 @@ public class HtmlValidationConfiguration /// each entry and validation only fails if there is still any errors left over. If you use a custom value, these will only apply if you explicitly call them. /// - public IDictionary> HtmlValidationFilters { get; } = - new Dictionary>(); + public IDictionary> HtmlValidationFilters { get; } = + new Dictionary>(); /// /// Gets or sets a delegate to run assertions on the when HTML validation /// happens. If you only want to filter the validation errors, use or and keep this . If you specify a custom value, consider using in it to apply the filters. + /// cref="HtmlValidationErrorExtensions.FilterWithConfiguration"/> in it to apply the filters. /// public Func AssertHtmlValidationResultAsync { get; set; } @@ -102,7 +102,7 @@ public HtmlValidationConfiguration WithRelativeConfigPath(params string[] pathSe /// /// Updates the . /// - public HtmlValidationConfiguration WithFilters(string name, Func filter) + public HtmlValidationConfiguration WithFilters(string name, Func filter) { HtmlValidationFilters[name] = filter; From 564efcc4e5b85646000799e7f6827003caf11fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Wed, 28 Jan 2026 23:21:08 +0100 Subject: [PATCH 050/132] Refactor AssertHtmlValidationResultAsync. --- .../HtmlValidationErrorExtensions.cs | 9 ----- ...reUITestExecutorConfigurationExtensions.cs | 4 +- .../HtmlValidationUITestContextExtensions.cs | 39 +++++++++---------- .../Services/HtmlValidationConfiguration.cs | 11 ++---- 4 files changed, 26 insertions(+), 37 deletions(-) diff --git a/Lombiq.Tests.UI/Extensions/HtmlValidationErrorExtensions.cs b/Lombiq.Tests.UI/Extensions/HtmlValidationErrorExtensions.cs index df47dd415..7c6201455 100644 --- a/Lombiq.Tests.UI/Extensions/HtmlValidationErrorExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/HtmlValidationErrorExtensions.cs @@ -1,4 +1,3 @@ -using Lombiq.Tests.UI.Services; using System; using System.Collections.Generic; using System.Linq; @@ -23,12 +22,4 @@ internal static IList RemoveIfFalse( return errors; } - - /// - /// Return a new list that filters out items using . - /// - public static IList FilterWithConfiguration( - this IEnumerable errors, - HtmlValidationConfiguration configuration) => - errors.ToList().RemoveIfFalse(configuration.HtmlValidationFilters.Values); } diff --git a/Lombiq.Tests.UI/Extensions/HtmlValidationOrchardCoreUITestExecutorConfigurationExtensions.cs b/Lombiq.Tests.UI/Extensions/HtmlValidationOrchardCoreUITestExecutorConfigurationExtensions.cs index a2314c540..e02eaba68 100644 --- a/Lombiq.Tests.UI/Extensions/HtmlValidationOrchardCoreUITestExecutorConfigurationExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/HtmlValidationOrchardCoreUITestExecutorConfigurationExtensions.cs @@ -1,6 +1,8 @@ using Atata.HtmlValidation; +using Lombiq.Tests.UI.Models; using Lombiq.Tests.UI.Services; using System; +using System.Collections.Generic; using System.Threading.Tasks; namespace Lombiq.Tests.UI.Extensions; @@ -21,7 +23,7 @@ public static class HtmlValidationOrchardCoreUITestExecutorConfigurationExtensio public static void SetUpHtmlValidationAssertionOnPageChange( this OrchardCoreUITestExecutorConfiguration configuration, Action htmlValidationOptionsAdjuster = null, - Func assertHtmlValidationResultAsync = null) + Func, Task> assertHtmlValidationResultAsync = null) { if (!configuration.CustomConfiguration.TryAdd("HtmlValidationAssertionOnPageChangeWasSetUp", value: true)) return; diff --git a/Lombiq.Tests.UI/Extensions/HtmlValidationUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/HtmlValidationUITestContextExtensions.cs index 02ebd3e54..c237b6bfa 100644 --- a/Lombiq.Tests.UI/Extensions/HtmlValidationUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/HtmlValidationUITestContextExtensions.cs @@ -6,6 +6,7 @@ using Shouldly; using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; namespace Lombiq.Tests.UI.Extensions; @@ -26,21 +27,33 @@ public static class HtmlValidationUITestContextExtensions public static async Task AssertHtmlValidityAsync( this UITestContext context, Action htmlValidationOptionsAdjuster = null, - Func assertHtmlValidationResultAsync = null) + Func, Task> assertHtmlValidationResultAsync = null) { - var validationResult = await context.ValidateHtmlAsync(htmlValidationOptionsAdjuster); var validationConfiguration = context.Configuration.HtmlValidationConfiguration; + var validationResult = await context.ValidateHtmlAsync(htmlValidationOptionsAdjuster); + if (validationResult?.GetParsedErrors()?.AsList() is not { Count: > 0 } errors) return; + var filters = validationConfiguration.HtmlValidationFilters; assertHtmlValidationResultAsync ??= validationConfiguration.AssertHtmlValidationResultAsync; + assertHtmlValidationResultAsync ??= errors => + { + var humanReadableErrors = HtmlValidationResultExtensions.GetParsedErrorMessageString(errors); + var filtersUsedMessage = $"The following {nameof(HtmlValidationConfiguration.HtmlValidationFilters)} were " + + $"used: {string.Join(", ", filters.Keys)}"; + + errors.ShouldBeEmpty(filters.Count > 0 ? $"{humanReadableErrors}\n\n{filtersUsedMessage}" : humanReadableErrors); + return Task.CompletedTask; + }; try { - if (assertHtmlValidationResultAsync == null) + foreach (var filter in filters.Values.Where(filter => filter != null)) { - var errors = validationResult.GetParsedErrors()?.AsList() ?? []; - if (errors.Count > 0) AssertHtmlValidityWithFilters(errors, validationConfiguration.HtmlValidationFilters); + errors.RemoveAll(error => !filter(error)); + if (errors.Count == 0) return; } - else if (assertHtmlValidationResultAsync is { } assert && assert(validationResult) is { } assertTask) + + if (assertHtmlValidationResultAsync(errors) is { } assertTask) { await assertTask; } @@ -51,20 +64,6 @@ public static async Task AssertHtmlValidityAsync( } } - private static void AssertHtmlValidityWithFilters( - IList errors, - IDictionary> filters) - { - errors.RemoveIfFalse(filters.Values); - if (errors.Count == 0) return; - - var humanReadableErrors = HtmlValidationResultExtensions.GetParsedErrorMessageString(errors); - var filtersUsedMessage = $"The following {nameof(HtmlValidationConfiguration.HtmlValidationFilters)} were " + - $"used: {string.Join(", ", filters.Keys)}"; - - errors.ShouldBeEmpty($"{humanReadableErrors}\n\n{filtersUsedMessage}"); - } - /// /// Runs an HTML markup validation with the html-validate library. Note that you need to run this after every page /// load, it won't accumulate during a session. diff --git a/Lombiq.Tests.UI/Services/HtmlValidationConfiguration.cs b/Lombiq.Tests.UI/Services/HtmlValidationConfiguration.cs index 3726adcac..45a0e0dcd 100644 --- a/Lombiq.Tests.UI/Services/HtmlValidationConfiguration.cs +++ b/Lombiq.Tests.UI/Services/HtmlValidationConfiguration.cs @@ -53,10 +53,8 @@ public class HtmlValidationConfiguration public Action HtmlValidationOptionsAdjuster { get; set; } /// - /// Gets a dictionary of filters. If is , then - /// these are automatically used instead. The errors from the are filtered by - /// each entry and validation only fails if there is still any errors left over. If you use a custom value, these will only apply if you explicitly call them. + /// Gets a dictionary of filters. The errors from the are filtered by + /// each entry and only those are kept that pass each filter entry. /// public IDictionary> HtmlValidationFilters { get; } = new Dictionary>(); @@ -64,10 +62,9 @@ public class HtmlValidationConfiguration /// /// Gets or sets a delegate to run assertions on the when HTML validation /// happens. If you only want to filter the validation errors, use or and keep this . If you specify a custom value, consider using in it to apply the filters. + /// cref="WithFilters"/>. /// - public Func AssertHtmlValidationResultAsync { get; set; } + public Func, Task> AssertHtmlValidationResultAsync { get; set; } /// /// Gets or sets a value indicating whether to automatically run HTML validation every time a page changes (either From 4935c6d34a3d64e77d8dddf3fa933113d46039f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Wed, 28 Jan 2026 23:24:20 +0100 Subject: [PATCH 051/132] Update HL NuGet. --- .../Lombiq.Tests.UI.AppExtensions.csproj | 2 +- Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj | 2 +- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index 4a3d08d8e..911793766 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -39,6 +39,6 @@ - + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index 935d93556..287a4d79d 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -48,7 +48,7 @@ - + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index d3651c639..567632513 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -109,9 +109,9 @@ - - - + + + From 30ef38f148c6715f75586f091284ef4b1c9405ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Thu, 5 Feb 2026 12:15:23 +0100 Subject: [PATCH 056/132] Update OC preview. --- .../Lombiq.Tests.UI.AppExtensions.csproj | 6 +++--- .../Lombiq.Tests.UI.Shortcuts.csproj | 20 +++++++++---------- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 8 ++++---- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index fcea1a1a3..43181d6aa 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -28,9 +28,9 @@ - - - + + + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index 13c8e86aa..b410afb0b 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -30,16 +30,16 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 258ee7566..92076922b 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -81,10 +81,10 @@ - - - - + + + + From f256cbb31e2b289feeec249e95daa8d1fc2e6fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Thu, 5 Feb 2026 21:24:08 +0100 Subject: [PATCH 057/132] Update HL Nuget. --- .../Lombiq.Tests.UI.AppExtensions.csproj | 2 +- Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj | 2 +- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index 43181d6aa..382b0370f 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -39,6 +39,6 @@ - + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index 31bdb3687..4b2f6fb19 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -47,7 +47,7 @@ - + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 52ed98e49..fb57f2ec6 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -109,9 +109,9 @@ - - - + + + From eb34f2f1b285cd454d9e9429eebbdc553b4430d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Mon, 9 Feb 2026 00:58:50 +0100 Subject: [PATCH 059/132] AssertHtmlValidationResultAsync-related code updates and fixes. --- .../Services/HtmlValidationConfiguration.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Lombiq.Tests.UI/Services/HtmlValidationConfiguration.cs b/Lombiq.Tests.UI/Services/HtmlValidationConfiguration.cs index 45a0e0dcd..1e6543a68 100644 --- a/Lombiq.Tests.UI/Services/HtmlValidationConfiguration.cs +++ b/Lombiq.Tests.UI/Services/HtmlValidationConfiguration.cs @@ -117,6 +117,16 @@ public HtmlValidationConfiguration WithOC15222Filter() => WithFilters("OC-15222", error => error.RuleId is not ("prefer-native-element" or "text-content" or "no-redundant-role")); + /// + /// Updates the with the OC-17907 key to handle a specific bug. + /// + /// + /// Rule exclusions due to https://github.com/OrchardCMS/OrchardCore/issues/17907, usages can be removed once it is + /// resolved. + /// + public HtmlValidationConfiguration WithOC17907Filter() => + WithFilters("OC-17907", error => error.RuleId is not "attribute-misuse"); + public static readonly Predicate EnableOnValidatablePagesHtmlValidationAndAssertionOnPageChangeRule = UrlCheckHelper.IsValidatablePage; } From 265f5b463b99f25bf5ba3ae84df4c714a379b747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Thu, 12 Feb 2026 23:27:28 +0100 Subject: [PATCH 060/132] Update OC to avoid missing method exceptions. --- .../Lombiq.Tests.UI.AppExtensions.csproj | 6 +++--- .../Lombiq.Tests.UI.Shortcuts.csproj | 20 +++++++++---------- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 8 ++++---- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index b7ac73b7d..c5b6d3223 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -28,9 +28,9 @@ - - - + + + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index 7b9e67b75..3c094fa3b 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -30,16 +30,16 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 45a5f91fc..ab70d33f3 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -81,10 +81,10 @@ - - - - + + + + From 387cad8038b623675fface46a9434001e7a9848f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Thu, 12 Feb 2026 23:40:05 +0100 Subject: [PATCH 061/132] Refactor AxeResult to avoid problems with serialization. --- .../AccessibilityAssertionException.cs | 8 ++++- ...reUITestExecutorConfigurationExtensions.cs | 4 +-- ...sibilityCheckingUITestContextExtensions.cs | 31 ++++++---------- .../Extensions/AxeResultItemExtensions.cs | 13 +++++-- Lombiq.Tests.UI/Models/SimpleAxeResult.cs | 35 +++++++++++++++++++ .../AccessibilityCheckingConfiguration.cs | 17 ++++----- 6 files changed, 75 insertions(+), 33 deletions(-) create mode 100644 Lombiq.Tests.UI/Models/SimpleAxeResult.cs diff --git a/Lombiq.Tests.UI/Exceptions/AccessibilityAssertionException.cs b/Lombiq.Tests.UI/Exceptions/AccessibilityAssertionException.cs index ca4de30f8..8877ff407 100644 --- a/Lombiq.Tests.UI/Exceptions/AccessibilityAssertionException.cs +++ b/Lombiq.Tests.UI/Exceptions/AccessibilityAssertionException.cs @@ -1,4 +1,5 @@ using Deque.AxeCore.Commons; +using Lombiq.Tests.UI.Models; using System; namespace Lombiq.Tests.UI.Exceptions; @@ -6,13 +7,18 @@ namespace Lombiq.Tests.UI.Exceptions; public class AccessibilityAssertionException : Exception, IAssertionException { public AxeResult AxeResult { get; } + public SimpleAxeResult Result { get; } public AccessibilityAssertionException(AxeResult axeResult, bool createReportOnFailure, Exception innerException) + : this((SimpleAxeResult)axeResult, createReportOnFailure, innerException) => + AxeResult = axeResult; + + public AccessibilityAssertionException(SimpleAxeResult result, bool createReportOnFailure, Exception innerException) : base( "Asserting the accessibility analysis result failed." + (createReportOnFailure ? " Check the accessibility report failure dump for details." : string.Empty), innerException) => - AxeResult = axeResult; + Result = result; public AccessibilityAssertionException() { diff --git a/Lombiq.Tests.UI/Extensions/AccessibilityCheckingOrchardCoreUITestExecutorConfigurationExtensions.cs b/Lombiq.Tests.UI/Extensions/AccessibilityCheckingOrchardCoreUITestExecutorConfigurationExtensions.cs index 9506688b1..af2b10548 100644 --- a/Lombiq.Tests.UI/Extensions/AccessibilityCheckingOrchardCoreUITestExecutorConfigurationExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/AccessibilityCheckingOrchardCoreUITestExecutorConfigurationExtensions.cs @@ -1,5 +1,5 @@ -using Deque.AxeCore.Commons; using Deque.AxeCore.Selenium; +using Lombiq.Tests.UI.Models; using Lombiq.Tests.UI.Services; using System; using System.Threading.Tasks; @@ -23,7 +23,7 @@ public static class AccessibilityCheckingOrchardCoreUITestExecutorConfigurationE public static void SetUpAccessibilityCheckingAssertionOnPageChange( this OrchardCoreUITestExecutorConfiguration configuration, Action axeBuilderConfigurator = null, - Action assertAxeResult = null) + Action assertAxeResult = null) { if (!configuration.CustomConfiguration.TryAdd("AccessibilityCheckingAssertionOnPageChangeWasSetUp", value: true)) return; diff --git a/Lombiq.Tests.UI/Extensions/AccessibilityCheckingUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/AccessibilityCheckingUITestContextExtensions.cs index d9d92f827..106b84cf1 100644 --- a/Lombiq.Tests.UI/Extensions/AccessibilityCheckingUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/AccessibilityCheckingUITestContextExtensions.cs @@ -2,11 +2,11 @@ using Deque.AxeCore.Selenium; using Lombiq.Tests.UI.Exceptions; using Lombiq.Tests.UI.Helpers; +using Lombiq.Tests.UI.Models; using Lombiq.Tests.UI.Services; -using Newtonsoft.Json.Linq; using System; +using System.Collections.Generic; using System.IO; -using System.Linq; using TWP.Selenium.Axe.Html; namespace Lombiq.Tests.UI.Extensions; @@ -28,9 +28,10 @@ public static class AccessibilityCheckingUITestContextExtensions public static void AssertAccessibility( this UITestContext context, Action axeBuilderConfigurator = null, - Action assertAxeResult = null) + Action assertAxeResult = null) { var axeResult = context.AnalyzeAccessibility(axeBuilderConfigurator); + var result = (SimpleAxeResult)axeResult; var accessibilityConfiguration = context.Configuration.AccessibilityCheckingConfiguration; try @@ -38,10 +39,10 @@ public static void AssertAccessibility( if (accessibilityConfiguration.AxeResultIncompleteFilters.Count > 0 || accessibilityConfiguration.AxeResultViolationsFilters.Count > 0) { - axeResult = FilterAccessibilityResults(axeResult, accessibilityConfiguration); + result = FilterAccessibilityResults(result, accessibilityConfiguration); } - (assertAxeResult ?? accessibilityConfiguration.AssertAxeResult)?.Invoke(axeResult); + (assertAxeResult ?? accessibilityConfiguration.AssertAxeResult)?.Invoke(result); } catch (Exception ex) { @@ -66,28 +67,18 @@ public static void AssertAccessibility( } } - private static AxeResult FilterAccessibilityResults(AxeResult axeResult, AccessibilityCheckingConfiguration accessibilityConfiguration) + private static SimpleAxeResult FilterAccessibilityResults( + SimpleAxeResult axeResult, + AccessibilityCheckingConfiguration accessibilityConfiguration) { - var incomplete = axeResult.Incomplete.ToList(); - var violations = axeResult.Violations.ToList(); - foreach (var filter in accessibilityConfiguration.AxeResultIncompleteFilters.Values) { - incomplete.RemoveAll(item => !filter(item)); + axeResult.Incomplete.RemoveAll(item => item is null || !filter(item)); } foreach (var filter in accessibilityConfiguration.AxeResultViolationsFilters.Values) { - violations.RemoveAll(item => !filter(item)); - } - - if (axeResult.Incomplete.Length != incomplete.Count || - axeResult.Violations.Length != violations.Count) - { - var jObject = JObject.FromObject(axeResult); - jObject["Violations"] = JArray.FromObject(violations); - jObject["Incomplete"] = JArray.FromObject(incomplete); - axeResult = new AxeResult(jObject); + axeResult.Violations.RemoveAll(item => item is null || !filter(item)); } return axeResult; diff --git a/Lombiq.Tests.UI/Extensions/AxeResultItemExtensions.cs b/Lombiq.Tests.UI/Extensions/AxeResultItemExtensions.cs index adfd68179..e83cc66f9 100644 --- a/Lombiq.Tests.UI/Extensions/AxeResultItemExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/AxeResultItemExtensions.cs @@ -1,7 +1,10 @@ +#nullable enable + using Deque.AxeCore.Commons; using Lombiq.Tests.UI.Services; using Shouldly; using System.Collections.Generic; +using System.Linq; namespace Lombiq.Tests.UI.Extensions; @@ -11,6 +14,12 @@ public static class AxeResultItemExtensions /// Asserts if is empty, and if not then produces an error with s converted into human-readable strings. /// - public static void AxeResultItemsShouldBeEmpty(this IEnumerable axeResultItems) => - axeResultItems.ShouldBeEmpty(AccessibilityCheckingConfiguration.AxeResultItemsToString(axeResultItems)); + public static void AxeResultItemsShouldBeEmpty(this IEnumerable axeResultItems) + { + var results = axeResultItems + .CastWhere() + .ToList(); + + results.ShouldBeEmpty(AccessibilityCheckingConfiguration.AxeResultItemsToString(results)); + } } diff --git a/Lombiq.Tests.UI/Models/SimpleAxeResult.cs b/Lombiq.Tests.UI/Models/SimpleAxeResult.cs new file mode 100644 index 000000000..641c3e5f5 --- /dev/null +++ b/Lombiq.Tests.UI/Models/SimpleAxeResult.cs @@ -0,0 +1,35 @@ +using Deque.AxeCore.Commons; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Lombiq.Tests.UI.Models; + +public class SimpleAxeResult +{ + public IList Violations { get; init; } = []; + public IList Passes { get; init; } = []; + public IList Inapplicable { get; init; } = []; + public IList Incomplete { get; init; } = []; + public DateTimeOffset? Timestamp { get; set; } + public AxeTestEnvironment TestEnvironment { get; set; } + public AxeTestRunner TestRunner { get; set; } + public string Url { get; set; } + public AxeTestEngine TestEngine { get; set; } + public object ToolOptions { get; set; } + + public static implicit operator SimpleAxeResult(AxeResult axeResult) => + new() + { + Violations = [.. axeResult.Violations], + Passes = [.. axeResult.Passes], + Inapplicable = [.. axeResult.Inapplicable], + Incomplete = [.. axeResult.Incomplete], + Timestamp = axeResult.Timestamp, + TestEnvironment = axeResult.TestEnvironment, + TestRunner = axeResult.TestRunner, + Url = axeResult.Url, + TestEngine = axeResult.TestEngine, + ToolOptions = axeResult.ToolOptions, + }; +} diff --git a/Lombiq.Tests.UI/Services/AccessibilityCheckingConfiguration.cs b/Lombiq.Tests.UI/Services/AccessibilityCheckingConfiguration.cs index e5c1cf13a..990bde86c 100644 --- a/Lombiq.Tests.UI/Services/AccessibilityCheckingConfiguration.cs +++ b/Lombiq.Tests.UI/Services/AccessibilityCheckingConfiguration.cs @@ -2,6 +2,7 @@ using Deque.AxeCore.Selenium; using Lombiq.Tests.UI.Extensions; using Lombiq.Tests.UI.Helpers; +using Lombiq.Tests.UI.Models; using System; using System.Collections.Generic; using System.Linq; @@ -47,24 +48,24 @@ public class AccessibilityCheckingConfiguration EnableOnValidatablePagesAccessibilityCheckingAndAssertionOnPageChangeRule; /// - /// Gets a collection of delegates that select which is retained. If there are - /// more than one filters, all of them must return . + /// Gets a collection of delegates that select which is retained. If there + /// are more than one filters, all of them must return . /// public IDictionary> AxeResultIncompleteFilters { get; } = new Dictionary>(); /// - /// Gets a collection of delegates that select which is retained. If there are - /// more than one filters, all of them must return . + /// Gets a collection of delegates that select which is retained. If there + /// are more than one filters, all of them must return . /// public IDictionary> AxeResultViolationsFilters { get; } = new Dictionary>(); /// - /// Gets or sets a delegate to run assertions on the when accessibility checking happens. - /// Defaults to . + /// Gets or sets a delegate to run assertions on the when accessibility checking + /// happens. Defaults to . /// - public Action AssertAxeResult { get; set; } = AssertAxeResultIsEmpty; + public Action AssertAxeResult { get; set; } = AssertAxeResultIsEmpty; // Returns AxeBuilder so it can be chained. public static readonly Func ConfigureWcag21aa = axeBuilder => @@ -73,7 +74,7 @@ public class AccessibilityCheckingConfiguration public static readonly Func ConfigureWcag22aa = axeBuilder => axeBuilder.WithTags("wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22a", "wcag22aa"); - public static readonly Action AssertAxeResultIsEmpty = axeResult => + public static readonly Action AssertAxeResultIsEmpty = axeResult => { axeResult.Violations.AxeResultItemsShouldBeEmpty(); axeResult.Incomplete.AxeResultItemsShouldBeEmpty(); From b7451508d643fd8f7003e6ccf9eb1ebc97c2163f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Thu, 12 Feb 2026 23:53:49 +0100 Subject: [PATCH 062/132] unusing --- Lombiq.Tests.UI/Models/SimpleAxeResult.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Lombiq.Tests.UI/Models/SimpleAxeResult.cs b/Lombiq.Tests.UI/Models/SimpleAxeResult.cs index 641c3e5f5..988cab330 100644 --- a/Lombiq.Tests.UI/Models/SimpleAxeResult.cs +++ b/Lombiq.Tests.UI/Models/SimpleAxeResult.cs @@ -1,7 +1,6 @@ using Deque.AxeCore.Commons; using System; using System.Collections.Generic; -using System.Linq; namespace Lombiq.Tests.UI.Models; From d661333e940143ff522b45f7062b019b88ca4048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 13 Feb 2026 00:04:17 +0100 Subject: [PATCH 063/132] Fix MA0184. --- Lombiq.Tests.UI/Extensions/FakeBrowserVideoSourceExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Tests.UI/Extensions/FakeBrowserVideoSourceExtensions.cs b/Lombiq.Tests.UI/Extensions/FakeBrowserVideoSourceExtensions.cs index d1caeba64..14a92d036 100644 --- a/Lombiq.Tests.UI/Extensions/FakeBrowserVideoSourceExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/FakeBrowserVideoSourceExtensions.cs @@ -7,7 +7,7 @@ namespace Lombiq.Tests.UI.Extensions; public static class FakeBrowserVideoSourceExtensions { - [Obsolete($"Use the overload that specifies the Temp directory path instead.")] + [Obsolete("Use the overload that specifies the Temp directory path instead.")] public static string SaveVideoToTempFolder(this FakeBrowserVideoSource source) => source.SaveVideoToTempFolder(tempDirectoryPath: null); From ed94f6e69310dc79f33492a95e921a238f11954d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sun, 15 Feb 2026 17:56:06 +0100 Subject: [PATCH 064/132] Rename SimpleAxeResult to AccessibilityAssertionException. --- .../AccessibilityAssertionException.cs | 6 +++--- ...dCoreUITestExecutorConfigurationExtensions.cs | 2 +- ...cessibilityCheckingUITestContextExtensions.cs | 8 ++++---- ...eResult.cs => AccessibilityCheckingResult.cs} | 4 ++-- .../AccessibilityCheckingConfiguration.cs | 16 ++++++++-------- 5 files changed, 18 insertions(+), 18 deletions(-) rename Lombiq.Tests.UI/Models/{SimpleAxeResult.cs => AccessibilityCheckingResult.cs} (90%) diff --git a/Lombiq.Tests.UI/Exceptions/AccessibilityAssertionException.cs b/Lombiq.Tests.UI/Exceptions/AccessibilityAssertionException.cs index 8877ff407..8e19a0683 100644 --- a/Lombiq.Tests.UI/Exceptions/AccessibilityAssertionException.cs +++ b/Lombiq.Tests.UI/Exceptions/AccessibilityAssertionException.cs @@ -7,13 +7,13 @@ namespace Lombiq.Tests.UI.Exceptions; public class AccessibilityAssertionException : Exception, IAssertionException { public AxeResult AxeResult { get; } - public SimpleAxeResult Result { get; } + public AccessibilityCheckingResult Result { get; } public AccessibilityAssertionException(AxeResult axeResult, bool createReportOnFailure, Exception innerException) - : this((SimpleAxeResult)axeResult, createReportOnFailure, innerException) => + : this((AccessibilityCheckingResult)axeResult, createReportOnFailure, innerException) => AxeResult = axeResult; - public AccessibilityAssertionException(SimpleAxeResult result, bool createReportOnFailure, Exception innerException) + public AccessibilityAssertionException(AccessibilityCheckingResult result, bool createReportOnFailure, Exception innerException) : base( "Asserting the accessibility analysis result failed." + (createReportOnFailure ? " Check the accessibility report failure dump for details." : string.Empty), diff --git a/Lombiq.Tests.UI/Extensions/AccessibilityCheckingOrchardCoreUITestExecutorConfigurationExtensions.cs b/Lombiq.Tests.UI/Extensions/AccessibilityCheckingOrchardCoreUITestExecutorConfigurationExtensions.cs index af2b10548..060507519 100644 --- a/Lombiq.Tests.UI/Extensions/AccessibilityCheckingOrchardCoreUITestExecutorConfigurationExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/AccessibilityCheckingOrchardCoreUITestExecutorConfigurationExtensions.cs @@ -23,7 +23,7 @@ public static class AccessibilityCheckingOrchardCoreUITestExecutorConfigurationE public static void SetUpAccessibilityCheckingAssertionOnPageChange( this OrchardCoreUITestExecutorConfiguration configuration, Action axeBuilderConfigurator = null, - Action assertAxeResult = null) + Action assertAxeResult = null) { if (!configuration.CustomConfiguration.TryAdd("AccessibilityCheckingAssertionOnPageChangeWasSetUp", value: true)) return; diff --git a/Lombiq.Tests.UI/Extensions/AccessibilityCheckingUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/AccessibilityCheckingUITestContextExtensions.cs index 106b84cf1..6359f689f 100644 --- a/Lombiq.Tests.UI/Extensions/AccessibilityCheckingUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/AccessibilityCheckingUITestContextExtensions.cs @@ -28,10 +28,10 @@ public static class AccessibilityCheckingUITestContextExtensions public static void AssertAccessibility( this UITestContext context, Action axeBuilderConfigurator = null, - Action assertAxeResult = null) + Action assertAxeResult = null) { var axeResult = context.AnalyzeAccessibility(axeBuilderConfigurator); - var result = (SimpleAxeResult)axeResult; + var result = (AccessibilityCheckingResult)axeResult; var accessibilityConfiguration = context.Configuration.AccessibilityCheckingConfiguration; try @@ -67,8 +67,8 @@ public static void AssertAccessibility( } } - private static SimpleAxeResult FilterAccessibilityResults( - SimpleAxeResult axeResult, + private static AccessibilityCheckingResult FilterAccessibilityResults( + AccessibilityCheckingResult axeResult, AccessibilityCheckingConfiguration accessibilityConfiguration) { foreach (var filter in accessibilityConfiguration.AxeResultIncompleteFilters.Values) diff --git a/Lombiq.Tests.UI/Models/SimpleAxeResult.cs b/Lombiq.Tests.UI/Models/AccessibilityCheckingResult.cs similarity index 90% rename from Lombiq.Tests.UI/Models/SimpleAxeResult.cs rename to Lombiq.Tests.UI/Models/AccessibilityCheckingResult.cs index 988cab330..ecabd4599 100644 --- a/Lombiq.Tests.UI/Models/SimpleAxeResult.cs +++ b/Lombiq.Tests.UI/Models/AccessibilityCheckingResult.cs @@ -4,7 +4,7 @@ namespace Lombiq.Tests.UI.Models; -public class SimpleAxeResult +public class AccessibilityCheckingResult { public IList Violations { get; init; } = []; public IList Passes { get; init; } = []; @@ -17,7 +17,7 @@ public class SimpleAxeResult public AxeTestEngine TestEngine { get; set; } public object ToolOptions { get; set; } - public static implicit operator SimpleAxeResult(AxeResult axeResult) => + public static implicit operator AccessibilityCheckingResult(AxeResult axeResult) => new() { Violations = [.. axeResult.Violations], diff --git a/Lombiq.Tests.UI/Services/AccessibilityCheckingConfiguration.cs b/Lombiq.Tests.UI/Services/AccessibilityCheckingConfiguration.cs index 990bde86c..856c6f6e6 100644 --- a/Lombiq.Tests.UI/Services/AccessibilityCheckingConfiguration.cs +++ b/Lombiq.Tests.UI/Services/AccessibilityCheckingConfiguration.cs @@ -48,24 +48,24 @@ public class AccessibilityCheckingConfiguration EnableOnValidatablePagesAccessibilityCheckingAndAssertionOnPageChangeRule; /// - /// Gets a collection of delegates that select which is retained. If there - /// are more than one filters, all of them must return . + /// Gets a collection of delegates that select which is + /// retained. If there are more than one filters, all of them must return . /// public IDictionary> AxeResultIncompleteFilters { get; } = new Dictionary>(); /// - /// Gets a collection of delegates that select which is retained. If there - /// are more than one filters, all of them must return . + /// Gets a collection of delegates that select which is + /// retained. If there are more than one filters, all of them must return . /// public IDictionary> AxeResultViolationsFilters { get; } = new Dictionary>(); /// - /// Gets or sets a delegate to run assertions on the when accessibility checking - /// happens. Defaults to . + /// Gets or sets a delegate to run assertions on the when accessibility + /// checking happens. Defaults to . /// - public Action AssertAxeResult { get; set; } = AssertAxeResultIsEmpty; + public Action AssertAxeResult { get; set; } = AssertAxeResultIsEmpty; // Returns AxeBuilder so it can be chained. public static readonly Func ConfigureWcag21aa = axeBuilder => @@ -74,7 +74,7 @@ public class AccessibilityCheckingConfiguration public static readonly Func ConfigureWcag22aa = axeBuilder => axeBuilder.WithTags("wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22a", "wcag22aa"); - public static readonly Action AssertAxeResultIsEmpty = axeResult => + public static readonly Action AssertAxeResultIsEmpty = axeResult => { axeResult.Violations.AxeResultItemsShouldBeEmpty(); axeResult.Incomplete.AxeResultItemsShouldBeEmpty(); From d6f9e34afbda3841b9b8d733587c6c2df6cb1cb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sun, 15 Feb 2026 18:02:04 +0100 Subject: [PATCH 065/132] Remove ToolOptions because it's not useful as "object". --- Lombiq.Tests.UI/Models/AccessibilityCheckingResult.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lombiq.Tests.UI/Models/AccessibilityCheckingResult.cs b/Lombiq.Tests.UI/Models/AccessibilityCheckingResult.cs index ecabd4599..4c4a82f06 100644 --- a/Lombiq.Tests.UI/Models/AccessibilityCheckingResult.cs +++ b/Lombiq.Tests.UI/Models/AccessibilityCheckingResult.cs @@ -15,7 +15,6 @@ public class AccessibilityCheckingResult public AxeTestRunner TestRunner { get; set; } public string Url { get; set; } public AxeTestEngine TestEngine { get; set; } - public object ToolOptions { get; set; } public static implicit operator AccessibilityCheckingResult(AxeResult axeResult) => new() @@ -29,6 +28,5 @@ public static implicit operator AccessibilityCheckingResult(AxeResult axeResult) TestRunner = axeResult.TestRunner, Url = axeResult.Url, TestEngine = axeResult.TestEngine, - ToolOptions = axeResult.ToolOptions, }; } From cf1a09d213626a46559c228fa02d410f5641917c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sun, 15 Feb 2026 20:44:28 +0100 Subject: [PATCH 066/132] Move "WithAxe*Filters" extensions into UITT. --- ...reUITestExecutorConfigurationExtensions.cs | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/Lombiq.Tests.UI/Extensions/AccessibilityCheckingOrchardCoreUITestExecutorConfigurationExtensions.cs b/Lombiq.Tests.UI/Extensions/AccessibilityCheckingOrchardCoreUITestExecutorConfigurationExtensions.cs index 060507519..7d4be1793 100644 --- a/Lombiq.Tests.UI/Extensions/AccessibilityCheckingOrchardCoreUITestExecutorConfigurationExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/AccessibilityCheckingOrchardCoreUITestExecutorConfigurationExtensions.cs @@ -1,6 +1,8 @@ +using Deque.AxeCore.Commons; using Deque.AxeCore.Selenium; using Lombiq.Tests.UI.Models; using Lombiq.Tests.UI.Services; +using Shouldly; using System; using System.Threading.Tasks; @@ -37,4 +39,62 @@ public static void SetUpAccessibilityCheckingAssertionOnPageChange( return Task.CompletedTask; }; } + + /// + /// Shortcut for adding a filter to 's . + /// + public static void WithAxeIncompletesFilter( + this OrchardCoreUITestExecutorConfiguration configuration, + string name, + Func filter) => + configuration.AccessibilityCheckingConfiguration.AxeResultIncompleteFilters[name] = filter; + + /// + /// Shortcut for adding a filter to 's . + /// + public static void WithAxeIncompletesFilter( + this OrchardCoreUITestExecutorConfiguration configuration, + string name, + string idToExclude) => + configuration.WithAxeIncompletesFilter(name, item => item.Id != idToExclude); + + /// + /// Shortcut for adding a filter to 's . + /// + public static void WithAxeViolationsFilters( + this OrchardCoreUITestExecutorConfiguration configuration, + string name, + Func filter) => + configuration.AccessibilityCheckingConfiguration.AxeResultViolationsFilters.Add(name, filter); + + /// + /// Shortcut for adding a filter to 's . + /// + public static void WithAxeViolationsFilters( + this OrchardCoreUITestExecutorConfiguration configuration, + string name, + string idToExclude) => + configuration.WithAxeViolationsFilters(name, item => item.Id != idToExclude); + + /// + /// Adds exceptions for color contrast accessibility violations by selector. + /// + public static void WithAxeColorContrastViolationsFilters( + this OrchardCoreUITestExecutorConfiguration configuration, + params string[] selectors) + { + selectors.ShouldNotBeEmpty(); + configuration.WithAxeViolationsFilters( + $"{nameof(WithAxeColorContrastViolationsFilters)}: \"{string.Join("\", \"", selectors)}\"", + item => !(item.Id is "color-contrast" && item.Nodes.TrueForAll(node => + selectors.Exists(selector => node.Target.Selector.Contains(selector))))); + } } From 9b91b775f2c4ae4ec6a6f4ec4d2a1f6ff5468880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sat, 21 Feb 2026 20:19:27 +0100 Subject: [PATCH 067/132] Update OC preview and fix build errors. --- .../Lombiq.Tests.UI.AppExtensions.csproj | 6 +++--- .../Lombiq.Tests.UI.Shortcuts.csproj | 20 +++++++++---------- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 8 ++++---- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index c5b6d3223..7e4e1c3ab 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -28,9 +28,9 @@ - - - + + + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index 3c094fa3b..e203c8a18 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -30,16 +30,16 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index ab70d33f3..5f448dedd 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -81,10 +81,10 @@ - - - - + + + + From df3ce0db45c68794bbf853b71bb63165494ff5b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sun, 22 Feb 2026 10:17:18 +0100 Subject: [PATCH 068/132] Update OC preview. --- .../Lombiq.Tests.UI.AppExtensions.csproj | 6 +++--- .../Lombiq.Tests.UI.Shortcuts.csproj | 20 +++++++++---------- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 8 ++++---- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index 7e4e1c3ab..6ed75227f 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -28,9 +28,9 @@ - - - + + + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index e203c8a18..8efb2bfb7 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -30,16 +30,16 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 5f448dedd..3e6d7a845 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -81,10 +81,10 @@ - - - - + + + + From 3f3db3ab9c2035bd883d82793e99c80b74748ebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sun, 22 Feb 2026 11:34:29 +0100 Subject: [PATCH 069/132] Update HL nuget. --- .../Lombiq.Tests.UI.AppExtensions.csproj | 2 +- Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj | 2 +- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index 6ed75227f..33afc5c21 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -39,6 +39,6 @@ - + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index 8efb2bfb7..7a934acc1 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -47,7 +47,7 @@ - + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 3e6d7a845..3e1d6f1bf 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -109,9 +109,9 @@ - - - + + + From 8b960eacae07b2680f2c318a627dca1b14714c10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sat, 7 Mar 2026 12:09:59 +0100 Subject: [PATCH 080/132] Update HL NuGet. --- .../Lombiq.Tests.UI.AppExtensions.csproj | 2 +- Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj | 2 +- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index 62c5a10f7..976e7a371 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -39,6 +39,6 @@ - + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index ee11a7169..ed2c547f5 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -47,7 +47,7 @@ - + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 08f7add46..10e50b913 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -109,9 +109,9 @@ - - - + + + - + diff --git a/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs b/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs index 8c8984add..ad6084425 100644 --- a/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs +++ b/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs @@ -51,11 +51,14 @@ public class OrchardCoreUITestExecutorConfiguration public static readonly Action> AssertBrowserLogIsEmpty = logEntries => logEntries.ShouldBeEmpty(logEntries.ToFormattedString()); + /// + /// The default browser log filter. Ignores logs below and "HTML Imports is deprecated" + /// messages. The latter is because HTML imports are somehow used by Selenium or something but this deprecation + /// notice is always there for every page. + /// public static readonly Func IsNonSuccessBrowserLogEntry = entry => entry.Level >= Level.Warn && - // HTML imports are somehow used by Selenium or something but this deprecation notice is always there for - // every page. !entry.Text.ContainsOrdinalIgnoreCase("HTML Imports is deprecated"); // The 404 is because of how browsers automatically request /favicon.ico even if a favicon is declared to be under a diff --git a/Lombiq.Tests.UI/Services/UITestContext.cs b/Lombiq.Tests.UI/Services/UITestContext.cs index c45076e70..45c30d75d 100644 --- a/Lombiq.Tests.UI/Services/UITestContext.cs +++ b/Lombiq.Tests.UI/Services/UITestContext.cs @@ -324,6 +324,7 @@ public static async Task CreateAsync( ZapManager zapManager) #pragma warning restore S107 // Methods should not have too many parameters { + var token = configuration.TestCancellationToken; var context = new UITestContext( id, testManifest, @@ -338,29 +339,33 @@ public static async Task CreateAsync( if (context.IsBrowserConfigured) { - context._biDirectionalDriver = await scope.Driver.AsBiDiAsync(); + context._biDirectionalDriver = await scope.Driver.AsBiDiAsync(cancellationToken: token); // We intentionally don't pass the UITestContext to these callbacks: The callbacks are called asynchronously // by the browser (and Selenium), and e.g. the current URL can change between when a JS exception was thrown // and the callback is called. Thus, BrowserLogFilter could e.g. ignore log entries for a URL that actually // originated from a different URL and shouldn't be ignored. - await context._biDirectionalDriver.Log.OnEntryAddedAsync(entry => - { - if (configuration.BrowserLogFilters.Values.All(filter => filter(entry))) + await context._biDirectionalDriver.Log.OnEntryAddedAsync( + entry => { - context._cumulativeBrowserLog.Enqueue(entry); - } - }); + if (configuration.BrowserLogFilters.Values.All(filter => filter(entry))) + { + context._cumulativeBrowserLog.Enqueue(entry); + } + }, + cancellationToken: token); if (configuration.TestDumpConfiguration.CaptureResponseLog) { - await context._biDirectionalDriver.Network.OnResponseCompletedAsync(responseCompleted => - { - if (configuration.ResponseLogFilters.All(filter => filter.Value(responseCompleted))) + await context._biDirectionalDriver.Network.OnResponseCompletedAsync( + responseCompleted => { - context._cumulativeResponseLog.Enqueue(responseCompleted.Response); - } - }); + if (configuration.ResponseLogFilters.All(filter => filter.Value(responseCompleted))) + { + context._cumulativeResponseLog.Enqueue(responseCompleted.Response); + } + }, + cancellationToken: token); } } diff --git a/Lombiq.Tests.UI/SqlQueryMonitoring/Extensions/SqlQueryMonitoringUITestContextExtensions.cs b/Lombiq.Tests.UI/SqlQueryMonitoring/Extensions/SqlQueryMonitoringUITestContextExtensions.cs index 6fcdc46b1..5bf802129 100644 --- a/Lombiq.Tests.UI/SqlQueryMonitoring/Extensions/SqlQueryMonitoringUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/SqlQueryMonitoring/Extensions/SqlQueryMonitoringUITestContextExtensions.cs @@ -169,7 +169,7 @@ private static async Task GetSqlQueryMonitoringStoreAs { ISqlQueryMonitoringStore store = null; - await context.Application.UsingScopeAsync( + await context.Application.UsingScopeServiceProviderAsync( serviceProvider => { store = serviceProvider.GetService(); From 38a7c7c39999de42cf0b0eb511ee61e6d1a638a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 20 Mar 2026 13:23:01 +0100 Subject: [PATCH 084/132] Fix possible multiple enumeration warnings. --- .../Services/OrchardCoreUITestExecutorConfiguration.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs b/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs index ad6084425..86576d902 100644 --- a/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs +++ b/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs @@ -48,7 +48,7 @@ public class OrchardCoreUITestExecutorConfiguration public static readonly Func AssertAppLogsCanContainCacheFolderErrorsAsync = app => app.LogsShouldNotContainAsync(AppLogAssertionHelper.NotMediaCacheEntriesPredicate, TestContext.Current.CancellationToken); - public static readonly Action> AssertBrowserLogIsEmpty = + public static readonly Action> AssertBrowserLogIsEmpty = logEntries => logEntries.ShouldBeEmpty(logEntries.ToFormattedString()); /// @@ -66,7 +66,7 @@ public class OrchardCoreUITestExecutorConfiguration public static readonly Func IsNonSuccessResponse = e => e.Response.Status is < 200 or >= 400 && !e.Response.Url.EndsWithOrdinalIgnoreCase("/favicon.ico"); - public static readonly Action> AssertResponseLogIsEmpty = + public static readonly Action> AssertResponseLogIsEmpty = responses => responses.ShouldBeEmpty(responses.ToFormattedString()); private CancellationToken _testCancellationToken; @@ -122,7 +122,7 @@ public Func BrowserLogFilter set => BrowserLogFilters[nameof(BrowserLogFilter)] = value; } - public Action> AssertBrowserLog { get; set; } = AssertBrowserLogIsEmpty; + public Action> AssertBrowserLog { get; set; } = AssertBrowserLogIsEmpty; /// /// Gets the delegates that select which response data get saved to ResponseLogFilter set => ResponseLogFilters[nameof(ResponseLogFilter)] = value; } - public Action> AssertResponseLog { get; set; } = AssertResponseLogIsEmpty; + public Action> AssertResponseLog { get; set; } = AssertResponseLogIsEmpty; public ITestOutputHelper TestOutputHelper { get; set; } From c3b81b250cf1cec4a91b9516e6a3c5a08dcaeed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 20 Mar 2026 14:52:24 +0100 Subject: [PATCH 085/132] Revert "Disable Renovate updates for smtp4dev: 3.15.0+ requires net10.0" This reverts commit 918070f4c35146f6454a0b73dd5289379109de21. --- renovate.json5 | 4 ---- 1 file changed, 4 deletions(-) diff --git a/renovate.json5 b/renovate.json5 index 76d8686f8..b71cb169e 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -58,10 +58,6 @@ prBodyNotes: [ 'For details about the `rnwood.smtp4dev` release, see [its release notes](https://github.com/rnwood/smtp4dev/releases/tag/{{newVersion}}).', ], - // smtp4dev 3.15.0 switched from net8.0 to net10.0, breaking dotnet tool restore on .NET 8 SDK (CI uses - // 8.0.x). The DotnetToolSettings.xml is only under tools/net10.0/any/ so the .NET 8 SDK can't find it. - // Re-enable once the project and CI migrate to .NET 10. - enabled: false, }, { groupName: 'ZAP', From 6fa4ae77feadb908bd53fc82edea2bf387f541f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 20 Mar 2026 14:53:11 +0100 Subject: [PATCH 086/132] Update smtp4dev. --- Lombiq.Tests.UI/.config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Tests.UI/.config/dotnet-tools.json b/Lombiq.Tests.UI/.config/dotnet-tools.json index 838b1f583..18a4ad588 100644 --- a/Lombiq.Tests.UI/.config/dotnet-tools.json +++ b/Lombiq.Tests.UI/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "rnwood.smtp4dev": { - "version": "3.14.0", + "version": "3.15.0", "commands": [ "smtp4dev" ] From 7f5feda967ef247220961e2c570351dcff353f73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 20 Mar 2026 14:54:22 +0100 Subject: [PATCH 087/132] Test if the "HTML Imports is deprecated" log suppression is still needed. --- .../Services/OrchardCoreUITestExecutorConfiguration.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs b/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs index 86576d902..1b850aeb3 100644 --- a/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs +++ b/Lombiq.Tests.UI/Services/OrchardCoreUITestExecutorConfiguration.cs @@ -52,14 +52,10 @@ public class OrchardCoreUITestExecutorConfiguration logEntries => logEntries.ShouldBeEmpty(logEntries.ToFormattedString()); /// - /// The default browser log filter. Ignores logs below and "HTML Imports is deprecated" - /// messages. The latter is because HTML imports are somehow used by Selenium or something but this deprecation - /// notice is always there for every page. + /// The default browser log filter. Ignores logs below . /// public static readonly Func IsNonSuccessBrowserLogEntry = - entry => - entry.Level >= Level.Warn && - !entry.Text.ContainsOrdinalIgnoreCase("HTML Imports is deprecated"); + entry => entry.Level >= Level.Warn; // The 404 is because of how browsers automatically request /favicon.ico even if a favicon is declared to be under a // different URL. From 4c6f05b44f42c2147fc1a8afe89426c7e76613ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 20 Mar 2026 16:46:52 +0100 Subject: [PATCH 088/132] Update OC preview. --- .../Lombiq.Tests.UI.AppExtensions.csproj | 6 +++--- .../Lombiq.Tests.UI.Shortcuts.csproj | 20 +++++++++---------- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 8 ++++---- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index 976e7a371..4322bde30 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -28,9 +28,9 @@ - - - + + + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index ed2c547f5..002a578c2 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -30,16 +30,16 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 16867946f..32e3180f2 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -81,10 +81,10 @@ - - - - + + + + From 08ffbe704c75d7acb8b22f23545b362ced71ed9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Wed, 25 Mar 2026 21:22:43 +0100 Subject: [PATCH 089/132] use Choose, because why not --- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 31 +++++++++++++++----------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 32e3180f2..2b57bb0b6 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -100,19 +100,24 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + From e71660e508d908d84aae49b5876c6df4de989ddc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sun, 29 Mar 2026 01:30:17 +0100 Subject: [PATCH 090/132] Fix new analyzer warnings. --- .../Models/VisualVerificationMatchApprovedContext.cs | 8 ++++---- .../SqlQueryMonitoring/SqlQueryExecutionEntry.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Lombiq.Tests.UI/Models/VisualVerificationMatchApprovedContext.cs b/Lombiq.Tests.UI/Models/VisualVerificationMatchApprovedContext.cs index e08eb16bb..6be61ce46 100644 --- a/Lombiq.Tests.UI/Models/VisualVerificationMatchApprovedContext.cs +++ b/Lombiq.Tests.UI/Models/VisualVerificationMatchApprovedContext.cs @@ -55,7 +55,7 @@ private static string GetModuleName(EnhancedStackFrame frame) } while (currentMethod is not null); - var depthMark = DepthMark(); + var depthMark = DepthMark; if (depthMark.IsMatch(moduleName)) { moduleName = depthMark.Match(moduleName).Groups["module"].Value; @@ -69,7 +69,7 @@ private static string GetModuleName(EnhancedStackFrame frame) private static string GetMethodName(EnhancedStackFrame frame) { var methodName = frame.MethodInfo.Name!; - var inheritedMethod = InheritedMethod(); + var inheritedMethod = InheritedMethod; if (inheritedMethod.IsMatch(methodName)) { methodName = inheritedMethod.Match(methodName).Groups["method"].Value; @@ -79,8 +79,8 @@ private static string GetMethodName(EnhancedStackFrame frame) } [GeneratedRegex("^(?.*)`[0-9]+$", RegexOptions.ExplicitCapture)] - private static partial Regex DepthMark(); + private static partial Regex DepthMark { get; } [GeneratedRegex("^<(?.*)>.*$", RegexOptions.ExplicitCapture)] - private static partial Regex InheritedMethod(); + private static partial Regex InheritedMethod { get; } } diff --git a/Lombiq.Tests.UI/SqlQueryMonitoring/SqlQueryExecutionEntry.cs b/Lombiq.Tests.UI/SqlQueryMonitoring/SqlQueryExecutionEntry.cs index e78c3e9c3..128f548cb 100644 --- a/Lombiq.Tests.UI/SqlQueryMonitoring/SqlQueryExecutionEntry.cs +++ b/Lombiq.Tests.UI/SqlQueryMonitoring/SqlQueryExecutionEntry.cs @@ -53,7 +53,7 @@ private static string NormalizeCommandText(string commandText) /// E.g. "SELECT *" and "SELECT *" should count as the same query text. /// private static string NormalizeWhitespace(string text) => - WhitespaceRegex().Replace(text ?? string.Empty, " ").Trim(); + WhitespaceRegex.Replace(text ?? string.Empty, " ").Trim(); private static string CaptureCallStack() { @@ -95,5 +95,5 @@ private static string NormalizeParameterValue(object value) } [GeneratedRegex(@"\s+", RegexOptions.CultureInvariant, matchTimeoutMilliseconds: 1000)] - private static partial Regex WhitespaceRegex(); + private static partial Regex WhitespaceRegex { get; } } From 9f20b23bc9ea1d1996f028e1f7cb42f3b72529f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sun, 29 Mar 2026 01:38:58 +0100 Subject: [PATCH 091/132] Update OC preview. --- .../Lombiq.Tests.UI.AppExtensions.csproj | 6 +++--- .../Lombiq.Tests.UI.Shortcuts.csproj | 20 +++++++++---------- .../Lombiq.Tests.UI.TestingModule.csproj | 4 ++-- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 8 ++++---- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index 4322bde30..39abbed90 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -28,9 +28,9 @@ - - - + + + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index 002a578c2..f322dc6df 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -30,16 +30,16 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj b/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj index 3f33db97b..7cf8d21d3 100644 --- a/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj +++ b/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 2b57bb0b6..c7c35a335 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -81,10 +81,10 @@ - - - - + + + + From 2192b762fa5e3c2cc5e5291b3e6f96c836f5e0c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Thu, 2 Apr 2026 15:19:55 +0200 Subject: [PATCH 092/132] Create new SLNX files. --- Lombiq.UITestingToolbox.slnx | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Lombiq.UITestingToolbox.slnx diff --git a/Lombiq.UITestingToolbox.slnx b/Lombiq.UITestingToolbox.slnx new file mode 100644 index 000000000..1666e233c --- /dev/null +++ b/Lombiq.UITestingToolbox.slnx @@ -0,0 +1,5 @@ + + + + + From 540d47063f8b8875bf2b1ba4ffd2e0a8da954938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Thu, 2 Apr 2026 16:04:33 +0200 Subject: [PATCH 093/132] Update validate-nuget-publish.yml issue branch. --- .github/workflows/validate-nuget-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate-nuget-publish.yml b/.github/workflows/validate-nuget-publish.yml index f0fd6be1e..489430861 100644 --- a/.github/workflows/validate-nuget-publish.yml +++ b/.github/workflows/validate-nuget-publish.yml @@ -9,4 +9,4 @@ on: jobs: validate-nuget-publish: name: Validate NuGet Publish - uses: Lombiq/GitHub-Actions/.github/workflows/validate-nuget-publish.yml@issue/OSOE-925 + uses: Lombiq/GitHub-Actions/.github/workflows/validate-nuget-publish.yml@issue/OSOE-1199 From 6721aa3bf8088fee8a6d958145d9d172588c4dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 3 Apr 2026 19:09:20 +0200 Subject: [PATCH 094/132] Remove SLN files. --- Lombiq.UITestingToolbox.sln | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 Lombiq.UITestingToolbox.sln diff --git a/Lombiq.UITestingToolbox.sln b/Lombiq.UITestingToolbox.sln deleted file mode 100644 index 1cedefbb0..000000000 --- a/Lombiq.UITestingToolbox.sln +++ /dev/null @@ -1,37 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.32112.339 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lombiq.Tests.UI", "Lombiq.Tests.UI\Lombiq.Tests.UI.csproj", "{DFD31967-1510-4C93-A7F9-E55C307CD230}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lombiq.Tests.UI.AppExtensions", "Lombiq.Tests.UI.AppExtensions\Lombiq.Tests.UI.AppExtensions.csproj", "{4E000537-7F13-4145-AA8F-DDDECBEAEC9F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lombiq.Tests.UI.Shortcuts", "Lombiq.Tests.UI.Shortcuts\Lombiq.Tests.UI.Shortcuts.csproj", "{8D9BE6BF-63D3-4329-803A-DCFE8CA94D54}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {DFD31967-1510-4C93-A7F9-E55C307CD230}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DFD31967-1510-4C93-A7F9-E55C307CD230}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DFD31967-1510-4C93-A7F9-E55C307CD230}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DFD31967-1510-4C93-A7F9-E55C307CD230}.Release|Any CPU.Build.0 = Release|Any CPU - {4E000537-7F13-4145-AA8F-DDDECBEAEC9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4E000537-7F13-4145-AA8F-DDDECBEAEC9F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4E000537-7F13-4145-AA8F-DDDECBEAEC9F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4E000537-7F13-4145-AA8F-DDDECBEAEC9F}.Release|Any CPU.Build.0 = Release|Any CPU - {8D9BE6BF-63D3-4329-803A-DCFE8CA94D54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8D9BE6BF-63D3-4329-803A-DCFE8CA94D54}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8D9BE6BF-63D3-4329-803A-DCFE8CA94D54}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8D9BE6BF-63D3-4329-803A-DCFE8CA94D54}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {030E57A3-0EFB-4B35-8587-328927F03BD2} - EndGlobalSection -EndGlobal From 125da10fc01471a508f1c66b669b883d3cbbf8aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Wed, 8 Apr 2026 14:30:09 +0200 Subject: [PATCH 095/132] Use SDKs even more. --- .../Lombiq.Tests.UI.AppExtensions.csproj | 32 +++------- .../Lombiq.Tests.UI.Samples.csproj | 36 ++++------- .../Lombiq.Tests.UI.Shortcuts.csproj | 44 +++----------- .../Lombiq.Tests.UI.TestingModule.csproj | 21 ++----- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 60 +++++-------------- 5 files changed, 47 insertions(+), 146 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index 39abbed90..9b7543cea 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -1,44 +1,28 @@ - net10.0 false - $(DefaultItemExcludes);.git* + + + Lombiq UI Testing Toolbox - App Extensions - Lombiq Technologies - Copyright © 2020, Lombiq Technologies Ltd. - Lombiq UI Testing Toolbox - App Extensions: UI testing-related configuration extensions for the web app under test. See the project website for detailed documentation. - NuGetIcon.png + 2020 + UI testing-related configuration extensions for the web app under test. OrchardCore;Lombiq;AspNetCore;Selenium;Atata;Shouldly;xUnit;Axe;AccessibilityTesting;UITesting;Testing;Automation https://github.com/Lombiq/UI-Testing-Toolbox - - BSD-3-Clause - - - - - - - - - - - - - - - - + + + diff --git a/Lombiq.Tests.UI.Samples/Lombiq.Tests.UI.Samples.csproj b/Lombiq.Tests.UI.Samples/Lombiq.Tests.UI.Samples.csproj index 1341d82fd..a1213ef50 100644 --- a/Lombiq.Tests.UI.Samples/Lombiq.Tests.UI.Samples.csproj +++ b/Lombiq.Tests.UI.Samples/Lombiq.Tests.UI.Samples.csproj @@ -1,11 +1,12 @@ - - + net10.0 false - Exe + + + @@ -14,29 +15,10 @@ - - PreserveNewest - - - PreserveNewest - true - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + @@ -44,4 +26,6 @@ + + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index f322dc6df..593a0b949 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -1,53 +1,25 @@ - - + net10.0 false - true - $(DefaultItemExcludes);.git* + + + Lombiq UI Testing Toolbox - Shortcuts - Lombiq Technologies - Copyright © 2020, Lombiq Technologies Ltd. - Lombiq UI Testing Toolbox - Shortcuts: Provides some useful shortcuts for common operations that UI tests might want to do or check, e.g. turning features on or off, or logging in users. See the project website for detailed documentation. + 2020 + Provides some useful shortcuts for common operations that UI tests might want to do or check, e.g. turning features on or off, or logging in users. OrchardCore;Lombiq;AspNetCore;Selenium;Atata;Shouldly;xUnit;Axe;AccessibilityTesting;UITesting;Testing;Automation - NuGetIcon.png https://github.com/Lombiq/UI-Testing-Toolbox https://github.com/Lombiq/UI-Testing-Toolbox/tree/dev/Lombiq.Tests.UI.Shortcuts - BSD-3-Clause - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj b/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj index 7cf8d21d3..84c13df6b 100644 --- a/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj +++ b/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj @@ -1,23 +1,12 @@ - - + net10.0 false - true - $(DefaultItemExcludes);.git* - - - - - - - - - - - - + + + + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index c7c35a335..93bef9e57 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -1,9 +1,7 @@ - net10.0 false - $(DefaultItemExcludes);.git* true @@ -12,55 +10,26 @@ true + + + Lombiq UI Testing Toolbox for Orchard Core - Lombiq Technologies - Copyright © 2020, Lombiq Technologies Ltd. - Lombiq UI Testing Toolbox for Orchard Core: Web UI testing toolbox mostly for Orchard Core applications. Everything you need to do UI testing with Selenium for an Orchard app is here. See the project website for detailed documentation. + 2020 + Web UI testing toolbox mostly for Orchard Core applications. Everything you need to do UI testing with Selenium for an Orchard app is here. OrchardCore;Lombiq;AspNetCore;Selenium;Atata;Shouldly;xUnit;Axe;AccessibilityTesting;UITesting;Testing;Automation;ZAP;Zed Attack Proxy;Security;Scanning;OWASP - NuGetIcon.png https://github.com/Lombiq/UI-Testing-Toolbox - https://github.com/Lombiq/UI-Testing-Toolbox - BSD-3-Clause - - - - PreserveNewest - - - - - - PreserveNewest - true - - - PreserveNewest - true - - - PreserveNewest - true - - - PreserveNewest - true - - - PreserveNewest - true - - - PreserveNewest - true - - - PreserveNewest - true - + + + + + + + + @@ -85,6 +54,7 @@ + @@ -144,4 +114,6 @@ + + From 3a70131c7b25023d9fc52928c5d3f69208f4efd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Wed, 8 Apr 2026 22:19:04 +0200 Subject: [PATCH 096/132] Update OC preview. --- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 93bef9e57..bdb083c77 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -50,11 +50,11 @@ - - - - - + + + + + From 5387d67cc1b8c4ce2a5e771310f7e3560c0cdf5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Wed, 8 Apr 2026 22:23:26 +0200 Subject: [PATCH 097/132] Revert "Use SDKs even more." This reverts commit 125da10f --- .../Lombiq.Tests.UI.AppExtensions.csproj | 32 +++++++--- .../Lombiq.Tests.UI.Samples.csproj | 36 +++++++---- .../Lombiq.Tests.UI.Shortcuts.csproj | 44 +++++++++++--- .../Lombiq.Tests.UI.TestingModule.csproj | 21 +++++-- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 59 ++++++++++++++----- 5 files changed, 146 insertions(+), 46 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index 9b7543cea..39abbed90 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -1,28 +1,44 @@ + net10.0 false + $(DefaultItemExcludes);.git* - - - Lombiq UI Testing Toolbox - App Extensions - 2020 - UI testing-related configuration extensions for the web app under test. + Lombiq Technologies + Copyright © 2020, Lombiq Technologies Ltd. + Lombiq UI Testing Toolbox - App Extensions: UI testing-related configuration extensions for the web app under test. See the project website for detailed documentation. + NuGetIcon.png OrchardCore;Lombiq;AspNetCore;Selenium;Atata;Shouldly;xUnit;Axe;AccessibilityTesting;UITesting;Testing;Automation https://github.com/Lombiq/UI-Testing-Toolbox + + BSD-3-Clause + + + + + + + + + + + + + + + + - - - diff --git a/Lombiq.Tests.UI.Samples/Lombiq.Tests.UI.Samples.csproj b/Lombiq.Tests.UI.Samples/Lombiq.Tests.UI.Samples.csproj index a1213ef50..1341d82fd 100644 --- a/Lombiq.Tests.UI.Samples/Lombiq.Tests.UI.Samples.csproj +++ b/Lombiq.Tests.UI.Samples/Lombiq.Tests.UI.Samples.csproj @@ -1,12 +1,11 @@ - + + net10.0 false + Exe - - - @@ -15,10 +14,29 @@ - - - - + + PreserveNewest + + + PreserveNewest + true + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -26,6 +44,4 @@ - - diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index 593a0b949..f322dc6df 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -1,25 +1,53 @@ - + + net10.0 false + true + $(DefaultItemExcludes);.git* - - - Lombiq UI Testing Toolbox - Shortcuts - 2020 - Provides some useful shortcuts for common operations that UI tests might want to do or check, e.g. turning features on or off, or logging in users. + Lombiq Technologies + Copyright © 2020, Lombiq Technologies Ltd. + Lombiq UI Testing Toolbox - Shortcuts: Provides some useful shortcuts for common operations that UI tests might want to do or check, e.g. turning features on or off, or logging in users. See the project website for detailed documentation. OrchardCore;Lombiq;AspNetCore;Selenium;Atata;Shouldly;xUnit;Axe;AccessibilityTesting;UITesting;Testing;Automation + NuGetIcon.png https://github.com/Lombiq/UI-Testing-Toolbox https://github.com/Lombiq/UI-Testing-Toolbox/tree/dev/Lombiq.Tests.UI.Shortcuts + BSD-3-Clause + + + + + + + + + + + + + + + + + + + + + + + + + + + - - diff --git a/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj b/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj index 84c13df6b..7cf8d21d3 100644 --- a/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj +++ b/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj @@ -1,12 +1,23 @@ - + + net10.0 false + true + $(DefaultItemExcludes);.git* - - + + + + + + + + + + + + - - diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index bdb083c77..54df5fe48 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -1,7 +1,9 @@ + net10.0 false + $(DefaultItemExcludes);.git* true @@ -10,26 +12,55 @@ true - - - Lombiq UI Testing Toolbox for Orchard Core - 2020 - Web UI testing toolbox mostly for Orchard Core applications. Everything you need to do UI testing with Selenium for an Orchard app is here. + Lombiq Technologies + Copyright © 2020, Lombiq Technologies Ltd. + Lombiq UI Testing Toolbox for Orchard Core: Web UI testing toolbox mostly for Orchard Core applications. Everything you need to do UI testing with Selenium for an Orchard app is here. See the project website for detailed documentation. OrchardCore;Lombiq;AspNetCore;Selenium;Atata;Shouldly;xUnit;Axe;AccessibilityTesting;UITesting;Testing;Automation;ZAP;Zed Attack Proxy;Security;Scanning;OWASP + NuGetIcon.png https://github.com/Lombiq/UI-Testing-Toolbox + https://github.com/Lombiq/UI-Testing-Toolbox + BSD-3-Clause - - - - - - - - + + + + PreserveNewest + + + + + + PreserveNewest + true + + + PreserveNewest + true + + + PreserveNewest + true + + + PreserveNewest + true + + + PreserveNewest + true + + + PreserveNewest + true + + + PreserveNewest + true + @@ -114,6 +145,4 @@ - - From 0f385122ccb88537ee54a7324995d9cba4ac92c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sat, 11 Apr 2026 16:18:57 +0200 Subject: [PATCH 098/132] Fix obsolete "As". --- .../Lombiq.Tests.UI.AppExtensions.csproj | 6 +++--- .../Lombiq.Tests.UI.Shortcuts.csproj | 20 +++++++++---------- .../Lombiq.Tests.UI.TestingModule.csproj | 4 ++-- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 10 +++++----- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index 39abbed90..908dd4ee4 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -28,9 +28,9 @@ - - - + + + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index f322dc6df..18559ee25 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -30,16 +30,16 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj b/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj index 7cf8d21d3..b29f03eab 100644 --- a/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj +++ b/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 54df5fe48..43f8a7aaf 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -81,11 +81,11 @@ - - - - - + + + + + From 9ec9b5c3e582476c305802cd5c81f3bb2e0582ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sat, 11 Apr 2026 16:58:23 +0200 Subject: [PATCH 099/132] Fix new problems after OC update and merge from dev. --- .../Lombiq.Tests.UI.Shortcuts.csproj | 2 +- .../Extensions/OrchardCoreConfigurationExtensions.cs | 2 +- Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index 18559ee25..1b0115eb0 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -39,7 +39,7 @@ - + diff --git a/Lombiq.Tests.UI/Extensions/OrchardCoreConfigurationExtensions.cs b/Lombiq.Tests.UI/Extensions/OrchardCoreConfigurationExtensions.cs index a2290ba98..6c45c7458 100644 --- a/Lombiq.Tests.UI/Extensions/OrchardCoreConfigurationExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/OrchardCoreConfigurationExtensions.cs @@ -1,5 +1,5 @@ using Lombiq.Tests.UI.Services; -using OrchardCore.Search.Elasticsearch.Core.Recipes; +using OrchardCore.Elasticsearch.Core.Recipes; using System; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; diff --git a/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs b/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs index c2d4571ba..e089de961 100644 --- a/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs +++ b/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs @@ -6,10 +6,10 @@ using OrchardCore.Indexing; using OrchardCore.Indexing.Core; using OrchardCore.Recipes.Models; -using OrchardCore.Search.Elasticsearch.Core.Deployment; -using OrchardCore.Search.Elasticsearch.Core.Models; -using OrchardCore.Search.Elasticsearch.Core.Recipes; -using OrchardCore.Search.Elasticsearch.Core.Services; +using OrchardCore.Elasticsearch.Core.Deployment; +using OrchardCore.Elasticsearch.Core.Models; +using OrchardCore.Elasticsearch.Core.Recipes; +using OrchardCore.Elasticsearch.Core.Services; using System; using System.Diagnostics.CodeAnalysis; using System.Text.Json; From d1b625763676772d911be094de2e2d4733b07781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sat, 11 Apr 2026 17:42:29 +0200 Subject: [PATCH 100/132] Fix new problems after OC update and merge from dev. --- .../Recipes/Lombiq.OSOCE.Tests.Elasticsearch.recipe.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Tests.UI.Samples/Recipes/Lombiq.OSOCE.Tests.Elasticsearch.recipe.json b/Lombiq.Tests.UI.Samples/Recipes/Lombiq.OSOCE.Tests.Elasticsearch.recipe.json index 2d2c97ad6..8c74f36a7 100644 --- a/Lombiq.Tests.UI.Samples/Recipes/Lombiq.OSOCE.Tests.Elasticsearch.recipe.json +++ b/Lombiq.Tests.UI.Samples/Recipes/Lombiq.OSOCE.Tests.Elasticsearch.recipe.json @@ -20,7 +20,7 @@ "name": "feature", "disable": [], "enable": [ - "OrchardCore.Search.Elasticsearch", + "OrchardCore.Elasticsearch", "OrchardCore.Search" ] }, From ff99390457f8530dbce333d5b71a7a76f9d36285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sat, 11 Apr 2026 18:48:38 +0200 Subject: [PATCH 101/132] Fix additional new static code analysis warnings. --- ...ckingOrchardCoreUITestExecutorConfigurationExtensions.cs | 4 ++-- .../Extensions/NavigationUITestContextExtensions.cs | 2 +- Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Lombiq.Tests.UI/Extensions/AccessibilityCheckingOrchardCoreUITestExecutorConfigurationExtensions.cs b/Lombiq.Tests.UI/Extensions/AccessibilityCheckingOrchardCoreUITestExecutorConfigurationExtensions.cs index 7d4be1793..1a8f67e4e 100644 --- a/Lombiq.Tests.UI/Extensions/AccessibilityCheckingOrchardCoreUITestExecutorConfigurationExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/AccessibilityCheckingOrchardCoreUITestExecutorConfigurationExtensions.cs @@ -94,7 +94,7 @@ public static void WithAxeColorContrastViolationsFilters( selectors.ShouldNotBeEmpty(); configuration.WithAxeViolationsFilters( $"{nameof(WithAxeColorContrastViolationsFilters)}: \"{string.Join("\", \"", selectors)}\"", - item => !(item.Id is "color-contrast" && item.Nodes.TrueForAll(node => - selectors.Exists(selector => node.Target.Selector.Contains(selector))))); + item => !(string.Equals(item.Id, "color-contrast", StringComparison.Ordinal) && + item.Nodes.TrueForAll(node => selectors.Exists(selector => node.Target.Selector.Contains(selector))))); } } diff --git a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs index 91081999c..841b77676 100644 --- a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs @@ -285,7 +285,7 @@ static async Task GetElementAsync(HttpResponseMessage response, string using (response) { await using var stream = await response.Content.ReadAsStreamAsync(TestContext.Current.CancellationToken); - var document = await new HtmlParser().ParseDocumentAsync(stream); + var document = await new HtmlParser().ParseDocumentAsync(stream, TestContext.Current.CancellationToken); return string.IsNullOrEmpty(query) ? document.DocumentElement : document.QuerySelector(query); } } diff --git a/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs b/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs index e089de961..ad8c1ae04 100644 --- a/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs +++ b/Lombiq.Tests.UI/Models/ElasticsearchRunningContext.cs @@ -3,13 +3,13 @@ using Lombiq.Tests.UI.Extensions; using Lombiq.Tests.UI.Services; using Microsoft.Extensions.DependencyInjection; -using OrchardCore.Indexing; -using OrchardCore.Indexing.Core; -using OrchardCore.Recipes.Models; using OrchardCore.Elasticsearch.Core.Deployment; using OrchardCore.Elasticsearch.Core.Models; using OrchardCore.Elasticsearch.Core.Recipes; using OrchardCore.Elasticsearch.Core.Services; +using OrchardCore.Indexing; +using OrchardCore.Indexing.Core; +using OrchardCore.Recipes.Models; using System; using System.Diagnostics.CodeAnalysis; using System.Text.Json; From ec98eaa29f740f5deaf22c521194bbc539f5a3e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sun, 12 Apr 2026 00:28:43 +0200 Subject: [PATCH 102/132] Update HL Nuget. --- .../Lombiq.Tests.UI.AppExtensions.csproj | 2 +- Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj | 2 +- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index 908dd4ee4..620048cef 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -39,6 +39,6 @@ - + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index 1b0115eb0..5ed7b4509 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -47,7 +47,7 @@ - + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 43f8a7aaf..c8c5c9680 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -105,9 +105,9 @@ - - - + + + From 43d2a4716f592193d81c48932d3973819c8a4f57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Mon, 13 Apr 2026 14:14:56 +0200 Subject: [PATCH 103/132] Update OC preview. --- .../Lombiq.Tests.UI.AppExtensions.csproj | 6 +++--- .../Lombiq.Tests.UI.Shortcuts.csproj | 20 +++++++++---------- .../Lombiq.Tests.UI.TestingModule.csproj | 4 ++-- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 10 +++++----- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index 620048cef..dbc2b7e89 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -28,9 +28,9 @@ - - - + + + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index 5ed7b4509..4277c661c 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -30,16 +30,16 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj b/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj index b29f03eab..62450ad72 100644 --- a/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj +++ b/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index c8c5c9680..c92ac7ba5 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -81,11 +81,11 @@ - - - - - + + + + + From 5a1192a149d4cf9013a2beea5ffd2644e8ac0477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 14 Apr 2026 03:06:01 +0200 Subject: [PATCH 104/132] Make setup by web request more reliable. --- Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs index 841b77676..12969bf2b 100644 --- a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs @@ -331,7 +331,7 @@ await client.PostAsync(uri, formContent, cancellationToken), (responseSetupScript == null).ShouldBe(shouldBeSuccess); - context.Driver.Url = uri.AbsoluteUri; + await context.GoToAbsoluteUrlAsync(uri, onlyIfNotAlreadyThere: false); return uri; } From 48843bf0023c0b3a40e9fab9a2e089830501190f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sat, 18 Apr 2026 14:55:04 +0200 Subject: [PATCH 105/132] Update MailKit to fix NU1902 Package 'MailKit' 4.15.1 has a known moderate severity vulnerability, https://github.com/advisories/GHSA-9j88-vvj5-vhgr --- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index c92ac7ba5..7ede056f8 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -73,7 +73,7 @@ - + From fd89444637101b134e8d7e43306bdb84a134fa4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sat, 18 Apr 2026 18:52:11 +0200 Subject: [PATCH 106/132] Clarify. --- Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs index 0db88e29a..642e4ff81 100644 --- a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs @@ -329,7 +329,7 @@ await client.GetAsync(uri, cancellationToken), await client.PostAsync(uri, formContent, cancellationToken), "script[src*='OrchardCore.Setup']"); - (responseSetupScript == null).ShouldBe(shouldBeSuccess); + (responseSetupScript == null).ShouldBe(shouldBeSuccess, shouldBeSuccess ? "Setup did not succeed." : "Setup succeeded when it shouldn't."); await context.GoToAbsoluteUrlAsync(uri, onlyIfNotAlreadyThere: false); return uri; From 0cbb2aec49ce8bda54dd592c683bd91de37ee05e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sat, 18 Apr 2026 20:19:33 +0200 Subject: [PATCH 107/132] Always exclude Gremlin info logs. --- .../Extensions/MonkeyTestingUITestContextExtensions.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lombiq.Tests.UI/Extensions/MonkeyTestingUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/MonkeyTestingUITestContextExtensions.cs index 6c9d5ce7d..7769bfb3c 100644 --- a/Lombiq.Tests.UI/Extensions/MonkeyTestingUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/MonkeyTestingUITestContextExtensions.cs @@ -2,6 +2,7 @@ using Lombiq.Tests.UI.MonkeyTesting; using Lombiq.Tests.UI.MonkeyTesting.UrlFilters; using Lombiq.Tests.UI.Services; +using OpenQA.Selenium.BiDi.Log; using System.Threading.Tasks; namespace Lombiq.Tests.UI.Extensions; @@ -107,6 +108,9 @@ public static async Task TestFrontendAuthenticatedAndAnonymouslyAsMonkeyRecursiv string signInDirectlyWithUserName = DefaultUser.UserName, string startingRelativeUrl = "/") { + context.Configuration.BrowserLogFilters["Exclude Gremlin info logs"] = entry => + !(entry.Level == Level.Info && entry.Text?.Contains("gremlin") == true); + await TestFrontendAuthenticatedAsMonkeyRecursivelyAsync( context, options, From 7f7dfc19615aa6a44049dbdd081ac8b102ed0d05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sun, 19 Apr 2026 19:40:08 +0200 Subject: [PATCH 108/132] Fix strange ElementNotFoundException when the scrollbar is hidden. --- .../VisualVerificationUITestContextExtensions.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Lombiq.Tests.UI/Extensions/VisualVerificationUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/VisualVerificationUITestContextExtensions.cs index 65d8d0144..46d72598c 100644 --- a/Lombiq.Tests.UI/Extensions/VisualVerificationUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/VisualVerificationUITestContextExtensions.cs @@ -55,8 +55,6 @@ public static void AssertVisualVerificationOnAllResolutions( double pixelErrorPercentageThreshold = 0, Action configurator = null) { - context.HideScrollbar(); - var exceptions = new List(); foreach (var size in sizes) { @@ -342,7 +340,10 @@ private static void AssertVisualVerificationApproved( By elementSelector, Action comparator, Rectangle? regionOfInterest = null, - Action configurator = null) => + Action configurator = null) + { + context.ScrollTo(elementSelector); + context.HideScrollbar(); context.AssertVisualVerificationApproved( elementSelector is null ? null : context.Get(elementSelector), comparator, @@ -359,6 +360,8 @@ private static void AssertVisualVerificationApproved( .JoinNotNullOrEmpty("-") ); }); + context.RestoreHiddenScrollbar(); + } [VisualVerificationApprovedMethod] private static void AssertVisualVerificationApproved( From 81840462703e6e0fcf61cd3aca784786fd2a7856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Mon, 20 Apr 2026 10:36:15 +0200 Subject: [PATCH 109/132] Fix NU1902: Package 'MailKit' 4.15.1 has a known moderate severity vulnerability --- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index c92ac7ba5..7ede056f8 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -73,7 +73,7 @@ - + From a50100ec70a7a8d0a505aec457e9276317e7b503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Mon, 20 Apr 2026 15:22:58 +0200 Subject: [PATCH 110/132] Fix strange bug. --- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 7ede056f8..8f4dbdd09 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -74,6 +74,7 @@ + From bc81bd4b4bd8946ecc226406862397583ac909d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Mon, 20 Apr 2026 18:45:53 +0200 Subject: [PATCH 111/132] Make menu navigation in test more reliable by using IDs. --- .../NavigationUITestContextExtensions.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs index 642e4ff81..2e0cc9129 100644 --- a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs @@ -528,6 +528,32 @@ public static Task ClickReliablyOnUntilUrlChangeAsync( public static Task ClickOnWithScriptAsync(this UITestContext context, By by) => context.Get(by).ClickWithScriptAsync(context); + /// + /// Clicks through a path of admin menu items, ensuring that the next steps is visible before trying to click. + /// + public static async Task ClickThroughAdminMenuAsync(this UITestContext context, params By[] selectors) + { + for (var i = 0; i < selectors.Length - 1; i++) + { + await context.ClickReliablyOnAsync(selectors[i]); + + // Ensure that the next selector exists. If not, try to click on the menu again, in case it was stuck open + // in the first place. If it's still not found, NotFoundException will be thrown now for clearer debugging. + if (!context.Exists(selectors[i + 1].Safely())) + { + await context.ClickReliablyOnAsync(selectors[i]); + context.Exists(selectors[i + 1]); + } + + // The menus have animations, which interfere with the click being recognized. Waiting a fraction of a + // second is the only reliable way to ensure the next step is clickable. + await Task.Delay(TimeSpan.FromMicroseconds(200), context.Configuration.TestCancellationToken); + } + + // The last item is clicked separately, because we don't do look-ahead checks here. + await context.ClickReliablyOnAsync(selectors[^1]); + } + /// /// Switches control to JS alert box, accepts it, and switches control back to main document or first frame. /// From 5fed618def0fab521c5ec651e2170da46a10bbb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Mon, 20 Apr 2026 18:57:06 +0200 Subject: [PATCH 112/132] Fix menu navigation reliability. --- .../NavigationUITestContextExtensions.cs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs index 2e0cc9129..9dff3406e 100644 --- a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs @@ -533,21 +533,16 @@ public static Task ClickOnWithScriptAsync(this UITestContext context, By by) => /// public static async Task ClickThroughAdminMenuAsync(this UITestContext context, params By[] selectors) { + var menuAnimationTime = TimeSpan.FromSeconds(1); + for (var i = 0; i < selectors.Length - 1; i++) { - await context.ClickReliablyOnAsync(selectors[i]); - - // Ensure that the next selector exists. If not, try to click on the menu again, in case it was stuck open - // in the first place. If it's still not found, NotFoundException will be thrown now for clearer debugging. - if (!context.Exists(selectors[i + 1].Safely())) - { - await context.ClickReliablyOnAsync(selectors[i]); - context.Exists(selectors[i + 1]); - } + // Only click on this menu item if the next item is not visible because this menu is already open. + if (context.Exists(selectors[i + 1].Safely().Within(menuAnimationTime))) continue; - // The menus have animations, which interfere with the click being recognized. Waiting a fraction of a - // second is the only reliable way to ensure the next step is clickable. - await Task.Delay(TimeSpan.FromMicroseconds(200), context.Configuration.TestCancellationToken); + // The menus have animations, which interfere with the click being recognized. + await context.ClickReliablyOnAsync(selectors[i]); + context.Exists(selectors[i + 1].Within(menuAnimationTime)); } // The last item is clicked separately, because we don't do look-ahead checks here. From 3a1fa940c2fae93661531ad1b8436fe067fefcd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Mon, 20 Apr 2026 19:17:03 +0200 Subject: [PATCH 113/132] Fix SA1028 --- Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs index 9dff3406e..683332645 100644 --- a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs @@ -537,7 +537,7 @@ public static async Task ClickThroughAdminMenuAsync(this UITestContext context, for (var i = 0; i < selectors.Length - 1; i++) { - // Only click on this menu item if the next item is not visible because this menu is already open. + // Only click on this menu item if the next item is not visible because this menu is already open. if (context.Exists(selectors[i + 1].Safely().Within(menuAnimationTime))) continue; // The menus have animations, which interfere with the click being recognized. From 04b8026a2c59f6746d32ec6bda8f7f452830ac5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Mon, 20 Apr 2026 20:32:26 +0200 Subject: [PATCH 114/132] Use ClickThroughAdminMenuAsync. --- .../Extensions/NavigationUITestContextExtensions.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs index 683332645..821eacc0b 100644 --- a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs @@ -13,6 +13,7 @@ using Shouldly; using System; using System.Collections.Generic; +using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Xunit; @@ -549,6 +550,12 @@ public static async Task ClickThroughAdminMenuAsync(this UITestContext context, await context.ClickReliablyOnAsync(selectors[^1]); } + /// + /// Clicks through a path of admin menu items, ensuring that the next steps is visible before trying to click. + /// + public static Task ClickThroughAdminMenuAsync(this UITestContext context, params string[] ids) => + context.ClickThroughAdminMenuAsync(ids.Select(By.Id).ToArray()); + /// /// Switches control to JS alert box, accepts it, and switches control back to main document or first frame. /// From d3dfe333cda25c7aa9d5dd1f08324dc8901157be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Mon, 20 Apr 2026 20:39:42 +0200 Subject: [PATCH 115/132] Improve ClickThroughAdminMenuAsync. --- .../Extensions/NavigationUITestContextExtensions.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs index 821eacc0b..872d31ea3 100644 --- a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs @@ -543,7 +543,10 @@ public static async Task ClickThroughAdminMenuAsync(this UITestContext context, // The menus have animations, which interfere with the click being recognized. await context.ClickReliablyOnAsync(selectors[i]); - context.Exists(selectors[i + 1].Within(menuAnimationTime)); + + // It's necessary to wait if a click occurred at this level, because the animation interactions unreliable. + await Task.Delay(menuAnimationTime, context.Configuration.TestCancellationToken); + context.Exists(selectors[i + 1]); } // The last item is clicked separately, because we don't do look-ahead checks here. From ebab932efb7a863c19de0c00fd4a0956ed506fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Mon, 20 Apr 2026 22:52:17 +0200 Subject: [PATCH 116/132] AssertVisualVerificationOnAllConfiguredResolutionsAsync --- ...sualVerificationUITestContextExtensions.cs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/Lombiq.Tests.UI/Extensions/VisualVerificationUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/VisualVerificationUITestContextExtensions.cs index 46d72598c..38e46d3f5 100644 --- a/Lombiq.Tests.UI/Extensions/VisualVerificationUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/VisualVerificationUITestContextExtensions.cs @@ -19,6 +19,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Text; +using System.Threading.Tasks; namespace Lombiq.Tests.UI.Extensions; @@ -48,17 +49,23 @@ public static class VisualVerificationUITestContextExtensions /// /// [VisualVerificationApprovedMethod] - public static void AssertVisualVerificationOnAllResolutions( + public static async Task AssertVisualVerificationOnAllResolutionsAsync( this UITestContext context, IEnumerable sizes, Func getSelector, double pixelErrorPercentageThreshold = 0, - Action configurator = null) + Action configurator = null, + TimeSpan? resolutionChangeDelay = null) { var exceptions = new List(); foreach (var size in sizes) { + // Waiting after viewport size change ensures that any animation (such as sticky mobile menu) has time to + // transition to its final state. context.SetViewportSize(size); + await Task.Delay( + resolutionChangeDelay ?? TimeSpan.FromSeconds(1), + context.Configuration.TestCancellationToken); try { @@ -113,17 +120,19 @@ public static void AssertVisualVerificationOnAllResolutions( /// /// [VisualVerificationApprovedMethod] - public static void AssertVisualVerificationApprovedOnAllResolutionsWithPlatformSuffix( + public static Task AssertVisualVerificationApprovedOnAllResolutionsWithPlatformSuffixAsync( this UITestContext context, IEnumerable sizes, Func getSelector, double pixelErrorPercentageThreshold = 0, - Action configurator = null) => - context.AssertVisualVerificationOnAllResolutions( + Action configurator = null, + TimeSpan? resolutionChangeDelay = null) => + context.AssertVisualVerificationOnAllResolutionsAsync( sizes, getSelector, pixelErrorPercentageThreshold, - configuration => configuration.WithUsePlatformAsSuffix()); + configuration => configuration.WithUsePlatformAsSuffix(), + resolutionChangeDelay); /// /// Compares the baseline image and screenshot of the whole page. From 6de83b86e3485d7d25620310bff807e5c6ec3f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 21 Apr 2026 00:27:05 +0200 Subject: [PATCH 117/132] Fix unnecessary "Caller method not found" exception. --- ...sualVerificationUITestContextExtensions.cs | 2 ++ Lombiq.Tests.UI/Models/UITestManifest.cs | 21 +++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Lombiq.Tests.UI/Extensions/VisualVerificationUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/VisualVerificationUITestContextExtensions.cs index 38e46d3f5..935c91349 100644 --- a/Lombiq.Tests.UI/Extensions/VisualVerificationUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/VisualVerificationUITestContextExtensions.cs @@ -400,6 +400,8 @@ private static void AssertVisualVerificationApproved( .FirstOrDefault(); } + testFrame ??= context.TestManifest?.StackFrame; + if (testFrame == null) { throw new VisualVerificationCallerMethodNotFoundException(); diff --git a/Lombiq.Tests.UI/Models/UITestManifest.cs b/Lombiq.Tests.UI/Models/UITestManifest.cs index 347c8779a..d5a7fd1a7 100644 --- a/Lombiq.Tests.UI/Models/UITestManifest.cs +++ b/Lombiq.Tests.UI/Models/UITestManifest.cs @@ -1,5 +1,9 @@ +#nullable enable + using Lombiq.Tests.UI.Services; using System; +using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using Xunit; using Xunit.Sdk; @@ -11,9 +15,18 @@ namespace Lombiq.Tests.UI.Models; /// public class UITestManifest { - public ITest XunitTest => TestContext.Current.Test; - public string Name => XunitTest.TestDisplayName; - public Func TestAsync { get; private set; } + public ITest? XunitTest => TestContext.Current.Test; + public string? Name => XunitTest?.TestDisplayName; + public Func TestAsync { get; } + public EnhancedStackFrame? StackFrame { get; } + + public UITestManifest(Func testAsync) + { + TestAsync = testAsync; - public UITestManifest(Func testAsync) => TestAsync = testAsync; + var typeName = testAsync.Method.DeclaringType?.FullName?.Split('+')[0]; + var methodName = testAsync.Method.Name.StartsWith('<') ? testAsync.Method.Name[1..].Split('>')[0] : testAsync.Method.Name; + StackFrame = new EnhancedStackTrace(new StackTrace(fNeedFileInfo: true)) + .FirstOrDefault(frame => frame.MethodInfo.DeclaringType?.FullName == typeName && frame.MethodInfo.Name == methodName); + } } From a43089357bb5421e7dcbdb3ad6954bfac6451095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 21 Apr 2026 14:49:39 +0200 Subject: [PATCH 118/132] Add UITT Admin Theme. --- .../Constants/FeatureIds.cs | 6 +++ .../Constants/ResourceNames.cs | 8 ++++ .../Lombiq.Tests.UI.AdminTheme.csproj | 44 ++++++++++++++++++ Lombiq.Tests.UI.AdminTheme/Manifest.cs | 12 +++++ Lombiq.Tests.UI.AdminTheme/NuGetIcon.png | Bin 0 -> 4657 bytes .../ResourceManagementOptionsConfiguration.cs | 12 +++++ Lombiq.Tests.UI.AdminTheme/Startup.cs | 19 ++++++++ 7 files changed, 101 insertions(+) create mode 100644 Lombiq.Tests.UI.AdminTheme/Constants/FeatureIds.cs create mode 100644 Lombiq.Tests.UI.AdminTheme/Constants/ResourceNames.cs create mode 100644 Lombiq.Tests.UI.AdminTheme/Lombiq.Tests.UI.AdminTheme.csproj create mode 100644 Lombiq.Tests.UI.AdminTheme/Manifest.cs create mode 100644 Lombiq.Tests.UI.AdminTheme/NuGetIcon.png create mode 100644 Lombiq.Tests.UI.AdminTheme/ResourceManagementOptionsConfiguration.cs create mode 100644 Lombiq.Tests.UI.AdminTheme/Startup.cs diff --git a/Lombiq.Tests.UI.AdminTheme/Constants/FeatureIds.cs b/Lombiq.Tests.UI.AdminTheme/Constants/FeatureIds.cs new file mode 100644 index 000000000..aae0b122b --- /dev/null +++ b/Lombiq.Tests.UI.AdminTheme/Constants/FeatureIds.cs @@ -0,0 +1,6 @@ +namespace Lombiq.Tests.UI.AdminTheme.Constants; + +public static class FeatureIds +{ + public const string Area = "Lombiq.Tests.UI.AdminTheme"; +} diff --git a/Lombiq.Tests.UI.AdminTheme/Constants/ResourceNames.cs b/Lombiq.Tests.UI.AdminTheme/Constants/ResourceNames.cs new file mode 100644 index 000000000..7ca3578e9 --- /dev/null +++ b/Lombiq.Tests.UI.AdminTheme/Constants/ResourceNames.cs @@ -0,0 +1,8 @@ +namespace Lombiq.Tests.UI.AdminTheme.Constants; + +public static class ResourceNames +{ + private const string Prefix = FeatureIds.Area + "."; + + public const string General = Prefix + nameof(General); +} diff --git a/Lombiq.Tests.UI.AdminTheme/Lombiq.Tests.UI.AdminTheme.csproj b/Lombiq.Tests.UI.AdminTheme/Lombiq.Tests.UI.AdminTheme.csproj new file mode 100644 index 000000000..e20e049f3 --- /dev/null +++ b/Lombiq.Tests.UI.AdminTheme/Lombiq.Tests.UI.AdminTheme.csproj @@ -0,0 +1,44 @@ + + + + net10.0 + true + enable + + + + Lombiq UI Testing Toolbox - Admin Theme + Lombiq Technologies + Copyright © 2020, Lombiq Technologies Ltd. + Lombiq UI Testing Toolbox - Admin Theme: Adjustments for the stock Orchard Core admin theme to make it more automation-friendly. + OrchardCore;Lombiq;AspNetCore;Selenium;Atata;Shouldly;xUnit;Axe;AccessibilityTesting;UITesting;Testing;Automation + NuGetIcon.png + https://github.com/Lombiq/UI-Testing-Toolbox + https://github.com/Lombiq/UI-Testing-Toolbox/tree/dev/Lombiq.Tests.UI.AdminTheme + BSD-3-Clause + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Lombiq.Tests.UI.AdminTheme/Manifest.cs b/Lombiq.Tests.UI.AdminTheme/Manifest.cs new file mode 100644 index 000000000..1942fe0c5 --- /dev/null +++ b/Lombiq.Tests.UI.AdminTheme/Manifest.cs @@ -0,0 +1,12 @@ +using OrchardCore.DisplayManagement.Manifest; +using OrchardCore.Modules.Manifest; + +[assembly: Theme( + Name = "Lombiq UI Testing Toolbox - Admin Theme", + Author = "Lombiq Technologies", + Website = "https://github.com/Lombiq/UI-Testing-Toolbox", + Version = "0.0.1", + Description = "Adjustments for the stock Orchard Core admin theme to make it more automation-friendly.", + BaseTheme = "TheAdmin", + Dependencies = ["OrchardCore.Themes", "TheAdmin"], + Tags = [ManifestConstants.AdminTag])] diff --git a/Lombiq.Tests.UI.AdminTheme/NuGetIcon.png b/Lombiq.Tests.UI.AdminTheme/NuGetIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..162a00508d8041833604d427216238c1b23b2d47 GIT binary patch literal 4657 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 literal 0 HcmV?d00001 diff --git a/Lombiq.Tests.UI.AdminTheme/ResourceManagementOptionsConfiguration.cs b/Lombiq.Tests.UI.AdminTheme/ResourceManagementOptionsConfiguration.cs new file mode 100644 index 000000000..58b598ec6 --- /dev/null +++ b/Lombiq.Tests.UI.AdminTheme/ResourceManagementOptionsConfiguration.cs @@ -0,0 +1,12 @@ +using Lombiq.HelpfulLibraries.OrchardCore.ResourceManagement; +using Lombiq.Tests.UI.AdminTheme.Constants; + +namespace Lombiq.Tests.UI.AdminTheme; + +public class ResourceManagementOptionsConfiguration : ResourceManagementOptionsConfiguratorBase +{ + protected override string Area => FeatureIds.Area; + + protected override void Configure(ResourceManagementContext context) => + context.DefineStyle(ResourceNames.General, "general.css"); +} diff --git a/Lombiq.Tests.UI.AdminTheme/Startup.cs b/Lombiq.Tests.UI.AdminTheme/Startup.cs new file mode 100644 index 000000000..758b41ffe --- /dev/null +++ b/Lombiq.Tests.UI.AdminTheme/Startup.cs @@ -0,0 +1,19 @@ +using Lombiq.Tests.UI.AdminTheme.Constants; +using Microsoft.Extensions.DependencyInjection; +using OrchardCore.Modules; + +namespace Lombiq.Tests.UI.AdminTheme; + +public class Startup : StartupBase +{ + public override void ConfigureServices(IServiceCollection services) + { + services.AddResourceFilter( + builder => builder + .Always() + .RegisterStylesheet(ResourceNames.General), + FeatureIds.Area); + + services.AddResourceManagementConfiguration(); + } +} From a350c42cf7da75fd44e13ebdd47a2c2084ce32df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 21 Apr 2026 14:53:59 +0200 Subject: [PATCH 119/132] Add project to SLN. --- Lombiq.UITestingToolbox.sln | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Lombiq.UITestingToolbox.sln b/Lombiq.UITestingToolbox.sln index 1cedefbb0..c534e412c 100644 --- a/Lombiq.UITestingToolbox.sln +++ b/Lombiq.UITestingToolbox.sln @@ -9,6 +9,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lombiq.Tests.UI.AppExtensio EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lombiq.Tests.UI.Shortcuts", "Lombiq.Tests.UI.Shortcuts\Lombiq.Tests.UI.Shortcuts.csproj", "{8D9BE6BF-63D3-4329-803A-DCFE8CA94D54}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{D1D4FDC3-F996-4C90-9CB2-1C46C8A1BA87}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{C48469F5-14FD-42BD-B0C4-2D7C485DEA08}" + ProjectSection(SolutionItems) = preProject + .github\workflows\create-jira-issues-for-community-activities.yml = .github\workflows\create-jira-issues-for-community-activities.yml + .github\workflows\publish-cloudsmith.yml = .github\workflows\publish-cloudsmith.yml + .github\workflows\publish-nuget.yml = .github\workflows\publish-nuget.yml + .github\workflows\validate-nuget-publish.yml = .github\workflows\validate-nuget-publish.yml + .github\workflows\validate-pull-request.yml = .github\workflows\validate-pull-request.yml + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lombiq.Tests.UI.AdminTheme", "Lombiq.Tests.UI.AdminTheme\Lombiq.Tests.UI.AdminTheme.csproj", "{126BE444-6BF9-4605-BF73-A043E9FC73B3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +40,10 @@ Global {8D9BE6BF-63D3-4329-803A-DCFE8CA94D54}.Debug|Any CPU.Build.0 = Debug|Any CPU {8D9BE6BF-63D3-4329-803A-DCFE8CA94D54}.Release|Any CPU.ActiveCfg = Release|Any CPU {8D9BE6BF-63D3-4329-803A-DCFE8CA94D54}.Release|Any CPU.Build.0 = Release|Any CPU + {126BE444-6BF9-4605-BF73-A043E9FC73B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {126BE444-6BF9-4605-BF73-A043E9FC73B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {126BE444-6BF9-4605-BF73-A043E9FC73B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {126BE444-6BF9-4605-BF73-A043E9FC73B3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -34,4 +51,7 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {030E57A3-0EFB-4B35-8587-328927F03BD2} EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {C48469F5-14FD-42BD-B0C4-2D7C485DEA08} = {D1D4FDC3-F996-4C90-9CB2-1C46C8A1BA87} + EndGlobalSection EndGlobal From 34d0d14cfb943289a590b7af68e33dcacd0a3fc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 21 Apr 2026 15:06:01 +0200 Subject: [PATCH 120/132] Add EnableThemeDirectlyAsync --- .../ShortcutsUITestContextExtensions.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Lombiq.Tests.UI/Extensions/ShortcutsUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/ShortcutsUITestContextExtensions.cs index df9b2f2f6..efdd07f7c 100644 --- a/Lombiq.Tests.UI/Extensions/ShortcutsUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/ShortcutsUITestContextExtensions.cs @@ -296,6 +296,26 @@ public static Task DisableFeatureDirectlyAsync( tenant, activateShell); + /// + /// Enables the theme with the given directly. + /// + public static async Task EnableThemeDirectlyAsync( + this UITestContext context, + string themeId, + bool isAdmin = false, + string tenant = null, + bool activateShell = true) + { + await context.EnableFeatureDirectlyAsync(themeId, tenant, activateShell); + await UsingScopeAsync( + context, + serviceProvider => isAdmin + ? serviceProvider.GetRequiredService().SetAdminThemeAsync(themeId) + : serviceProvider.GetRequiredService().SetSiteThemeAsync(themeId), + tenant, + activateShell); + } + /// /// Turns the Lombiq.Tests.UI.Shortcuts.FeatureToggleTestBench feature on, then off, and checks if the /// operations indeed worked. This can be used to test if anything breaks when a feature is enabled or disabled. From 768bac0beb490fc9ee5d828804069af5d5d5fbf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 21 Apr 2026 15:16:19 +0200 Subject: [PATCH 121/132] Add missing files. --- Lombiq.Tests.UI.AdminTheme/wwwroot/Theme.png | Bin 0 -> 38311 bytes .../wwwroot/css/general.css | 5 +++++ 2 files changed, 5 insertions(+) create mode 100644 Lombiq.Tests.UI.AdminTheme/wwwroot/Theme.png create mode 100644 Lombiq.Tests.UI.AdminTheme/wwwroot/css/general.css diff --git a/Lombiq.Tests.UI.AdminTheme/wwwroot/Theme.png b/Lombiq.Tests.UI.AdminTheme/wwwroot/Theme.png new file mode 100644 index 0000000000000000000000000000000000000000..44dfef206c546d571aad450bc8c0bbee49d5d30e GIT binary patch literal 38311 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 Date: Tue, 21 Apr 2026 15:47:48 +0200 Subject: [PATCH 122/132] Remove wait from menu navigation, as it's no longer necessary. --- Lombiq.Tests.UI.AdminTheme/Startup.cs | 3 +-- .../Extensions/NavigationUITestContextExtensions.cs | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Lombiq.Tests.UI.AdminTheme/Startup.cs b/Lombiq.Tests.UI.AdminTheme/Startup.cs index 758b41ffe..5ec0c47c9 100644 --- a/Lombiq.Tests.UI.AdminTheme/Startup.cs +++ b/Lombiq.Tests.UI.AdminTheme/Startup.cs @@ -8,12 +8,11 @@ public class Startup : StartupBase { public override void ConfigureServices(IServiceCollection services) { + services.AddResourceManagementConfiguration(); services.AddResourceFilter( builder => builder .Always() .RegisterStylesheet(ResourceNames.General), FeatureIds.Area); - - services.AddResourceManagementConfiguration(); } } diff --git a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs index 872d31ea3..24c8cda9a 100644 --- a/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs +++ b/Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs @@ -544,8 +544,6 @@ public static async Task ClickThroughAdminMenuAsync(this UITestContext context, // The menus have animations, which interfere with the click being recognized. await context.ClickReliablyOnAsync(selectors[i]); - // It's necessary to wait if a click occurred at this level, because the animation interactions unreliable. - await Task.Delay(menuAnimationTime, context.Configuration.TestCancellationToken); context.Exists(selectors[i + 1]); } From 062268852d9fc8449c55867ede4065f8eab2c019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 21 Apr 2026 16:01:09 +0200 Subject: [PATCH 123/132] Clean up Lombiq.Tests.UI.AdminTheme.csproj --- .../Lombiq.Tests.UI.AdminTheme.csproj | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Lombiq.Tests.UI.AdminTheme/Lombiq.Tests.UI.AdminTheme.csproj b/Lombiq.Tests.UI.AdminTheme/Lombiq.Tests.UI.AdminTheme.csproj index e20e049f3..e7d969470 100644 --- a/Lombiq.Tests.UI.AdminTheme/Lombiq.Tests.UI.AdminTheme.csproj +++ b/Lombiq.Tests.UI.AdminTheme/Lombiq.Tests.UI.AdminTheme.csproj @@ -18,6 +18,11 @@ BSD-3-Clause + + + + + @@ -37,8 +42,4 @@ - - - - From 5f194254fd0afc3568183e51b9eb8d30c93e7522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Wed, 22 Apr 2026 15:00:53 +0200 Subject: [PATCH 124/132] Add missing file-end newline for general.css. --- Lombiq.Tests.UI.AdminTheme/wwwroot/css/general.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Tests.UI.AdminTheme/wwwroot/css/general.css b/Lombiq.Tests.UI.AdminTheme/wwwroot/css/general.css index a82e2d077..f069d07f2 100644 --- a/Lombiq.Tests.UI.AdminTheme/wwwroot/css/general.css +++ b/Lombiq.Tests.UI.AdminTheme/wwwroot/css/general.css @@ -2,4 +2,4 @@ without compromising the test's authenticity. */ * { transition: none !important; /* stylelint-disable-line declaration-no-important */ -} \ No newline at end of file +} From 0beea22d8108547b7fa3a2be99dd8dd647f6d0d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 24 Apr 2026 16:48:26 +0200 Subject: [PATCH 125/132] Revert "Revert "Use SDKs even more."" This reverts commit 5387d67c --- .../Lombiq.Tests.UI.AppExtensions.csproj | 32 +++------- .../Lombiq.Tests.UI.Samples.csproj | 36 ++++------- .../Lombiq.Tests.UI.Shortcuts.csproj | 44 +++----------- .../Lombiq.Tests.UI.TestingModule.csproj | 21 ++----- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 59 +++++-------------- 5 files changed, 46 insertions(+), 146 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index dbc2b7e89..f7aba57b2 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -1,44 +1,28 @@ - net10.0 false - $(DefaultItemExcludes);.git* + + + Lombiq UI Testing Toolbox - App Extensions - Lombiq Technologies - Copyright © 2020, Lombiq Technologies Ltd. - Lombiq UI Testing Toolbox - App Extensions: UI testing-related configuration extensions for the web app under test. See the project website for detailed documentation. - NuGetIcon.png + 2020 + UI testing-related configuration extensions for the web app under test. OrchardCore;Lombiq;AspNetCore;Selenium;Atata;Shouldly;xUnit;Axe;AccessibilityTesting;UITesting;Testing;Automation https://github.com/Lombiq/UI-Testing-Toolbox - - BSD-3-Clause - - - - - - - - - - - - - - - - + + + diff --git a/Lombiq.Tests.UI.Samples/Lombiq.Tests.UI.Samples.csproj b/Lombiq.Tests.UI.Samples/Lombiq.Tests.UI.Samples.csproj index 1341d82fd..a1213ef50 100644 --- a/Lombiq.Tests.UI.Samples/Lombiq.Tests.UI.Samples.csproj +++ b/Lombiq.Tests.UI.Samples/Lombiq.Tests.UI.Samples.csproj @@ -1,11 +1,12 @@ - - + net10.0 false - Exe + + + @@ -14,29 +15,10 @@ - - PreserveNewest - - - PreserveNewest - true - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + @@ -44,4 +26,6 @@ + + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index 4277c661c..593a0b949 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -1,53 +1,25 @@ - - + net10.0 false - true - $(DefaultItemExcludes);.git* + + + Lombiq UI Testing Toolbox - Shortcuts - Lombiq Technologies - Copyright © 2020, Lombiq Technologies Ltd. - Lombiq UI Testing Toolbox - Shortcuts: Provides some useful shortcuts for common operations that UI tests might want to do or check, e.g. turning features on or off, or logging in users. See the project website for detailed documentation. + 2020 + Provides some useful shortcuts for common operations that UI tests might want to do or check, e.g. turning features on or off, or logging in users. OrchardCore;Lombiq;AspNetCore;Selenium;Atata;Shouldly;xUnit;Axe;AccessibilityTesting;UITesting;Testing;Automation - NuGetIcon.png https://github.com/Lombiq/UI-Testing-Toolbox https://github.com/Lombiq/UI-Testing-Toolbox/tree/dev/Lombiq.Tests.UI.Shortcuts - BSD-3-Clause - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj b/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj index 62450ad72..84c13df6b 100644 --- a/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj +++ b/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj @@ -1,23 +1,12 @@ - - + net10.0 false - true - $(DefaultItemExcludes);.git* - - - - - - - - - - - - + + + + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 8f4dbdd09..030696bab 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -1,9 +1,7 @@ - net10.0 false - $(DefaultItemExcludes);.git* true @@ -12,55 +10,26 @@ true + + + Lombiq UI Testing Toolbox for Orchard Core - Lombiq Technologies - Copyright © 2020, Lombiq Technologies Ltd. - Lombiq UI Testing Toolbox for Orchard Core: Web UI testing toolbox mostly for Orchard Core applications. Everything you need to do UI testing with Selenium for an Orchard app is here. See the project website for detailed documentation. + 2020 + Web UI testing toolbox mostly for Orchard Core applications. Everything you need to do UI testing with Selenium for an Orchard app is here. OrchardCore;Lombiq;AspNetCore;Selenium;Atata;Shouldly;xUnit;Axe;AccessibilityTesting;UITesting;Testing;Automation;ZAP;Zed Attack Proxy;Security;Scanning;OWASP - NuGetIcon.png https://github.com/Lombiq/UI-Testing-Toolbox - https://github.com/Lombiq/UI-Testing-Toolbox - BSD-3-Clause - - - - PreserveNewest - - - - - - PreserveNewest - true - - - PreserveNewest - true - - - PreserveNewest - true - - - PreserveNewest - true - - - PreserveNewest - true - - - PreserveNewest - true - - - PreserveNewest - true - + + + + + + + + @@ -146,4 +115,6 @@ + + From ff9a6caeffdb6e03c8fc6416e360203b8b980b47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 24 Apr 2026 17:47:10 +0200 Subject: [PATCH 126/132] Post merge fixes. --- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 030696bab..19fac0bfd 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -53,7 +53,6 @@ - From 57b6ffb7fa28b5a747259a53b1681146c9cff633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Fri, 24 Apr 2026 21:18:52 +0200 Subject: [PATCH 127/132] Use SDK in Admin Theme. --- .../Lombiq.Tests.UI.AdminTheme.csproj | 40 ++++--------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/Lombiq.Tests.UI.AdminTheme/Lombiq.Tests.UI.AdminTheme.csproj b/Lombiq.Tests.UI.AdminTheme/Lombiq.Tests.UI.AdminTheme.csproj index e7d969470..8442b077b 100644 --- a/Lombiq.Tests.UI.AdminTheme/Lombiq.Tests.UI.AdminTheme.csproj +++ b/Lombiq.Tests.UI.AdminTheme/Lombiq.Tests.UI.AdminTheme.csproj @@ -1,45 +1,21 @@ - - + net10.0 - true enable + + + Lombiq UI Testing Toolbox - Admin Theme - Lombiq Technologies - Copyright © 2020, Lombiq Technologies Ltd. - Lombiq UI Testing Toolbox - Admin Theme: Adjustments for the stock Orchard Core admin theme to make it more automation-friendly. + 2020 + Adjustments for the stock Orchard Core admin theme to make it more automation-friendly. OrchardCore;Lombiq;AspNetCore;Selenium;Atata;Shouldly;xUnit;Axe;AccessibilityTesting;UITesting;Testing;Automation - NuGetIcon.png https://github.com/Lombiq/UI-Testing-Toolbox https://github.com/Lombiq/UI-Testing-Toolbox/tree/dev/Lombiq.Tests.UI.AdminTheme - BSD-3-Clause - - - - - - - - - - - - - - - - - - - - - - - - + + From 3b961568caffc596c9f391daade7c5dc4563751d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sat, 25 Apr 2026 01:56:28 +0200 Subject: [PATCH 128/132] Update NuGet and apply library SDK where applicable --- .../Lombiq.Tests.UI.AdminTheme.csproj | 4 ++-- .../Lombiq.Tests.UI.AppExtensions.csproj | 10 +++++----- Lombiq.Tests.UI.Samples/Lombiq.Tests.UI.Samples.csproj | 4 ++-- .../Lombiq.Tests.UI.Shortcuts.csproj | 4 ++-- .../Lombiq.Tests.UI.TestingModule.csproj | 4 ++-- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 10 +++++----- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Lombiq.Tests.UI.AdminTheme/Lombiq.Tests.UI.AdminTheme.csproj b/Lombiq.Tests.UI.AdminTheme/Lombiq.Tests.UI.AdminTheme.csproj index 8442b077b..028445814 100644 --- a/Lombiq.Tests.UI.AdminTheme/Lombiq.Tests.UI.AdminTheme.csproj +++ b/Lombiq.Tests.UI.AdminTheme/Lombiq.Tests.UI.AdminTheme.csproj @@ -5,7 +5,7 @@ - + Lombiq UI Testing Toolbox - Admin Theme @@ -17,5 +17,5 @@ - + diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index f7aba57b2..74525c101 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -1,11 +1,11 @@ - + net10.0 false - - + + Lombiq UI Testing Toolbox - App Extensions @@ -23,6 +23,6 @@ - - + + diff --git a/Lombiq.Tests.UI.Samples/Lombiq.Tests.UI.Samples.csproj b/Lombiq.Tests.UI.Samples/Lombiq.Tests.UI.Samples.csproj index a1213ef50..e525d24e7 100644 --- a/Lombiq.Tests.UI.Samples/Lombiq.Tests.UI.Samples.csproj +++ b/Lombiq.Tests.UI.Samples/Lombiq.Tests.UI.Samples.csproj @@ -5,7 +5,7 @@ - + @@ -27,5 +27,5 @@ - + diff --git a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj index 593a0b949..45c352cd2 100644 --- a/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj +++ b/Lombiq.Tests.UI.Shortcuts/Lombiq.Tests.UI.Shortcuts.csproj @@ -5,7 +5,7 @@ - + Lombiq UI Testing Toolbox - Shortcuts @@ -21,5 +21,5 @@ - + diff --git a/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj b/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj index 84c13df6b..73fe15989 100644 --- a/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj +++ b/Lombiq.Tests.UI.TestingModule/Lombiq.Tests.UI.TestingModule.csproj @@ -5,8 +5,8 @@ - + - + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 19fac0bfd..3abec6eef 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -1,4 +1,4 @@ - + net10.0 false @@ -10,8 +10,8 @@ true - - + + Lombiq UI Testing Toolbox for Orchard Core @@ -114,6 +114,6 @@ - - + + From 32b24dda447929deb7177b75965e789fd0c603d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sat, 25 Apr 2026 02:35:58 +0200 Subject: [PATCH 129/132] Update SDK NuGet. --- .../Lombiq.Tests.UI.AppExtensions.csproj | 4 ++-- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj index 74525c101..0d55de30b 100644 --- a/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj +++ b/Lombiq.Tests.UI.AppExtensions/Lombiq.Tests.UI.AppExtensions.csproj @@ -5,7 +5,7 @@ - + Lombiq UI Testing Toolbox - App Extensions @@ -24,5 +24,5 @@ - + diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index 3abec6eef..b2cf48e52 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -11,7 +11,7 @@ - + Lombiq UI Testing Toolbox for Orchard Core @@ -115,5 +115,5 @@ - + From 8fa553cf7d66b7ccb020e69eb4cac7a8cf2e66c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Mon, 27 Apr 2026 02:07:42 +0200 Subject: [PATCH 130/132] Set YesSql EnableThreadSafetyChecks so we can catch DB concurrency issues. --- Lombiq.Tests.UI/Services/UITestExecutionSession.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Lombiq.Tests.UI/Services/UITestExecutionSession.cs b/Lombiq.Tests.UI/Services/UITestExecutionSession.cs index 7f9413bbc..d85e73b4d 100644 --- a/Lombiq.Tests.UI/Services/UITestExecutionSession.cs +++ b/Lombiq.Tests.UI/Services/UITestExecutionSession.cs @@ -700,6 +700,7 @@ private async Task CreateContextAsync(Uri testStartRelativeUri) Task UITestingBeforeAppStartHandlerAsync(OrchardCoreAppStartContext context, InstanceCommandLineArgumentsBuilder arguments) { + arguments.AddWithValue("OrchardCore:OrchardCore_YesSql:EnableThreadSafetyChecks", value: true); arguments.AddWithValue("Lombiq_Tests_UI:IsUITesting", value: true); arguments.AddWithValue( "Lombiq_Tests_UI:EnableSqlQueryMonitoring", From 4348605a4b49dfab05655cbf6b14435802a15377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Mon, 27 Apr 2026 23:57:04 +0200 Subject: [PATCH 131/132] Update GHA branch selectors. --- .github/workflows/validate-nuget-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate-nuget-publish.yml b/.github/workflows/validate-nuget-publish.yml index 489430861..f0fd6be1e 100644 --- a/.github/workflows/validate-nuget-publish.yml +++ b/.github/workflows/validate-nuget-publish.yml @@ -9,4 +9,4 @@ on: jobs: validate-nuget-publish: name: Validate NuGet Publish - uses: Lombiq/GitHub-Actions/.github/workflows/validate-nuget-publish.yml@issue/OSOE-1199 + uses: Lombiq/GitHub-Actions/.github/workflows/validate-nuget-publish.yml@issue/OSOE-925 From 8fdc274814948795678bd2455f1a40c1d1e4f02f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 28 Apr 2026 12:39:27 +0200 Subject: [PATCH 132/132] Update OC preview. --- Lombiq.Tests.UI/Lombiq.Tests.UI.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj index c033be445..986dbf1e2 100644 --- a/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj +++ b/Lombiq.Tests.UI/Lombiq.Tests.UI.csproj @@ -51,10 +51,10 @@ - - - - + + + +