Skip to content

Commit

Permalink
Changing browser logging to BiDi
Browse files Browse the repository at this point in the history
  • Loading branch information
Piedone committed Dec 30, 2024
1 parent b1d839c commit d521837
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 105 deletions.
8 changes: 3 additions & 5 deletions Lombiq.Tests.UI.Samples/Tests/ErrorHandlingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public Task ClientSideErrorOnLoadedPageShouldHaltTest() =>
catch (PageChangeAssertionException)
{
// Remove browser logs to have a clean slate.
context.ClearHistoricBrowserLog();
context.ClearCumulativeBrowserLog();
}
});

Expand Down Expand Up @@ -89,11 +89,9 @@ public Task BrowserLogsShouldPersist() =>
WriteConsoleLog();
WriteConsoleLog();

await context.UpdateHistoricBrowserLogAsync();

context
.HistoricBrowserLog
.Count(entry => entry.Message.Contains(testLog))
.CumulativeBrowserLog
.Count(entry => entry.Text.Contains(testLog))
.ShouldBe(6);
});

Expand Down
13 changes: 6 additions & 7 deletions Lombiq.Tests.UI.Samples/Tests/MonkeyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
using Lombiq.Tests.UI.MonkeyTesting;
using Lombiq.Tests.UI.MonkeyTesting.UrlFilters;
using Lombiq.Tests.UI.Services;
using OpenQA.Selenium;
using OpenQA.Selenium.BiDi.Modules.Log;
using Shouldly;
using System;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
using LogLevel = OpenQA.Selenium.LogLevel;

namespace Lombiq.Tests.UI.Samples.Tests;

Expand Down Expand Up @@ -96,13 +95,13 @@ public Task TestAdminBackgroundTasksAsMonkeyRecursivelyShouldWorkWithAdminUser()
configuration => configuration.AssertBrowserLog = (logEntries) => logEntries
.Where(logEntry =>
!logEntry
.Message
.Text
.Contains("An invalid form control with name='LockTimeout' is not focusable.")
&& !logEntry
.Message
.Text
.Contains("An invalid form control with name='LockExpiration' is not focusable.")
&& !logEntry.IsNotFoundLogEntry("/favicon.ico")
&& logEntry.Level != LogLevel.Info)
&& logEntry.Level != Level.Info)
.ShouldBeEmpty());

// Monkey testing has its own configuration too. Check out the docs of the options too.
Expand All @@ -112,11 +111,11 @@ private static MonkeyTestingOptions CreateMonkeyTestingOptions() =>
PageTestTime = TimeSpan.FromSeconds(5),
};

private static bool IsValidAdminBrowserLogEntry(LogEntry logEntry) =>
private static bool IsValidAdminBrowserLogEntry(Entry logEntry) =>
OrchardCoreUITestExecutorConfiguration.IsValidBrowserLogEntry(logEntry) &&
// Requests to /api/graphql without further parameters will fail with HTTP 400, but that's OK, since some
// parameters are required.
!logEntry.Message.ContainsOrdinalIgnoreCase("/api/graphql - Failed to load resource: the server responded with a status of 400");
!logEntry.Text.ContainsOrdinalIgnoreCase("/api/graphql - Failed to load resource: the server responded with a status of 400");
}

// END OF TRAINING SECTION: Monkey tests.
Expand Down
21 changes: 0 additions & 21 deletions Lombiq.Tests.UI/Extensions/LoggingWebDriverExtensions.cs

This file was deleted.

8 changes: 4 additions & 4 deletions Lombiq.Tests.UI/Extensions/SeleniumLogEntryExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
using OpenQA.Selenium;
using OpenQA.Selenium.BiDi.Modules.Log;
using System;
using System.Collections.Generic;

namespace Lombiq.Tests.UI.Extensions;

public static class SeleniumLogEntryExtensions
{
public static string ToFormattedString(this IEnumerable<LogEntry> logEntries) =>
public static string ToFormattedString(this IEnumerable<Entry> logEntries) =>
string.Join(Environment.NewLine, logEntries);

public static bool IsNotFoundLogEntry(this LogEntry logEntry, string url) =>
logEntry.Message.ContainsOrdinalIgnoreCase(
public static bool IsNotFoundLogEntry(this Entry logEntry, string url) =>
logEntry.Text.ContainsOrdinalIgnoreCase(
@$"{url} - Failed to load resource: the server responded with a status of 404");
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ public static async Task AssertLogsAsync(this UITestContext context)
var configuration = context.Configuration;
var testOutputHelper = configuration.TestOutputHelper;

if (context.IsBrowserRunning) await context.UpdateHistoricBrowserLogAsync();

try
{
await configuration.AssertAppLogsAsync.InvokeFuncAsync(context.Application);
Expand All @@ -38,12 +36,12 @@ public static async Task AssertLogsAsync(this UITestContext context)
{
try
{
configuration.AssertBrowserLog?.Invoke(context.HistoricBrowserLog);
configuration.AssertBrowserLog?.Invoke(context.CumulativeBrowserLog);
}
catch (Exception)
{
testOutputHelper.WriteLine("Browser logs: " + Environment.NewLine);
testOutputHelper.WriteLine(context.HistoricBrowserLog.ToFormattedString());
testOutputHelper.WriteLine(context.CumulativeBrowserLog.ToFormattedString());

throw;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Lombiq.Tests.UI.Helpers;
using Lombiq.Tests.UI.SecurityScanning;
using Lombiq.Tests.UI.Services.GitHub;
using OpenQA.Selenium;
using OpenQA.Selenium.BiDi.Modules.Log;
using Shouldly;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -36,20 +36,20 @@ public class OrchardCoreUITestExecutorConfiguration
public static readonly Func<IWebApplicationInstance, Task> AssertAppLogsCanContainCacheFolderErrorsAsync =
app => app.LogsShouldNotContainAsync(AppLogAssertionHelper.NotMediaCacheEntriesPredicate);

public static readonly Action<IEnumerable<LogEntry>> AssertBrowserLogIsEmpty =
public static readonly Action<IEnumerable<Entry>> AssertBrowserLogIsEmpty =
logEntries => logEntries.ShouldNotContain(
logEntry => IsValidBrowserLogEntry(logEntry),
logEntries.Where(IsValidBrowserLogEntry).ToFormattedString());

public static readonly Func<LogEntry, bool> IsValidBrowserLogEntry =
logEntry =>
logEntry.Level >= LogLevel.Warning &&
public static readonly Func<Entry, bool> IsValidBrowserLogEntry =
entry =>
entry.Level >= Level.Warn &&
// HTML imports are somehow used by Selenium or something but this deprecation notice is always there for
// every page.
!logEntry.Message.ContainsOrdinalIgnoreCase("HTML Imports is deprecated") &&
!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 different URL.
!logEntry.IsNotFoundLogEntry("/favicon.ico");
!entry.IsNotFoundLogEntry("/favicon.ico");

/// <summary>
/// Gets the global events available during UI test execution.
Expand Down Expand Up @@ -108,7 +108,7 @@ public class OrchardCoreUITestExecutorConfiguration
: Environment.ProcessorCount;

public Func<IWebApplicationInstance, Task> AssertAppLogsAsync { get; set; } = AssertAppLogsCanContainCacheFolderErrorsAsync;
public Action<IEnumerable<LogEntry>> AssertBrowserLog { get; set; } = AssertBrowserLogIsEmpty;
public Action<IEnumerable<Entry>> AssertBrowserLog { get; set; } = AssertBrowserLogIsEmpty;
public ITestOutputHelper TestOutputHelper { get; set; }

/// <summary>
Expand Down
93 changes: 39 additions & 54 deletions Lombiq.Tests.UI/Services/UITestContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using Lombiq.Tests.UI.Models;
using Lombiq.Tests.UI.SecurityScanning;
using OpenQA.Selenium;
using OpenQA.Selenium.BiDi;
using OpenQA.Selenium.BiDi.Modules.Log;
using OrchardCore.Environment.Shell;
using System;
using System.Collections.Generic;
Expand All @@ -15,7 +17,7 @@ namespace Lombiq.Tests.UI.Services;

public class UITestContext
{
private readonly List<LogEntry> _historicBrowserLog = [];
private readonly List<Entry> _cumulativeBrowserLog = [];

/// <summary>
/// Gets the globally unique ID of this context. You can use this ID to refer to the current text execution in
Expand Down Expand Up @@ -103,9 +105,9 @@ public class UITestContext
public ZapManager ZapManager { get; }

/// <summary>
/// Gets a cumulative log of browser console entries.
/// Gets a cumulative log of browser console entries, containing all entries since the start of the test.
/// </summary>
public IReadOnlyList<LogEntry> HistoricBrowserLog => _historicBrowserLog;
public IReadOnlyList<Entry> CumulativeBrowserLog => _cumulativeBrowserLog;

/// <summary>
/// Gets a dictionary storing some custom contextual data.
Expand Down Expand Up @@ -149,7 +151,7 @@ public class UITestContext

// This is a central context object, we need the data to be passed in the constructor.
#pragma warning disable S107 // Methods should not have too many parameters
public UITestContext(
private UITestContext(
string id,
UITestManifest testManifest,
OrchardCoreUITestExecutorConfiguration configuration,
Expand All @@ -173,64 +175,18 @@ public UITestContext(
}

/// <summary>
/// Updates <see cref="HistoricBrowserLog"/> with current console entries from the browser.
/// Clears accumulated browser log messages from <see cref="CumulativeBrowserLog"/>.
/// </summary>
public Task<IReadOnlyList<LogEntry>> UpdateHistoricBrowserLogAsync()
{
var windowHandles = Driver.WindowHandles;

if (windowHandles.Count > 1)
{
var currentWindowHandle = Driver.CurrentWindowHandle;

foreach (var windowHandle in windowHandles)
{
// Not using the logging SwitchTo() deliberately as this is not part of what the test does.
Driver.SwitchTo().Window(windowHandle);
_historicBrowserLog.AddRange(Driver.GetAndEmptyBrowserLog());
}

try
{
Driver.SwitchTo().Window(currentWindowHandle);
}
catch (NoSuchWindowException)
{
// This can happen in rare instances if the current window/tab was just closed.
Driver.SwitchTo().Window(Driver.WindowHandles[^1]);
}
}
else
{
_historicBrowserLog.AddRange(Driver.GetAndEmptyBrowserLog());
}

return Task.FromResult<IReadOnlyList<LogEntry>>(_historicBrowserLog);
}

/// <summary>
/// Clears accumulated historic browser log messages from <see cref="HistoricBrowserLog"/>.
/// </summary>
public void ClearHistoricBrowserLog() => _historicBrowserLog.Clear();

/// <summary>
/// Run an assertion on the browser logs of the current tab with the delegate configured in <see
/// cref="Configuration"/>. This doesn't use <see cref="HistoricBrowserLog"/>.
/// </summary>
public Task AssertCurrentBrowserLogAsync()
{
Configuration.AssertBrowserLog?.Invoke(Scope.Driver.GetAndEmptyBrowserLog());
return Task.CompletedTask;
}
public void ClearCumulativeBrowserLog() => _cumulativeBrowserLog.Clear();

/// <summary>
/// Clears the application and historic browser logs.
/// Clears the application and cumulative browser logs.
/// </summary>
/// <param name="cancellationToken">Optional cancellation token for reading the application logs.</param>
public async Task ClearLogsAsync(CancellationToken cancellationToken = default)
{
foreach (var log in await Application.GetLogsAsync(cancellationToken)) await log.RemoveAsync();
ClearHistoricBrowserLog();
ClearCumulativeBrowserLog();
}

/// <summary>
Expand Down Expand Up @@ -305,6 +261,35 @@ public void SwitchCurrentTenant(string tenantName, Uri baseUri)
Scope.BaseUri = baseUri;
}

// This is a central context object, we need the data to be passed in the constructor.
#pragma warning disable S107 // Methods should not have too many parameters
public static async Task<UITestContext> CreateAsync(
string id,
UITestManifest testManifest,
OrchardCoreUITestExecutorConfiguration configuration,
IWebApplicationInstance application,
AtataScope scope,
Uri testStartUri,
RunningContextContainer runningContextContainer,
ZapManager zapManager)
#pragma warning restore S107 // Methods should not have too many parameters
{
var context = new UITestContext(
id,
testManifest,
configuration,
application,
scope,
testStartUri,
runningContextContainer,
zapManager);

var biDi = await scope.Driver.AsBiDiAsync();
await biDi.Log.OnEntryAddedAsync(context._cumulativeBrowserLog.Add);

return context;
}

/// <summary>
/// Returns the subdirectory described by <paramref name="subDirectoryNames"/> inside the current test instance's
/// <see cref="DirectoryPaths.Temp"/> directory.
Expand Down
4 changes: 2 additions & 2 deletions Lombiq.Tests.UI/Services/UITestExecutionSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,7 @@ Task UITestingBeforeAppStartHandlerAsync(OrchardCoreAppStartContext context, Ins

var atataScope = await AtataFactory.StartAtataScopeAsync(contextId, _testOutputHelper, appBaseUri, _configuration);

return new UITestContext(
return await UITestContext.CreateAsync(
contextId,
_testManifest,
_configuration,
Expand Down Expand Up @@ -814,7 +814,7 @@ private async Task CaptureBrowserUsingDumpsAsync(string debugInformationPath)

await File.WriteAllLinesAsync(
browserLogPath,
(await _context.UpdateHistoricBrowserLogAsync()).Select(message => message.ToString()));
_context.CumulativeBrowserLog.Select(entry => entry.ToString()));

if (_configuration.ReportTeamCityMetadata)
{
Expand Down
1 change: 1 addition & 0 deletions Lombiq.Tests.UI/Services/WebDriverFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ private static TDriverOptions SetCommonOptions<TDriverOptions>(this TDriverOptio
driverOptions.AcceptInsecureCertificates = true;
driverOptions.UnhandledPromptBehavior = UnhandledPromptBehavior.Ignore;
driverOptions.PageLoadStrategy = PageLoadStrategy.Normal;
driverOptions.UseWebSocketUrl = true;
return driverOptions;
}

Expand Down

0 comments on commit d521837

Please sign in to comment.