

Also from Kynth Studios


Also from Kynth Studios


Also from Kynth Studios
12345# UI Testing Guidelines for .NET MAUI67## Overview89This document provides specific guidance for GitHub Copilot when writing UI tests for the .NET MAUI repository.10111213**Critical Principle**: UI tests should run on all applicable platforms (iOS, Android, Windows, MacCatalyst) by default unless there is a specific technical limitation.1415## UI Test Structure1617### Two-Project Requirement1819**CRITICAL: Every UI test requires code in TWO separate projects:**20211. **HostApp UI Test Page** (`src/Controls/tests/TestCases.HostApp/Issues/`)22 - Create the actual UI page that demonstrates the feature or reproduces the issue23 - **Prefer C# only** (`.cs` file) unless testing XAML-specific features (bindings, templates, styles)24 - Add `AutomationId` attributes on interactive controls for test automation25 - Follow naming convention: `IssueXXXXX.cs` (C# only) or `IssueXXXXX.xaml` + `IssueXXXXX.xaml.cs` (when XAML required)26 - XXXXX should correspond to a GitHub issue number when applicable27 - Ensure the UI provides clear visual feedback for the behavior being tested28 - Class must include `[Issue()]` attribute with tracker, number, description, and platform29302. **NUnit Test Implementation** (`src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/`)31 - Create corresponding Appium-based NUnit tests that inherit from `_IssuesUITest`32 - Use the `AutomationId` values to locate and interact with UI elements33 - Follow naming convention: `IssueXXXXX.cs` (matches the HostApp page file)34 - Include appropriate `[Category(UITestCategories.XYZ)]` attributes (only ONE per test)35 - Test should validate expected behavior through UI interactions and assertions3637### Base Class and Infrastructure3839- Each test class must inherit from `_IssuesUITest`40- The `_IssuesUITest` base class provides:41 - The `App` property for interacting with UI elements42 - Test initialization and setup43 - Helper methods for common UI test operations44- The test infrastructure automatically handles platform detection and page navigation4546### Naming Conventions4748**Test Files:**49- Pattern: `IssueXXXXX.cs` where XXXXX corresponds to a GitHub issue number50- Must match the corresponding HostApp page file name in TestCases.HostApp (either `.cs` only or `.xaml`)5152**Test Methods:**53- Use descriptive names that clearly explain what behavior is being verified54- ✅ Good: `VerifySafeAreaBottomPaddingWithKeyboard()`, `ButtonClickUpdatesLabel()`55- ❌ Bad: `Test1()`, `TestMethod()`, `RunTest()`5657**AutomationId Values:**58- Always use unique, descriptive `AutomationId` values59- Reference the same `AutomationId` in both C# code (or XAML if used) and test code60- Use PascalCase for AutomationId values6162## Complete Test Example6364### Example 1: C# Only (Preferred for Most Tests)6566**HostApp Page** (`TestCases.HostApp/Issues/Issue12345.cs`):67```csharp68namespace Maui.Controls.Sample.Issues;6970[Issue(IssueTracker.Github, 12345, "Button click updates label text", PlatformAffected.All)]71public class Issue12345 : ContentPage72{73 public Issue12345()74 {75 var resultLabel = new Label76 {77 Text = "Initial Text",78 AutomationId = "ResultLabel"79 };8081 Content = new VerticalStackLayout82 {83 Children =84 {85 new Button86 {87 Text = "Click Me",88 AutomationId = "TestButton",89 Command = new Command(() => resultLabel.Text = "Expected Text")90 },91 resultLabel92 }93 };94 }95}96```9798### Example 2: XAML (When Testing XAML-Specific Features)99100**HostApp XAML** (`TestCases.HostApp/Issues/Issue12346.xaml`):101```xaml102<?xml version="1.0" encoding="utf-8" ?>103<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"104 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"105 x:Class="Maui.Controls.Sample.Issues.Issue12346">106 <VerticalStackLayout>107 <Button Text="Click Me"108 AutomationId="TestButton"109 Clicked="OnButtonClicked" />110 <Label Text="Initial Text"111 x:Name="ResultLabel"112 AutomationId="ResultLabel" />113 </VerticalStackLayout>114</ContentPage>115```116117**HostApp Code-Behind** (`TestCases.HostApp/Issues/Issue12346.xaml.cs`):118```csharp119namespace Maui.Controls.Sample.Issues;120121[Issue(IssueTracker.Github, 12346, "Testing XAML binding behavior", PlatformAffected.All)]122public partial class Issue12346 : ContentPage123{124 public Issue12346()125 {126 InitializeComponent();127 }128129 void OnButtonClicked(object sender, EventArgs e)130 {131 ResultLabel.Text = "Expected Text";132 }133}134```135136### NUnit Test (Same for Both Examples)137138**NUnit Test** (`TestCases.Shared.Tests/Tests/Issues/Issue12345.cs` or `Issue12346.cs`):139```csharp140public class Issue12345 : _IssuesUITest141{142 public override string Issue => "Description of the issue being tested";143144 public Issue12345(TestDevice device) : base(device) { }145146 [Test]147 [Category(UITestCategories.Layout)] // Pick the most appropriate category148 public void ButtonClickUpdatesLabel()149 {150 // Wait for element to be ready151 App.WaitForElement("TestButton");152153 // Interact with the UI154 App.Tap("TestButton");155156 // Verify expected behavior157 var labelText = App.FindElement("ResultLabel").GetText();158 Assert.That(labelText, Is.EqualTo("Expected Text"));159160 // Optional: Visual verification161 VerifyScreenshot();162 }163}164```165166## Common Patterns167168### Waiting for Elements169```csharp170App.WaitForElement("AutomationId");171```172173### Interacting with Elements174```csharp175App.Tap("AutomationId");176App.FindElement("AutomationId").GetText();177var rect = App.WaitForElement("AutomationId").GetRect();178```179180### Assertions181```csharp182Assert.That(actualValue, Is.EqualTo(expectedValue).Within(tolerance));183Assert.That(rect.Height, Is.LessThanOrEqualTo(maxHeight));184```185186### Screenshot Verification187```csharp188// Verify visual appearance (automated comparison)189VerifyScreenshot();190191// With custom name192VerifyScreenshot("CustomTestName");193194// With tolerance (0.0-100.0 percentage) - use sparingly195VerifyScreenshot(tolerance: 0.5); // Allow 0.5% difference for cross-machine rendering variance196197// PREFERRED: Keep retrying for up to 2 seconds (for animations)198VerifyScreenshot(retryTimeout: TimeSpan.FromSeconds(2));199200// Combined: tolerance for rendering variance + retryTimeout for timing201VerifyScreenshot(tolerance: 0.5, retryTimeout: TimeSpan.FromSeconds(2));202203// Manual screenshot for debugging204App.Screenshot("TestStep1");205```206207**CRITICAL - VerifyScreenshot() Built-in Features:**208209`VerifyScreenshot()` **already includes** stability mechanisms. Do NOT add redundant delays:210211| Feature | Behavior | Parameter |212|---------|----------|-----------|213| **Android delay** | Automatic 350ms wait for animations | Built-in, cannot override |214| **Retry logic** | Default: retries once; with retryTimeout: keeps retrying | Built-in |215| **Retry delay** | 500ms delay between retry attempts | `retryDelay: TimeSpan` (customizable) |216| **Retry timeout** | Total time to keep retrying | `retryTimeout: TimeSpan` (PREFERRED for flaky tests) |217| **Tolerance** | Allow percentage difference (0-100) | `tolerance: double` (default: 0.0) |218219**When to customize:**220- ✅ Use `retryTimeout` parameter for animations with variable timing (PREFERRED approach)221- ✅ Use small `tolerance` (0.5%) for cross-machine rendering variance, NOT to hide timing issues222- ✅ Use `retryDelay` if you need to change the delay between retry attempts223- ❌ **DO NOT** add `Task.Delay()` or `Thread.Sleep()` before `VerifyScreenshot()` - use `retryTimeout` instead224225## Writing Robust UI Tests226227### Best Practices for Screenshot Tests228229When writing tests that use `VerifyScreenshot()`, follow these patterns to avoid flakiness:230231```232┌─────────────────────────────────────────────────────────────────┐233│ 1. UNDERSTAND TEST INFRASTRUCTURE │234│ - Read UITest.cs base class implementation │235│ - Understand built-in retry/delay/tolerance mechanisms │236│ - Check what helpers/extensions already exist │237├─────────────────────────────────────────────────────────────────┤238│ 2. USE PROPER WAITING PATTERNS │239│ - Use WaitForElement before interacting with elements │240│ - Use retryTimeout for screenshots after animations │241│ - Never use arbitrary Task.Delay() before VerifyScreenshot │242├─────────────────────────────────────────────────────────────────┤243│ 3. APPLY MINIMAL TOLERANCES │244│ - Use retryTimeout for timing issues (preferred) │245│ - Use small tolerance (0.5%) only for rendering variance │246│ - Never use tolerance > 5% without justification │247└─────────────────────────────────────────────────────────────────┘248```249250### Common Flaky Test Patterns251252| Symptom | Root Cause | Fix Pattern | Anti-Pattern |253|---------|------------|-------------|--------------|254| **Visual diff in screenshot** | Animation not finished | `VerifyScreenshot(retryTimeout: TimeSpan.FromSeconds(2))` | ❌ Adding `Task.Delay()` before |255| **Element not found** | Element not rendered yet | `App.WaitForElement("Id", timeout: TimeSpan.FromSeconds(10))` | ❌ `Thread.Sleep()` then `FindElement()` |256| **Timeout on interaction** | Page not fully loaded | Wait for specific element that indicates ready state | ❌ Arbitrary 3-second delay |257| **Inconsistent rect/position** | Layout not settled | Multiple `GetRect()` calls with comparison | ❌ Single `GetRect()` after delay |258| **WebView failures** | External URL/network | Use mock URLs instead of external URLs | ❌ Adding longer timeouts |259260### Anti-Patterns (DO NOT DO)261262| Anti-Pattern | Why It's Wrong | Better Alternative |263|--------------|----------------|-------------------|264| ❌ `Task.Delay(500).Wait()` before `VerifyScreenshot()` | VerifyScreenshot already has built-in retry; use retryTimeout instead | Use `VerifyScreenshot(retryTimeout: TimeSpan.FromSeconds(2))` |265| ❌ `Thread.Sleep(2000)` before element interaction | Arbitrary wait; doesn't guarantee element is ready | `App.WaitForElement("Id", timeout: ...)` |266| ❌ Adding tolerance > 5% without justification | Hides real bugs; too permissive | Use `retryTimeout` for timing issues; small tolerance (0.5%) for rendering variance |267| ❌ Using external URLs in WebView tests | External dependency; unreliable | Use mock URLs or local content |268| ❌ Fixing symptoms without understanding infrastructure | Redundant fixes; doesn't address root cause | Read `UITest.cs` first (step 1 above) |269270### When to Use What271272**VerifyScreenshot() parameters (preferred):**273```csharp274// Animation timing issues - keep retrying for up to 2 seconds275// This is the PREFERRED approach for flaky screenshot tests276VerifyScreenshot(retryTimeout: TimeSpan.FromSeconds(2));277278// Small tolerance for cross-machine rendering variance + retryTimeout for timing279// Use 0.5% tolerance as safety margin, NOT to hide timing issues280VerifyScreenshot(tolerance: 0.5, retryTimeout: TimeSpan.FromSeconds(2));281282// Legacy: retryDelay only changes delay BETWEEN retries (default 500ms)283// retryTimeout is preferred because it keeps trying until success284VerifyScreenshot(retryDelay: TimeSpan.FromSeconds(1));285```286287**Key difference: retryDelay vs retryTimeout:**288- `retryDelay`: Delay between retry attempts (default 500ms). Only retries ONCE.289- `retryTimeout`: Total time to keep retrying. Retries every `retryDelay` until timeout.290- **Prefer `retryTimeout`** for animations with variable completion times.291292**WaitForElement (for element readiness):**293```csharp294// Wait up to 10 seconds for element to appear295App.WaitForElement("ButtonId", timeout: TimeSpan.FromSeconds(10));296297// Then interact298App.Tap("ButtonId");299```300301**Task.Delay/Thread.Sleep (avoid if possible):**302```csharp303// AVOID: With retryTimeout, you rarely need explicit delays anymore304//305// Old pattern (before retryTimeout):306// Task.Delay(300).Wait();307// VerifyScreenshot(tolerance: 2.0);308//309// New pattern (preferred):310VerifyScreenshot(tolerance: 0.5, retryTimeout: TimeSpan.FromSeconds(2));311312// ONLY use explicit delays when:313// 1. Waiting for non-element state with no screenshot (rare)314// 2. External system delay that can't be detected otherwise315// 3. After exhausting other options AND documenting why316```317318### Understanding Test Infrastructure319320**Key files to understand when writing UI tests:**3213221. **UITest.cs** - Base class with `VerifyScreenshot()` implementation323 - Path: `src/Controls/tests/TestCases.Shared.Tests/UITest.cs`324 - Contains: retry logic, tolerance parsing, platform-specific delays3253262. **_IssuesUITest.cs** - Issues test base class327 - Path: `src/Controls/tests/TestCases.Shared.Tests/_IssuesUITest.cs`328 - Contains: Navigation helpers, common patterns3293303. **Extension methods** - Platform-specific helpers331 - Path: `src/Controls/tests/TestCases.Shared.Tests/` (various extension files)332 - Contains: Existing helpers for common operations333334**Find existing patterns:**335```bash336# See VerifyScreenshot implementation (including retryTimeout)337grep -A 30 "public void VerifyScreenshot" src/Controls/tests/TestCases.Shared.Tests/UITest.cs338339# Find existing tests using retryTimeout (preferred pattern)340grep -r "retryTimeout" src/Controls/tests/TestCases.Shared.Tests/Tests/341342# Find existing tolerance patterns343grep -r "tolerance:" src/Controls/tests/TestCases.Shared.Tests/Tests/344```345346### Infrastructure Notes347348**Tolerance regex handles multiple locales:** The tolerance parsing uses regex pattern `\d+[.,]\d+` to match both `.` and `,` as decimal separators (e.g., "2.5%" or "2,5%"). If tolerance appears to not be applied, verify the regex patterns in `UITest.cs` `VerifyWithTolerance()` method.349350## Test Categories351352### Category Guidelines353- Use appropriate categories from `UITestCategories`354- **Only ONE** `[Category]` attribute per test355- Pick the most specific category that applies356357### Test Categories358359**CRITICAL**: Always check [UITestCategories.cs](../../src/Controls/tests/TestCases.Shared.Tests/UITestCategories.cs) for the authoritative, complete list of categories.360361**Selection rule**: Choose the MOST SPECIFIC category that applies to your test. If multiple categories seem applicable, choose the one that best describes the primary focus of the test.362363**Common categories** (examples only - not exhaustive):364- **SafeArea**: `SafeAreaEdges` - Safe area and padding tests365- **Basic controls**: `Button`, `Label`, `Entry`, `Editor` - Specific control tests366- **Collection controls**: `CollectionView`, `ListView`, `CarouselView` - Collection control tests367- **Layout**: `Layout` - Layout-related tests368- **Navigation**: `Shell`, `Navigation`, `TabbedPage` - Navigation tests369- **Interaction**: `Gestures`, `Focus`, `Accessibility` - Interaction tests370- **Lifecycle**: `Window`, `Page`, `LifeCycle` - Page lifecycle tests371372**List all categories programmatically**:373```bash374grep -E "public const string [A-Za-z]+ = " src/Controls/tests/TestCases.Shared.Tests/UITestCategories.cs375```376377**Important**: When a new UI test category is added to `UITestCategories.cs`, also update `eng/pipelines/common/ui-tests.yml` to include the new category.378379## Platform Coverage380381### Default Behavior382383Tests should run on all applicable platforms by default. The test infrastructure handles platform detection automatically.384385### No Inline #if Directives in Test Methods386387**Do NOT use `#if ANDROID`, `#if IOS`, etc. directly in test methods.** Platform-specific behavior must be hidden behind extension methods for readability.388389**Note:** This rule is about **code cleanliness**, not platform scope. Using `#if ANDROID ... #else ...` still compiles for all platforms - the issue is that inline directives make test logic hard to read and maintain.390391```csharp392// ❌ BAD - inline #if in test method (hard to read)393[Test]394public void MyTest()395{396#if ANDROID397 App.TapCoordinates(100, 200);398#else399 App.Tap("MyElement");400#endif401}402403// ✅ GOOD - platform logic in extension method (clean test)404[Test]405public void MyTest()406{407 App.TapElementCrossPlatform("MyElement");408}409```410411Move platform-specific logic to extension methods to keep test code clean and readable.412413## Running UI Tests Locally414415**CRITICAL: ALWAYS use the BuildAndRunHostApp.ps1 script to run UI tests. NEVER run `dotnet test` or `dotnet build` commands manually.**416417### BuildAndRunHostApp.ps1 Script (ONLY Way to Run Tests)418419**Script location**: `.github/scripts/BuildAndRunHostApp.ps1`420421**Usage:**422```powershell423# Run specific test on Android424pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform android -TestFilter "FullyQualifiedName~Issue12345"425426# Run specific test on iOS427pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "FullyQualifiedName~Issue12345"428429# Run specific test on MacCatalyst430pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform maccatalyst -TestFilter "FullyQualifiedName~Issue12345"431432# Run tests by category433pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform android -Category "SafeAreaEdges"434435# Run specific test with custom device (iOS only)436pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "Issue12345" -DeviceUdid "12345678-1234567890ABCDEF"437```438439**What the script handles automatically:**440- ✅ Automatic device detection and boot (iPhone Xs for iOS, first available for Android)441- ✅ Building TestCases.HostApp (always fresh build)442- ✅ App installation and deployment443- ✅ Running your NUnit test via `dotnet test`444- ✅ Complete log capture to `CustomAgentLogsTmp/UITests/` directory:445 - `android-device.log` or `ios-device.log` - Device logs filtered to HostApp446 - `test-output.log` - Test execution output447448**Why you must use the script:**449- The script ensures correct device targeting and environment variables450- It handles platform-specific quirks and setup requirements451- It provides consistent test execution across all platforms452- It captures logs automatically for debugging453- Manual `dotnet` commands often fail due to missing environment setup454455### Prerequisites: Kill Existing Appium Processes456457**CRITICAL**: Before running UITests with BuildAndRunHostApp.ps1, always kill any existing Appium processes. The UITest framework needs to start its own Appium server, and having a stale process running will cause the tests to fail with an error like:458459```460AppiumServerHasNotBeenStartedLocallyException: The local appium server has not been started.461Time 120000 ms for the service starting has been expired!462```463464**Solution: Always kill existing Appium processes before running tests:**465466```bash467# Kill any Appium processes on port 4723468lsof -i :4723 | grep LISTEN | awk '{print $2}' | xargs kill -9 2>/dev/null && echo "✅ Killed existing Appium processes" || echo "ℹ️ No Appium processes running on port 4723"469```470471**Why this is needed:** The UITest framework automatically starts and manages its own Appium server. If there's already an Appium process running (from a previous test run or manual testing), the framework will timeout trying to start a new one.472473### Troubleshooting474475**Android App Crashes on Launch:**476477If you encounter navigation fragment errors or resource ID issues:478```479java.lang.IllegalArgumentException: No view found for id 0x7f0800f8 (com.microsoft.maui.uitests:id/inward) for fragment NavigationRootManager_ElementBasedFragment480```481482**Solution:** Read the crash logs to find the actual exception:483```bash484# Monitor logcat for the crash485adb logcat | grep -E "(FATAL|AndroidRuntime|Exception|Error|Crash)"486```487488**Debugging steps:**4891. **Find the exception** in logcat - look for the stack trace4902. **Investigate the root cause** - What line of code is throwing? Why?4913. **Check for null references** - Are required resources missing?4924. **Verify resource IDs exist** - Check if the ID referenced actually exists in the app4935. If you can't determine the fix, **ask for guidance** with the full exception details494495**iOS App Crashes on Launch or Won't Start with Appium:**496497If the iOS app crashes when launched by Appium or manually with `xcrun simctl launch`:498499**Solution:** Read the crash logs to find the actual exception:500```bash501# Capture crash logs502xcrun simctl spawn booted log stream --predicate 'processImagePath contains "TestCases.HostApp"' --level=debug > /tmp/ios_crash.log 2>&1 &503LOG_PID=$!504505# Try to launch the app506xcrun simctl launch $UDID com.microsoft.maui.uitests507508# Wait a moment for crash509sleep 3510511# Stop log capture512kill $LOG_PID513514# Review the crash log515cat /tmp/ios_crash.log | grep -A 20 -B 5 "Exception"516```517518**Debugging steps:**5191. **Find the exception** in the crash log - look for stack traces5202. **Investigate the root cause** - What's causing the crash?5213. **Check for missing resources** - Are all required files included in the bundle?5224. **Verify Info.plist** - Are required keys present?5235. **Check for platform-specific issues** - iOS version compatibility, permissions, etc.5246. If you can't determine the fix, **ask for guidance** with the full exception details525526### Dangerous System Commands (Never Run)527528**🚨 NEVER run these commands — they cause destructive system-wide side effects:**529530- **`tccutil reset`** — Wipes ALL macOS permissions (Accessibility, Camera, etc.) system-wide. This breaks Appium/WebDriverAgent, Xcode, and other tools. Once reset, permissions must be manually re-granted through System Settings.531- **`csrutil disable`** — Disables System Integrity Protection532- **`networksetup`** — Modifies network configuration533- **`defaults delete`** on system domains — Resets system preferences534535**General rule:** Do not run commands that modify macOS system-level privacy, security, or permission settings. If you need to check permissions, read them — never reset or modify them.536537## Before Committing538539Verify the following checklist before committing UI tests:540541- [ ] Compile both the HostApp project and TestCases.Shared.Tests project successfully542- [ ] Verify AutomationId references match between HostApp UI (C# or XAML) and test code543- [ ] Ensure file names follow the `IssueXXXXX` pattern and match between projects544- [ ] Ensure test methods have descriptive names545- [ ] Verify test inherits from `_IssuesUITest`546- [ ] Confirm only ONE `[Category]` attribute per test547- [ ] No inline `#if` directives in test code (use extension methods)548- [ ] Test passes locally on at least one platform549550### Test State Management551552- Tests should be independent and not rely on state from other tests553- The test infrastructure handles navigation to the test page and basic cleanup554- If your test modifies global app state, consider whether cleanup is needed555- Most tests don't require explicit cleanup as each test gets a fresh page instance556557## Best Practices558559### Default: C# Over XAML560561**Use C# files (`.cs`) for UI tests. Only use XAML files (`.xaml`) when the test scenario requires XAML-specific features.**562563**When to use C# only (`.cs` file):**564- ✅ Simple control tests (Button, Label, Entry, etc.)565- ✅ Layout tests (Grid, StackLayout, FlexLayout, etc.)566- ✅ Navigation tests567- ✅ Event handling tests568- ✅ Property tests569- ✅ Most UI behavior tests570571**When XAML is required (`.xaml` + `.xaml.cs` files):**572- ✅ Testing XAML binding syntax573- ✅ Testing XAML templates (DataTemplate, ControlTemplate)574- ✅ Testing XAML styles and resources575- ✅ Testing XAML markup extensions576- ✅ Testing XamlC compilation behavior577- ✅ Testing XAML-specific parsing or compilation issues578579**Examples:**580581```csharp582// ✅ GOOD: C# only test (most common pattern)583public class Issue12345 : ContentPage584{585 public Issue12345()586 {587 Content = new StackLayout588 {589 Children =590 {591 new Label { Text = "Hello", AutomationId = "MyLabel" },592 new Button { Text = "Click Me", AutomationId = "MyButton" }593 }594 };595 }596}597```598599```xaml600<!-- ❌ AVOID unless testing XAML bindings/templates/styles -->601<ContentPage ...>602 <StackLayout>603 <Label Text="Hello" AutomationId="MyLabel" />604 <Button Text="Click Me" AutomationId="MyButton" />605 </StackLayout>606</ContentPage>607```608609### Use Test Helper Base Classes610611**ALWAYS check for and use existing test helper base classes instead of creating from scratch:**612613| Base Class | Use For | Example |614|------------|---------|---------|615| `TestShell` | Shell-related tests | `public class Issue12345 : TestShell` |616| `TestContentPage` | ContentPage tests needing `Init()` pattern | `public class Issue12345 : TestContentPage` |617| `TestNavigationPage` | NavigationPage tests | `public class Issue12345 : TestNavigationPage` |618| `ContentPage` | Simple page tests (direct inheritance) | `public class Issue12345 : ContentPage` |619620**TestShell provides:**621- Platform-specific automation IDs for flyout and back buttons622- Helper methods: `AddContentPage()`, `AddBottomTab()`, `AddTopTab()`, `AddFlyoutItem()`623- Abstract `Init()` method for setup624- `DisplayedPage` property for accessing current page625626**TestContentPage/TestNavigationPage provide:**627- Abstract `Init()` method for deferred initialization628- Cleaner separation of setup logic629630**Example:**631632```csharp633// ✅ GOOD: Using TestShell for Shell tests634[Issue(IssueTracker.Github, 12345, "Shell navigation bug", PlatformAffected.All)]635public class Issue12345 : TestShell636{637 protected override void Init()638 {639 AddContentPage(new ContentPage640 {641 Content = new Label { Text = "Test" }642 });643 }644}645646// ❌ BAD: Creating Shell from scratch647public class Issue12345 : Shell648{649 public Issue12345()650 {651 Items.Add(new ShellItem { ... }); // Verbose, error-prone652 }653}654```655656### Avoid Obsolete APIs657658**NEVER use obsolete APIs in new tests. Use modern equivalents:**659660| ❌ Obsolete API | ✅ Modern API | Notes |661|----------------|--------------|-------|662| `Application.MainPage` | `Window.Page` | Access via `this.Window.Page` in ContentPage |663| `Application.MainPage` | `Application.Current.Windows[0].Page` | When not in Page context |664| `Frame` | `Border` | Frame is deprecated, use Border instead |665| `Device.BeginInvokeOnMainThread` | `Dispatcher.Dispatch` or `MainThread.BeginInvokeOnMainThread` | Modern threading APIs |666667**Examples:**668669```csharp670// ✅ GOOD: Modern Window API671this.Window.Page = new NavigationPage(new MyPage());672673// ❌ BAD: Obsolete Application.MainPage674Application.MainPage = new NavigationPage(new MyPage());675676// ✅ GOOD: Border677new Border { Content = new Label { Text = "Hello" } }678679// ❌ BAD: Frame (deprecated)680new Frame { Content = new Label { Text = "Hello" } }681```682683### Use UITest Optimized Controls for Screenshot Tests684685**For tests that use `VerifyScreenshot()`, use UITest optimized controls instead of standard text input controls.** These controls provide `IsCursorVisible` to prevent cursor blinking from causing flaky screenshot comparisons.686687| Standard Control | UITest Control | Purpose |688|------------------|----------------|---------|689| `Entry` | `UITestEntry` | Text input without cursor blink |690| `Editor` | `UITestEditor` | Multi-line input without cursor blink |691| `SearchBar` | `UITestSearchBar` | Search input without cursor blink |692693**Example:**694695```csharp696// For screenshot tests, use UITest controls (UITestEntry, UITestEditor, UITestSearchBar)697var entry = new UITestEntry698{699 Placeholder = "Enter text",700 IsCursorVisible = false, // Prevents flaky screenshots701 AutomationId = "TestEntry"702};703704// For non-screenshot tests, standard Entry is fine705var entry = new Entry { Placeholder = "Enter text", AutomationId = "TestEntry" };706```707708**Location:** `src/Controls/tests/TestCases.HostApp/Controls/UITest*.cs`709710### Check Similar Tests for Patterns711712**Before creating a new test, search for similar tests to reuse patterns:**713714```bash715# Find similar control tests716grep -r "class.*Issue.*Button" src/Controls/tests/TestCases.HostApp/Issues/*.cs717718# Find Shell tests719grep -r "TestShell" src/Controls/tests/TestCases.HostApp/Issues/*.cs720721# Find tests for specific control722grep -r "CollectionView" src/Controls/tests/TestCases.HostApp/Issues/*.cs723724# Find tests using UITest optimized controls725grep -r "UITestEntry\|UITestEditor\|UITestSearchBar" src/Controls/tests/TestCases.HostApp/Issues/*.cs726```727728**Reuse established patterns:**729- AutomationId naming conventions730- Test structure and layout731- Common helper methods732- Platform-specific workarounds733- UITest optimized control usage734735### Safe Area Testing (iOS/MacCatalyst)736737**⚠️ CRITICAL for macCatalyst safe area tests:**738739Safe area behavior differs significantly between macOS versions. Tests must account for this variability.740741| macOS Version | Title Bar Safe Area | CI Environment |742|---------------|---------------------|----------------|743| **macOS 14/15** | ~28px top inset | ✅ Used by CI |744| **macOS 26 (Liquid Glass)** | ~0px top inset | ❌ Local dev only |745746**Rules for safe area tests:**7477481. **Use tolerances for safe area measurements** - Exact pixel values vary by macOS version7492. **Test behavior, not exact values** - Verify content is NOT obscured, rather than checking exact padding pixels7503. **Use `GetRect()` for child content position** - Measure where content actually appears, not parent size7514. **Never hardcode safe area expectations** - Tests should pass on macOS 14/15 AND macOS 26752753**Example patterns:**754755```csharp756// ❌ BAD: Hardcoded safe area value (breaks across macOS versions)757var safeArea = element.GetRect();758Assert.That(safeArea.Y, Is.EqualTo(28)); // Fails on macOS 26759760// ✅ GOOD: Test that content is not obscured by title bar761var contentRect = App.WaitForElement("MyContent").GetRect();762var titleBarRect = App.WaitForElement("TitleBar").GetRect();763Assert.That(contentRect.Y, Is.GreaterThanOrEqualTo(titleBarRect.Height),764 "Content should not be obscured by title bar");765766// ✅ GOOD: Use tolerance for safe area (accounts for OS differences)767Assert.That(contentRect.Y, Is.GreaterThan(0).And.LessThan(50),768 "Content should have some top padding but not excessive");769```770771**Test category**: Use `UITestCategories.SafeAreaEdges` for safe area tests.772773**Platform scope**: Safe area tests should typically run on iOS and MacCatalyst (not just one).774775**See also**: `.github/instructions/safe-area-debugging.instructions.md` for investigation guidelines776
One repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| dotnet/maui.github/instructions/public-api.instructions.md · 23k | Copilot instructions | apido-not | 59/100 | 9 days ago | |
| dotnet/maui.github/copilot-instructions.md · 23k | Copilot instructions | setuptestlint-formatstyle+6 | 76/100 | 14 days ago | |
| dotnet/maui.github/instructions/android.instructions.md · 23k | Copilot instructions | buildstyle | 70/100 | 14 days ago | |
| dotnet/maui.github/instructions/ci-copilot-pipeline-security.instructions.md · 23k | Copilot instructions | gitsecuritydeploymentdo-not+1 | 76/100 | 14 days ago | |
| dotnet/maui.github/instructions/collectionview-android.instructions.md · 23k | Copilot instructions | stylearchperformanceagent-behaviour | 52/100 | 14 days ago | |
| dotnet/maui.github/instructions/collectionview-handler-detection.instructions.md · 23k | Copilot instructions | stylegitdo-not | 73/100 | 14 days ago | |
| dotnet/maui.github/instructions/collectionview-ios.instructions.md · 23k | Copilot instructions | styleperformance | 48/100 | 14 days ago | |
| dotnet/maui.github/instructions/collectionview-windows.instructions.md · 23k | Copilot instructions | stylearch | 52/100 | 14 days ago | |
| dotnet/maui.github/instructions/handler-patterns.instructions.md · 23k | Copilot instructions | styledo-not | 55/100 | 14 days ago | |
| dotnet/maui.github/instructions/helix-device-tests.instructions.md · 23k | Copilot instructions | setupbuildtestarch | 74/100 | 14 days ago | |
| dotnet/maui.github/instructions/integration-tests.instructions.md · 23k | Copilot instructions | setupteststyledo-not | 92/100 | 14 days ago | |
| dotnet/maui.github/instructions/layout-system.instructions.md · 23k | Copilot instructions | archapiperformancedo-not | 55/100 | 14 days ago | |
| dotnet/maui.github/instructions/performance-hotpaths.instructions.md · 23k | Copilot instructions | styleperformancedo-not | 55/100 | 14 days ago | |
| dotnet/maui.github/instructions/sandbox.instructions.md · 23k | Copilot instructions | buildteststyletesting-strategy+4 | 81/100 | 14 days ago | |
| dotnet/maui.github/instructions/templates.instructions.md · 23k | Copilot instructions | buildteststylearch+1 | 92/100 | 14 days ago | |
| dotnet/maui.github/instructions/threading-async.instructions.md · 23k | Copilot instructions | styleui | 48/100 | 14 days ago | |
| dotnet/maui.github/instructions/xaml-unittests.instructions.md · 23k | Copilot instructions | teststyledocs | 70/100 | 14 days ago | |
| dotnet/maui.github/instructions/safe-area-ios.instructions.md · 23k | Copilot instructions | stylegit | 43/100 | 14 days ago |
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| dotnet/roslyn.github/instructions/Compiler.instructions.md · 21k | Copilot instructions | buildteststylearch+3 | 99/100 | today | |
| ardalis/CleanArchitecture.github/copilot-instructions.md · 18k | Copilot instructions | buildteststylearch+4 | 96/100 | 14 days ago | |
| dotnet/maui.github/instructions/templates.instructions.md · 23k | Copilot instructions | buildteststylearch+1 | 92/100 | 14 days ago | |
| dotnet/maui.github/instructions/integration-tests.instructions.md · 23k | Copilot instructions | setupteststyledo-not | 92/100 | 14 days ago | |
| dotnet/roslyn.github/copilot-instructions.md · 21k | Copilot instructions | buildteststylearch+3 | 89/100 | 8 days ago | |
| we-promise/sure.github/copilot-instructions.md · 9.5k | Copilot instructions | setuptestlint-formatstyle+10 | 88/100 | 13 days ago | |
| microsoft/WSL.github/copilot-instructions.md · 33k | Copilot instructions | setupbuildtestlint-format+7 | 88/100 | 14 days ago | |
| PowerShell/PowerShell.github/instructions/start-native-execution.instructions.md · 55k | Copilot instructions | buildstylearchgit+1 | 86/100 | 14 days ago |
A badge carrying the measured quality of the strongest agent config file in this repository, out of 100. It reads from this index every time somebody loads your page, so it changes when the measurement changes and there is nothing to keep up to date. Free, no account, and the value is not something you or we can set by hand.
[](https://rulestack.kynth.studio/configs/dotnet-maui-github-instructions-uitests-instructions)Would rather not hotlink us? Every badge is also served in shields.io’s endpoint schema, so shields renders the image and your readers never talk to our domain:
Published by Toolproof, the masthead over this index and eight others. The method behind the number is at toolproof.kynth.studio/methodology, and the whole thing is readable as JSON with no key at /api.