Add E2E test for unlock mechanism (#60)

This commit is contained in:
Leendert de Borst
2024-06-25 23:26:54 +02:00
parent 75933efbdd
commit 2bee131ff4
5 changed files with 173 additions and 86 deletions

View File

@@ -0,0 +1,113 @@
//-----------------------------------------------------------------------
// <copyright file="PlaywrightInputHelper.cs" company="lanedirt">
// Copyright (c) lanedirt. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------
namespace AliasVault.E2ETests.Common;
/// <summary>
/// Playwright input helper class.
/// </summary>
/// <param name="page">The IPage instance for the current test.</param>
public class PlaywrightInputHelper(IPage page)
{
private static readonly Random Random = new();
/// <summary>
/// Generate a random string of specified length.
/// </summary>
/// <param name="length">Length of the string.</param>
/// <returns>The random generated string.</returns>
public static string GenerateRandomString(int length = 10)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[Random.Next(s.Length)]).ToArray());
}
/// <summary>
/// Generate a random email address.
/// </summary>
/// <returns>The random email address.</returns>
public static string GenerateRandomEmail()
{
return $"{GenerateRandomString(5)}@example.com";
}
/// <summary>
/// Generate a random number.
/// </summary>
/// <returns>The random number.</returns>
public static string GenerateRandomNumber()
{
return Random.Next(0, 10000).ToString();
}
/// <summary>
/// Generate a random password of specified length.
/// </summary>
/// <param name="length">The length of the password.</param>
/// <returns>The random generated password.</returns>
public static string GenerateRandomPassword(int length = 12)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[Random.Next(s.Length)]).ToArray());
}
/// <summary>
/// Helper method to fill specified input fields on a page with given values.
/// </summary>
/// <param name="fieldValues">Dictionary with html element ids and values to input as field value.</param>
/// <returns>Async task.</returns>
public async Task FillInputFields(Dictionary<string, string>? fieldValues = null)
{
var inputFields = page.Locator("input");
var count = await inputFields.CountAsync();
for (int i = 0; i < count; i++)
{
var input = inputFields.Nth(i);
var inputId = await input.GetAttributeAsync("id");
// If fieldValues dictionary is provided and the inputId is found in it, fill the input with the value.
if (inputId is not null && fieldValues is not null && fieldValues.TryGetValue(inputId, out var fieldValue))
{
await input.FillAsync(fieldValue);
}
}
}
/// <summary>
/// Helper method to fill all empty input fields on a page with random data if not provided.
/// </summary>
/// <returns>Async task.</returns>
public async Task FillEmptyInputFieldsWithRandom()
{
var inputFields = page.Locator("input");
var count = await inputFields.CountAsync();
for (int i = 0; i < count; i++)
{
var input = inputFields.Nth(i);
var inputType = await input.GetAttributeAsync("type");
// If is not empty, skip.
if (!string.IsNullOrEmpty(await input.InputValueAsync()))
{
continue;
}
// Generate appropriate random data based on input type.
string randomData = inputType switch
{
"email" => GenerateRandomEmail(),
"number" => GenerateRandomNumber(),
"password" => GenerateRandomPassword(),
_ => GenerateRandomString(), // Default for all other types.
};
await input.FillAsync(randomData);
}
}
}

View File

@@ -54,6 +54,11 @@ public class PlaywrightTest
/// </summary>
protected IPage Page { get; private set; }
/// <summary>
/// Gets the input helper for Playwright tests.
/// </summary>
protected PlaywrightInputHelper InputHelper { get; private set; } = null!;
/// <summary>
/// One time setup for the Playwright test which runs before all tests in the class.
/// </summary>
@@ -97,6 +102,7 @@ public class PlaywrightTest
});
Page = await Context.NewPageAsync();
InputHelper = new(Page);
// Register a new account via the UI
await Register();

View File

@@ -5,7 +5,7 @@
// </copyright>
//-----------------------------------------------------------------------
namespace AliasVault.E2ETests;
namespace AliasVault.E2ETests.Tests;
/// <summary>
/// End-to-end tests for the alias management.
@@ -75,8 +75,7 @@ public class AliasTests : PlaywrightTest
// Replace the service name with "Alias service after".
var serviceNameAfter = "Alias service after";
await FillInputFields(
page: Page,
await InputHelper.FillInputFields(
fieldValues: new Dictionary<string, string>
{
{ "service-name", serviceNameAfter },
@@ -91,86 +90,6 @@ public class AliasTests : PlaywrightTest
Assert.That(pageContent, Does.Contain(serviceNameAfter), "Alias not updated correctly.");
}
/// <summary>
/// Helper method to fill specified input fields on a page with given values.
/// </summary>
/// <param name="page">IPage instance where to fill the input fields for.</param>
/// <param name="fieldValues">Dictionary with html element ids and values to input as field value.</param>
/// <returns>Async task.</returns>
private static async Task FillInputFields(IPage page, Dictionary<string, string>? fieldValues = null)
{
var inputFields = page.Locator("input");
var count = await inputFields.CountAsync();
for (int i = 0; i < count; i++)
{
var input = inputFields.Nth(i);
var inputId = await input.GetAttributeAsync("id");
// If fieldValues dictionary is provided and the inputId is found in it, fill the input with the value.
if (inputId is not null && fieldValues is not null && fieldValues.TryGetValue(inputId, out var fieldValue))
{
await input.FillAsync(fieldValue);
}
}
}
/// <summary>
/// Helper method to fill all empty input fields on a page with random data if not provided.
/// </summary>
/// <param name="page">IPage instance where to fill the input fields for.</param>
/// <returns>Async task.</returns>
private static async Task FillEmptyInputFieldsWithRandom(IPage page)
{
var inputFields = page.Locator("input");
var count = await inputFields.CountAsync();
for (int i = 0; i < count; i++)
{
var input = inputFields.Nth(i);
var inputType = await input.GetAttributeAsync("type");
// If is not empty, skip.
if (!string.IsNullOrEmpty(await input.InputValueAsync()))
{
continue;
}
// Generate appropriate random data based on input type.
string randomData = inputType switch
{
"email" => GenerateRandomEmail(),
"number" => GenerateRandomNumber(),
"password" => GenerateRandomPassword(),
_ => GenerateRandomString(), // Default for all other types.
};
await input.FillAsync(randomData);
}
}
private static string GenerateRandomString(int length = 10)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[Random.Next(s.Length)]).ToArray());
}
private static string GenerateRandomEmail()
{
return $"{GenerateRandomString(5)}@example.com";
}
private static string GenerateRandomNumber()
{
return Random.Next(0, 10000).ToString();
}
private static string GenerateRandomPassword(int length = 12)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[Random.Next(s.Length)]).ToArray());
}
/// <summary>
/// Create new alias.
/// </summary>
@@ -186,8 +105,8 @@ public class AliasTests : PlaywrightTest
Assert.That(generateButton, Is.Not.Null, "Generate button not found.");
// Fill all input fields with specified values and remaining empty fields with random data.
await FillInputFields(Page, formValues);
await FillEmptyInputFieldsWithRandom(Page);
await InputHelper.FillInputFields(formValues);
await InputHelper.FillEmptyInputFieldsWithRandom();
var submitButton = Page.Locator("text=Save Alias").First;
await submitButton.ClickAsync();

View File

@@ -5,7 +5,7 @@
// </copyright>
//-----------------------------------------------------------------------
namespace AliasVault.E2ETests;
namespace AliasVault.E2ETests.Tests;
/// <summary>
/// End-to-end tests for authentication.

View File

@@ -0,0 +1,49 @@
//-----------------------------------------------------------------------
// <copyright file="UnlockTests.cs" company="lanedirt">
// Copyright (c) lanedirt. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------
namespace AliasVault.E2ETests.Tests;
/// <summary>
/// End-to-end tests for the database unlock functionality.
/// </summary>
[Parallelizable(ParallelScope.Self)]
[TestFixture]
public class UnlockTests : PlaywrightTest
{
private static readonly Random Random = new();
/// <summary>
/// Test that the unlock page is displayed after hard refresh which should
/// clear the encryption key from memory.
/// </summary>
/// <returns>Async task.</returns>
[Test]
public async Task UnlockPageTest()
{
// Soft navigate to "aliases" page to test the unlock redirect.
var startUrl = "aliases";
await NavigateUsingBlazorRouter(startUrl);
// Hard refresh the page.
await Page.ReloadAsync();
// Check if the unlock page is displayed.
await WaitForURLAsync("**/unlock", "unlock");
// Check if by entering password the unlock page is replaced by the alias listing page.
await InputHelper.FillInputFields(new Dictionary<string, string>
{
{ "password", TestUserPassword },
});
var submitButton = Page.GetByRole(AriaRole.Button, new() { Name = "Unlock" });
await submitButton.ClickAsync();
// Check if we get redirected back to the page we were trying to access.
await WaitForURLAsync("**/" + startUrl, "AliasVault");
}
}