dotnet-uno-testing
Tests Uno Platform apps. Playwright for WASM, platform-specific patterns, runtime heads.
Best use case
dotnet-uno-testing is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Tests Uno Platform apps. Playwright for WASM, platform-specific patterns, runtime heads.
Teams using dotnet-uno-testing should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/dotnet-uno-testing/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How dotnet-uno-testing Compares
| Feature / Agent | dotnet-uno-testing | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Tests Uno Platform apps. Playwright for WASM, platform-specific patterns, runtime heads.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# dotnet-uno-testing
Testing Uno Platform applications across target heads (WASM, Desktop, Mobile). Covers Playwright-based browser
automation for Uno WASM apps, platform-specific testing patterns for different runtime heads, and test infrastructure
for cross-platform Uno projects.
**Version assumptions:** .NET 8.0+ baseline, Uno Platform 5.x+, Playwright 1.40+ for WASM testing. Uno Platform uses
single-project structure with multiple target frameworks.
## Scope
- Playwright-based browser automation for Uno WASM apps
- Platform-specific testing patterns for different runtime heads
- Test infrastructure for cross-platform Uno projects
## Out of scope
- Shared UI testing patterns (page object model, selectors, wait strategies) -- see [skill:dotnet-ui-testing-core]
- Playwright fundamentals (installation, CI caching, trace viewer) -- see [skill:dotnet-playwright]
- Test project scaffolding -- see [skill:dotnet-add-testing]
**Prerequisites:** Uno Platform application with WASM head configured. For WASM testing: Playwright browsers installed
(see [skill:dotnet-playwright]). For mobile testing: platform SDKs configured (Android SDK, Xcode).
Cross-references: [skill:dotnet-ui-testing-core] for page object model and selector strategies,
[skill:dotnet-playwright] for Playwright installation, CI caching, and trace viewer, [skill:dotnet-uno-platform] for Uno
Extensions, MVUX, Toolkit, and theme guidance, [skill:dotnet-uno-targets] for per-target deployment and
platform-specific gotchas.
---
## Uno Testing Strategy by Head
Uno Platform apps run on multiple heads (WASM, Desktop/Skia, iOS, Android, Windows). Each head has different testing
tools and trade-offs.
| Head | Testing Approach | Tool | Speed | Fidelity |
| --------------------------- | -------------------- | --------------------- | ------ | --------------------------------- |
| **WASM** | Browser automation | Playwright | Medium | High -- real browser rendering |
| **Desktop (Skia/GTK, WPF)** | UI automation | Appium / WinAppDriver | Medium | High -- real desktop rendering |
| **iOS** | Simulator automation | Appium + XCUITest | Slow | Highest -- real iOS rendering |
| **Android** | Emulator automation | Appium + UIAutomator2 | Slow | Highest -- real Android rendering |
| **Unit (shared logic)** | In-memory | xUnit (no UI) | Fast | N/A -- logic only |
**Recommended priority:** Test shared business logic with unit tests first. Use Playwright against the WASM head for UI
verification -- it is the fastest UI testing path with the broadest coverage. Add platform-specific Appium tests only
for behaviors that differ between heads.
---
## Playwright for Uno WASM
The WASM head renders Uno apps in a browser, making Playwright the natural choice for UI testing.
### Test Infrastructure
````csharp
// NuGet: Microsoft.Playwright
public class UnoWasmFixture : IAsyncLifetime
{
public IPlaywright Playwright { get; private set; } = null!;
public IBrowser Browser { get; private set; } = null!;
public IPage Page { get; private set; } = null!;
private Process? _serverProcess;
public async ValueTask InitializeAsync()
{
// Start the WASM app (dotnet run or serve the published output)
_serverProcess = await StartWasmServerAsync();
Playwright = await Microsoft.Playwright.Playwright.CreateAsync();
Browser = await Playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = true
});
Page = await Browser.NewPageAsync();
// Wait for Uno WASM app to fully load
await Page.GotoAsync("http://localhost:5000");
await WaitForUnoAppReadyAsync();
}
public async ValueTask DisposeAsync()
{
await Page.CloseAsync();
await Browser.CloseAsync();
Playwright.Dispose();
_serverProcess?.Kill(entireProcessTree: true);
_serverProcess?.Dispose();
}
private async Task WaitForUnoAppReadyAsync()
{
// Uno WASM apps show a loading splash; wait for the app root to appear
await Page.WaitForSelectorAsync(
"[data-testid='app-root'], #uno-body",
new() { State = WaitForSelectorState.Visible, Timeout = 30_000 });
// Additional wait for Uno runtime initialization
await Page.WaitForFunctionAsync(
"() => document.querySelector('#uno-body')?.children.length > 0",
null,
new() { Timeout = 15_000 });
}
private static async Task<Process> StartWasmServerAsync()
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = "run --project src/MyApp/MyApp.Wasm.csproj --no-build",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
}
};
process.Start();
// Wait for server to be ready by probing the health endpoint
using var httpClient = new HttpClient();
var deadline = DateTime.UtcNow.AddSeconds(30);
while (DateTime.UtcNow < deadline)
{
try
{
var resp = await httpClient.GetAsync("http://localhost:5000");
if (resp.IsSuccessStatusCode) break;
}
catch (HttpRequestException)
{
// Server not ready yet
}
await Task.Delay(500);
}
return process;
}
}
```text
### WASM UI Tests
```csharp
public class UnoNavigationTests : IClassFixture<UnoWasmFixture>
{
private readonly IPage _page;
public UnoNavigationTests(UnoWasmFixture fixture)
{
_page = fixture.Page;
}
[Fact]
public async Task MainPage_LoadsSuccessfully()
{
// Uno WASM renders XAML controls as HTML elements
// Use AutomationProperties.AutomationId for selectors
var title = _page.Locator("[data-testid='main-title']");
await Expect(title).ToBeVisibleAsync();
await Expect(title).ToHaveTextAsync("Welcome");
}
[Fact]
public async Task Navigation_ClickSettings_ShowsSettingsPage()
{
await _page.ClickAsync("[data-testid='nav-settings']");
var settingsHeader = _page.Locator("[data-testid='settings-header']");
await Expect(settingsHeader).ToBeVisibleAsync();
await Expect(settingsHeader).ToHaveTextAsync("Settings");
}
}
```text
### Form Interaction Tests
```csharp
[Fact]
public async Task LoginForm_SubmitValid_NavigatesToDashboard()
{
await _page.FillAsync("[data-testid='username-input']", "testuser");
await _page.FillAsync("[data-testid='password-input']", "P@ssw0rd!");
await _page.ClickAsync("[data-testid='login-button']");
// Wait for navigation after login
var dashboard = _page.Locator("[data-testid='dashboard-title']");
await Expect(dashboard).ToBeVisibleAsync(
new() { Timeout = 10_000 });
}
[Fact]
public async Task TodoList_AddItem_AppearsInList()
{
await _page.FillAsync("[data-testid='todo-input']", "Buy groceries");
await _page.ClickAsync("[data-testid='add-todo-btn']");
var items = _page.Locator("[data-testid='todo-item']");
await Expect(items).ToHaveCountAsync(1);
await Expect(items.First).ToContainTextAsync("Buy groceries");
}
```text
---
## Platform-Specific Testing
### AutomationProperties for Cross-Platform Selectors
Uno maps `AutomationProperties.AutomationId` to each platform's native identifier:
```xml
<!-- Uno XAML -- works across all heads -->
<Page x:Class="MyApp.Views.LoginPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<TextBox AutomationProperties.AutomationId="username-input"
PlaceholderText="Username" />
<PasswordBox AutomationProperties.AutomationId="password-input"
PlaceholderText="Password" />
<Button AutomationProperties.AutomationId="login-button"
Content="Log In" />
<TextBlock AutomationProperties.AutomationId="error-message"
Foreground="Red" />
</StackPanel>
</Page>
```text
Platform mapping:
- **WASM:** Rendered as `data-testid` attribute (Playwright selector)
- **Android:** Maps to `content-desc` (Appium `AccessibilityId`)
- **iOS:** Maps to `accessibilityIdentifier` (Appium `AccessibilityId`)
- **Windows:** Maps to `AutomationId` (WinAppDriver `AccessibilityId`)
### Testing Platform-Specific Code
For code that varies by platform, use conditional compilation and separate test classes:
```csharp
// Shared test -- runs on all platforms
[Fact]
public async Task Settings_ChangeTheme_UpdatesUI()
{
await _page.ClickAsync("[data-testid='theme-toggle']");
var body = _page.Locator("[data-testid='app-root']");
await Expect(body).ToHaveAttributeAsync("data-theme", "dark");
}
// Platform-specific test
[Fact]
[Trait("Platform", "WASM")]
public async Task FileUpload_BrowserDialog_AcceptsFiles()
{
// WASM uses browser file picker -- test with Playwright file chooser
var fileChooserTask = _page.WaitForFileChooserAsync();
await _page.ClickAsync("[data-testid='upload-btn']");
var fileChooser = await fileChooserTask;
await fileChooser.SetFilesAsync("testdata/sample.pdf");
var fileName = _page.Locator("[data-testid='file-name']");
await Expect(fileName).ToHaveTextAsync("sample.pdf");
}
```text
---
## Runtime Head Validation
Validate that the same UI logic works correctly across different Uno runtime heads using shared test logic with platform-specific drivers.
### Shared Test Logic Pattern
```csharp
/// <summary>
/// Abstract base that defines UI tests once. Concrete subclasses provide
/// the driver for each platform (Playwright for WASM, Appium for mobile).
/// </summary>
public abstract class LoginTestsBase
{
protected abstract Task FillFieldAsync(string automationId, string value);
protected abstract Task ClickAsync(string automationId);
protected abstract Task<string> GetTextAsync(string automationId);
protected abstract Task WaitForElementAsync(string automationId, int timeoutMs = 5000);
[Fact]
public async Task Login_ValidCredentials_ShowsDashboard()
{
await FillFieldAsync("username-input", "alice");
await FillFieldAsync("password-input", "P@ssw0rd!");
await ClickAsync("login-button");
await WaitForElementAsync("dashboard-title");
var title = await GetTextAsync("dashboard-title");
Assert.Equal("Dashboard", title);
}
[Fact]
public async Task Login_EmptyPassword_ShowsValidationError()
{
await FillFieldAsync("username-input", "alice");
await ClickAsync("login-button");
await WaitForElementAsync("error-message");
var error = await GetTextAsync("error-message");
Assert.Contains("required", error, StringComparison.OrdinalIgnoreCase);
}
}
// WASM implementation
public class LoginTestsWasm : LoginTestsBase, IClassFixture<UnoWasmFixture>
{
private readonly IPage _page;
public LoginTestsWasm(UnoWasmFixture fixture) => _page = fixture.Page;
protected override async Task FillFieldAsync(string automationId, string value) =>
await _page.FillAsync($"[data-testid='{automationId}']", value);
protected override async Task ClickAsync(string automationId) =>
await _page.ClickAsync($"[data-testid='{automationId}']");
protected override async Task<string> GetTextAsync(string automationId) =>
await _page.Locator($"[data-testid='{automationId}']").TextContentAsync() ?? "";
protected override async Task WaitForElementAsync(string automationId, int timeoutMs = 5000) =>
await _page.WaitForSelectorAsync(
$"[data-testid='{automationId}']",
new() { Timeout = timeoutMs });
}
```text
---
## Key Principles
- **Test shared logic with unit tests first.** Uno's MVVM pattern means most business logic is testable without any UI framework.
- **Use Playwright + WASM as the primary UI testing path.** It is faster than mobile emulators and provides real rendering fidelity in a browser.
- **Use `AutomationProperties.AutomationId` on all testable controls.** It is the only selector strategy that works identically across all Uno heads.
- **Separate shared tests from platform-specific tests.** Use abstract base classes for shared test logic, concrete subclasses per platform.
- **Add platform-specific tests only for platform-divergent behavior.** File pickers, hardware buttons, gestures, and notifications differ across platforms; test these separately.
---
## Agent Gotchas
1. **Do not assume Uno WASM apps load instantly.** The Uno runtime must initialize (mono WASM bootstrap), which takes several seconds. Always wait for the app root element before interacting.
2. **Do not use CSS selectors for Uno WASM elements.** Uno generates its own DOM structure; CSS classes are internal and unstable. Use `data-testid` (from `AutomationProperties.AutomationId`) exclusively.
3. **Do not forget to build the WASM head before running Playwright tests.** `dotnet run` builds on demand, but `dotnet publish` is needed for production-like testing. Stale builds cause confusing test failures.
4. **Do not test mobile-specific features in the WASM head.** File system access, push notifications, biometrics, and NFC are not available in the browser. Skip or mock these in WASM tests.
5. **Do not run all platform tests in a single CI job.** Each platform requires its own SDK (Android SDK, Xcode, WinAppDriver). Use separate CI jobs per platform with appropriate runners.
---
## References
- [Uno Platform Testing Documentation](https://platform.uno/docs/articles/features/working-with-accessibility.html)
- [Playwright for .NET](https://playwright.dev/dotnet/)
- [Uno Platform WASM Head](https://platform.uno/docs/articles/getting-started/wizard/wasm.html)
- [AutomationProperties in UWP/WinUI](https://learn.microsoft.com/en-us/windows/apps/design/accessibility/basic-accessibility-information)
- [Uno Platform GitHub](https://github.com/unoplatform/uno)
````
## Code Navigation (Serena MCP)
**Primary approach:** Use Serena symbol operations for efficient code navigation:
1. **Find definitions**: `serena_find_symbol` instead of text search
2. **Understand structure**: `serena_get_symbols_overview` for file organization
3. **Track references**: `serena_find_referencing_symbols` for impact analysis
4. **Precise edits**: `serena_replace_symbol_body` for clean modifications
**When to use Serena vs traditional tools:**
- **Use Serena**: Navigation, refactoring, dependency analysis, precise edits
- **Use Read/Grep**: Reading full files, pattern matching, simple text operations
- **Fallback**: If Serena unavailable, traditional tools work fine
**Example workflow:**
```text
# Instead of:
Read: src/Services/OrderService.cs
Grep: "public void ProcessOrder"
# Use:
serena_find_symbol: "OrderService/ProcessOrder"
serena_get_symbols_overview: "src/Services/OrderService.cs"
```Related Skills
implementing-e2e-testing
Master end-to-end testing with Playwright and Cypress to build reliable test suites that catch bugs, improve confidence, and enable fast deployment. Use when implementing E2E tests, debugging flaky tests, or establishing testing standards.
idor-testing
This skill should be used when the user asks to "test for insecure direct object references," "find IDOR vulnerabilities," "exploit broken access control," "enumerate user IDs or obje...
File Path Traversal Testing
This skill should be used when the user asks to "test for directory traversal", "exploit path traversal vulnerabilities", "read arbitrary files through web applications", "find LFI vulnerabilities", or "access files outside web root". It provides comprehensive file path traversal attack and testing methodologies.
e2e-testing
End-to-end testing workflow with Playwright for browser automation, visual regression, cross-browser testing, and CI/CD integration.
e2e-testing-patterns
Master end-to-end testing with Playwright and Cypress to build reliable test suites that catch bugs, improve confidence, and enable fast deployment. Use when implementing E2E tests, debugging flaky tests, or establishing testing standards.
dotnet
.NET development standards and practices for zero-fabrication, test-driven development with strict quality gates. Use when working on .NET/C# projects that require rigorous testing, real integrations only, and co-located tests.
dotnet-security-owasp
Hardens .NET apps per OWASP Top 10 -- injection, auth, XSS, deprecated security APIs.
cve-testing
CVE vulnerability testing coordinator that identifies technology stacks, researches known vulnerabilities, and tests applications for exploitable CVEs using public exploits and proof-of-concept code.
cui-javascript-unit-testing
Jest unit testing standards covering configuration, test structure, testing patterns, and coverage requirements
configure-ux-testing
Check and configure UX testing infrastructure (Playwright, accessibility, visual regression)
comprehensive-unit-testing-with-pytest
Aims for high test coverage using pytest, testing both common and edge cases.
Burp Suite Web Application Testing
This skill should be used when the user asks to "intercept HTTP traffic", "modify web requests", "use Burp Suite for testing", "perform web vulnerability scanning", "test with Burp Repeater", "analyze HTTP history", or "configure proxy for web testing". It provides comprehensive guidance for using Burp Suite's core features for web application security testing.