Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Page objects models #11

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/OrchardCoreContrib.Testing.UI/PageObjects/AdminPage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Microsoft.Playwright;
using OrchardCoreContrib.Testing.UI.Helpers;

namespace OrchardCoreContrib.Testing.UI.PageObjects;

/// <summary>
/// Represents a base class for admin page objects.
/// </summary>
public abstract class AdminPage : PageBase
{
/// <inheritdoc/>
public abstract override string Slug { get; }

/// <summary>
/// Changes the theme.
/// </summary>
/// <param name="themeMode">The theme mode to be applied.</param>
public async Task ChangeThemeAsync(ThemeMode themeMode)
{
await Page.FindElement(By.Id("bd-theme")).ClickAsync();
await Page.FindElement(By.Attribute("data-bs-theme-value", themeMode.ToString().ToLower(), "button")).ClickAsync();
}

/// <summary>
/// Navigates to the profile page.
/// </summary>
public async Task<ProfilePage> GoToProfilePage() => await PageFactory.CreateAsync<ProfilePage>();

/// <summary>
/// Navigates to the change password page.
/// </summary>
public async Task<ChangePasswordPage> GoToChangePasswordPage() => await PageFactory.CreateAsync<ChangePasswordPage>();

/// <summary>
/// Log out the current user.
/// </summary>
public async Task LogoutAsync()
{
await Page.InnerPage.GetByRole(AriaRole.Link, new() { Name = "admin" }).ClickAsync();
await Page.InnerPage.GetByRole(AriaRole.Button, new() { Name = "Log off" }).ClickAsync();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using OrchardCoreContrib.Testing.UI.Helpers;

namespace OrchardCoreContrib.Testing.UI.PageObjects;

/// <summary>
/// Represents a change password page.
/// </summary>
public class ChangePasswordPage : AdminPage
{
/// <inheritdoc/>
public override string Slug => "ChangePassword";

/// <summary>
/// Changes the user password.
/// </summary>
/// <param name="currentPassword">The current password.</param>
/// <param name="newPassword">The new password.</param>
public async Task<bool> ChangeAsync(string currentPassword, string newPassword)
{
await Page.FindElement(By.Attribute("name", "CurrentPassword")).TypeAsync(currentPassword);
await Page.FindElement(By.Attribute("name", "Password")).TypeAsync(newPassword);
await Page.FindElement(By.Attribute("name", "PasswordConfirmation")).TypeAsync(newPassword);
await Page.FindElement(By.Attribute("type", "submit", "button")).ClickAsync();

return Page.Content.Contains("Your password has been changed successfully.");
}
}
10 changes: 10 additions & 0 deletions src/OrchardCoreContrib.Testing.UI/PageObjects/DashboardPage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace OrchardCoreContrib.Testing.UI.PageObjects;

/// <summary>
/// Represents the dashboard page.
/// </summary>
public class DashboardPage : AdminPage
{
/// <inheritdoc/>
public override string Slug => "Admin/";
}
31 changes: 31 additions & 0 deletions src/OrchardCoreContrib.Testing.UI/PageObjects/LoginPage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Microsoft.Playwright;
using OrchardCoreContrib.Testing.UI.Helpers;

namespace OrchardCoreContrib.Testing.UI.PageObjects;

/// <summary>
/// Represents a login page.
/// </summary>
public class LoginPage : PageBase
{
/// <inheritdoc/>
public override string Slug => "Login";

/// <summary>
/// Logs in with the specified username and password.
/// </summary>
/// <param name="username">The user name.</param>
/// <param name="password">The password.</param>
public async Task<bool> LoginAsync(string username, string password)
{
await Page.FindElement(By.Attribute("name", "UserName")).TypeAsync(username);
await Page.FindElement(By.Attribute("name", "Password")).TypeAsync(password);
await Page.FindElement(By.Attribute("type", "submit")).ClickAsync();

var isAuthenticated = await Page.InnerPage
.GetByRole(AriaRole.Link, new() { Name = username })
.IsVisibleAsync();

return isAuthenticated;
}
}
26 changes: 26 additions & 0 deletions src/OrchardCoreContrib.Testing.UI/PageObjects/PageBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace OrchardCoreContrib.Testing.UI.PageObjects;

/// <summary>
/// Represents a base class for page objects.
/// </summary>
public abstract class PageBase
{
/// <summary>
/// Gets the underlying <see cref="IPage"/> object.
/// </summary>
public IPage Page { get; internal set; }

/// <summary>
/// Gets the slug of the page.
/// </summary>
public abstract string Slug { get; }

internal string BaseUrl { get; set; }

internal async Task<PageBase> GoToAsync()
{
await Page.GoToAsync(BaseUrl + Slug);

return this;
}
}
37 changes: 37 additions & 0 deletions src/OrchardCoreContrib.Testing.UI/PageObjects/PageFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
namespace OrchardCoreContrib.Testing.UI.PageObjects;

/// <summary>
/// Represents a factory for creating page objects.
/// </summary>
public class PageFactory
{
private static IPage _page;
private static string _baseUrl;

/// <summary>
/// Initializes the page factory.
/// </summary>
/// <param name="browser">The <see cref="IBrowser"/>.</param>
/// <param name="baseUrl">The base URL.</param>
/// <returns></returns>
public static async Task InitializeAsync(IBrowser browser, string baseUrl)
{
_page = await browser.OpenPageAsync(baseUrl);

_baseUrl = baseUrl;
}

/// <summary>
/// Creates a page object of the specified type.
/// </summary>
/// <typeparam name="TPage">The page type.</typeparam>
public static async Task<TPage> CreateAsync<TPage>() where TPage : PageBase
{
var page = Activator.CreateInstance<TPage>();

page.Page = _page;
page.BaseUrl = _baseUrl;

return (TPage)await page.GoToAsync();
}
}
17 changes: 17 additions & 0 deletions src/OrchardCoreContrib.Testing.UI/PageObjects/ProfilePage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace OrchardCoreContrib.Testing.UI.PageObjects;

/// <summary>
/// Represents the profile page.
/// </summary>
public class ProfilePage : AdminPage
{
/// <inheritdoc/>
public override string Slug => "Admin/Users/Edit";

/// <summary>
/// Changes the user profile information.
/// </summary>
/// <param name="phoneNumber">The phone number.</param>
/// <param name="roleNames">The user reoles.</param>
public Task<bool> ChangeAsync(string phoneNumber, params string[] roleNames) => throw new NotImplementedException();
}
20 changes: 20 additions & 0 deletions src/OrchardCoreContrib.Testing.UI/PageObjects/ThemeMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace OrchardCoreContrib.Testing.UI.PageObjects;

/// <summary>
/// Defines the theme modes.
/// </summary>
public enum ThemeMode
{
/// <summary>
/// The theme mode will set automatically based on the windows theme.
/// </summary>
Auto,
/// <summary>
/// The light theme mode.
/// </summary>
Light,
/// <summary>
/// The dark theme mode.
/// </summary>
Dark
}
8 changes: 8 additions & 0 deletions src/OrchardCoreContrib.Testing.UI/UITestOfT.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using OrchardCoreContrib.Testing.UI.Infrastructure;
using OrchardCoreContrib.Testing.UI.PageObjects;

namespace OrchardCoreContrib.Testing.UI;

Expand All @@ -17,4 +18,11 @@ public class UITest<TStartup>(BrowserType browserType = BrowserType.Edge, bool h
Delay = delay
}), IUITest where TStartup : class
{
/// <inheritdoc/>
public override async Task InitializeAsync()
{
await base.InitializeAsync();

await PageFactory.InitializeAsync(Browser, BaseUrl);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace OrchardCoreContrib.Testing.UI.PageObjects.Tests;

public class PageFactoryTests
{
[Fact]
public async Task CreatePage()
{
// Arrange
var baseUrl = "https://localhost:8080";
var browserMock = new Mock<IBrowser>();
browserMock.Setup(b => b.OpenPageAsync(It.IsAny<string>()))
.ReturnsAsync(() => new Page(new PlaywrightPageAccessor(Mock.Of<Microsoft.Playwright.IPage>()), browserMock.Object));

await PageFactory.InitializeAsync(browserMock.Object, baseUrl);

// Act
var page = await PageFactory.CreateAsync<MyPage>();

// Assert
Assert.NotNull(page.Page);
Assert.Equal(baseUrl, page.BaseUrl);
Assert.Equal("/my-page", page.Slug);
}

public class MyPage : PageBase
{
public override string Slug => "/my-page";
}
}