RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Diff/dotnet-maui-github-copilot-instructions ↔ dotnet-maui-github-instructions-uitests-instructions

Comparison

A · Copilot instructions · dotnet/mauiB · Copilot instructions · dotnet/maui
What each file covers, counted
DimensionSharedOnly in AOnly in BOverlap
Sections036590%
Commands11825%
Section tags73258%

What each file covers

Sections

0 shared · 36 only in A · 59 only in B
  • − GitHub Copilot Development Environment Instructions
  • − Code Review Instructions
  • − Repository Overview
  • − Key Technologies
  • − Development Environment Setup
  • − Platform-Specific Requirements
  • − Project Structure
  • − Important Directories
  • − Platform-Specific Code Organization
  • − Platform-Specific File Extensions
  • − Sample Projects
  • − Development Workflow
  • − Testing
  • − CI Pipelines (Azure DevOps)
  • − Investigating CI Failures
  • − Gradle / Maven Dependency Failures (CFSClean)
  • − Code Formatting
  • − Contribution Guidelines
  • − Handling Existing PRs for Assigned Issues
  • − Auto-Generated Files (Never Commit)
  • − PublicAPI.Unshipped.txt File Management
  • − Branching
  • − Git Workflow (Copilot CLI Rules)
  • − Create a feature branch (NEVER work directly on main)
  • − Make commits normally
  • − Push to remote (for new branches)
  • − For subsequent pushes on the same branch
  • − Check out the PR branch directly (do NOT create a new branch off it)
  • − Make fixes and commit to the PR branch
  • − Documentation
  • − Opening PRs
  • − Custom Agents and Skills
  • − Skills vs Agents
  • − Available Custom Agents
  • − Reusable Skills
  • − Using Custom Agents
  • + UI Testing Guidelines for .NET MAUI
  • + Overview
  • + UI Test Structure
  • + Two-Project Requirement
  • + Base Class and Infrastructure
  • + Naming Conventions
  • + Complete Test Example
  • + Example 1: C# Only (Preferred for Most Tests)
  • + Example 2: XAML (When Testing XAML-Specific Features)
  • + NUnit Test (Same for Both Examples)
  • + Common Patterns
  • + Waiting for Elements
  • + Interacting with Elements
  • + Assertions
  • + Screenshot Verification
  • + Writing Robust UI Tests
  • + Best Practices for Screenshot Tests
  • + Common Flaky Test Patterns
  • + Anti-Patterns (DO NOT DO)
  • + When to Use What
  • + Understanding Test Infrastructure
  • + See VerifyScreenshot implementation (including retryTimeout)
  • + Find existing tests using retryTimeout (preferred pattern)
  • + Find existing tolerance patterns
  • + Infrastructure Notes
  • + Test Categories
  • + Category Guidelines
  • + Platform Coverage
  • + Default Behavior
  • + No Inline #if Directives in Test Methods
  • + Running UI Tests Locally
  • + BuildAndRunHostApp.ps1 Script (ONLY Way to Run Tests)
  • + Run specific test on Android
  • + Run specific test on iOS
  • + Run specific test on MacCatalyst
  • + Run tests by category
  • + Run specific test with custom device (iOS only)
  • + Prerequisites: Kill Existing Appium Processes
  • + Kill any Appium processes on port 4723
  • + Troubleshooting
  • + Monitor logcat for the crash
  • + Capture crash logs
  • + Try to launch the app
  • + Wait a moment for crash
  • + Stop log capture
  • + Review the crash log
  • + Dangerous System Commands (Never Run)
  • + Before Committing
  • + Test State Management
  • + Best Practices
  • + Default: C# Over XAML
  • + Use Test Helper Base Classes
  • + Avoid Obsolete APIs
  • + Use UITest Optimized Controls for Screenshot Tests
  • + Check Similar Tests for Patterns
  • + Find similar control tests
  • + Find Shell tests
  • + Find tests for specific control
  • + Find tests using UITest optimized controls

Commands

1 shared · 18 only in A · 2 only in B
  • − dotnet format Microsoft.Maui.sln --no-restore --exclude Templates/src --exclude-diagnostics CA1822
  • − git checkout -b feature/issue-12345
  • − git add .
  • − git commit -m "Fix: Description of the change"
  • − git push -u origin feature/issue-12345
  • − git push
  • − gh pr checkout 12345
  • − dotnet cake
  • − dotnet tool restore
  • − dotnet --version
  • − gh pr checks
  • − dotnet-public-maven
  • − dotnet/android#10738
  • − dotnet format analyzers
  • − git fetch origin
  • − git for-each-ref --sort=-version:refname --count=1 --format='%(refname:lstrip=3)' refs/remotes/origin/net*.0
  • − gh pr checkout
  • − dotnet-dnceng@dotnet-arcade-skills
  • + dotnet build
  • + dotnet
  •   dotnet test

Section tags

7 shared · 3 only in A · 2 only in B
  • − lint-format
  • − agent-behaviour
  • − docs
  • + build
  • + ui
  •   setup
  •   test
  •   code-style
  •   architecture
  •   testing-strategy
  •   git-pr
  •   do-not

Line diff

+664 added−267 removed112 unchanged14.4% identical
dotnet/maui · .github/copilot-instructions.md
@@ −1 @@
1---
2description: "Guidance for GitHub Copilot when working on the .NET MAUI repository."
3---
4 
5# GitHub Copilot Development Environment Instructions
6 
7This document provides specific guidance for GitHub Copilot when working on the .NET MAUI repository. It serves as context for understanding the project structure, development workflow, and best practices.
8 
9## Code Review Instructions
10 
11When performing a code review on PRs that change functional code, run the pr-finalize skill to verify that the PR title and description accurately match the actual implementation. This ensures proper documentation and helps maintain high-quality commit messages.
12 
13## Repository Overview
14 
15**.NET MAUI** is a cross-platform framework for creating mobile and desktop applications with C# and XAML. This repository contains the core framework code that enables development for Android, iOS, iPadOS, macOS, and Windows from a single shared codebase.
16 
17### Key Technologies
18 
19- **.NET SDK** - Version is **ALWAYS** defined in `global.json` at repository root
20 - **main branch**: Latest stable .NET version
21 - **Feature branches**: Each `netN.0` branch targets the .NET N SDK. By convention, the highest `netN.0` branch is the current development branch for new features and API changes.
22- **Cake build system** for compilation and packaging (`dotnet cake`)
23- **MSBuild** with custom build tasks (must build `Microsoft.Maui.BuildTasks.slnf` first)
24- **Testing frameworks**:
25 - **xUnit** - Unit tests (`*.UnitTests.csproj`)
26 - **NUnit** - UI tests (`TestCases.Shared.Tests`)
27 - **Appium WebDriver** - UI test automation
28 
29## Development Environment Setup
30 
31This guidance assumes:
32- Repository is already cloned and tools are restored (`dotnet tool restore` completed)
33- Build tasks are compiled (`Microsoft.Maui.BuildTasks.slnf` built successfully)
34- Correct .NET SDK version installed (verify with `dotnet --version` against `global.json`)
 
 
 
 
35 
36### Platform-Specific Requirements
 
 
 
 
 
37 
38- **Android**: OpenJDK 17 + Android SDK (install via `android` command after `dotnet tool restore`)
39- **iOS/macOS**: Xcode (current stable version)
40- **Windows**: Windows SDK
41 
42## Project Structure
 
 
 
 
 
43 
44### Important Directories
45- `src/Core/` - Core MAUI framework code
46- `src/Controls/` - UI controls and components
47- `src/Essentials/` - Platform APIs and essentials
48- `src/TestUtils/` - Testing utilities and infrastructure
49- `docs/` - Development documentation
50- `eng/` - Build engineering and tooling
51- `.github/` - GitHub workflows and configuration
52 
53### Platform-Specific Code Organization
54- **Android** specific code is inside folders labeled `Android`
55- **iOS** specific code is inside folders labeled `iOS`
56- **MacCatalyst** specific code is inside folders named `MacCatalyst`
57- **Windows** specific code is inside folders named `Windows`
58 
59### Platform-Specific File Extensions
 
 
 
60 
61Platform-specific files use naming conventions to control compilation:
 
 
 
62 
63**File extension patterns**:
64- `.windows.cs` - Windows TFM only
65- `.android.cs` - Android TFM only
66- `.ios.cs` - iOS and MacCatalyst TFMs (both)
67- `.maccatalyst.cs` - MacCatalyst TFM only (does NOT compile for iOS)
68 
69**Important**: Both `.ios.cs` and `.maccatalyst.cs` files compile for MacCatalyst. There is no precedence mechanism that excludes one when the other exists.
70 
71**Example**: If you have both `CollectionView.ios.cs` and `CollectionView.maccatalyst.cs`, both will compile for MacCatalyst builds. The `.maccatalyst.cs` file won't compile for iOS, but the `.ios.cs` file will compile for both iOS and MacCatalyst.
 
 
72 
73### Sample Projects
 
 
 
 
 
 
 
 
 
74 
75- `src/Controls/samples/Maui.Controls.Sample` - Full gallery sample with all controls and features
76- `src/Controls/samples/Maui.Controls.Sample.Sandbox` - Empty project for testing/reproduction
77- `src/Essentials/samples/Essentials.Sample` - Essentials API demonstrations (non-UI MAUI APIs)
78- `src/BlazorWebView/samples/` - BlazorWebView sample applications
 
 
 
 
 
 
 
 
 
 
 
 
79 
80## Development Workflow
81 
82### Testing
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83 
84Major test projects:
85- **Core**: `src/Core/tests/UnitTests/Core.UnitTests.csproj`
86- **Essentials**: `src/Essentials/test/UnitTests/Essentials.UnitTests.csproj`
87- **Controls**: `src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj`
88- **XAML**: `src/Controls/tests/Xaml.UnitTests/Controls.Xaml.UnitTests.csproj`
89 
90Find all tests: `find . -name "*.UnitTests.csproj"`
 
 
 
 
 
 
91 
92### CI Pipelines (Azure DevOps)
 
 
 
 
 
93 
94When referencing or triggering CI pipelines, use these current pipeline names:
95 
96| Pipeline | Name | Purpose |
97|----------|------|---------|
98| Overall CI | `maui-pr` | Full PR validation build |
99| Device Tests | `maui-pr-devicetests` | Helix-based device tests |
100| UI Tests | `maui-pr-uitests` | Appium-based UI tests |
101 
102**⚠️ Old pipeline names** (e.g., `MAUI-UITests-public`, `MAUI-public`) are **outdated** and should NOT be used. Always use the names above.
103 
104### Investigating CI Failures
 
 
 
 
 
105 
106**🚨 ALWAYS use the `azdo-build-investigator` skill when investigating CI failures or assessing merge readiness.** Its instructions direct you to invoke the `ci-analysis` skill first for the core investigation workflow, then apply MAUI-specific corrections (correct pipeline names, XHarness quirks, binlog guidance).
 
107 
108Do NOT default to manually querying AzDO APIs or rely solely on `gh pr checks` pass/fail counts.
 
 
109 
110**When to use it:**
111- "How does CI look?" / "Is CI green?" / "Can we merge?"
112- "What's failing?" / "Are these known failures?"
113- "Is this PR safe to merge?" / "Any CI concerns?"
114- After any PR push to verify the build
115 
116**Verifying specific tests:** When asked "did test X pass?" or "did the new test run?", query the **actual AzDO test results** — do NOT infer whether a test ran by inspecting code attributes. Class-level traits, base class categories, and assembly-level attributes can all cause a test to run even when the method itself has no visible category. Check the evidence, not the code.
117 
118**Anti-pattern:** Writing ad-hoc scripts to parse AzDO build timelines. The skills handle Helix work item details, known issue cross-referencing, and test result aggregation that manual approaches miss.
 
 
 
119 
120### Gradle / Maven Dependency Failures (CFSClean)
 
 
 
 
 
121 
122The official CI build uses CFSClean network isolation which blocks `repo.maven.apache.org`. All Gradle/Maven dependencies resolve through the `dotnet-public-maven` Azure Artifacts feed.
 
 
 
 
123 
124**If CI fails with Gradle 401 errors** like `"No local versions of package"` or `"Please provide authentication to save package from upstream"`, it means a Maven package hasn't been ingested into the feed yet. **Fix:** run `./eng/ingest-maven-deps.sh` locally to pre-populate the feed. See `src/Core/AndroidNative/settings.gradle` for details.
 
 
 
125 
126**Do NOT upgrade Gradle past 8.x** — the Android SDK's `net.android.init.gradle.kts` is incompatible with Gradle 9.x (`dotnet/android#10738`).
 
127 
128### Code Formatting
 
129 
130Always format code before committing:
 
131 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132```bash
133dotnet format Microsoft.Maui.sln --no-restore --exclude Templates/src --exclude-diagnostics CA1822
 
 
 
 
 
 
 
134```
135 
136## Contribution Guidelines
137 
138### Handling Existing PRs for Assigned Issues
139 
140**🚨 CRITICAL REQUIREMENT: Always develop your own solution first, then compare with existing PRs.**
141 
1421. **Develop your own solution first** - Analyze the issue independently and design your approach without looking at existing PRs
1432. **Search for existing PRs** - After developing your solution, search for open PRs addressing the same issue
1443. **Compare and evaluate** - Examine existing PR approaches and decide which solution better addresses the issue
1454. **Document your decision** - In your PR description, compare your solution to existing PRs and explain why you chose your approach, including concerns with alternatives
1465. **Improve either solution** - Whether using your solution or an existing one, enhance with better tests, code quality, error handling, or documentation
147 
148### Auto-Generated Files (Never Commit)
149 
150These files are auto-generated and must NOT be committed:
151- `cgmanifest.json` - Generated during CI builds
152- `templatestrings.json` - Auto-generated localization
153 
154**For AI agents:** Always reset changes to these files before committing.
155 
156### PublicAPI.Unshipped.txt File Management
 
 
 
 
 
 
 
157 
158When working with public API changes:
159- **Never disable analyzers** to bypass PublicAPI.Unshipped.txt issues
160- **Always add correct API entries** to PublicAPI.Unshipped.txt files
161- **Use `dotnet format analyzers`** if having trouble
162- **If files are incorrect**: Revert all changes, then add only the necessary new API entries
163 
164### Branching
165- `main` - For bug fixes without API changes
166- The highest `netN.0` branch (by convention) - For new features and API changes. To find it, run `git fetch origin` then: `git for-each-ref --sort=-version:refname --count=1 --format='%(refname:lstrip=3)' refs/remotes/origin/net*.0`
167 
168### Git Workflow (Copilot CLI Rules)
169 
170**🚨 CRITICAL Git Rules for Copilot CLI:**
171 
1721. **NEVER commit directly to `main`** - Always create a feature branch for your work. Direct commits to `main` are strictly prohibited.
173 
1742. **When amending an existing PR, work on the PR's branch directly** - Do NOT create a separate branch off a PR branch. The PR branch already IS a feature branch. Creating a new branch off it means CI won't run on the original PR, defeating the purpose. Use `gh pr checkout` to switch to the PR branch, make your changes, commit, **then** ask before pushing so the user can review locally first.
175 
1763. **Do NOT rebase, squash, or force-push** unless explicitly requested by the user. These operations rewrite git history and can cause problems for other contributors. Default behavior should be regular commits and pushes.
177 
178**Safe Git Workflow:**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179```bash
180# Create a feature branch (NEVER work directly on main)
181git checkout -b feature/issue-12345
 
182 
183# Make commits normally
184git add .
185git commit -m "Fix: Description of the change"
186 
187# Push to remote (for new branches)
188git push -u origin feature/issue-12345
189 
190# For subsequent pushes on the same branch
191git push
 
192```
 
 
193 
194**When asked to update an existing PR:**
195```bash
196# Check out the PR branch directly (do NOT create a new branch off it)
197gh pr checkout 12345
 
198 
199# Make fixes and commit to the PR branch
200git add .
201git commit -m "Fix: Description of the change"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202```
2031. **STOP and ask the user** before pushing: "Changes are committed locally. Would you like me to push these changes to the PR?"
2042. Exception: If the user's instructions explicitly include pushing, proceed without asking.
205 
206### Documentation
207- Update XML documentation for public APIs
208- Follow existing code documentation patterns
209- Update relevant docs in `docs/` folder when needed
 
 
 
210 
211### Opening PRs
212 
213All PRs are required to have this at the top of the description:
214 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215```
216<!-- Please let the below note in for people that find this PR -->
217> [!NOTE]
218> Are you waiting for the changes in this PR to be merged?
219> It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you!
 
 
 
 
 
220```
221 
222Always put that at the top, without the block quotes. Without it, users will NOT be able to try the PR and your work will have been in vain!
223 
 
224 
 
 
 
 
 
 
225 
226## Custom Agents and Skills
 
 
 
 
227 
228The repository includes specialized custom agents and reusable skills for specific tasks.
 
 
229 
230### Skills vs Agents
231 
232| Aspect | Skills | Agents |
233|--------|--------|--------|
234| **Invoke** | `/skill-name` or direct request | Delegate to agent |
235| **Output** | Analysis, recommendations | Actions, changes applied |
236| **Interaction** | Interactive discussion | Autonomous workflow |
237| **Example** | `/learn-from-pr` → recommendations | learn-from-pr agent → applies changes |
 
 
 
 
 
 
 
238 
239### Available Custom Agents
 
 
 
 
 
 
 
 
240 
2411. **pr** - Sequential 4-phase workflow for reviewing and working on PRs
242 - **Use when**: A PR already exists and needs review or work, OR an issue needs a fix
243 - **Capabilities**: PR review, test verification, fix exploration, alternative comparison
244 - **Trigger phrases**: "review PR #XXXXX", "work on PR #XXXXX", "fix issue #XXXXX", "continue PR #XXXXX"
245 - **Do NOT use for**: Just running tests manually → Use `sandbox-agent`
246 
2472. **write-tests-agent** - Agent for writing tests. Determines test type (UI vs XAML) and invokes the appropriate skill (`write-ui-tests`, `write-xaml-tests`)
248 - **Use when**: Creating new tests for issues or PRs
249 - **Capabilities**: Test type determination (UI and XAML), skill invocation, test verification
250 - **Trigger phrases**: "write tests for #XXXXX", "create tests", "add test coverage"
251 
2523. **sandbox-agent** - Specialized agent for working with the Sandbox app for testing, validation, and experimentation
253 - **Use when**: User wants to manually test PR functionality or reproduce issues
254 - **Capabilities**: Sandbox app setup, Appium-based manual testing, PR functional validation
255 - **Trigger phrases**: "test this PR", "validate PR #XXXXX in Sandbox", "reproduce issue #XXXXX", "try out in Sandbox"
256 - **Do NOT use for**: Code review (use pr agent), writing automated tests (use write-tests-agent)
 
257 
2584. **learn-from-pr** - Extracts lessons from PRs and applies improvements to the repository
259 - **Use when**: After complex PR, want to improve instruction files/skills based on lessons learned
260 - **Capabilities**: Analyzes PR, identifies failure modes, applies improvements to instruction files, skills, code comments
261 - **Trigger phrases**: "learn from PR #XXXXX and apply improvements", "improve repo based on what we learned", "update skills based on PR"
262 - **Output**: Applied changes to instruction files, skills, architecture docs, code comments
263 - **Do NOT use for**: Analysis only without applying changes → Use `/learn-from-pr` skill instead
264 
2655. **release-readiness-agent** - Assesses ship-readiness for a .NET MAUI release branch — both **SR** (`release/*-srN`) and **Preview** (`release/*-previewN`)
266 - **Use when**: A release (SR or Preview) is approaching ship date and you need a synthesized verdict with WorkIQ/MCP enrichment on top of the deterministic report — **or** for a portfolio question across all active releases ("status on releases", "what needs attention across releases") where the user may not know which releases exist
267 - **Capabilities**: Resolves the branch (SR or Preview) from natural language, picks the right script (`Get-ReleaseReadiness.ps1` for SR, `Get-PreviewReadiness.ps1` for Preview), enriches `rejected-from-sr` candidates with WorkIQ context (SR lane), patches `UNKNOWN` ship-check rows via MCP (`maestro_default_channels`, `maestro_builds`), presents an overall verdict
268 - **Trigger phrases**: "is SR7 ready to ship", "release readiness for release/10.0.1xx-sr7", "survey the SR8 branch", "how does net11 preview6 look", "is preview6 ready to cut", "release readiness for release/11.0.1xx-preview6" — **plus portfolio / cross-release questions with no specific release named**: "give me a status on releases", "release status overview", "what's the status across all releases", "what needs attention across releases", "what's next for MAUI releases"
269 - **Output**: Verdict (Ready / Conditionally Ready / Not Ready) + per-candidate classification (SR) or per-section table (Preview) + actionable next steps
270 - **Do NOT use for**: Programmatic / scripted consumers that just need the raw JSON — use the `release-readiness` skill directly. Reviewing a single PR (use **pr**). Running tests manually (use **sandbox-agent**).
271 
272### Reusable Skills
 
273 
274Skills are modular capabilities that can be invoked directly or used by agents. Located in `.github/skills/`:
 
275 
276#### User-Facing Skills
 
 
277 
2781. **pr-review** (`.github/skills/pr-review/SKILL.md`)
279 - **Purpose**: End-to-end PR review orchestrator — 3 phases: pr-preflight, try-fix, pr-report. Gate runs separately before this skill via Review-PR.ps1.
280 - **Trigger phrases**: "review PR #XXXXX", "work on PR #XXXXX", "fix issue #XXXXX", "continue PR #XXXXX"
281 - **Capabilities**: Multi-model fix exploration, alternative comparison, PR review recommendation
282 - **Do NOT use for**: Just running tests manually → Use `sandbox-agent`
283 - **Phase instructions** (in `.github/pr-review/`):
284 - `pr-preflight.md` — Context gathering from issue/PR
285 - `pr-report.md` — Final recommendation
286 - **Phase skill**: `try-fix` — Multi-model fix exploration
287 - **Note**: Gate (test verification) runs as a script step in `Review-PR.ps1` before this skill is invoked. Gate result is passed in the prompt.
288 
2892. **issue-triage** (`.github/skills/issue-triage/SKILL.md`)
290 - **Purpose**: Query and triage open issues that need milestones, labels, or investigation
291 - **Trigger phrases**: "find issues to triage", "show me old Android issues", "what issues need attention"
292 - **Scripts**: `init-triage-session.ps1`, `query-issues.ps1`, `record-triage.ps1`
293 
2942. **find-reviewable-pr** (`.github/skills/find-reviewable-pr/SKILL.md`)
295 - **Purpose**: Finds open PRs in dotnet/maui and dotnet/docs-maui that need review
296 - **Trigger phrases**: "find PRs to review", "show milestoned PRs", "find partner PRs"
297 - **Scripts**: `query-reviewable-prs.ps1`
298 - **Categories**: P/0, milestoned, partner, community, recent, docs-maui
299 
3003. **pr-finalize** (`.github/skills/pr-finalize/SKILL.md`)
301 - **Purpose**: Verifies PR title and description match actual implementation, AND performs code review for best practices before merge.
302 - **Trigger phrases**: "finalize PR #XXXXX", "check PR description for #XXXXX", "review commit message"
303 - **Used by**: Before merging any PR, when description may be stale
304 - **Note**: Does NOT require agent involvement or session markdown - works on any PR
305 - **🚨 CRITICAL**: NEVER use `--approve` or `--request-changes` - only post comments. Approval is a human decision.
306 
3074. **code-review** (`.github/skills/code-review/SKILL.md`)
308 - **Purpose**: Reviews PR code changes for correctness, safety, and consistency with MAUI conventions. Walks through a MAUI-specific checklist covering handler lifecycle, platform code, safe area, threading, public API, and test patterns.
309 - **Trigger phrases**: "review code for PR #XXXXX", "code review PR #XXXXX", "review this PR's code"
310 - **Note**: Standalone skill — uses independence-first assessment (reads code before PR description to avoid anchoring bias). Can be used by any agent or invoked directly.
311 - **🚨 CRITICAL**: NEVER use `--approve` or `--request-changes` — only post comments. Approval is a human decision.
 
 
 
312 
3135. **learn-from-pr** (`.github/skills/learn-from-pr/SKILL.md`)
314 - **Purpose**: Analyzes completed PR to identify repository improvements (analysis only, no changes applied)
315 - **Trigger phrases**: "what can we learn from PR #XXXXX?", "how can we improve agents based on PR #XXXXX?"
316 - **Used by**: After complex PRs, when agent struggled to find solution
317 - **Output**: Prioritized recommendations for instruction files, skills, code comments
318 - **Note**: For applying changes automatically, use the learn-from-pr agent instead
319 
3206. **write-ui-tests** (`.github/skills/write-ui-tests/SKILL.md`)
321 - **Purpose**: Creates UI tests for GitHub issues and verifies they reproduce the bug
322 - **Trigger phrases**: "write UI tests for #XXXXX", "create UI test for issue", "add UI test coverage"
323 - **Output**: Test files that fail without fix, pass with fix
324 
3257. **write-xaml-tests** (`.github/skills/write-xaml-tests/SKILL.md`)
326 - **Purpose**: Creates XAML unit tests for XAML parsing, compilation, and source generation
327 - **Trigger phrases**: "write XAML tests for #XXXXX", "test XamlC behavior", "reproduce XAML parsing bug"
328 - **Output**: Test files for Controls.Xaml.UnitTests
329 
3309. **verify-tests-fail-without-fix** (`.github/skills/verify-tests-fail-without-fix/SKILL.md`)
331 - **Purpose**: Verifies tests catch the bug before fix and pass with fix. Auto-detects test type (UI, device, unit, XAML) and dispatches to the appropriate runner.
332 - **Two modes**: Verify failure only (test creation) or full verification (test + fix)
333 - **Used by**: After creating tests, before considering PR complete
334 
33510. **run-integration-tests** (`.github/skills/run-integration-tests/SKILL.md`)
336 - **Purpose**: Build, pack, and run .NET MAUI integration tests locally
337 - **Trigger phrases**: "run integration tests", "test templates locally", "run macOSTemplates tests", "run RunOniOS tests"
338 - **Categories**: Build, WindowsTemplates, macOSTemplates, Blazor, MultiProject, Samples, AOT, RunOnAndroid, RunOniOS
339 - **Note**: **ALWAYS use this skill** instead of manual `dotnet test` commands for integration tests
340 
34111. **dependency-flow** (`.github/skills/dependency-flow/SKILL.md`)
342 - **Purpose**: MAUI-specific dependency flow rules, channel conventions, and feed lookup workflows
343 - **Trigger phrases**: "feeds for .NET MAUI X.Y.Z", "where is MAUI build", "promote build to public feed", "what channels is MAUI on", "subscription health for MAUI"
344 - **Wraps**: `maestro-cli` skill (from `dotnet-dnceng@dotnet-arcade-skills` plugin) and maestro MCP tools
345 - **Note**: Provides MAUI-specific guardrails on top of core Maestro/darc operations — channel naming, safety deny-list, input validation, and prompt injection defense
346 
34712. **release-readiness** (`.github/skills/release-readiness/SKILL.md`)
348 - **Purpose**: Deterministic ship-readiness engine for .NET MAUI release branches — both **SR** (`release/*-srN`) and **Preview** (`release/*-previewN`). Surveys CI, computes what's actually shipping, classifies open regressions, identifies port candidates and rejected backports
349 - **Trigger phrases**: "release readiness for SRN", "is SR7 ready to ship", "survey the SR branch", "release readiness for preview6", "how does preview6 look (deterministic)", "status across all releases" (reads the live `[Release Readiness]` tracker issues by body marker — no survey re-run needed)
350 - **Scripts**: `Get-ReleaseReadiness.ps1` (SR lane), `Get-PreviewReadiness.ps1` (Preview lane), `Find-ReleaseReadinessTrackers.ps1` (tracker discovery)
351 - **Output**: JSON + Markdown report, list of source PRs, classification of regression issues (in-sr-active, rejected-from-sr, no-fix-yet, etc.)
352 - **Note**: Deterministic and reproducible — no MCP, no LLM judgment. Use **this skill directly** when you need raw output for a script, dashboard, cron job, or programmatic consumer. For natural-language verdict synthesis with WorkIQ enrichment, use the **`release-readiness-agent`** instead.
353 
354#### Internal Skills (Used by Agents)
 
 
355 
35613. **try-fix** (`.github/skills/try-fix/SKILL.md`)
357 - **Purpose**: Proposes ONE independent fix approach, applies it, tests, records result with failure analysis, then reverts
358 - **Used by**: pr agent Phase 3 (Fix phase) - rarely invoked directly by users
359 - **Behavior**: Reads prior attempts to learn from failures. Max 5 attempts per session.
360 - **Output**: Updates session markdown with attempt results and failure analysis
 
361 
362### Using Custom Agents
363 
364**Delegation Policy**: When user request matches agent trigger phrases, **ALWAYS delegate to the appropriate agent immediately**. Do not ask for permission or explain alternatives unless the request is ambiguous.
365 
366**Examples of correct delegation**:
367- User: "Review PR #12345" → Immediately invoke **pr** agent
368- User: "Test this PR" → Immediately invoke **sandbox-agent**
369- User: "Fix issue #67890" (no PR exists) → Suggest using `/delegate` command
370- User: "Write tests for issue #12345" → Immediately invoke **write-tests-agent**
371- User: "Is SR7 ready to ship?" → Immediately invoke **release-readiness-agent**
372- User: "How does net11 preview6 look?" → Immediately invoke **release-readiness-agent**
373- User: "Give me a status on releases / what needs attention across releases?" → Immediately invoke **release-readiness-agent** (portfolio mode — it enumerates active releases by reading the `[Release Readiness]` tracker issues; don't ask "which release?")
374- User: "Give me the raw release-readiness JSON for SR8" → Use the **release-readiness** skill directly (no enrichment needed)
375 
376**When NOT to delegate**:
377- User asks "What does PR #12345 do?" → Informational query, handle yourself
378- User asks "How do I test PRs?" → Documentation query, handle yourself
379- User has follow-up questions after agent completes → Continue the conversation yourself
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dotnet/maui · .github/instructions/uitests.instructions.md
@@ +1 @@
1---
2applyTo: "src/Controls/tests/TestCases.Shared.Tests/**,src/Controls/tests/TestCases.HostApp/**"
3---
4 
5# UI Testing Guidelines for .NET MAUI
6 
7## Overview
8 
9This document provides specific guidance for GitHub Copilot when writing UI tests for the .NET MAUI repository.
10 
 
11 
 
12 
13**Critical Principle**: UI tests should run on all applicable platforms (iOS, Android, Windows, MacCatalyst) by default unless there is a specific technical limitation.
14 
15## UI Test Structure
16 
17### Two-Project Requirement
 
 
 
 
 
 
 
 
18 
19**CRITICAL: Every UI test requires code in TWO separate projects:**
20 
211. **HostApp UI Test Page** (`src/Controls/tests/TestCases.HostApp/Issues/`)
22 - Create the actual UI page that demonstrates the feature or reproduces the issue
23 - **Prefer C# only** (`.cs` file) unless testing XAML-specific features (bindings, templates, styles)
24 - Add `AutomationId` attributes on interactive controls for test automation
25 - 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 applicable
27 - Ensure the UI provides clear visual feedback for the behavior being tested
28 - Class must include `[Issue()]` attribute with tracker, number, description, and platform
29 
302. **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 elements
33 - 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 assertions
36 
37### Base Class and Infrastructure
 
 
38 
39- Each test class must inherit from `_IssuesUITest`
40- The `_IssuesUITest` base class provides:
41 - The `App` property for interacting with UI elements
42 - Test initialization and setup
43 - Helper methods for common UI test operations
44- The test infrastructure automatically handles platform detection and page navigation
45 
46### Naming Conventions
 
 
 
 
 
 
 
47 
48**Test Files:**
49- Pattern: `IssueXXXXX.cs` where XXXXX corresponds to a GitHub issue number
50- Must match the corresponding HostApp page file name in TestCases.HostApp (either `.cs` only or `.xaml`)
 
 
51 
52**Test Methods:**
53- Use descriptive names that clearly explain what behavior is being verified
54- ✅ Good: `VerifySafeAreaBottomPaddingWithKeyboard()`, `ButtonClickUpdatesLabel()`
55- ❌ Bad: `Test1()`, `TestMethod()`, `RunTest()`
56 
57**AutomationId Values:**
58- Always use unique, descriptive `AutomationId` values
59- Reference the same `AutomationId` in both C# code (or XAML if used) and test code
60- Use PascalCase for AutomationId values
61 
62## Complete Test Example
 
 
 
 
63 
64### Example 1: C# Only (Preferred for Most Tests)
65 
66**HostApp Page** (`TestCases.HostApp/Issues/Issue12345.cs`):
67```csharp
68namespace Maui.Controls.Sample.Issues;
69 
70[Issue(IssueTracker.Github, 12345, "Button click updates label text", PlatformAffected.All)]
71public class Issue12345 : ContentPage
72{
73 public Issue12345()
74 {
75 var resultLabel = new Label
76 {
77 Text = "Initial Text",
78 AutomationId = "ResultLabel"
79 };
80 
81 Content = new VerticalStackLayout
82 {
83 Children =
84 {
85 new Button
86 {
87 Text = "Click Me",
88 AutomationId = "TestButton",
89 Command = new Command(() => resultLabel.Text = "Expected Text")
90 },
91 resultLabel
92 }
93 };
94 }
95}
96```
97 
98### Example 2: XAML (When Testing XAML-Specific Features)
99 
100**HostApp XAML** (`TestCases.HostApp/Issues/Issue12346.xaml`):
101```xaml
102<?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```
116 
117**HostApp Code-Behind** (`TestCases.HostApp/Issues/Issue12346.xaml.cs`):
118```csharp
119namespace Maui.Controls.Sample.Issues;
 
 
120 
121[Issue(IssueTracker.Github, 12346, "Testing XAML binding behavior", PlatformAffected.All)]
122public partial class Issue12346 : ContentPage
123{
124 public Issue12346()
125 {
126 InitializeComponent();
127 }
128 
129 void OnButtonClicked(object sender, EventArgs e)
130 {
131 ResultLabel.Text = "Expected Text";
132 }
133}
134```
135 
136### NUnit Test (Same for Both Examples)
137 
138**NUnit Test** (`TestCases.Shared.Tests/Tests/Issues/Issue12345.cs` or `Issue12346.cs`):
139```csharp
140public class Issue12345 : _IssuesUITest
141{
142 public override string Issue => "Description of the issue being tested";
143 
144 public Issue12345(TestDevice device) : base(device) { }
145 
146 [Test]
147 [Category(UITestCategories.Layout)] // Pick the most appropriate category
148 public void ButtonClickUpdatesLabel()
149 {
150 // Wait for element to be ready
151 App.WaitForElement("TestButton");
152 
153 // Interact with the UI
154 App.Tap("TestButton");
155 
156 // Verify expected behavior
157 var labelText = App.FindElement("ResultLabel").GetText();
158 Assert.That(labelText, Is.EqualTo("Expected Text"));
159 
160 // Optional: Visual verification
161 VerifyScreenshot();
162 }
163}
164```
165 
166## Common Patterns
167 
168### Waiting for Elements
169```csharp
170App.WaitForElement("AutomationId");
171```
172 
173### Interacting with Elements
174```csharp
175App.Tap("AutomationId");
176App.FindElement("AutomationId").GetText();
177var rect = App.WaitForElement("AutomationId").GetRect();
178```
179 
180### Assertions
181```csharp
182Assert.That(actualValue, Is.EqualTo(expectedValue).Within(tolerance));
183Assert.That(rect.Height, Is.LessThanOrEqualTo(maxHeight));
184```
185 
186### Screenshot Verification
187```csharp
188// Verify visual appearance (automated comparison)
189VerifyScreenshot();
190 
191// With custom name
192VerifyScreenshot("CustomTestName");
193 
194// With tolerance (0.0-100.0 percentage) - use sparingly
195VerifyScreenshot(tolerance: 0.5); // Allow 0.5% difference for cross-machine rendering variance
196 
197// PREFERRED: Keep retrying for up to 2 seconds (for animations)
198VerifyScreenshot(retryTimeout: TimeSpan.FromSeconds(2));
199 
200// Combined: tolerance for rendering variance + retryTimeout for timing
201VerifyScreenshot(tolerance: 0.5, retryTimeout: TimeSpan.FromSeconds(2));
202 
203// Manual screenshot for debugging
204App.Screenshot("TestStep1");
205```
206 
207**CRITICAL - VerifyScreenshot() Built-in Features:**
208 
209`VerifyScreenshot()` **already includes** stability mechanisms. Do NOT add redundant delays:
210 
211| 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) |
218 
219**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 issues
222- ✅ Use `retryDelay` if you need to change the delay between retry attempts
223- ❌ **DO NOT** add `Task.Delay()` or `Thread.Sleep()` before `VerifyScreenshot()` - use `retryTimeout` instead
224 
225## Writing Robust UI Tests
226 
227### Best Practices for Screenshot Tests
228 
229When writing tests that use `VerifyScreenshot()`, follow these patterns to avoid flakiness:
230 
231```
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```
249 
250### Common Flaky Test Patterns
251 
252| 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 |
259 
260### Anti-Patterns (DO NOT DO)
261 
262| 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) |
269 
270### When to Use What
271 
272**VerifyScreenshot() parameters (preferred):**
273```csharp
274// Animation timing issues - keep retrying for up to 2 seconds
275// This is the PREFERRED approach for flaky screenshot tests
276VerifyScreenshot(retryTimeout: TimeSpan.FromSeconds(2));
277 
278// Small tolerance for cross-machine rendering variance + retryTimeout for timing
279// Use 0.5% tolerance as safety margin, NOT to hide timing issues
280VerifyScreenshot(tolerance: 0.5, retryTimeout: TimeSpan.FromSeconds(2));
281 
282// Legacy: retryDelay only changes delay BETWEEN retries (default 500ms)
283// retryTimeout is preferred because it keeps trying until success
284VerifyScreenshot(retryDelay: TimeSpan.FromSeconds(1));
285```
286 
287**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.
291 
292**WaitForElement (for element readiness):**
293```csharp
294// Wait up to 10 seconds for element to appear
295App.WaitForElement("ButtonId", timeout: TimeSpan.FromSeconds(10));
296 
297// Then interact
298App.Tap("ButtonId");
299```
300 
301**Task.Delay/Thread.Sleep (avoid if possible):**
302```csharp
303// AVOID: With retryTimeout, you rarely need explicit delays anymore
304//
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));
311 
312// 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 otherwise
315// 3. After exhausting other options AND documenting why
316```
317 
318### Understanding Test Infrastructure
319 
320**Key files to understand when writing UI tests:**
321 
3221. **UITest.cs** - Base class with `VerifyScreenshot()` implementation
323 - Path: `src/Controls/tests/TestCases.Shared.Tests/UITest.cs`
324 - Contains: retry logic, tolerance parsing, platform-specific delays
325 
3262. **_IssuesUITest.cs** - Issues test base class
327 - Path: `src/Controls/tests/TestCases.Shared.Tests/_IssuesUITest.cs`
328 - Contains: Navigation helpers, common patterns
329 
3303. **Extension methods** - Platform-specific helpers
331 - Path: `src/Controls/tests/TestCases.Shared.Tests/` (various extension files)
332 - Contains: Existing helpers for common operations
333 
334**Find existing patterns:**
335```bash
336# See VerifyScreenshot implementation (including retryTimeout)
337grep -A 30 "public void VerifyScreenshot" src/Controls/tests/TestCases.Shared.Tests/UITest.cs
338 
339# Find existing tests using retryTimeout (preferred pattern)
340grep -r "retryTimeout" src/Controls/tests/TestCases.Shared.Tests/Tests/
341 
342# Find existing tolerance patterns
343grep -r "tolerance:" src/Controls/tests/TestCases.Shared.Tests/Tests/
344```
345 
346### Infrastructure Notes
347 
348**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.
349 
350## Test Categories
351 
352### Category Guidelines
353- Use appropriate categories from `UITestCategories`
354- **Only ONE** `[Category]` attribute per test
355- Pick the most specific category that applies
 
356 
357### Test Categories
358 
359**CRITICAL**: Always check [UITestCategories.cs](../../src/Controls/tests/TestCases.Shared.Tests/UITestCategories.cs) for the authoritative, complete list of categories.
 
 
360 
361**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.
362 
363**Common categories** (examples only - not exhaustive):
364- **SafeArea**: `SafeAreaEdges` - Safe area and padding tests
365- **Basic controls**: `Button`, `Label`, `Entry`, `Editor` - Specific control tests
366- **Collection controls**: `CollectionView`, `ListView`, `CarouselView` - Collection control tests
367- **Layout**: `Layout` - Layout-related tests
368- **Navigation**: `Shell`, `Navigation`, `TabbedPage` - Navigation tests
369- **Interaction**: `Gestures`, `Focus`, `Accessibility` - Interaction tests
370- **Lifecycle**: `Window`, `Page`, `LifeCycle` - Page lifecycle tests
371 
372**List all categories programmatically**:
373```bash
374grep -E "public const string [A-Za-z]+ = " src/Controls/tests/TestCases.Shared.Tests/UITestCategories.cs
375```
 
376 
377**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.
 
 
378 
379## Platform Coverage
380 
381### Default Behavior
382 
383Tests should run on all applicable platforms by default. The test infrastructure handles platform detection automatically.
384 
385### No Inline #if Directives in Test Methods
386 
387**Do NOT use `#if ANDROID`, `#if IOS`, etc. directly in test methods.** Platform-specific behavior must be hidden behind extension methods for readability.
388 
389**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.
390 
391```csharp
392// ❌ BAD - inline #if in test method (hard to read)
393[Test]
394public void MyTest()
395{
396#if ANDROID
397 App.TapCoordinates(100, 200);
398#else
399 App.Tap("MyElement");
400#endif
401}
402 
403// ✅ GOOD - platform logic in extension method (clean test)
404[Test]
405public void MyTest()
406{
407 App.TapElementCrossPlatform("MyElement");
408}
409```
410 
411Move platform-specific logic to extension methods to keep test code clean and readable.
412 
413## Running UI Tests Locally
414 
415**CRITICAL: ALWAYS use the BuildAndRunHostApp.ps1 script to run UI tests. NEVER run `dotnet test` or `dotnet build` commands manually.**
416 
417### BuildAndRunHostApp.ps1 Script (ONLY Way to Run Tests)
418 
419**Script location**: `.github/scripts/BuildAndRunHostApp.ps1`
420 
421**Usage:**
422```powershell
423# Run specific test on Android
424pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform android -TestFilter "FullyQualifiedName~Issue12345"
425 
426# Run specific test on iOS
427pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "FullyQualifiedName~Issue12345"
428 
429# Run specific test on MacCatalyst
430pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform maccatalyst -TestFilter "FullyQualifiedName~Issue12345"
431 
432# Run tests by category
433pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform android -Category "SafeAreaEdges"
434 
435# Run specific test with custom device (iOS only)
436pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "Issue12345" -DeviceUdid "12345678-1234567890ABCDEF"
437```
438 
439**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 deployment
443- ✅ 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 HostApp
446 - `test-output.log` - Test execution output
447 
448**Why you must use the script:**
449- The script ensures correct device targeting and environment variables
450- It handles platform-specific quirks and setup requirements
451- It provides consistent test execution across all platforms
452- It captures logs automatically for debugging
453- Manual `dotnet` commands often fail due to missing environment setup
454 
455### Prerequisites: Kill Existing Appium Processes
456 
457**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:
458 
459```
460AppiumServerHasNotBeenStartedLocallyException: The local appium server has not been started.
461Time 120000 ms for the service starting has been expired!
462```
463 
464**Solution: Always kill existing Appium processes before running tests:**
465 
466```bash
467# Kill any Appium processes on port 4723
468lsof -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```
470 
471**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.
 
 
472 
473### Troubleshooting
 
474 
475**Android App Crashes on Launch:**
476 
477If 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_ElementBasedFragment
480```
481 
482**Solution:** Read the crash logs to find the actual exception:
483```bash
484# Monitor logcat for the crash
485adb logcat | grep -E "(FATAL|AndroidRuntime|Exception|Error|Crash)"
486```
487 
488**Debugging steps:**
4891. **Find the exception** in logcat - look for the stack trace
4902. **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 app
4935. If you can't determine the fix, **ask for guidance** with the full exception details
494 
495**iOS App Crashes on Launch or Won't Start with Appium:**
496 
497If the iOS app crashes when launched by Appium or manually with `xcrun simctl launch`:
498 
499**Solution:** Read the crash logs to find the actual exception:
500```bash
501# Capture crash logs
502xcrun simctl spawn booted log stream --predicate 'processImagePath contains "TestCases.HostApp"' --level=debug > /tmp/ios_crash.log 2>&1 &
503LOG_PID=$!
504 
505# Try to launch the app
506xcrun simctl launch $UDID com.microsoft.maui.uitests
507 
508# Wait a moment for crash
509sleep 3
510 
511# Stop log capture
512kill $LOG_PID
513 
514# Review the crash log
515cat /tmp/ios_crash.log | grep -A 20 -B 5 "Exception"
516```
 
 
517 
518**Debugging steps:**
5191. **Find the exception** in the crash log - look for stack traces
5202. **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 details
525 
526### Dangerous System Commands (Never Run)
527 
528**🚨 NEVER run these commands — they cause destructive system-wide side effects:**
529 
530- **`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 Protection
532- **`networksetup`** — Modifies network configuration
533- **`defaults delete`** on system domains — Resets system preferences
534 
535**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.
536 
537## Before Committing
538 
539Verify the following checklist before committing UI tests:
540 
541- [ ] Compile both the HostApp project and TestCases.Shared.Tests project successfully
542- [ ] Verify AutomationId references match between HostApp UI (C# or XAML) and test code
543- [ ] Ensure file names follow the `IssueXXXXX` pattern and match between projects
544- [ ] Ensure test methods have descriptive names
545- [ ] Verify test inherits from `_IssuesUITest`
546- [ ] Confirm only ONE `[Category]` attribute per test
547- [ ] No inline `#if` directives in test code (use extension methods)
548- [ ] Test passes locally on at least one platform
549 
550### Test State Management
551 
552- Tests should be independent and not rely on state from other tests
553- The test infrastructure handles navigation to the test page and basic cleanup
554- If your test modifies global app state, consider whether cleanup is needed
555- Most tests don't require explicit cleanup as each test gets a fresh page instance
556 
557## Best Practices
558 
559### Default: C# Over XAML
560 
561**Use C# files (`.cs`) for UI tests. Only use XAML files (`.xaml`) when the test scenario requires XAML-specific features.**
562 
563**When to use C# only (`.cs` file):**
564- ✅ Simple control tests (Button, Label, Entry, etc.)
565- ✅ Layout tests (Grid, StackLayout, FlexLayout, etc.)
566- ✅ Navigation tests
567- ✅ Event handling tests
568- ✅ Property tests
569- ✅ Most UI behavior tests
570 
571**When XAML is required (`.xaml` + `.xaml.cs` files):**
572- ✅ Testing XAML binding syntax
573- ✅ Testing XAML templates (DataTemplate, ControlTemplate)
574- ✅ Testing XAML styles and resources
575- ✅ Testing XAML markup extensions
576- ✅ Testing XamlC compilation behavior
577- ✅ Testing XAML-specific parsing or compilation issues
578 
579**Examples:**
580 
581```csharp
582// ✅ GOOD: C# only test (most common pattern)
583public class Issue12345 : ContentPage
584{
585 public Issue12345()
586 {
587 Content = new StackLayout
588 {
589 Children =
590 {
591 new Label { Text = "Hello", AutomationId = "MyLabel" },
592 new Button { Text = "Click Me", AutomationId = "MyButton" }
593 }
594 };
595 }
596}
597```
598 
599```xaml
600<!-- ❌ 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```
608 
609### Use Test Helper Base Classes
610 
611**ALWAYS check for and use existing test helper base classes instead of creating from scratch:**
612 
613| 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` |
619 
620**TestShell provides:**
621- Platform-specific automation IDs for flyout and back buttons
622- Helper methods: `AddContentPage()`, `AddBottomTab()`, `AddTopTab()`, `AddFlyoutItem()`
623- Abstract `Init()` method for setup
624- `DisplayedPage` property for accessing current page
625 
626**TestContentPage/TestNavigationPage provide:**
627- Abstract `Init()` method for deferred initialization
628- Cleaner separation of setup logic
629 
630**Example:**
631 
632```csharp
633// ✅ GOOD: Using TestShell for Shell tests
634[Issue(IssueTracker.Github, 12345, "Shell navigation bug", PlatformAffected.All)]
635public class Issue12345 : TestShell
636{
637 protected override void Init()
638 {
639 AddContentPage(new ContentPage
640 {
641 Content = new Label { Text = "Test" }
642 });
643 }
644}
645 
646// ❌ BAD: Creating Shell from scratch
647public class Issue12345 : Shell
648{
649 public Issue12345()
650 {
651 Items.Add(new ShellItem { ... }); // Verbose, error-prone
652 }
653}
654```
655 
656### Avoid Obsolete APIs
 
 
 
 
657 
658**NEVER use obsolete APIs in new tests. Use modern equivalents:**
 
 
 
659 
660| ❌ 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 |
666 
667**Examples:**
 
 
 
 
 
668 
669```csharp
670// ✅ GOOD: Modern Window API
671this.Window.Page = new NavigationPage(new MyPage());
 
 
 
672 
673// ❌ BAD: Obsolete Application.MainPage
674Application.MainPage = new NavigationPage(new MyPage());
675 
676// ✅ GOOD: Border
677new Border { Content = new Label { Text = "Hello" } }
678 
679// ❌ BAD: Frame (deprecated)
680new Frame { Content = new Label { Text = "Hello" } }
681```
682 
683### Use UITest Optimized Controls for Screenshot Tests
 
 
 
 
 
 
 
 
 
684 
685**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.
 
 
 
686 
687| 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 |
692 
693**Example:**
 
 
 
 
 
694 
695```csharp
696// For screenshot tests, use UITest controls (UITestEntry, UITestEditor, UITestSearchBar)
697var entry = new UITestEntry
698{
699 Placeholder = "Enter text",
700 IsCursorVisible = false, // Prevents flaky screenshots
701 AutomationId = "TestEntry"
702};
703 
704// For non-screenshot tests, standard Entry is fine
705var entry = new Entry { Placeholder = "Enter text", AutomationId = "TestEntry" };
706```
 
 
 
707 
708**Location:** `src/Controls/tests/TestCases.HostApp/Controls/UITest*.cs`
 
 
 
709 
710### Check Similar Tests for Patterns
 
 
 
711 
712**Before creating a new test, search for similar tests to reuse patterns:**
 
 
 
713 
714```bash
715# Find similar control tests
716grep -r "class.*Issue.*Button" src/Controls/tests/TestCases.HostApp/Issues/*.cs
 
 
717 
718# Find Shell tests
719grep -r "TestShell" src/Controls/tests/TestCases.HostApp/Issues/*.cs
 
 
 
720 
721# Find tests for specific control
722grep -r "CollectionView" src/Controls/tests/TestCases.HostApp/Issues/*.cs
 
 
 
 
723 
724# Find tests using UITest optimized controls
725grep -r "UITestEntry\|UITestEditor\|UITestSearchBar" src/Controls/tests/TestCases.HostApp/Issues/*.cs
726```
727 
728**Reuse established patterns:**
729- AutomationId naming conventions
730- Test structure and layout
731- Common helper methods
732- Platform-specific workarounds
733- UITest optimized control usage
734 
735### Safe Area Testing (iOS/MacCatalyst)
736 
737**⚠️ CRITICAL for macCatalyst safe area tests:**
738 
739Safe area behavior differs significantly between macOS versions. Tests must account for this variability.
 
 
 
 
 
 
 
 
740 
741| 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 |
745 
746**Rules for safe area tests:**
747 
7481. **Use tolerances for safe area measurements** - Exact pixel values vary by macOS version
7492. **Test behavior, not exact values** - Verify content is NOT obscured, rather than checking exact padding pixels
7503. **Use `GetRect()` for child content position** - Measure where content actually appears, not parent size
7514. **Never hardcode safe area expectations** - Tests should pass on macOS 14/15 AND macOS 26
752 
753**Example patterns:**
754 
755```csharp
756// ❌ BAD: Hardcoded safe area value (breaks across macOS versions)
757var safeArea = element.GetRect();
758Assert.That(safeArea.Y, Is.EqualTo(28)); // Fails on macOS 26
759 
760// ✅ GOOD: Test that content is not obscured by title bar
761var 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");
765 
766// ✅ 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```
770 
771**Test category**: Use `UITestCategories.SafeAreaEdges` for safe area tests.
772 
773**Platform scope**: Safe area tests should typically run on iOS and MacCatalyst (not just one).
774 
775**See also**: `.github/instructions/safe-area-debugging.instructions.md` for investigation guidelines
776 
@@ −1 +1 @@
11 ---
2−description: "Guidance for GitHub Copilot when working on the .NET MAUI repository."
2+applyTo: "src/Controls/tests/TestCases.Shared.Tests/**,src/Controls/tests/TestCases.HostApp/**"
33 ---
44  
5−# GitHub Copilot Development Environment Instructions
5+# UI Testing Guidelines for .NET MAUI
66  
7−This document provides specific guidance for GitHub Copilot when working on the .NET MAUI repository. It serves as context for understanding the project structure, development workflow, and best practices.
7+## Overview
88  
9−## Code Review Instructions
9+This document provides specific guidance for GitHub Copilot when writing UI tests for the .NET MAUI repository.
1010  
11−When performing a code review on PRs that change functional code, run the pr-finalize skill to verify that the PR title and description accurately match the actual implementation. This ensures proper documentation and helps maintain high-quality commit messages.
1211  
13−## Repository Overview
1412  
15−**.NET MAUI** is a cross-platform framework for creating mobile and desktop applications with C# and XAML. This repository contains the core framework code that enables development for Android, iOS, iPadOS, macOS, and Windows from a single shared codebase.
13+**Critical Principle**: UI tests should run on all applicable platforms (iOS, Android, Windows, MacCatalyst) by default unless there is a specific technical limitation.
1614  
17−### Key Technologies
15+## UI Test Structure
1816  
19−- **.NET SDK** - Version is **ALWAYS** defined in `global.json` at repository root
20− - **main branch**: Latest stable .NET version
21− - **Feature branches**: Each `netN.0` branch targets the .NET N SDK. By convention, the highest `netN.0` branch is the current development branch for new features and API changes.
22−- **Cake build system** for compilation and packaging (`dotnet cake`)
23−- **MSBuild** with custom build tasks (must build `Microsoft.Maui.BuildTasks.slnf` first)
24−- **Testing frameworks**:
25− - **xUnit** - Unit tests (`*.UnitTests.csproj`)
26− - **NUnit** - UI tests (`TestCases.Shared.Tests`)
27− - **Appium WebDriver** - UI test automation
17+### Two-Project Requirement
2818  
29−## Development Environment Setup
19+**CRITICAL: Every UI test requires code in TWO separate projects:**
3020  
31−This guidance assumes:
32−- Repository is already cloned and tools are restored (`dotnet tool restore` completed)
33−- Build tasks are compiled (`Microsoft.Maui.BuildTasks.slnf` built successfully)
34−- Correct .NET SDK version installed (verify with `dotnet --version` against `global.json`)
21+1. **HostApp UI Test Page** (`src/Controls/tests/TestCases.HostApp/Issues/`)
22+ - Create the actual UI page that demonstrates the feature or reproduces the issue
23+ - **Prefer C# only** (`.cs` file) unless testing XAML-specific features (bindings, templates, styles)
24+ - Add `AutomationId` attributes on interactive controls for test automation
25+ - 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 applicable
27+ - Ensure the UI provides clear visual feedback for the behavior being tested
28+ - Class must include `[Issue()]` attribute with tracker, number, description, and platform
3529  
36−### Platform-Specific Requirements
30+2. **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 elements
33+ - 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 assertions
3736  
38−- **Android**: OpenJDK 17 + Android SDK (install via `android` command after `dotnet tool restore`)
39−- **iOS/macOS**: Xcode (current stable version)
40−- **Windows**: Windows SDK
37+### Base Class and Infrastructure
4138  
42−## Project Structure
39+- Each test class must inherit from `_IssuesUITest`
40+- The `_IssuesUITest` base class provides:
41+ - The `App` property for interacting with UI elements
42+ - Test initialization and setup
43+ - Helper methods for common UI test operations
44+- The test infrastructure automatically handles platform detection and page navigation
4345  
44−### Important Directories
45−- `src/Core/` - Core MAUI framework code
46−- `src/Controls/` - UI controls and components
47−- `src/Essentials/` - Platform APIs and essentials
48−- `src/TestUtils/` - Testing utilities and infrastructure
49−- `docs/` - Development documentation
50−- `eng/` - Build engineering and tooling
51−- `.github/` - GitHub workflows and configuration
46+### Naming Conventions
5247  
53−### Platform-Specific Code Organization
54−- **Android** specific code is inside folders labeled `Android`
55−- **iOS** specific code is inside folders labeled `iOS`
56−- **MacCatalyst** specific code is inside folders named `MacCatalyst`
57−- **Windows** specific code is inside folders named `Windows`
48+**Test Files:**
49+- Pattern: `IssueXXXXX.cs` where XXXXX corresponds to a GitHub issue number
50+- Must match the corresponding HostApp page file name in TestCases.HostApp (either `.cs` only or `.xaml`)
5851  
59−### Platform-Specific File Extensions
52+**Test Methods:**
53+- Use descriptive names that clearly explain what behavior is being verified
54+- ✅ Good: `VerifySafeAreaBottomPaddingWithKeyboard()`, `ButtonClickUpdatesLabel()`
55+- ❌ Bad: `Test1()`, `TestMethod()`, `RunTest()`
6056  
61−Platform-specific files use naming conventions to control compilation:
57+**AutomationId Values:**
58+- Always use unique, descriptive `AutomationId` values
59+- Reference the same `AutomationId` in both C# code (or XAML if used) and test code
60+- Use PascalCase for AutomationId values
6261  
63−**File extension patterns**:
64−- `.windows.cs` - Windows TFM only
65−- `.android.cs` - Android TFM only
66−- `.ios.cs` - iOS and MacCatalyst TFMs (both)
67−- `.maccatalyst.cs` - MacCatalyst TFM only (does NOT compile for iOS)
62+## Complete Test Example
6863  
69−**Important**: Both `.ios.cs` and `.maccatalyst.cs` files compile for MacCatalyst. There is no precedence mechanism that excludes one when the other exists.
64+### Example 1: C# Only (Preferred for Most Tests)
7065  
71−**Example**: If you have both `CollectionView.ios.cs` and `CollectionView.maccatalyst.cs`, both will compile for MacCatalyst builds. The `.maccatalyst.cs` file won't compile for iOS, but the `.ios.cs` file will compile for both iOS and MacCatalyst.
66+**HostApp Page** (`TestCases.HostApp/Issues/Issue12345.cs`):
67+```csharp
68+namespace Maui.Controls.Sample.Issues;
7269  
73−### Sample Projects
70+[Issue(IssueTracker.Github, 12345, "Button click updates label text", PlatformAffected.All)]
71+public class Issue12345 : ContentPage
72+{
73+ public Issue12345()
74+ {
75+ var resultLabel = new Label
76+ {
77+ Text = "Initial Text",
78+ AutomationId = "ResultLabel"
79+ };
7480  
75−- `src/Controls/samples/Maui.Controls.Sample` - Full gallery sample with all controls and features
76−- `src/Controls/samples/Maui.Controls.Sample.Sandbox` - Empty project for testing/reproduction
77−- `src/Essentials/samples/Essentials.Sample` - Essentials API demonstrations (non-UI MAUI APIs)
78−- `src/BlazorWebView/samples/` - BlazorWebView sample applications
81+ Content = new VerticalStackLayout
82+ {
83+ Children =
84+ {
85+ new Button
86+ {
87+ Text = "Click Me",
88+ AutomationId = "TestButton",
89+ Command = new Command(() => resultLabel.Text = "Expected Text")
90+ },
91+ resultLabel
92+ }
93+ };
94+ }
95+}
96+```
7997  
80−## Development Workflow
98+### Example 2: XAML (When Testing XAML-Specific Features)
8199  
82−### Testing
100+**HostApp XAML** (`TestCases.HostApp/Issues/Issue12346.xaml`):
101+```xaml
102+<?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+```
83116  
84−Major test projects:
85−- **Core**: `src/Core/tests/UnitTests/Core.UnitTests.csproj`
86−- **Essentials**: `src/Essentials/test/UnitTests/Essentials.UnitTests.csproj`
87−- **Controls**: `src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj`
88−- **XAML**: `src/Controls/tests/Xaml.UnitTests/Controls.Xaml.UnitTests.csproj`
117+**HostApp Code-Behind** (`TestCases.HostApp/Issues/Issue12346.xaml.cs`):
118+```csharp
119+namespace Maui.Controls.Sample.Issues;
89120  
90−Find all tests: `find . -name "*.UnitTests.csproj"`
121+[Issue(IssueTracker.Github, 12346, "Testing XAML binding behavior", PlatformAffected.All)]
122+public partial class Issue12346 : ContentPage
123+{
124+ public Issue12346()
125+ {
126+ InitializeComponent();
127+ }
91128  
92−### CI Pipelines (Azure DevOps)
129+ void OnButtonClicked(object sender, EventArgs e)
130+ {
131+ ResultLabel.Text = "Expected Text";
132+ }
133+}
134+```
93135  
94−When referencing or triggering CI pipelines, use these current pipeline names:
136+### NUnit Test (Same for Both Examples)
95137  
96−| Pipeline | Name | Purpose |
97−|----------|------|---------|
98−| Overall CI | `maui-pr` | Full PR validation build |
99−| Device Tests | `maui-pr-devicetests` | Helix-based device tests |
100−| UI Tests | `maui-pr-uitests` | Appium-based UI tests |
138+**NUnit Test** (`TestCases.Shared.Tests/Tests/Issues/Issue12345.cs` or `Issue12346.cs`):
139+```csharp
140+public class Issue12345 : _IssuesUITest
141+{
142+ public override string Issue => "Description of the issue being tested";
101143  
102−**⚠️ Old pipeline names** (e.g., `MAUI-UITests-public`, `MAUI-public`) are **outdated** and should NOT be used. Always use the names above.
144+ public Issue12345(TestDevice device) : base(device) { }
103145  
104−### Investigating CI Failures
146+ [Test]
147+ [Category(UITestCategories.Layout)] // Pick the most appropriate category
148+ public void ButtonClickUpdatesLabel()
149+ {
150+ // Wait for element to be ready
151+ App.WaitForElement("TestButton");
105152  
106−**🚨 ALWAYS use the `azdo-build-investigator` skill when investigating CI failures or assessing merge readiness.** Its instructions direct you to invoke the `ci-analysis` skill first for the core investigation workflow, then apply MAUI-specific corrections (correct pipeline names, XHarness quirks, binlog guidance).
153+ // Interact with the UI
154+ App.Tap("TestButton");
107155  
108−Do NOT default to manually querying AzDO APIs or rely solely on `gh pr checks` pass/fail counts.
156+ // Verify expected behavior
157+ var labelText = App.FindElement("ResultLabel").GetText();
158+ Assert.That(labelText, Is.EqualTo("Expected Text"));
109159  
110−**When to use it:**
111−- "How does CI look?" / "Is CI green?" / "Can we merge?"
112−- "What's failing?" / "Are these known failures?"
113−- "Is this PR safe to merge?" / "Any CI concerns?"
114−- After any PR push to verify the build
160+ // Optional: Visual verification
161+ VerifyScreenshot();
162+ }
163+}
164+```
115165  
116−**Verifying specific tests:** When asked "did test X pass?" or "did the new test run?", query the **actual AzDO test results** — do NOT infer whether a test ran by inspecting code attributes. Class-level traits, base class categories, and assembly-level attributes can all cause a test to run even when the method itself has no visible category. Check the evidence, not the code.
166+## Common Patterns
117167  
118−**Anti-pattern:** Writing ad-hoc scripts to parse AzDO build timelines. The skills handle Helix work item details, known issue cross-referencing, and test result aggregation that manual approaches miss.
168+### Waiting for Elements
169+```csharp
170+App.WaitForElement("AutomationId");
171+```
119172  
120−### Gradle / Maven Dependency Failures (CFSClean)
173+### Interacting with Elements
174+```csharp
175+App.Tap("AutomationId");
176+App.FindElement("AutomationId").GetText();
177+var rect = App.WaitForElement("AutomationId").GetRect();
178+```
121179  
122−The official CI build uses CFSClean network isolation which blocks `repo.maven.apache.org`. All Gradle/Maven dependencies resolve through the `dotnet-public-maven` Azure Artifacts feed.
180+### Assertions
181+```csharp
182+Assert.That(actualValue, Is.EqualTo(expectedValue).Within(tolerance));
183+Assert.That(rect.Height, Is.LessThanOrEqualTo(maxHeight));
184+```
123185  
124−**If CI fails with Gradle 401 errors** like `"No local versions of package"` or `"Please provide authentication to save package from upstream"`, it means a Maven package hasn't been ingested into the feed yet. **Fix:** run `./eng/ingest-maven-deps.sh` locally to pre-populate the feed. See `src/Core/AndroidNative/settings.gradle` for details.
186+### Screenshot Verification
187+```csharp
188+// Verify visual appearance (automated comparison)
189+VerifyScreenshot();
125190  
126−**Do NOT upgrade Gradle past 8.x** — the Android SDK's `net.android.init.gradle.kts` is incompatible with Gradle 9.x (`dotnet/android#10738`).
191+// With custom name
192+VerifyScreenshot("CustomTestName");
127193  
128−### Code Formatting
194+// With tolerance (0.0-100.0 percentage) - use sparingly
195+VerifyScreenshot(tolerance: 0.5); // Allow 0.5% difference for cross-machine rendering variance
129196  
130−Always format code before committing:
197+// PREFERRED: Keep retrying for up to 2 seconds (for animations)
198+VerifyScreenshot(retryTimeout: TimeSpan.FromSeconds(2));
131199  
200+// Combined: tolerance for rendering variance + retryTimeout for timing
201+VerifyScreenshot(tolerance: 0.5, retryTimeout: TimeSpan.FromSeconds(2));
202+ 
203+// Manual screenshot for debugging
204+App.Screenshot("TestStep1");
205+```
206+ 
207+**CRITICAL - VerifyScreenshot() Built-in Features:**
208+ 
209+`VerifyScreenshot()` **already includes** stability mechanisms. Do NOT add redundant delays:
210+ 
211+| 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) |
218+ 
219+**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 issues
222+- ✅ Use `retryDelay` if you need to change the delay between retry attempts
223+- ❌ **DO NOT** add `Task.Delay()` or `Thread.Sleep()` before `VerifyScreenshot()` - use `retryTimeout` instead
224+ 
225+## Writing Robust UI Tests
226+ 
227+### Best Practices for Screenshot Tests
228+ 
229+When writing tests that use `VerifyScreenshot()`, follow these patterns to avoid flakiness:
230+ 
231+```
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+```
249+ 
250+### Common Flaky Test Patterns
251+ 
252+| 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 |
259+ 
260+### Anti-Patterns (DO NOT DO)
261+ 
262+| 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) |
269+ 
270+### When to Use What
271+ 
272+**VerifyScreenshot() parameters (preferred):**
273+```csharp
274+// Animation timing issues - keep retrying for up to 2 seconds
275+// This is the PREFERRED approach for flaky screenshot tests
276+VerifyScreenshot(retryTimeout: TimeSpan.FromSeconds(2));
277+ 
278+// Small tolerance for cross-machine rendering variance + retryTimeout for timing
279+// Use 0.5% tolerance as safety margin, NOT to hide timing issues
280+VerifyScreenshot(tolerance: 0.5, retryTimeout: TimeSpan.FromSeconds(2));
281+ 
282+// Legacy: retryDelay only changes delay BETWEEN retries (default 500ms)
283+// retryTimeout is preferred because it keeps trying until success
284+VerifyScreenshot(retryDelay: TimeSpan.FromSeconds(1));
285+```
286+ 
287+**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.
291+ 
292+**WaitForElement (for element readiness):**
293+```csharp
294+// Wait up to 10 seconds for element to appear
295+App.WaitForElement("ButtonId", timeout: TimeSpan.FromSeconds(10));
296+ 
297+// Then interact
298+App.Tap("ButtonId");
299+```
300+ 
301+**Task.Delay/Thread.Sleep (avoid if possible):**
302+```csharp
303+// AVOID: With retryTimeout, you rarely need explicit delays anymore
304+//
305+// Old pattern (before retryTimeout):
306+// Task.Delay(300).Wait();
307+// VerifyScreenshot(tolerance: 2.0);
308+//
309+// New pattern (preferred):
310+VerifyScreenshot(tolerance: 0.5, retryTimeout: TimeSpan.FromSeconds(2));
311+ 
312+// 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 otherwise
315+// 3. After exhausting other options AND documenting why
316+```
317+ 
318+### Understanding Test Infrastructure
319+ 
320+**Key files to understand when writing UI tests:**
321+ 
322+1. **UITest.cs** - Base class with `VerifyScreenshot()` implementation
323+ - Path: `src/Controls/tests/TestCases.Shared.Tests/UITest.cs`
324+ - Contains: retry logic, tolerance parsing, platform-specific delays
325+ 
326+2. **_IssuesUITest.cs** - Issues test base class
327+ - Path: `src/Controls/tests/TestCases.Shared.Tests/_IssuesUITest.cs`
328+ - Contains: Navigation helpers, common patterns
329+ 
330+3. **Extension methods** - Platform-specific helpers
331+ - Path: `src/Controls/tests/TestCases.Shared.Tests/` (various extension files)
332+ - Contains: Existing helpers for common operations
333+ 
334+**Find existing patterns:**
132335 ```bash
133−dotnet format Microsoft.Maui.sln --no-restore --exclude Templates/src --exclude-diagnostics CA1822
336+# See VerifyScreenshot implementation (including retryTimeout)
337+grep -A 30 "public void VerifyScreenshot" src/Controls/tests/TestCases.Shared.Tests/UITest.cs
338+ 
339+# Find existing tests using retryTimeout (preferred pattern)
340+grep -r "retryTimeout" src/Controls/tests/TestCases.Shared.Tests/Tests/
341+ 
342+# Find existing tolerance patterns
343+grep -r "tolerance:" src/Controls/tests/TestCases.Shared.Tests/Tests/
134344 ```
135345  
136−## Contribution Guidelines
346+### Infrastructure Notes
137347  
138−### Handling Existing PRs for Assigned Issues
348+**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.
139349  
140−**🚨 CRITICAL REQUIREMENT: Always develop your own solution first, then compare with existing PRs.**
350+## Test Categories
141351  
142−1. **Develop your own solution first** - Analyze the issue independently and design your approach without looking at existing PRs
143−2. **Search for existing PRs** - After developing your solution, search for open PRs addressing the same issue
144−3. **Compare and evaluate** - Examine existing PR approaches and decide which solution better addresses the issue
145−4. **Document your decision** - In your PR description, compare your solution to existing PRs and explain why you chose your approach, including concerns with alternatives
146−5. **Improve either solution** - Whether using your solution or an existing one, enhance with better tests, code quality, error handling, or documentation
352+### Category Guidelines
353+- Use appropriate categories from `UITestCategories`
354+- **Only ONE** `[Category]` attribute per test
355+- Pick the most specific category that applies
147356  
148−### Auto-Generated Files (Never Commit)
357+### Test Categories
149358  
150−These files are auto-generated and must NOT be committed:
151−- `cgmanifest.json` - Generated during CI builds
152−- `templatestrings.json` - Auto-generated localization
359+**CRITICAL**: Always check [UITestCategories.cs](../../src/Controls/tests/TestCases.Shared.Tests/UITestCategories.cs) for the authoritative, complete list of categories.
153360  
154−**For AI agents:** Always reset changes to these files before committing.
361+**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.
155362  
156−### PublicAPI.Unshipped.txt File Management
363+**Common categories** (examples only - not exhaustive):
364+- **SafeArea**: `SafeAreaEdges` - Safe area and padding tests
365+- **Basic controls**: `Button`, `Label`, `Entry`, `Editor` - Specific control tests
366+- **Collection controls**: `CollectionView`, `ListView`, `CarouselView` - Collection control tests
367+- **Layout**: `Layout` - Layout-related tests
368+- **Navigation**: `Shell`, `Navigation`, `TabbedPage` - Navigation tests
369+- **Interaction**: `Gestures`, `Focus`, `Accessibility` - Interaction tests
370+- **Lifecycle**: `Window`, `Page`, `LifeCycle` - Page lifecycle tests
157371  
158−When working with public API changes:
159−- **Never disable analyzers** to bypass PublicAPI.Unshipped.txt issues
160−- **Always add correct API entries** to PublicAPI.Unshipped.txt files
161−- **Use `dotnet format analyzers`** if having trouble
162−- **If files are incorrect**: Revert all changes, then add only the necessary new API entries
372+**List all categories programmatically**:
373+```bash
374+grep -E "public const string [A-Za-z]+ = " src/Controls/tests/TestCases.Shared.Tests/UITestCategories.cs
375+```
163376  
164−### Branching
165−- `main` - For bug fixes without API changes
166−- The highest `netN.0` branch (by convention) - For new features and API changes. To find it, run `git fetch origin` then: `git for-each-ref --sort=-version:refname --count=1 --format='%(refname:lstrip=3)' refs/remotes/origin/net*.0`
377+**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.
167378  
168−### Git Workflow (Copilot CLI Rules)
379+## Platform Coverage
169380  
170−**🚨 CRITICAL Git Rules for Copilot CLI:**
381+### Default Behavior
171382  
172−1. **NEVER commit directly to `main`** - Always create a feature branch for your work. Direct commits to `main` are strictly prohibited.
383+Tests should run on all applicable platforms by default. The test infrastructure handles platform detection automatically.
173384  
174−2. **When amending an existing PR, work on the PR's branch directly** - Do NOT create a separate branch off a PR branch. The PR branch already IS a feature branch. Creating a new branch off it means CI won't run on the original PR, defeating the purpose. Use `gh pr checkout` to switch to the PR branch, make your changes, commit, **then** ask before pushing so the user can review locally first.
385+### No Inline #if Directives in Test Methods
175386  
176−3. **Do NOT rebase, squash, or force-push** unless explicitly requested by the user. These operations rewrite git history and can cause problems for other contributors. Default behavior should be regular commits and pushes.
387+**Do NOT use `#if ANDROID`, `#if IOS`, etc. directly in test methods.** Platform-specific behavior must be hidden behind extension methods for readability.
177388  
178−**Safe Git Workflow:**
389+**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.
390+ 
391+```csharp
392+// ❌ BAD - inline #if in test method (hard to read)
393+[Test]
394+public void MyTest()
395+{
396+#if ANDROID
397+ App.TapCoordinates(100, 200);
398+#else
399+ App.Tap("MyElement");
400+#endif
401+}
402+ 
403+// ✅ GOOD - platform logic in extension method (clean test)
404+[Test]
405+public void MyTest()
406+{
407+ App.TapElementCrossPlatform("MyElement");
408+}
409+```
410+ 
411+Move platform-specific logic to extension methods to keep test code clean and readable.
412+ 
413+## Running UI Tests Locally
414+ 
415+**CRITICAL: ALWAYS use the BuildAndRunHostApp.ps1 script to run UI tests. NEVER run `dotnet test` or `dotnet build` commands manually.**
416+ 
417+### BuildAndRunHostApp.ps1 Script (ONLY Way to Run Tests)
418+ 
419+**Script location**: `.github/scripts/BuildAndRunHostApp.ps1`
420+ 
421+**Usage:**
422+```powershell
423+# Run specific test on Android
424+pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform android -TestFilter "FullyQualifiedName~Issue12345"
425+ 
426+# Run specific test on iOS
427+pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "FullyQualifiedName~Issue12345"
428+ 
429+# Run specific test on MacCatalyst
430+pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform maccatalyst -TestFilter "FullyQualifiedName~Issue12345"
431+ 
432+# Run tests by category
433+pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform android -Category "SafeAreaEdges"
434+ 
435+# Run specific test with custom device (iOS only)
436+pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "Issue12345" -DeviceUdid "12345678-1234567890ABCDEF"
437+```
438+ 
439+**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 deployment
443+- ✅ 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 HostApp
446+ - `test-output.log` - Test execution output
447+ 
448+**Why you must use the script:**
449+- The script ensures correct device targeting and environment variables
450+- It handles platform-specific quirks and setup requirements
451+- It provides consistent test execution across all platforms
452+- It captures logs automatically for debugging
453+- Manual `dotnet` commands often fail due to missing environment setup
454+ 
455+### Prerequisites: Kill Existing Appium Processes
456+ 
457+**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:
458+ 
459+```
460+AppiumServerHasNotBeenStartedLocallyException: The local appium server has not been started.
461+Time 120000 ms for the service starting has been expired!
462+```
463+ 
464+**Solution: Always kill existing Appium processes before running tests:**
465+ 
179466 ```bash
180−# Create a feature branch (NEVER work directly on main)
181−git checkout -b feature/issue-12345
467+# Kill any Appium processes on port 4723
468+lsof -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+```
182470  
183−# Make commits normally
184−git add .
185−git commit -m "Fix: Description of the change"
471+**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.
186472  
187−# Push to remote (for new branches)
188−git push -u origin feature/issue-12345
473+### Troubleshooting
189474  
190−# For subsequent pushes on the same branch
191−git push
475+**Android App Crashes on Launch:**
476+ 
477+If you encounter navigation fragment errors or resource ID issues:
192478 ```
479+java.lang.IllegalArgumentException: No view found for id 0x7f0800f8 (com.microsoft.maui.uitests:id/inward) for fragment NavigationRootManager_ElementBasedFragment
480+```
193481  
194−**When asked to update an existing PR:**
482+**Solution:** Read the crash logs to find the actual exception:
195483 ```bash
196−# Check out the PR branch directly (do NOT create a new branch off it)
197−gh pr checkout 12345
484+# Monitor logcat for the crash
485+adb logcat | grep -E "(FATAL|AndroidRuntime|Exception|Error|Crash)"
486+```
198487  
199−# Make fixes and commit to the PR branch
200−git add .
201−git commit -m "Fix: Description of the change"
488+**Debugging steps:**
489+1. **Find the exception** in logcat - look for the stack trace
490+2. **Investigate the root cause** - What line of code is throwing? Why?
491+3. **Check for null references** - Are required resources missing?
492+4. **Verify resource IDs exist** - Check if the ID referenced actually exists in the app
493+5. If you can't determine the fix, **ask for guidance** with the full exception details
494+ 
495+**iOS App Crashes on Launch or Won't Start with Appium:**
496+ 
497+If the iOS app crashes when launched by Appium or manually with `xcrun simctl launch`:
498+ 
499+**Solution:** Read the crash logs to find the actual exception:
500+```bash
501+# Capture crash logs
502+xcrun simctl spawn booted log stream --predicate 'processImagePath contains "TestCases.HostApp"' --level=debug > /tmp/ios_crash.log 2>&1 &
503+LOG_PID=$!
504+ 
505+# Try to launch the app
506+xcrun simctl launch $UDID com.microsoft.maui.uitests
507+ 
508+# Wait a moment for crash
509+sleep 3
510+ 
511+# Stop log capture
512+kill $LOG_PID
513+ 
514+# Review the crash log
515+cat /tmp/ios_crash.log | grep -A 20 -B 5 "Exception"
202516 ```
203−1. **STOP and ask the user** before pushing: "Changes are committed locally. Would you like me to push these changes to the PR?"
204−2. Exception: If the user's instructions explicitly include pushing, proceed without asking.
205517  
206−### Documentation
207−- Update XML documentation for public APIs
208−- Follow existing code documentation patterns
209−- Update relevant docs in `docs/` folder when needed
518+**Debugging steps:**
519+1. **Find the exception** in the crash log - look for stack traces
520+2. **Investigate the root cause** - What's causing the crash?
521+3. **Check for missing resources** - Are all required files included in the bundle?
522+4. **Verify Info.plist** - Are required keys present?
523+5. **Check for platform-specific issues** - iOS version compatibility, permissions, etc.
524+6. If you can't determine the fix, **ask for guidance** with the full exception details
210525  
211−### Opening PRs
526+### Dangerous System Commands (Never Run)
212527  
213−All PRs are required to have this at the top of the description:
528+**🚨 NEVER run these commands — they cause destructive system-wide side effects:**
214529  
530+- **`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 Protection
532+- **`networksetup`** — Modifies network configuration
533+- **`defaults delete`** on system domains — Resets system preferences
534+ 
535+**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.
536+ 
537+## Before Committing
538+ 
539+Verify the following checklist before committing UI tests:
540+ 
541+- [ ] Compile both the HostApp project and TestCases.Shared.Tests project successfully
542+- [ ] Verify AutomationId references match between HostApp UI (C# or XAML) and test code
543+- [ ] Ensure file names follow the `IssueXXXXX` pattern and match between projects
544+- [ ] Ensure test methods have descriptive names
545+- [ ] Verify test inherits from `_IssuesUITest`
546+- [ ] Confirm only ONE `[Category]` attribute per test
547+- [ ] No inline `#if` directives in test code (use extension methods)
548+- [ ] Test passes locally on at least one platform
549+ 
550+### Test State Management
551+ 
552+- Tests should be independent and not rely on state from other tests
553+- The test infrastructure handles navigation to the test page and basic cleanup
554+- If your test modifies global app state, consider whether cleanup is needed
555+- Most tests don't require explicit cleanup as each test gets a fresh page instance
556+ 
557+## Best Practices
558+ 
559+### Default: C# Over XAML
560+ 
561+**Use C# files (`.cs`) for UI tests. Only use XAML files (`.xaml`) when the test scenario requires XAML-specific features.**
562+ 
563+**When to use C# only (`.cs` file):**
564+- ✅ Simple control tests (Button, Label, Entry, etc.)
565+- ✅ Layout tests (Grid, StackLayout, FlexLayout, etc.)
566+- ✅ Navigation tests
567+- ✅ Event handling tests
568+- ✅ Property tests
569+- ✅ Most UI behavior tests
570+ 
571+**When XAML is required (`.xaml` + `.xaml.cs` files):**
572+- ✅ Testing XAML binding syntax
573+- ✅ Testing XAML templates (DataTemplate, ControlTemplate)
574+- ✅ Testing XAML styles and resources
575+- ✅ Testing XAML markup extensions
576+- ✅ Testing XamlC compilation behavior
577+- ✅ Testing XAML-specific parsing or compilation issues
578+ 
579+**Examples:**
580+ 
581+```csharp
582+// ✅ GOOD: C# only test (most common pattern)
583+public class Issue12345 : ContentPage
584+{
585+ public Issue12345()
586+ {
587+ Content = new StackLayout
588+ {
589+ Children =
590+ {
591+ new Label { Text = "Hello", AutomationId = "MyLabel" },
592+ new Button { Text = "Click Me", AutomationId = "MyButton" }
593+ }
594+ };
595+ }
596+}
215597 ```
216−<!-- Please let the below note in for people that find this PR -->
217−> [!NOTE]
218−> Are you waiting for the changes in this PR to be merged?
219−> It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you!
598+ 
599+```xaml
600+<!-- ❌ 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>
220607 ```
221608  
222−Always put that at the top, without the block quotes. Without it, users will NOT be able to try the PR and your work will have been in vain!
609+### Use Test Helper Base Classes
223610  
611+**ALWAYS check for and use existing test helper base classes instead of creating from scratch:**
224612  
613+| 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` |
225619  
226−## Custom Agents and Skills
620+**TestShell provides:**
621+- Platform-specific automation IDs for flyout and back buttons
622+- Helper methods: `AddContentPage()`, `AddBottomTab()`, `AddTopTab()`, `AddFlyoutItem()`
623+- Abstract `Init()` method for setup
624+- `DisplayedPage` property for accessing current page
227625  
228−The repository includes specialized custom agents and reusable skills for specific tasks.
626+**TestContentPage/TestNavigationPage provide:**
627+- Abstract `Init()` method for deferred initialization
628+- Cleaner separation of setup logic
229629  
230−### Skills vs Agents
630+**Example:**
231631  
232−| Aspect | Skills | Agents |
233−|--------|--------|--------|
234−| **Invoke** | `/skill-name` or direct request | Delegate to agent |
235−| **Output** | Analysis, recommendations | Actions, changes applied |
236−| **Interaction** | Interactive discussion | Autonomous workflow |
237−| **Example** | `/learn-from-pr` → recommendations | learn-from-pr agent → applies changes |
632+```csharp
633+// ✅ GOOD: Using TestShell for Shell tests
634+[Issue(IssueTracker.Github, 12345, "Shell navigation bug", PlatformAffected.All)]
635+public class Issue12345 : TestShell
636+{
637+ protected override void Init()
638+ {
639+ AddContentPage(new ContentPage
640+ {
641+ Content = new Label { Text = "Test" }
642+ });
643+ }
644+}
238645  
239−### Available Custom Agents
646+// ❌ BAD: Creating Shell from scratch
647+public class Issue12345 : Shell
648+{
649+ public Issue12345()
650+ {
651+ Items.Add(new ShellItem { ... }); // Verbose, error-prone
652+ }
653+}
654+```
240655  
241−1. **pr** - Sequential 4-phase workflow for reviewing and working on PRs
242− - **Use when**: A PR already exists and needs review or work, OR an issue needs a fix
243− - **Capabilities**: PR review, test verification, fix exploration, alternative comparison
244− - **Trigger phrases**: "review PR #XXXXX", "work on PR #XXXXX", "fix issue #XXXXX", "continue PR #XXXXX"
245− - **Do NOT use for**: Just running tests manually → Use `sandbox-agent`
656+### Avoid Obsolete APIs
246657  
247−2. **write-tests-agent** - Agent for writing tests. Determines test type (UI vs XAML) and invokes the appropriate skill (`write-ui-tests`, `write-xaml-tests`)
248− - **Use when**: Creating new tests for issues or PRs
249− - **Capabilities**: Test type determination (UI and XAML), skill invocation, test verification
250− - **Trigger phrases**: "write tests for #XXXXX", "create tests", "add test coverage"
658+**NEVER use obsolete APIs in new tests. Use modern equivalents:**
251659  
252−3. **sandbox-agent** - Specialized agent for working with the Sandbox app for testing, validation, and experimentation
253− - **Use when**: User wants to manually test PR functionality or reproduce issues
254− - **Capabilities**: Sandbox app setup, Appium-based manual testing, PR functional validation
255− - **Trigger phrases**: "test this PR", "validate PR #XXXXX in Sandbox", "reproduce issue #XXXXX", "try out in Sandbox"
256− - **Do NOT use for**: Code review (use pr agent), writing automated tests (use write-tests-agent)
660+| ❌ 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 |
257666  
258−4. **learn-from-pr** - Extracts lessons from PRs and applies improvements to the repository
259− - **Use when**: After complex PR, want to improve instruction files/skills based on lessons learned
260− - **Capabilities**: Analyzes PR, identifies failure modes, applies improvements to instruction files, skills, code comments
261− - **Trigger phrases**: "learn from PR #XXXXX and apply improvements", "improve repo based on what we learned", "update skills based on PR"
262− - **Output**: Applied changes to instruction files, skills, architecture docs, code comments
263− - **Do NOT use for**: Analysis only without applying changes → Use `/learn-from-pr` skill instead
667+**Examples:**
264668  
265−5. **release-readiness-agent** - Assesses ship-readiness for a .NET MAUI release branch — both **SR** (`release/*-srN`) and **Preview** (`release/*-previewN`)
266− - **Use when**: A release (SR or Preview) is approaching ship date and you need a synthesized verdict with WorkIQ/MCP enrichment on top of the deterministic report — **or** for a portfolio question across all active releases ("status on releases", "what needs attention across releases") where the user may not know which releases exist
267− - **Capabilities**: Resolves the branch (SR or Preview) from natural language, picks the right script (`Get-ReleaseReadiness.ps1` for SR, `Get-PreviewReadiness.ps1` for Preview), enriches `rejected-from-sr` candidates with WorkIQ context (SR lane), patches `UNKNOWN` ship-check rows via MCP (`maestro_default_channels`, `maestro_builds`), presents an overall verdict
268− - **Trigger phrases**: "is SR7 ready to ship", "release readiness for release/10.0.1xx-sr7", "survey the SR8 branch", "how does net11 preview6 look", "is preview6 ready to cut", "release readiness for release/11.0.1xx-preview6" — **plus portfolio / cross-release questions with no specific release named**: "give me a status on releases", "release status overview", "what's the status across all releases", "what needs attention across releases", "what's next for MAUI releases"
269− - **Output**: Verdict (Ready / Conditionally Ready / Not Ready) + per-candidate classification (SR) or per-section table (Preview) + actionable next steps
270− - **Do NOT use for**: Programmatic / scripted consumers that just need the raw JSON — use the `release-readiness` skill directly. Reviewing a single PR (use **pr**). Running tests manually (use **sandbox-agent**).
669+```csharp
670+// ✅ GOOD: Modern Window API
671+this.Window.Page = new NavigationPage(new MyPage());
271672  
272−### Reusable Skills
673+// ❌ BAD: Obsolete Application.MainPage
674+Application.MainPage = new NavigationPage(new MyPage());
273675  
274−Skills are modular capabilities that can be invoked directly or used by agents. Located in `.github/skills/`:
676+// ✅ GOOD: Border
677+new Border { Content = new Label { Text = "Hello" } }
275678  
276−#### User-Facing Skills
679+// ❌ BAD: Frame (deprecated)
680+new Frame { Content = new Label { Text = "Hello" } }
681+```
277682  
278−1. **pr-review** (`.github/skills/pr-review/SKILL.md`)
279− - **Purpose**: End-to-end PR review orchestrator — 3 phases: pr-preflight, try-fix, pr-report. Gate runs separately before this skill via Review-PR.ps1.
280− - **Trigger phrases**: "review PR #XXXXX", "work on PR #XXXXX", "fix issue #XXXXX", "continue PR #XXXXX"
281− - **Capabilities**: Multi-model fix exploration, alternative comparison, PR review recommendation
282− - **Do NOT use for**: Just running tests manually → Use `sandbox-agent`
283− - **Phase instructions** (in `.github/pr-review/`):
284− - `pr-preflight.md` — Context gathering from issue/PR
285− - `pr-report.md` — Final recommendation
286− - **Phase skill**: `try-fix` — Multi-model fix exploration
287− - **Note**: Gate (test verification) runs as a script step in `Review-PR.ps1` before this skill is invoked. Gate result is passed in the prompt.
683+### Use UITest Optimized Controls for Screenshot Tests
288684  
289−2. **issue-triage** (`.github/skills/issue-triage/SKILL.md`)
290− - **Purpose**: Query and triage open issues that need milestones, labels, or investigation
291− - **Trigger phrases**: "find issues to triage", "show me old Android issues", "what issues need attention"
292− - **Scripts**: `init-triage-session.ps1`, `query-issues.ps1`, `record-triage.ps1`
685+**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.
293686  
294−2. **find-reviewable-pr** (`.github/skills/find-reviewable-pr/SKILL.md`)
295− - **Purpose**: Finds open PRs in dotnet/maui and dotnet/docs-maui that need review
296− - **Trigger phrases**: "find PRs to review", "show milestoned PRs", "find partner PRs"
297− - **Scripts**: `query-reviewable-prs.ps1`
298− - **Categories**: P/0, milestoned, partner, community, recent, docs-maui
687+| 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 |
299692  
300−3. **pr-finalize** (`.github/skills/pr-finalize/SKILL.md`)
301− - **Purpose**: Verifies PR title and description match actual implementation, AND performs code review for best practices before merge.
302− - **Trigger phrases**: "finalize PR #XXXXX", "check PR description for #XXXXX", "review commit message"
303− - **Used by**: Before merging any PR, when description may be stale
304− - **Note**: Does NOT require agent involvement or session markdown - works on any PR
305− - **🚨 CRITICAL**: NEVER use `--approve` or `--request-changes` - only post comments. Approval is a human decision.
693+**Example:**
306694  
307−4. **code-review** (`.github/skills/code-review/SKILL.md`)
308− - **Purpose**: Reviews PR code changes for correctness, safety, and consistency with MAUI conventions. Walks through a MAUI-specific checklist covering handler lifecycle, platform code, safe area, threading, public API, and test patterns.
309− - **Trigger phrases**: "review code for PR #XXXXX", "code review PR #XXXXX", "review this PR's code"
310− - **Note**: Standalone skill — uses independence-first assessment (reads code before PR description to avoid anchoring bias). Can be used by any agent or invoked directly.
311− - **🚨 CRITICAL**: NEVER use `--approve` or `--request-changes` — only post comments. Approval is a human decision.
695+```csharp
696+// For screenshot tests, use UITest controls (UITestEntry, UITestEditor, UITestSearchBar)
697+var entry = new UITestEntry
698+{
699+ Placeholder = "Enter text",
700+ IsCursorVisible = false, // Prevents flaky screenshots
701+ AutomationId = "TestEntry"
702+};
312703  
313−5. **learn-from-pr** (`.github/skills/learn-from-pr/SKILL.md`)
314− - **Purpose**: Analyzes completed PR to identify repository improvements (analysis only, no changes applied)
315− - **Trigger phrases**: "what can we learn from PR #XXXXX?", "how can we improve agents based on PR #XXXXX?"
316− - **Used by**: After complex PRs, when agent struggled to find solution
317− - **Output**: Prioritized recommendations for instruction files, skills, code comments
318− - **Note**: For applying changes automatically, use the learn-from-pr agent instead
704+// For non-screenshot tests, standard Entry is fine
705+var entry = new Entry { Placeholder = "Enter text", AutomationId = "TestEntry" };
706+```
319707  
320−6. **write-ui-tests** (`.github/skills/write-ui-tests/SKILL.md`)
321− - **Purpose**: Creates UI tests for GitHub issues and verifies they reproduce the bug
322− - **Trigger phrases**: "write UI tests for #XXXXX", "create UI test for issue", "add UI test coverage"
323− - **Output**: Test files that fail without fix, pass with fix
708+**Location:** `src/Controls/tests/TestCases.HostApp/Controls/UITest*.cs`
324709  
325−7. **write-xaml-tests** (`.github/skills/write-xaml-tests/SKILL.md`)
326− - **Purpose**: Creates XAML unit tests for XAML parsing, compilation, and source generation
327− - **Trigger phrases**: "write XAML tests for #XXXXX", "test XamlC behavior", "reproduce XAML parsing bug"
328− - **Output**: Test files for Controls.Xaml.UnitTests
710+### Check Similar Tests for Patterns
329711  
330−9. **verify-tests-fail-without-fix** (`.github/skills/verify-tests-fail-without-fix/SKILL.md`)
331− - **Purpose**: Verifies tests catch the bug before fix and pass with fix. Auto-detects test type (UI, device, unit, XAML) and dispatches to the appropriate runner.
332− - **Two modes**: Verify failure only (test creation) or full verification (test + fix)
333− - **Used by**: After creating tests, before considering PR complete
712+**Before creating a new test, search for similar tests to reuse patterns:**
334713  
335−10. **run-integration-tests** (`.github/skills/run-integration-tests/SKILL.md`)
336− - **Purpose**: Build, pack, and run .NET MAUI integration tests locally
337− - **Trigger phrases**: "run integration tests", "test templates locally", "run macOSTemplates tests", "run RunOniOS tests"
338− - **Categories**: Build, WindowsTemplates, macOSTemplates, Blazor, MultiProject, Samples, AOT, RunOnAndroid, RunOniOS
339− - **Note**: **ALWAYS use this skill** instead of manual `dotnet test` commands for integration tests
714+```bash
715+# Find similar control tests
716+grep -r "class.*Issue.*Button" src/Controls/tests/TestCases.HostApp/Issues/*.cs
340717  
341−11. **dependency-flow** (`.github/skills/dependency-flow/SKILL.md`)
342− - **Purpose**: MAUI-specific dependency flow rules, channel conventions, and feed lookup workflows
343− - **Trigger phrases**: "feeds for .NET MAUI X.Y.Z", "where is MAUI build", "promote build to public feed", "what channels is MAUI on", "subscription health for MAUI"
344− - **Wraps**: `maestro-cli` skill (from `dotnet-dnceng@dotnet-arcade-skills` plugin) and maestro MCP tools
345− - **Note**: Provides MAUI-specific guardrails on top of core Maestro/darc operations — channel naming, safety deny-list, input validation, and prompt injection defense
718+# Find Shell tests
719+grep -r "TestShell" src/Controls/tests/TestCases.HostApp/Issues/*.cs
346720  
347−12. **release-readiness** (`.github/skills/release-readiness/SKILL.md`)
348− - **Purpose**: Deterministic ship-readiness engine for .NET MAUI release branches — both **SR** (`release/*-srN`) and **Preview** (`release/*-previewN`). Surveys CI, computes what's actually shipping, classifies open regressions, identifies port candidates and rejected backports
349− - **Trigger phrases**: "release readiness for SRN", "is SR7 ready to ship", "survey the SR branch", "release readiness for preview6", "how does preview6 look (deterministic)", "status across all releases" (reads the live `[Release Readiness]` tracker issues by body marker — no survey re-run needed)
350− - **Scripts**: `Get-ReleaseReadiness.ps1` (SR lane), `Get-PreviewReadiness.ps1` (Preview lane), `Find-ReleaseReadinessTrackers.ps1` (tracker discovery)
351− - **Output**: JSON + Markdown report, list of source PRs, classification of regression issues (in-sr-active, rejected-from-sr, no-fix-yet, etc.)
352− - **Note**: Deterministic and reproducible — no MCP, no LLM judgment. Use **this skill directly** when you need raw output for a script, dashboard, cron job, or programmatic consumer. For natural-language verdict synthesis with WorkIQ enrichment, use the **`release-readiness-agent`** instead.
721+# Find tests for specific control
722+grep -r "CollectionView" src/Controls/tests/TestCases.HostApp/Issues/*.cs
353723  
354−#### Internal Skills (Used by Agents)
724+# Find tests using UITest optimized controls
725+grep -r "UITestEntry\|UITestEditor\|UITestSearchBar" src/Controls/tests/TestCases.HostApp/Issues/*.cs
726+```
355727  
356−13. **try-fix** (`.github/skills/try-fix/SKILL.md`)
357− - **Purpose**: Proposes ONE independent fix approach, applies it, tests, records result with failure analysis, then reverts
358− - **Used by**: pr agent Phase 3 (Fix phase) - rarely invoked directly by users
359− - **Behavior**: Reads prior attempts to learn from failures. Max 5 attempts per session.
360− - **Output**: Updates session markdown with attempt results and failure analysis
728+**Reuse established patterns:**
729+- AutomationId naming conventions
730+- Test structure and layout
731+- Common helper methods
732+- Platform-specific workarounds
733+- UITest optimized control usage
361734  
362−### Using Custom Agents
735+### Safe Area Testing (iOS/MacCatalyst)
363736  
364−**Delegation Policy**: When user request matches agent trigger phrases, **ALWAYS delegate to the appropriate agent immediately**. Do not ask for permission or explain alternatives unless the request is ambiguous.
737+**⚠️ CRITICAL for macCatalyst safe area tests:**
365738  
366−**Examples of correct delegation**:
367−- User: "Review PR #12345" → Immediately invoke **pr** agent
368−- User: "Test this PR" → Immediately invoke **sandbox-agent**
369−- User: "Fix issue #67890" (no PR exists) → Suggest using `/delegate` command
370−- User: "Write tests for issue #12345" → Immediately invoke **write-tests-agent**
371−- User: "Is SR7 ready to ship?" → Immediately invoke **release-readiness-agent**
372−- User: "How does net11 preview6 look?" → Immediately invoke **release-readiness-agent**
373−- User: "Give me a status on releases / what needs attention across releases?" → Immediately invoke **release-readiness-agent** (portfolio mode — it enumerates active releases by reading the `[Release Readiness]` tracker issues; don't ask "which release?")
374−- User: "Give me the raw release-readiness JSON for SR8" → Use the **release-readiness** skill directly (no enrichment needed)
739+Safe area behavior differs significantly between macOS versions. Tests must account for this variability.
375740  
376−**When NOT to delegate**:
377−- User asks "What does PR #12345 do?" → Informational query, handle yourself
378−- User asks "How do I test PRs?" → Documentation query, handle yourself
379−- User has follow-up questions after agent completes → Continue the conversation yourself
741+| 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 |
745+ 
746+**Rules for safe area tests:**
747+ 
748+1. **Use tolerances for safe area measurements** - Exact pixel values vary by macOS version
749+2. **Test behavior, not exact values** - Verify content is NOT obscured, rather than checking exact padding pixels
750+3. **Use `GetRect()` for child content position** - Measure where content actually appears, not parent size
751+4. **Never hardcode safe area expectations** - Tests should pass on macOS 14/15 AND macOS 26
752+ 
753+**Example patterns:**
754+ 
755+```csharp
756+// ❌ BAD: Hardcoded safe area value (breaks across macOS versions)
757+var safeArea = element.GetRect();
758+Assert.That(safeArea.Y, Is.EqualTo(28)); // Fails on macOS 26
759+ 
760+// ✅ GOOD: Test that content is not obscured by title bar
761+var contentRect = App.WaitForElement("MyContent").GetRect();
762+var titleBarRect = App.WaitForElement("TitleBar").GetRect();
763+Assert.That(contentRect.Y, Is.GreaterThanOrEqualTo(titleBarRect.Height),
764+ "Content should not be obscured by title bar");
765+ 
766+// ✅ GOOD: Use tolerance for safe area (accounts for OS differences)
767+Assert.That(contentRect.Y, Is.GreaterThan(0).And.LessThan(50),
768+ "Content should have some top padding but not excessive");
769+```
770+ 
771+**Test category**: Use `UITestCategories.SafeAreaEdges` for safe area tests.
772+ 
773+**Platform scope**: Safe area tests should typically run on iOS and MacCatalyst (not just one).
774+ 
775+**See also**: `.github/instructions/safe-area-debugging.instructions.md` for investigation guidelines
776+ 
RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack