Skip to content

Commit

Permalink
Add automation test cases using Playwright for login, register and lo…
Browse files Browse the repository at this point in the history
…gout pages
  • Loading branch information
Nfactor26 committed Apr 15, 2022
1 parent 8fde37f commit 92a22e7
Show file tree
Hide file tree
Showing 16 changed files with 495 additions and 11 deletions.
23 changes: 20 additions & 3 deletions Pixel.Identity.sln
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pixel.Identity.Messenger.Co
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{D1F9C105-5E1A-42B0-A47F-3DD3B223AD8C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.Service.Api", "samples\Sample.Service.Api\Sample.Service.Api.csproj", "{18ADE8F9-D7F2-400A-A2EB-78439F82A468}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Service.Api", "samples\Sample.Service.Api\Sample.Service.Api.csproj", "{18ADE8F9-D7F2-400A-A2EB-78439F82A468}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.Blazor.App", "samples\Samples.Blazor.App\Samples.Blazor.App.csproj", "{7D75919B-B900-4370-A9AB-913104AEA2F0}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.Blazor.App", "samples\Samples.Blazor.App\Samples.Blazor.App.csproj", "{7D75919B-B900-4370-A9AB-913104AEA2F0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.Blazor.App.Host", "samples\Samples.Blazor.App.Host\Samples.Blazor.App.Host.csproj", "{535B958A-7CEF-43EC-9545-3CE999D74889}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.Blazor.App.Host", "samples\Samples.Blazor.App.Host\Samples.Blazor.App.Host.csproj", "{535B958A-7CEF-43EC-9545-3CE999D74889}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{90291008-5EF2-4A1F-9B89-19216A3B017F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pixel.Identity.UI.Tests", "src\Pixel.Identity.UI.Tests\Pixel.Identity.UI.Tests.csproj", "{440B2ECE-5DCF-481D-9C18-0EC8F4F18E98}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -211,6 +215,18 @@ Global
{535B958A-7CEF-43EC-9545-3CE999D74889}.Release|x64.Build.0 = Release|Any CPU
{535B958A-7CEF-43EC-9545-3CE999D74889}.Release|x86.ActiveCfg = Release|Any CPU
{535B958A-7CEF-43EC-9545-3CE999D74889}.Release|x86.Build.0 = Release|Any CPU
{440B2ECE-5DCF-481D-9C18-0EC8F4F18E98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{440B2ECE-5DCF-481D-9C18-0EC8F4F18E98}.Debug|Any CPU.Build.0 = Debug|Any CPU
{440B2ECE-5DCF-481D-9C18-0EC8F4F18E98}.Debug|x64.ActiveCfg = Debug|Any CPU
{440B2ECE-5DCF-481D-9C18-0EC8F4F18E98}.Debug|x64.Build.0 = Debug|Any CPU
{440B2ECE-5DCF-481D-9C18-0EC8F4F18E98}.Debug|x86.ActiveCfg = Debug|Any CPU
{440B2ECE-5DCF-481D-9C18-0EC8F4F18E98}.Debug|x86.Build.0 = Debug|Any CPU
{440B2ECE-5DCF-481D-9C18-0EC8F4F18E98}.Release|Any CPU.ActiveCfg = Release|Any CPU
{440B2ECE-5DCF-481D-9C18-0EC8F4F18E98}.Release|Any CPU.Build.0 = Release|Any CPU
{440B2ECE-5DCF-481D-9C18-0EC8F4F18E98}.Release|x64.ActiveCfg = Release|Any CPU
{440B2ECE-5DCF-481D-9C18-0EC8F4F18E98}.Release|x64.Build.0 = Release|Any CPU
{440B2ECE-5DCF-481D-9C18-0EC8F4F18E98}.Release|x86.ActiveCfg = Release|Any CPU
{440B2ECE-5DCF-481D-9C18-0EC8F4F18E98}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -219,6 +235,7 @@ Global
{18ADE8F9-D7F2-400A-A2EB-78439F82A468} = {D1F9C105-5E1A-42B0-A47F-3DD3B223AD8C}
{7D75919B-B900-4370-A9AB-913104AEA2F0} = {D1F9C105-5E1A-42B0-A47F-3DD3B223AD8C}
{535B958A-7CEF-43EC-9545-3CE999D74889} = {D1F9C105-5E1A-42B0-A47F-3DD3B223AD8C}
{440B2ECE-5DCF-481D-9C18-0EC8F4F18E98} = {90291008-5EF2-4A1F-9B89-19216A3B017F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D2D4C886-7F44-4E77-9010-D9AB3C1B559C}
Expand Down
4 changes: 2 additions & 2 deletions src/Pixel.Identity.Provider/Components/LoginDisplay.razor
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<MudToolBar Spacing="5">
<MudButton Link="authentication/register"
<MudButton id="registerPageLink" Link="authentication/register"
Variant="Variant.Outlined"
Color="Color.Tertiary" Class="mr-5">
Register
</MudButton>
<MudButton Link="authentication/login"
<MudButton id="loginPageLink" Link="authentication/login"
Variant="Variant.Outlined"
Color="Color.Tertiary" Class="mr-5">
Login
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
<input type="hidden" name="@parameter.Key" value="@parameter.Value" />
}

<input class="btn btn-lg btn-success" name="Confirm" type="submit" value="Yes" />
<input id="confirmLogoutButton" class="btn btn-lg btn-success" name="Confirm" type="submit" value="Yes" />
</form>
</div>
10 changes: 5 additions & 5 deletions src/Pixel.Identity.UI.Client/Shared/LoginDisplay.razor
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@

<AuthorizeView>
<Authorized>
<MudMenu Direction="Direction.Left" OffsetX="true">
<MudMenu id="signedInMenu" Direction="Direction.Left" OffsetX="true">
<ActivatorContent>
<MudAvatar>@context.User.Identity.Name.First()</MudAvatar>
</ActivatorContent>
<ChildContent>
<MudMenuItem Link="/authentication/profile">Profile</MudMenuItem>
<MudMenuItem @onclick="BeginSignOut">Sign Out</MudMenuItem>
<MudMenuItem id="profileMenuItem" Link="/authentication/profile">Profile</MudMenuItem>
<MudMenuItem id="signOutMenuItem" @onclick="BeginSignOut">Sign Out</MudMenuItem>
</ChildContent>
</MudMenu>
</Authorized>
<NotAuthorized>
<MudToolBar Spacing="5">
<MudButton Link="authentication/register"
<MudButton id="registerPageLink" Link="authentication/register"
Variant="Variant.Outlined"
Color="Color.Tertiary" Class="mr-5">
Register
</MudButton>
<MudButton Link="authentication/login"
<MudButton id="loginPageLink" Link="authentication/login"
Variant="Variant.Outlined"
Color="Color.Tertiary" Class="mr-5">
Login
Expand Down
12 changes: 12 additions & 0 deletions src/Pixel.Identity.UI.Tests/Helpers/ConfigurationFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Microsoft.Extensions.Configuration;

namespace Pixel.Identity.UI.Tests.Helpers;

internal class ConfigurationFactory
{
private static IConfiguration configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json")
.AddEnvironmentVariables().Build();

public static IConfiguration Create() => configuration;

}
35 changes: 35 additions & 0 deletions src/Pixel.Identity.UI.Tests/Helpers/UserCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Collections.Generic;
using System.Security.Claims;

namespace Pixel.Identity.UI.Tests.Helpers;

internal static class UserCollection
{
private static readonly List<User> users = new();

static UserCollection()
{
users.Add(new User("[email protected]", "tesT-useR-secreT-1"));
users.Add(new User("[email protected]", "tesT-useR-secreT-2"));
}

public static IEnumerable<User> GetAllUsers() => users;
}

internal class User
{
public string Email { get; set; }

public string Password { get; set; }

public List<string> Roles { get; private set; } = new ();

public List<Claim> Claims { get; private set; } = new ();

public User(string email, string password)
{
this.Email = email;
this.Password = password;
}
}

71 changes: 71 additions & 0 deletions src/Pixel.Identity.UI.Tests/PageModels/LoginPage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using Microsoft.Playwright;
using System.Threading.Tasks;

namespace Pixel.Identity.UI.Tests.PageModels;

internal class LoginPage
{
private readonly IPage page;

/// <summary>
/// constructor
/// </summary>
/// <param name="page"></param>
public LoginPage(IPage page)
{
this.page = page;
}

/// <summary>
/// Navigate to login page
/// </summary>
/// <param name="baseUrl"></param>
/// <returns></returns>
public async Task GoToAsync(string baseUrl)
{
await page.GotoAsync($"{baseUrl}/Identity/Account/Login");
}

/// <summary>
/// Perform login action on page. Make sure to call AuthorizeRequestedScopesAsync()
/// </summary>
/// <param name="userEmail">UserName for account</param>
/// <param name="password">Password for account</param>
/// <param name="rememberMe">Indicates whether the remember me checkbox should be checked or not</param>
/// <returns></returns>
public async Task LoginAsync(string userEmail, string password, bool rememberMe)
{
await this.page.FillAsync("#Input_Email",userEmail);
await this.page.FillAsync("#Input_Password", password);
if(rememberMe)
{
await this.page.CheckAsync("#Input_RememberMe");
}
//await page.RunAndWaitForNavigationAsync(async () =>
//{
// await this.page.ClickAsync("#login-submit");
//});
await this.page.ClickAsync("#login-submit");
}

/// <summary>
/// Click on forgot password link
/// </summary>
/// <returns></returns>
public async Task ClickForgotPasswordAsync()
{
var forgotPassword = this.page.Locator("#forgot-password");
await forgotPassword.ClickAsync();
}

/// <summary>
/// Click on resend email confirmation link
/// </summary>
/// <returns></returns>
public async Task ClickResendEmailConfirmationAsync()
{
var resentConfirmation = this.page.Locator("#resend-confirmation");
await resentConfirmation.ClickAsync();
}

}
32 changes: 32 additions & 0 deletions src/Pixel.Identity.UI.Tests/PageModels/LogoutPage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Microsoft.Playwright;
using System.Threading.Tasks;

namespace Pixel.Identity.UI.Tests.PageModels;

internal class LogoutPage
{
private readonly IPage page;

/// <summary>
/// constructor
/// </summary>
/// <param name="page"></param>
public LogoutPage(IPage page)
{
this.page = page;
}

/// <summary>
/// Logout using the sign out menu item
/// </summary>
/// <returns></returns>
public async Task LogoutAsync()
{
await this.page.ClickAsync("#signedInMenu");
await this.page.ClickAsync("#signOutMenuItem");
await page.RunAndWaitForNavigationAsync(async () =>
{
await this.page.ClickAsync("#confirmLogoutButton");
});
}
}
42 changes: 42 additions & 0 deletions src/Pixel.Identity.UI.Tests/PageModels/RegisterPage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Microsoft.Playwright;
using System.Threading.Tasks;

namespace Pixel.Identity.UI.Tests.PageModels
{
internal class RegisterPage
{
private readonly IPage page;

/// <summary>
/// constructor
/// </summary>
/// <param name="page"></param>
public RegisterPage(IPage page)
{
this.page = page;
}

/// <summary>
/// Click the register button to navigate to registration page
/// </summary>
/// <returns></returns>
public async Task GoToAsync()
{
await page.ClickAsync("#registerPageLink");
}

/// <summary>
/// Fill out required details and click the register button to complete registration
/// </summary>
/// <param name="userEmail"></param>
/// <param name="password"></param>
/// <returns></returns>
public async Task RegisterAsync(string userEmail, string password)
{
await page.FillAsync("#Input_Email", userEmail);
await page.FillAsync("#Input_Password", password);
await page.FillAsync("#Input_ConfirmPassword", password);
await page.ClickAsync("#registerSubmit");
}
}
}
27 changes: 27 additions & 0 deletions src/Pixel.Identity.UI.Tests/Pixel.Identity.UI.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Microsoft.Playwright" Version="1.20.2" />
<PackageReference Include="Microsoft.Playwright.NUnit" Version="1.20.2" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="coverlet.collector" Version="3.1.0" />
</ItemGroup>

<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
63 changes: 63 additions & 0 deletions src/Pixel.Identity.UI.Tests/Tests/LoginTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Playwright.NUnit;
using NUnit.Framework;
using Pixel.Identity.UI.Tests.Helpers;
using Pixel.Identity.UI.Tests.PageModels;
using System.Threading.Tasks;

namespace Pixel.Identity.UI.Tests.Tests;

public class LoginTests : PageTest
{
private IConfiguration configuration;
private string baseUrl;

/// <summary>
/// constructor
/// </summary>
public LoginTests()
{
configuration = ConfigurationFactory.Create();
baseUrl = configuration["BaseUrl"];
}

/// <summary>
/// Validate that email and password fields are required for sign in
/// </summary>
/// <returns></returns>
[Test, Order(1)]
public async Task Validate_That_UserEmail_And_Credentials_Are_Required()
{
var loginPage = new LoginPage(this.Page);
await loginPage.GoToAsync(this.baseUrl);
await loginPage.LoginAsync(string.Empty, string.Empty, false);
await Expect(Page.Locator("#Input_Email-error")).ToHaveTextAsync("The Email field is required.");
await Expect(Page.Locator("#Input_Password-error")).ToHaveTextAsync("The Password field is required.");
}

/// <summary>
/// Validate that user can not login with incorrect credentials
/// </summary>
/// <returns></returns>
[Test, Order(2)]
public async Task Validate_That_User_Can_Not_LogIn_With_Incorrect_Credentials()
{
var loginPage = new LoginPage(this.Page);
await loginPage.GoToAsync(this.baseUrl);
await loginPage.LoginAsync(this.configuration["UserEmail"], "unknown-secret", false);
await Expect(Page.Locator("#account>div.text-danger")).ToHaveTextAsync("Invalid login attempt.");
}

/// <summary>
/// validate taht user can login with correct email and password combination
/// </summary>
/// <returns></returns>
[Test, Order(3)]
public async Task Validate_That_User_Can_LogIn_With_Correct_Credentials()
{
var loginPage = new LoginPage(this.Page);
await loginPage.GoToAsync(baseUrl);
await loginPage.LoginAsync(configuration["UserEmail"], configuration["UserSecret"], false);
await Expect(this.Page.Locator("#signedInMenu")).ToBeVisibleAsync();
}
}
Loading

0 comments on commit 92a22e7

Please sign in to comment.