Loading...
Loading...
Compare original and translation side by side
docker-compose up -ddocker-compose up -ddocker-compose up -ddocker-compose up -dundefinedundefinedtests/Sorcha.UI.E2E.Tests/PageObjects/{PageName}Page.csusing Microsoft.Playwright;
using Sorcha.UI.E2E.Tests.Infrastructure;
using Sorcha.UI.E2E.Tests.PageObjects.Shared;
namespace Sorcha.UI.E2E.Tests.PageObjects;
public class WalletListPage
{
private readonly IPage _page;
public WalletListPage(IPage page) => _page = page;
// Use data-testid as primary selector strategy
public ILocator WalletCards => MudBlazorHelpers.TestIdPrefix(_page, "wallet-card-");
public ILocator CreateButton => MudBlazorHelpers.TestId(_page, "create-wallet-btn");
public ILocator EmptyState => MudBlazorHelpers.TestId(_page, "wallet-empty-state");
public ILocator SearchInput => _page.Locator("input[placeholder*='Search']");
// Fallback to MudBlazor class selectors
public ILocator Table => MudBlazorHelpers.Table(_page);
public ILocator LoadingSpinner => MudBlazorHelpers.CircularProgress(_page);
public async Task NavigateAsync()
{
await _page.GotoAsync($"{TestConstants.UiWebUrl}{TestConstants.AuthenticatedRoutes.Wallets}");
await _page.WaitForLoadStateAsync(LoadState.NetworkIdle);
await MudBlazorHelpers.WaitForBlazorAsync(_page);
}
public async Task<int> GetWalletCountAsync() => await WalletCards.CountAsync();
public async Task<bool> IsEmptyStateVisibleAsync() =>
await EmptyState.CountAsync() > 0 && await EmptyState.IsVisibleAsync();
}tests/Sorcha.UI.E2E.Tests/PageObjects/{PageName}Page.csusing Microsoft.Playwright;
using Sorcha.UI.E2E.Tests.Infrastructure;
using Sorcha.UI.E2E.Tests.PageObjects.Shared;
namespace Sorcha.UI.E2E.Tests.PageObjects;
public class WalletListPage
{
private readonly IPage _page;
public WalletListPage(IPage page) => _page = page;
// Use data-testid as primary selector strategy
public ILocator WalletCards => MudBlazorHelpers.TestIdPrefix(_page, \"wallet-card-\");
public ILocator CreateButton => MudBlazorHelpers.TestId(_page, \"create-wallet-btn\");
public ILocator EmptyState => MudBlazorHelpers.TestId(_page, \"wallet-empty-state\");
public ILocator SearchInput => _page.Locator(\"input[placeholder*='Search']\");
// Fallback to MudBlazor class selectors
public ILocator Table => MudBlazorHelpers.Table(_page);
public ILocator LoadingSpinner => MudBlazorHelpers.CircularProgress(_page);
public async Task NavigateAsync()
{
await _page.GotoAsync($\"{TestConstants.UiWebUrl}{TestConstants.AuthenticatedRoutes.Wallets}\"));
await _page.WaitForLoadStateAsync(LoadState.NetworkIdle);
await MudBlazorHelpers.WaitForBlazorAsync(_page);
}
public async Task<int> GetWalletCountAsync() => await WalletCards.CountAsync();
public async Task<bool> IsEmptyStateVisibleAsync() =>
await EmptyState.CountAsync() > 0 && await EmptyState.IsVisibleAsync();
}tests/Sorcha.UI.E2E.Tests/Docker/{Feature}Tests.csusing Sorcha.UI.E2E.Tests.Infrastructure;
using Sorcha.UI.E2E.Tests.PageObjects;
namespace Sorcha.UI.E2E.Tests.Docker;
[Parallelizable(ParallelScope.Self)]
[TestFixture]
[Category("Docker")]
[Category("Wallets")]
[Category("Authenticated")]
public class WalletListTests : AuthenticatedDockerTestBase
{
private WalletListPage _walletList = null!;
[SetUp]
public override async Task BaseSetUp()
{
await base.BaseSetUp();
_walletList = new WalletListPage(Page);
}
[Test]
[Retry(2)]
public async Task WalletList_LoadsWithoutErrors()
{
await NavigateAuthenticatedAsync(TestConstants.AuthenticatedRoutes.Wallets);
// Base class automatically checks console errors, network 5xx, CSS health
}
[Test]
public async Task WalletList_ShowsEmptyStateOrWallets()
{
await _walletList.NavigateAsync();
var count = await _walletList.GetWalletCountAsync();
if (count == 0)
{
Assert.That(await _walletList.IsEmptyStateVisibleAsync(), Is.True,
"Empty system should show empty state message");
}
else
{
Assert.That(count, Is.GreaterThan(0));
}
}
}tests/Sorcha.UI.E2E.Tests/Docker/{Feature}Tests.csusing Sorcha.UI.E2E.Tests.Infrastructure;
using Sorcha.UI.E2E.Tests.PageObjects;
namespace Sorcha.UI.E2E.Tests.Docker;
[Parallelizable(ParallelScope.Self)]
[TestFixture]
[Category(\"Docker\")]
[Category(\"Wallets\")]
[Category(\"Authenticated\")]
public class WalletListTests : AuthenticatedDockerTestBase
{
private WalletListPage _walletList = null!;
[SetUp]
public override async Task BaseSetUp()
{
await base.BaseSetUp();
_walletList = new WalletListPage(Page);
}
[Test]
[Retry(2)]
public async Task WalletList_LoadsWithoutErrors()
{
await NavigateAuthenticatedAsync(TestConstants.AuthenticatedRoutes.Wallets);
// Base class automatically checks console errors, network 5xx, CSS health
}
[Test]
public async Task WalletList_ShowsEmptyStateOrWallets()
{
await _walletList.NavigateAsync();
var count = await _walletList.GetWalletCountAsync();
if (count == 0)
{
Assert.That(await _walletList.IsEmptyStateVisibleAsync(), Is.True,
\"Empty system should show empty state message\");
}
else
{
Assert.That(count, Is.GreaterThan(0));
}
}
}src/Apps/Sorcha.UI/Sorcha.UI.Web.Client/Pages/{Page}.razor@page "/wallets"
@layout MainLayout
@rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false))
@attribute [Authorize]
@inject HttpClient Http
<PageTitle>Wallets - Sorcha</PageTitle>
@if (_isLoading)
{
<MudProgressCircular Indeterminate="true" data-testid="wallet-loading" />
}
else if (_wallets.Count == 0)
{
<MudAlert Severity="Severity.Info" data-testid="wallet-empty-state">
No wallets found. Create your first wallet to get started.
</MudAlert>
}
else
{
@foreach (var wallet in _wallets)
{
<MudCard data-testid="wallet-card-@wallet.Id" Class="mb-3">
<MudCardContent>
<MudText Typo="Typo.h6">@wallet.Name</MudText>
</MudCardContent>
</MudCard>
}
}
<MudButton data-testid="create-wallet-btn" Variant="Variant.Filled"
Color="Color.Primary" Href="wallets/create">
Create Wallet
</MudButton>src/Apps/Sorcha.UI/Sorcha.UI.Web.Client/Pages/{Page}.razor@page \"/wallets\"
@layout MainLayout
@rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false))
@attribute [Authorize]
@inject HttpClient Http
<PageTitle>Wallets - Sorcha</PageTitle>
@if (_isLoading)
{
<MudProgressCircular Indeterminate=\"true\" data-testid=\"wallet-loading\" />
}
else if (_wallets.Count == 0)
{
<MudAlert Severity=\"Severity.Info\" data-testid=\"wallet-empty-state\">
No wallets found. Create your first wallet to get started.
</MudAlert>
}
else
{
@foreach (var wallet in _wallets)
{
<MudCard data-testid=\"wallet-card-@wallet.Id\" Class=\"mb-3\">
<MudCardContent>
<MudText Typo=\"Typo.h6\">@wallet.Name</MudText>
</MudCardContent>
</MudCard>
}
}
<MudButton data-testid=\"create-wallet-btn\" Variant=\"Variant.Filled\"
Color=\"Color.Primary\" Href=\"wallets/create\">
Create Wallet
</MudButton>undefinedundefinedundefinedundefinedtests/Sorcha.UI.E2E.Tests/bin/Debug/net10.0/screenshots/tests/Sorcha.UI.E2E.Tests/bin/Debug/net10.0/screenshots/| Concept | Usage | Location |
|---|---|---|
| Unauthenticated tests with auto error capture | |
| Login once, reuse auth state across tests | |
| URLs, credentials, routes, timeouts | |
| Page Objects | Encapsulate selectors and actions per page | |
| Shared MudBlazor locators and layout validation | |
| Primary selector strategy for resilient tests | Added to Razor components |
| Categories | Filter tests by feature or concern | |
| 概念 | 用途 | 位置 |
|---|---|---|
| 未授权测试,自动捕获错误 | |
| 登录一次,在测试间复用认证状态 | |
| URL、凭据、路由、超时时间 | |
| Page Objects | 封装每个页面的选择器和操作 | |
| 共享的MudBlazor定位器和布局验证 | |
| 用于构建高稳定性测试的主要选择器策略 | 添加到Razor组件中 |
| Categories | 按功能或关注点过滤测试 | 测试类上的 |
PageTest (Playwright NUnit)
└── DockerTestBase (console errors, network failures, screenshots)
├── ComponentHealthTests, LoginTests (unauthenticated)
└── AuthenticatedDockerTestBase (login once, reuse state, layout health)
├── DashboardTests
├── NavigationTests
├── WalletTests
└── ... (one per feature area)PageTest (Playwright NUnit)
└── DockerTestBase(控制台错误、网络故障、屏幕截图)
├── ComponentHealthTests、LoginTests(未授权)
└── AuthenticatedDockerTestBase(登录一次、复用状态、布局健康检查)
├── DashboardTests
├── NavigationTests
├── WalletTests
└── ...(每个功能区域对应一个)| Category | Scope | Use |
|---|---|---|
| All pages load, no JS errors | CI gate |
| All tests targeting Docker | Full Docker suite |
| Login, logout, redirects | Authentication features |
| All tests needing login | Post-login features |
| Dashboard page | Dashboard development |
| Drawer, app bar, routing | Layout changes |
| CSS, MudBlazor, responsive | Style/component changes |
| Wallet pages | Wallet features |
| Blueprint pages | Blueprint features |
| Register pages | Register features |
| Schema library | Schema features |
| Administration pages | Admin features |
| 分类 | 范围 | 用途 |
|---|---|---|
| 所有页面可加载,无JS错误 | CI验证门 |
| 所有针对Docker的测试 | 完整Docker测试套件 |
| 登录、登出、重定向 | 认证功能 |
| 所有需要登录的测试 | 登录后功能 |
| 仪表板页面 | 仪表板开发 |
| 抽屉、应用栏、路由 | 布局变更 |
| CSS、MudBlazor、响应式 | 样式/组件变更 |
| 钱包页面 | 钱包功能 |
| 蓝图页面 | 蓝图功能 |
| 注册页面 | 注册功能 |
| 模式库 | 模式功能 |
| 管理页面 | 管理功能 |
| What | Where |
|---|---|
| Blazor pages | |
| Shared components | |
| Layout | |
| Test infrastructure | |
| Page objects | |
| Docker tests | |
| MudBlazor helpers | |
| 内容 | 位置 |
|---|---|
| Blazor页面 | |
| 共享组件 | |
| 布局 | |
| 测试基础设施 | |
| 页面对象 | |
| Docker测试 | |
| MudBlazor助手 | |
Fetch latest Playwright .NET and MudBlazor documentation with Context7.
/websites/playwright_dev_dotnet/websites/mudblazor使用Context7获取最新的Playwright .NET和MudBlazor文档。
/websites/playwright_dev_dotnet/websites/mudblazor