Merge pull request #20 from lanedirt/17-add-e2e-test-for-alias-edit-page

Add E2E test for AddEdit page
This commit is contained in:
Leendert de Borst
2024-06-17 05:22:11 -07:00
committed by GitHub
6 changed files with 173 additions and 87 deletions

View File

@@ -2,12 +2,18 @@
@inject ClipboardCopyService ClipboardCopyService
@inject IJSRuntime JsRuntime
<label for="@_inputId" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Label</label>
<label for="@Id" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Label</label>
<div class="relative">
<input type="text" id="@_inputId" class="outline-0 shadow-sm bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg block w-full p-2.5 pr-10 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white" value="@Value" @oninput="OnInputChanged">
<input type="text" id="@Id" class="outline-0 shadow-sm bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg block w-full p-2.5 pr-10 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white" value="@Value" @oninput="OnInputChanged">
</div>
@code {
/// <summary>
/// Id for the input field.
/// </summary>
[Parameter]
public string Id { get; set; } = string.Empty;
/// <summary>
/// Label for the input field.
/// </summary>
@@ -26,8 +32,6 @@
[Parameter]
public EventCallback<string?> ValueChanged { get; set; }
private string _inputId = Guid.NewGuid().ToString();
private async Task OnInputChanged(ChangeEventArgs e)
{
Value = e.Value?.ToString() ?? string.Empty;

View File

@@ -51,10 +51,10 @@ else
<h3 class="mb-4 text-xl font-semibold dark:text-white">Service</h3>
<div class="grid gap-6">
<div class="col-span-6 sm:col-span-3">
<EditFormRow Label="Service Name" Value="@(Obj.Service.Name)" ValueChanged="@(val => Obj.Service.Name = val)"></EditFormRow>
<EditFormRow Id="service-name" Label="Service Name" Value="@(Obj.Service.Name)" ValueChanged="@(val => Obj.Service.Name = val)"></EditFormRow>
</div>
<div class="col-span-6 sm:col-span-3">
<EditFormRow Label="Service URL" Value="@(Obj.Service.Url)" ValueChanged="@(val => Obj.Service.Url = val)"></EditFormRow>
<EditFormRow Id="service-url" Label="Service URL" Value="@(Obj.Service.Url)" ValueChanged="@(val => Obj.Service.Url = val)"></EditFormRow>
</div>
</div>
</div>
@@ -72,14 +72,14 @@ else
</div>
<div class="grid gap-6">
<div class="col-span-6 sm:col-span-3">
<EditFormRow Label="Email" Value="@(Obj.Identity.EmailPrefix)" ValueChanged="@(val => Obj.Identity.EmailPrefix = val)"></EditFormRow>
<EditFormRow Id="email" Label="Email" Value="@(Obj.Identity.EmailPrefix)" ValueChanged="@(val => Obj.Identity.EmailPrefix = val)"></EditFormRow>
</div>
<div class="col-span-6 sm:col-span-3">
<EditFormRow Label="Username" Value="@(Obj.Identity.NickName)" ValueChanged="@(val => Obj.Identity.NickName = val)"></EditFormRow>
<EditFormRow Id="username" Label="Username" Value="@(Obj.Identity.NickName)" ValueChanged="@(val => Obj.Identity.NickName = val)"></EditFormRow>
</div>
<div class="col-span-6 sm:col-span-3">
<div class="relative">
<EditFormRow Label="Password" Value="@(Obj.Password.Value)" ValueChanged="@(val => Obj.Password.Value = val)"></EditFormRow>
<EditFormRow Id="password" Label="Password" Value="@(Obj.Password.Value)" ValueChanged="@(val => Obj.Password.Value = val)"></EditFormRow>
<button type="submit" class="text-white absolute end-1 bottom-1 bg-gray-700 hover:bg-gray-800 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-lg text-sm px-4 py-2 dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800" @onclick="GenerateRandomPassword">(Re)generate Random Password</button>
</div>
</div>
@@ -94,43 +94,43 @@ else
<h3 class="mb-4 text-xl font-semibold dark:text-white">Identity</h3>
<div class="grid gap-6">
<div class="col-span-6 sm:col-span-3">
<EditFormRow Label="First Name" Value="@(Obj.Identity.FirstName)" ValueChanged="@(val => Obj.Identity.FirstName = val)"></EditFormRow>
<EditFormRow Id="first-name" Label="First Name" Value="@(Obj.Identity.FirstName)" ValueChanged="@(val => Obj.Identity.FirstName = val)"></EditFormRow>
</div>
<div class="col-span-6 sm:col-span-3">
<EditFormRow Label="Last Name" Value="@(Obj.Identity.LastName)" ValueChanged="@(val => Obj.Identity.LastName = val)"></EditFormRow>
<EditFormRow Id="last-name" Label="Last Name" Value="@(Obj.Identity.LastName)" ValueChanged="@(val => Obj.Identity.LastName = val)"></EditFormRow>
</div>
<div class="col-span-6 sm:col-span-3">
<EditFormRow Label="Gender" Value="@(Obj.Identity.Gender)" ValueChanged="@(val => Obj.Identity.Gender = val)"></EditFormRow>
<EditFormRow Id="gender" Label="Gender" Value="@(Obj.Identity.Gender)" ValueChanged="@(val => Obj.Identity.Gender = val)"></EditFormRow>
</div>
<div class="col-span-6 sm:col-span-3">
<EditFormRow Label="Nick Name" Value="@(Obj.Identity.NickName)" ValueChanged="@(val => Obj.Identity.NickName = val)"></EditFormRow>
<EditFormRow Id="nickname" Label="Nick Name" Value="@(Obj.Identity.NickName)" ValueChanged="@(val => Obj.Identity.NickName = val)"></EditFormRow>
</div>
<div class="col-span-6 sm:col-span-3">
<EditFormRow Label="Birth Date" Value="@(Obj.Identity.BirthDate)" ValueChanged="@(val => Obj.Identity.BirthDate = val)"></EditFormRow>
<EditFormRow Id="birthdate" Label="Birth Date" Value="@(Obj.Identity.BirthDate)" ValueChanged="@(val => Obj.Identity.BirthDate = val)"></EditFormRow>
</div>
<div class="col-span-6 sm:col-span-3">
<EditFormRow Label="Address Street" Value="@(Obj.Identity.AddressStreet)" ValueChanged="@(val => Obj.Identity.AddressStreet = val)"></EditFormRow>
<EditFormRow Id="street" Label="Address Street" Value="@(Obj.Identity.AddressStreet)" ValueChanged="@(val => Obj.Identity.AddressStreet = val)"></EditFormRow>
</div>
<div class="col-span-6 sm:col-span-3">
<EditFormRow Label="Address City" Value="@(Obj.Identity.AddressCity)" ValueChanged="@(val => Obj.Identity.AddressCity = val)"></EditFormRow>
<EditFormRow Id="city" Label="Address City" Value="@(Obj.Identity.AddressCity)" ValueChanged="@(val => Obj.Identity.AddressCity = val)"></EditFormRow>
</div>
<div class="col-span-6 sm:col-span-3">
<EditFormRow Label="Address State" Value="@(Obj.Identity.AddressState)" ValueChanged="@(val => Obj.Identity.AddressState = val)"></EditFormRow>
<EditFormRow Id="state" Label="Address State" Value="@(Obj.Identity.AddressState)" ValueChanged="@(val => Obj.Identity.AddressState = val)"></EditFormRow>
</div>
<div class="col-span-6 sm:col-span-3">
<EditFormRow Label="Address Zip Code" Value="@(Obj.Identity.AddressZipCode)" ValueChanged="@(val => Obj.Identity.AddressZipCode = val)"></EditFormRow>
<EditFormRow Id="zipcode" Label="Address Zip Code" Value="@(Obj.Identity.AddressZipCode)" ValueChanged="@(val => Obj.Identity.AddressZipCode = val)"></EditFormRow>
</div>
<div class="col-span-6 sm:col-span-3">
<EditFormRow Label="Address Country" Value="@(Obj.Identity.AddressCountry)" ValueChanged="@(val => Obj.Identity.AddressCountry = val)"></EditFormRow>
<EditFormRow Id="country" Label="Address Country" Value="@(Obj.Identity.AddressCountry)" ValueChanged="@(val => Obj.Identity.AddressCountry = val)"></EditFormRow>
</div>
<div class="col-span-6 sm:col-span-3">
<EditFormRow Label="Hobbies" Value="@(Obj.Identity.Hobbies)" ValueChanged="@(val => Obj.Identity.Hobbies = val)"></EditFormRow>
<EditFormRow Id="hobbies" Label="Hobbies" Value="@(Obj.Identity.Hobbies)" ValueChanged="@(val => Obj.Identity.Hobbies = val)"></EditFormRow>
</div>
<div class="col-span-6 sm:col-span-3">
<EditFormRow Label="Phone Mobile" Value="@(Obj.Identity.PhoneMobile)" ValueChanged="@(val => Obj.Identity.PhoneMobile = val)"></EditFormRow>
<EditFormRow Id="phone-mobile" Label="Phone Mobile" Value="@(Obj.Identity.PhoneMobile)" ValueChanged="@(val => Obj.Identity.PhoneMobile = val)"></EditFormRow>
</div>
<div class="col-span-6 sm:col-span-3">
<EditFormRow Label="Bank Account IBAN" Value="@(Obj.Identity.BankAccountIBAN)" ValueChanged="@(val => Obj.Identity.BankAccountIBAN = val)"></EditFormRow>
<EditFormRow Id="iban" Label="Bank Account IBAN" Value="@(Obj.Identity.BankAccountIBAN)" ValueChanged="@(val => Obj.Identity.BankAccountIBAN = val)"></EditFormRow>
</div>
</div>
</div>

View File

@@ -16,50 +16,15 @@ public class AliasTests : PlaywrightTest
{
private static readonly Random Random = new();
/// <summary>
/// Helper method to fill all input fields on a page with random data.
/// </summary>
/// <param name="page">IPage instance where to fill the input fields for.</param>
/// <returns>Async task.</returns>
public static async Task FillAllInputFields(IPage page)
{
// Locate all input fields
var inputFields = page.Locator("input");
// Get the count of input fields
var count = await inputFields.CountAsync();
// Iterate through each input field and fill with random data
for (int i = 0; i < count; i++)
{
var input = inputFields.Nth(i);
var inputType = await input.GetAttributeAsync("type");
// 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);
}
}
/// <summary>
/// Test if the alias listing index page works.
/// </summary>
/// <returns>Async task.</returns>
[Test]
public async Task AliasListingCorrect()
public async Task AliasListingTest()
{
await Page.GotoAsync(AppBaseUrl + "aliases");
await WaitForURLAsync("**/aliases");
// Wait for the content to load.
await Page.WaitForSelectorAsync("text=AliasVault");
await WaitForURLAsync("**/aliases", "AliasVault");
// Check if the expected content is present.
var pageContent = await Page.TextContentAsync("body");
@@ -71,34 +36,115 @@ public class AliasTests : PlaywrightTest
/// </summary>
/// <returns>Async task.</returns>
[Test]
public async Task CreateAlias()
public async Task CreateAliasTest()
{
await Page.GotoAsync(AppBaseUrl + "add-alias");
await WaitForURLAsync("**/add-alias");
// Create a new alias with service name = "Test Service".
var serviceName = "Test Service";
await CreateAlias(new Dictionary<string, string>
{
{ "service-name", serviceName },
});
// Wait for the content to load.
await Page.WaitForSelectorAsync("text=AliasVault");
// Check that the service name is present in the content.
var pageContent = await Page.TextContentAsync("body");
Assert.That(pageContent, Does.Contain(serviceName), "Created alias service name does not appear on alias page.");
}
// Check if a button with text "Generate Random Identity" appears
var generateButton = Page.Locator("text=Generate Random Identity");
Assert.That(generateButton, Is.Not.Null, "Generate button not found.");
/// <summary>
/// Test if editing a created alias works.
/// </summary>
/// <returns>Async task.</returns>
[Test]
public async Task EditAliasTest()
{
// Create a new alias with service name = "Alias service before".
var serviceNameBefore = "Alias service before";
await CreateAlias(new Dictionary<string, string>
{
{ "service-name", serviceNameBefore },
});
// Fill all input fields with random data
await FillAllInputFields(Page);
// Check that the service name is present in the content.
var pageContent = await Page.TextContentAsync("body");
Assert.That(pageContent, Does.Contain(serviceNameBefore), "Created alias service name does not appear on alias page.");
// Click the edit button.
var editButton = Page.Locator("text=Edit alias").First;
await editButton.ClickAsync();
await WaitForURLAsync("**/edit", "Save Alias");
// Replace the service name with "Alias service after".
var serviceNameAfter = "Alias service after";
await FillInputFields(
page: Page,
fieldValues: new Dictionary<string, string>
{
{ "service-name", serviceNameAfter },
});
// Press submit button with text "Create Alias"
var submitButton = Page.Locator("text=Save Alias").First;
await submitButton.ClickAsync();
await WaitForURLAsync("**/alias/**");
await WaitForURLAsync("**/alias/**", "View alias");
// Wait for the content to load.
await Page.WaitForSelectorAsync("text=Login credentials");
// Check if the alias was correctly updated.
pageContent = await Page.TextContentAsync("body");
Assert.That(pageContent, Does.Contain(serviceNameAfter), "Alias not updated correctly.");
}
// Check if the alias was created
var pageContent = await Page.TextContentAsync("body");
Assert.That(pageContent, Does.Contain("Login credentials"), "Alias not created.");
/// <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");
// TODO: Implement proper data input and verification if what was created is correct.
// 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)
@@ -124,4 +170,31 @@ public class AliasTests : PlaywrightTest
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[Random.Next(s.Length)]).ToArray());
}
/// <summary>
/// Create new alias.
/// </summary>
/// <param name="formValues">Dictionary with html element ids and values to input as field value.</param>
/// <returns>Async task.</returns>
private async Task CreateAlias(Dictionary<string, string>? formValues = null)
{
await Page.GotoAsync(AppBaseUrl + "add-alias");
await WaitForURLAsync("**/add-alias", "Add alias");
// Check if a button with text "Generate Random Identity" appears
var generateButton = Page.Locator("text=Generate Random Identity");
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);
var submitButton = Page.Locator("text=Save Alias").First;
await submitButton.ClickAsync();
await WaitForURLAsync("**/alias/**", "Login credentials");
// Check if the alias was created
var pageContent = await Page.TextContentAsync("body");
Assert.That(pageContent, Does.Contain("Login credentials"), "Alias not created.");
}
}

View File

@@ -5,8 +5,6 @@
// </copyright>
//-----------------------------------------------------------------------
using Microsoft.Playwright;
namespace AliasVault.E2ETests;
/// <summary>
@@ -17,7 +15,7 @@ namespace AliasVault.E2ETests;
public class AuthTests : PlaywrightTest
{
/// <summary>
/// Test if registering a new account works.
/// Test if logging out and logging in works.
/// </summary>
/// <returns>Async task.</returns>
[Test]
@@ -25,13 +23,10 @@ public class AuthTests : PlaywrightTest
{
// Logout.
await Page.GotoAsync(AppBaseUrl + "user/logout");
await Page.WaitForURLAsync("**/user/logout", new PageWaitForURLOptions() { Timeout = 2000 });
// Wait for the content to load.
await Page.WaitForSelectorAsync("text=AliasVault");
await WaitForURLAsync("**/user/logout", "AliasVault");
// Wait and check if we get redirected to /user/login.
await Page.WaitForURLAsync("**/user/login", new PageWaitForURLOptions() { Timeout = 2000 });
await WaitForURLAsync("**/user/login");
await Login();
}
@@ -40,7 +35,7 @@ public class AuthTests : PlaywrightTest
/// Test if logging in works.
/// </summary>
/// <returns>Async task.</returns>
public async Task Login()
private async Task Login()
{
await Page.GotoAsync(AppBaseUrl);

View File

@@ -138,6 +138,21 @@ public class PlaywrightTest
await Page.WaitForURLAsync(url, new PageWaitForURLOptions() { Timeout = timeoutInMs });
}
/// <summary>
/// Wait for the specified URL to be loaded with a custom timeout.
/// </summary>
/// <param name="url">The URL to wait for. This may also contains wildcard such as "**/user/login".</param>
/// <param name="waitForText">Wait until a certain text appears on the page.
/// This can be useful for content that is loaded via AJAX after navigation.</param>
/// <returns>Async task.</returns>
protected async Task WaitForURLAsync(string url, string waitForText)
{
await Page.WaitForURLAsync(url, new PageWaitForURLOptions() { Timeout = TestDefaults.DefaultTimeout });
// Wait for actual content to load (web API calls, etc.)
await Page.WaitForSelectorAsync("text=" + waitForText, new PageWaitForSelectorOptions() { Timeout = TestDefaults.DefaultTimeout });
}
/// <summary>
/// Register a new random account.
/// </summary>

View File

@@ -113,7 +113,6 @@ public class BlazorWasmAppManager
}
Console.WriteLine(e.Message);
await Task.Delay(500);
}
}
}