Skip to content

Commit

Permalink
Merge pull request #455 from Lombiq/issue/OFFI-134
Browse files Browse the repository at this point in the history
OFFI-134: Add IMAP-specific functionality
  • Loading branch information
sarahelsaig authored Feb 18, 2025
2 parents 78df079 + 49768b9 commit 8c2c3bf
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 16 deletions.
9 changes: 8 additions & 1 deletion Lombiq.Tests.UI/CompatibilitySuppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -421,4 +421,11 @@
<Right>lib/net8.0/Lombiq.Tests.UI.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
</Suppressions>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Lombiq.Tests.UI.Services.SmtpServiceRunningContext.#ctor(System.Int32,System.Uri)</Target>
<Left>lib/net8.0/Lombiq.Tests.UI.dll</Left>
<Right>lib/net8.0/Lombiq.Tests.UI.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
</Suppressions>
2 changes: 1 addition & 1 deletion Lombiq.Tests.UI/Docs/Tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
- There are multiple recording tools available for Selenium but the "official" one which works pretty well is [Selenium IDE](https://www.selenium.dev/selenium-ide/) (which is a Chrome/Firefox extension). To fine-tune XPath queries and CSS selectors and also to record tests check out [ChroPath](https://chrome.google.com/webstore/detail/chropath/ljngjbnaijcbncmcnjfhigebomdlkcjo/) (the [Xpath cheat sheet](https://devhints.io/xpath) is a great resource too, and [XmlToolBox](https://xmltoolbox.appspot.com/xpath_generator.html) can help you with quick XPath queries).
- Accessibility checking can be done with [axe](https://github.com/dequelabs/axe-core) via [Selenium.Axe for .NET](https://github.com/TroyWalshProf/SeleniumAxeDotnet).
- HTML markup validation can be done with [html-validate](https://gitlab.com/html-validate/html-validate) via [Atata.HtmlValidation](https://github.com/atata-framework/atata-htmlvalidation).
- When testing e-mail sending, we use [smtp4dev](https://github.com/rnwood/smtp4dev) as a local SMTP server.
- When testing e-mail sending, we use [smtp4dev](https://github.com/rnwood/smtp4dev) as a local SMTP server (as well as an IMAP server with basic operations).
- Monkey testing is implemented using [Gremlins.js](https://github.com/marmelab/gremlins.js/) library.
- Visual verification is implemented using [ImageSharpCompare](https://github.com/Codeuctivity/ImageSharp.Compare).
- [Ben.Demystifier](https://github.com/benaadams/Ben.Demystifier) is used to simplify stack traces, mainly around async methods.
Expand Down
65 changes: 58 additions & 7 deletions Lombiq.Tests.UI/Extensions/EmailUITestContextExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Atata;
using Lombiq.Tests.UI.Helpers;
using Lombiq.Tests.UI.Services;
using MailKit.Net.Smtp;
using MimeKit;
using OpenQA.Selenium;
using Shouldly;
using System;
Expand All @@ -17,13 +19,7 @@ public static class EmailUITestContextExtensions
/// <exception cref="InvalidOperationException">Thrown if the smtp4dev server is not running.</exception>
public static async Task GoToSmtpWebUIAsync(this UITestContext context)
{
if (context.SmtpServiceRunningContext == null)
{
throw new InvalidOperationException(
"The SMTP service is not running. Did you turn it on with " +
nameof(OrchardCoreUITestExecutorConfiguration) + "." + nameof(OrchardCoreUITestExecutorConfiguration.UseSmtpService) +
" and could it properly start?");
}
ThrowIfSmtpServiceIsNotRunning(context);

await context.GoToAbsoluteUrlAsync(context.SmtpServiceRunningContext.WebUIUri);

Expand Down Expand Up @@ -202,4 +198,59 @@ public static async Task ClickReliablyOnSmtpInboxRowAndSwitchToFrame0WithRetries
}
}
}

/// <summary>
/// Creates an <see cref="SmtpClient"/> and runs the provided <paramref name="action"/> with it. The client is
/// automatically connected to the SMTP server running in the UI testing context. The client is disconnected after
/// the action is done.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if the smtp4dev server is not running.</exception>
public static async Task CreateAndUseLocalSmtpClientAsync(this UITestContext context, Func<SmtpClient, Task> action)
{
ThrowIfSmtpServiceIsNotRunning(context);

var client = new SmtpClient();
await client.ConnectAsync(
context.SmtpServiceRunningContext.Host,
context.SmtpServiceRunningContext.Port,
useSsl: false,
context.Configuration.TestCancellationToken);

try
{
await action(client);
}
finally
{
await client.DisconnectAsync(quit: true, context.Configuration.TestCancellationToken);
client.Dispose();
}
}

/// <summary>
/// Creates an <see cref="SmtpClient"/> and sends emails from the provided files with it. The client is
/// automatically connected to the SMTP server running in the UI testing context. The client is disconnected after
/// the action is done.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if the smtp4dev server is not running.</exception>
public static Task CreateAndUseLocalSmtpClientToSendEmailsFromFilesAsync(this UITestContext context, string[] emailFiles) =>
CreateAndUseLocalSmtpClientAsync(context, async client =>
{
foreach (var emailFile in emailFiles)
{
var mimeMessage = await MimeMessage.LoadAsync(emailFile, context.Configuration.TestCancellationToken);
await client.SendAsync(mimeMessage, context.Configuration.TestCancellationToken);
}
});

private static void ThrowIfSmtpServiceIsNotRunning(UITestContext context)
{
if (context.SmtpServiceRunningContext == null)
{
throw new InvalidOperationException(
"The SMTP service is not running. Did you turn it on with " +
nameof(OrchardCoreUITestExecutorConfiguration) + "." + nameof(OrchardCoreUITestExecutorConfiguration.UseSmtpService) +
" and could it properly start?");
}
}
}
1 change: 1 addition & 0 deletions Lombiq.Tests.UI/Lombiq.Tests.UI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
<PackageReference Include="Codeuctivity.ImageSharpCompare" Version="4.0.298" />
<PackageReference Include="Deque.AxeCore.Commons" Version="4.10.1" />
<PackageReference Include="Deque.AxeCore.Selenium" Version="4.10.1" />
<PackageReference Include="MailKit" Version="4.9.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.Testing" Version="8.10.0" />
Expand Down
14 changes: 10 additions & 4 deletions Lombiq.Tests.UI/Services/SmtpService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ public class SmtpServiceConfiguration
public class SmtpServiceRunningContext
{
public int Port { get; }
public string Host => "localhost:" + Port.ToTechnicalString();
public int ImapPort { get; set; }
public string Host => "localhost";
public Uri WebUIUri { get; }

public SmtpServiceRunningContext(int port, Uri webUIUri)
public SmtpServiceRunningContext(int port, int imapPort, Uri webUIUri)
{
Port = port;
ImapPort = imapPort;
WebUIUri = webUIUri;
}
}
Expand All @@ -31,6 +33,7 @@ public sealed class SmtpService : IAsyncDisposable
{
private static readonly PortLeaseManager _smtpPortLeaseManager;
private static readonly PortLeaseManager _webUIPortLeaseManager;
private static readonly PortLeaseManager _imapPortLeaseManager;
private static readonly SemaphoreSlim _restoreSemaphore = new(1, 1);

private readonly SmtpServiceConfiguration _configuration;
Expand All @@ -40,13 +43,15 @@ public sealed class SmtpService : IAsyncDisposable

private int _smtpPort;
private int _webUIPort;
private int _imapPort;
private bool _isDisposed;

static SmtpService()
{
var agentIndexTimesHundred = TestConfigurationManager.GetAgentIndexOrDefault() * 100;
_smtpPortLeaseManager = new PortLeaseManager(11000 + agentIndexTimesHundred, 11099 + agentIndexTimesHundred);
_webUIPortLeaseManager = new PortLeaseManager(12000 + agentIndexTimesHundred, 12099 + agentIndexTimesHundred);
_imapPortLeaseManager = new PortLeaseManager(16000 + agentIndexTimesHundred, 16099 + agentIndexTimesHundred);
}

public SmtpService(SmtpServiceConfiguration configuration) => _configuration = configuration;
Expand Down Expand Up @@ -76,6 +81,7 @@ public async Task<SmtpServiceRunningContext> StartAsync()

_smtpPort = await _smtpPortLeaseManager.LeaseAvailableRandomPortAsync(token);
_webUIPort = await _webUIPortLeaseManager.LeaseAvailableRandomPortAsync(token);
_imapPort = await _imapPortLeaseManager.LeaseAvailableRandomPortAsync(token);

var webUIPortString = _webUIPort.ToTechnicalString();
var smtpPortString = _smtpPort.ToTechnicalString();
Expand Down Expand Up @@ -104,7 +110,7 @@ public async Task<SmtpServiceRunningContext> StartAsync()
// An empty db parameter means an in-memory DB. For all possible command line arguments see:
// https://github.com/rnwood/smtp4dev/blob/master/Rnwood.Smtp4dev/Program.cs#L132.
await CliProgram.DotNet
.GetCommand("tool", "run", "smtp4dev", "--db", string.Empty, "--smtpport", _smtpPort, "--urls", webUIUri)
.GetCommand("tool", "run", "smtp4dev", "--db", string.Empty, "--smtpport", _smtpPort, "--imapport", _imapPort, "--urls", webUIUri)
.WithEnvironmentVariables(new Dictionary<string, string>
{
["ServerOptions__DisableMessageSanitisation"] = "true",
Expand All @@ -117,7 +123,7 @@ await CliProgram.DotNet
$"{webUIPortString} due to the following error:{Environment.NewLine}{stdErr.Text}"),
token);

return new SmtpServiceRunningContext(_smtpPort, webUIUri);
return new SmtpServiceRunningContext(_smtpPort, _imapPort, webUIUri);
}

public async ValueTask DisposeAsync()
Expand Down
9 changes: 6 additions & 3 deletions Lombiq.Tests.UI/Services/UITestContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -325,10 +325,13 @@ await biDi.Log.OnEntryAddedAsync(entry =>
if (configuration.BrowserLogFilter(entry)) context._cumulativeBrowserLog.Enqueue(entry);
});

await biDi.Network.OnResponseCompletedAsync(responseCompleted =>
if (configuration.TestDumpConfiguration.CaptureResponseLog)
{
if (configuration.ResponseLogFilter(responseCompleted)) context._cumulativeResponseLog.Enqueue(responseCompleted.Response);
});
await biDi.Network.OnResponseCompletedAsync(responseCompleted =>
{
if (configuration.ResponseLogFilter(responseCompleted)) context._cumulativeResponseLog.Enqueue(responseCompleted.Response);
});
}
}

return context;
Expand Down

0 comments on commit 8c2c3bf

Please sign in to comment.