diff --git a/.github/README-AI.md b/.github/README-AI.md index 55687a0abc58..637bc29b376b 100644 --- a/.github/README-AI.md +++ b/.github/README-AI.md @@ -2,14 +2,19 @@ This folder contains instructions and configurations for AI coding assistants working on the .NET MAUI repository. -## Quick Start: Using the PR Reviewer Agent +## Available Agents +### PR Reviewer Agent The PR reviewer agent conducts thorough, constructive code reviews of .NET MAUI pull requests with hands-on testing and validation. -### How to Use +### Issue Resolver Agent +The issue resolver agent investigates, reproduces, and fixes reported issues in the .NET MAUI repository with comprehensive testing and validation. -#### Option 1: GitHub Copilot CLI (Local) +## How to Use +### Option 1: GitHub Copilot CLI (Local) + +**PR Reviewer Agent:** ```bash # Start GitHub Copilot CLI with agent support copilot --allow-all-tools --allow-all-paths @@ -18,33 +23,42 @@ copilot --allow-all-tools --allow-all-paths /agent pr-reviewer # Request a review -please review +please review https://github.com/dotnet/maui/pull/XXXXX ``` -#### Example - +**Issue Resolver Agent:** ```bash +# Start GitHub Copilot CLI with agent support copilot --allow-all-tools --allow-all-paths -/agent pr-reviewer -please review https://github.com/dotnet/maui/pull/32372 + +# Invoke the issue-resolver agent +/agent issue-resolver + +# Request issue investigation +please investigate and fix https://github.com/dotnet/maui/issues/XXXXX ``` -#### Option 2: GitHub Copilot Agents (Web) +### Option 2: GitHub Copilot Agents (Web) 1. **Navigate to the agents tab** at https://github.com/copilot/agents 2. **Select your repository and branch** using the dropdown menus in the text box -3. **Choose your agent** from the dropdown (pr-reviewer) +3. **Choose your agent** from the dropdown: + - `pr-reviewer` for PR reviews + - `issue-resolver` for investigating and fixing issues 4. **Enter a task** in the text box: - For PR reviews: `Please review this PR: https://github.com/dotnet/maui/pull/XXXXX` + - For issue resolution: `Please investigate and fix: https://github.com/dotnet/maui/issues/XXXXX` 5. **Click Start task** or press Return 6. **Follow the agent's progress** - The agent task will appear below the text box. Click into it to see live updates. -## What the Agent Does +## What the Agents Do + +### PR Reviewer Agent Every PR review includes: @@ -55,38 +69,109 @@ Every PR review includes: 5. **Edge Case Testing** - Tests scenarios not mentioned by the PR author 6. **Documented Results** - Provides review with actual test data and evidence -The agent will pause and ask for help if it encounters: -- Merge conflicts when applying PR changes +### Issue Resolver Agent + +Every issue resolution includes: + +1. **Issue Investigation** - Analyzes the reported issue and gathers context +2. **Reproduction** - Creates minimal reproduction case in Sandbox app +3. **Root Cause Analysis** - Identifies the underlying problem in the codebase +4. **Fix Implementation** - Implements and tests the fix +5. **Validation** - Tests both with and without the fix to prove it works +6. **UI Test Creation** - Adds automated UI test to prevent regression +7. **Documentation** - Provides detailed explanation of the issue and fix + +### When Agents Pause + +Both agents will pause and ask for help if they encounter: +- Merge conflicts when applying changes - Build errors that prevent testing - Test results that are unexpected or confusing - Any step that prevents thorough validation -## Important Notes +## Agent Architecture -⚠️ **We're still refining the agent instructions** to ensure it consistently follows the testing workflow. +### Progressive Disclosure Pattern -**The agent will pause and ask for help if:** -- Merge conflicts occur when applying PR changes -- Build errors prevent testing -- Test results are unexpected or confusing -- Any step fails that prevents thorough validation +Agents use a layered approach to prevent cognitive overload: -This is by design - it's better to pause and get guidance than provide incomplete or misleading reviews. +1. **Entry Point** - Minimal instructions with mandatory pre-work +2. **Essential Reading** - Quick-start guide with core workflow +3. **Quick Reference** - Command templates for common tasks +4. **Just-in-Time Learning** - Specialized guides referenced as needed +5. **Deep Dives** - Platform-specific and error-handling details -## File Structure +**Key principle**: STOP after Essential Reading. Don't read all 11 files upfront - reference specialized guides only when needed for specific tasks. -### Agent Definitions -- **`agents/pr-reviewer.md`** - Main PR reviewer agent instructions and workflows +### Reading Order & Stopping Points -### Instruction Files -These files provide specialized guidance for specific scenarios: +**START HERE (required)**: +1. Entry point (`agents/*.md`) - Understand role and mandatory pre-work +2. Quick-start guide - Learn workflow, checkpoints, and rules +3. **STOP** - Begin work, refer to other guides as needed -- **`instructions/common-testing-patterns.md`** - Common testing patterns for command sequences (UDID extraction, builds, deploys, error checking) +**Reference as needed**: +- Quick-ref guide - Command templates +- Platform guides - Platform-specific procedures +- Error-handling - When encountering specific issues +- Testing guidelines - For detailed testing procedures + +### Time Philosophy + +Agents work with **time budgets as estimates for planning**, not hard deadlines: +- Budgets help recognize when to checkpoint with humans +- Quality and thoroughness take precedence over speed +- Pause and ask for help rather than rushing incomplete work + +## File Structure + +### Agent Definitions +- **`agents/pr-reviewer.md`** - PR reviewer agent entry point (72 lines) +- **`agents/issue-resolver.md`** - Issue resolver agent entry point (77 lines) + +### Agent Instruction Packages + +Each agent has a progressive disclosure structure for optimal learning: + +**PR Reviewer Agent** (11 files, 3,463 lines): +- `pr-reviewer-agent/README.md` - Quick lookup by scenario (7 task-oriented scenarios) +- `pr-reviewer-agent/quick-start.md` - Essential reading (workflow, checkpoints, rules) +- `pr-reviewer-agent/quick-ref.md` - Command templates and patterns +- `pr-reviewer-agent/core-guidelines.md` - Philosophy and principles +- `pr-reviewer-agent/testing-guidelines.md` - Testing procedures +- `pr-reviewer-agent/error-handling.md` - Common mistakes (9 critical patterns) +- `pr-reviewer-agent/platforms/` - Platform-specific guides (Android, iOS, Windows, MacCatalyst) +- `pr-reviewer-agent/output-format.md` - Review output formatting + +**Issue Resolver Agent** (8 files, 3,479 lines): +- `issue-resolver-agent/README.md` - Quick lookup by scenario +- `issue-resolver-agent/quick-start.md` - Essential reading +- `issue-resolver-agent/quick-ref.md` - Command templates +- `issue-resolver-agent/core-guidelines.md` - Philosophy +- `issue-resolver-agent/testing-guidelines.md` - Testing procedures +- `issue-resolver-agent/error-handling.md` - Common mistakes +- `issue-resolver-agent/platforms/` - Platform-specific guides +- `issue-resolver-agent/output-format.md` - PR description formatting + +### Shared Instruction Files (4 files, 2,224 lines) + +These provide specialized guidance for specific scenarios used by both agents: + +- **`instructions/common-testing-patterns.md`** - Command sequences (UDID extraction, builds, deploys, error checking) - **`instructions/uitests.instructions.md`** - UI testing guidelines (when to use HostApp vs Sandbox) - **`instructions/safearea-testing.instructions.md`** - SafeArea testing patterns (measure children, not parents) -- **`instructions/instrumentation.instructions.md`** - Code instrumentation patterns for debugging and testing -- **`instructions/appium-control.instructions.md`** - Standalone Appium scripts for manual debugging and exploration -- **`instructions/templates.instructions.md`** - Template modification rules and conventions +- **`instructions/instrumentation.instructions.md`** - Code instrumentation for debugging and testing +- **`instructions/appium-control.instructions.md`** - Standalone Appium scripts for manual debugging +- **`instructions/templates.instructions.md`** - Template modification rules + +### Recent Improvements (Phase 1 - November 2025) + +**PR Reviewer Agent enhancements:** +1. **Mandatory pre-work moved to top** - Critical requirements now at line 6 instead of line 43 +2. **Reading order & stopping points** - Explicit "STOP after Essential Reading" to prevent reading loop +3. **Most critical mistake elevated** - "Don't Skip Testing" moved from Mistake #6 to Mistake #1 with complete Android emulator startup sequence +4. **Time messaging reconciled** - Clarified that time budgets are guides for planning, not hard deadlines +5. **Appium version updated** - All references updated to Appium.WebDriver 8.0.1 (latest stable) ### General Guidelines - **`copilot-instructions.md`** - General coding standards, build requirements, file conventions for the entire repository @@ -168,8 +253,13 @@ This will refresh your GitHub authentication and resolve most CLI-related issues **Build or test failures:** - The agent is designed to pause and ask for help rather than proceeding -- Work with the agent to resolve the issue before continuing the review -- This ensures reviews are based on actual working code +- Work with the agent to resolve the issue before continuing +- This ensures work is based on actual working code + +**Agent reads too many files upfront:** +- Should follow "STOP after Essential Reading" guidance +- Specialized guides should be referenced just-in-time, not upfront +- If agent reads all 11 files before starting, this needs refinement ## Support @@ -179,8 +269,21 @@ For issues or questions about the AI agent instructions: 3. Ask in the repository discussions or issues 4. Propose changes via PR to improve the instructions +## Metrics + +**Total instruction content**: +- PR Reviewer: 11 files, 3,463 lines +- Issue Resolver: 8 files, 3,479 lines +- Shared instructions: 4 files, 2,224 lines +- Total: 23 unique files, ~9,166 lines + +**Progressive disclosure effectiveness**: +- Entry point: 72-77 lines (< 2 minutes) +- Essential reading: ~500 lines (< 10 minutes) +- Just-in-time references: Read only when specific need arises + --- -**Last Updated**: 2025-11-06 +**Last Updated**: 2025-11-23 -**Note**: These instructions are actively being refined. Feedback and improvements are welcome! +**Note**: These instructions are actively being refined based on real-world usage. Phase 1 improvements completed November 2025. Feedback and improvements are welcome! diff --git a/.github/agents/issue-resolver.md b/.github/agents/issue-resolver.md new file mode 100644 index 000000000000..feb2dca1f4a0 --- /dev/null +++ b/.github/agents/issue-resolver.md @@ -0,0 +1,152 @@ +--- +name: issue-resolver +description: Specialized agent for investigating and resolving community-reported .NET MAUI issues through hands-on testing and implementation +--- + +# .NET MAUI Issue Resolver Agent + +You are a specialized issue resolution agent for the .NET MAUI repository. Your role is to investigate, reproduce, and resolve community-reported issues. + +## How to Use This Agent + +**The developer MUST provide the issue number in their prompt** + +**Example prompts:** +- "Investigate and resolve issue #12345" +- "Fix issue #67890 - CollectionView crash on Android" +- "Work on #11111" +- "Fix https://github.com/dotnet/maui/issues/XXXXX" (Replace `XXXXX` with actual issue number) + +**The issue number is required to fetch the correct issue details from GitHub.** + +## Core Instructions + +## 🚨 CRITICAL: Handling App Crashes + +**If an app crashes on launch, NEVER use `--no-incremental` or `dotnet clean` as a first solution.** + +**The correct approach**: +1. **Read the crash logs** to find the actual exception +2. **Investigate the root cause** from the stack trace +3. **Fix the underlying issue** (null reference, missing resource, etc.) +4. **If you can't determine the fix**, ask for guidance with the full exception details + +**Why**: Crashes are caused by actual code issues, not build artifacts. The exception tells you exactly what's wrong. + +**Log capture commands**: +- **iOS**: `xcrun simctl spawn booted log stream --predicate 'processImagePath contains "[AppName]"' --level=debug` +- **Android**: `adb logcat | grep -E "(FATAL|AndroidRuntime|Exception)"` + +See `.github/instructions/common-testing-patterns.md` section "Error: App Crashes on Launch" for complete patterns. + +## ⚡ GETTING STARTED (Progressive Disclosure) + +**Before starting ANY issue resolution work**: + +1. **Read [quick-start.md](../instructions/issue-resolver-agent/quick-start.md) FIRST** (5 minutes) + - Essential workflow overview with mandatory checkpoints + - App selection rules (Sandbox for repro, HostApp for tests) + - Time budgets and when to ask for help + +2. **Keep [quick-ref.md](../instructions/issue-resolver-agent/quick-ref.md) OPEN** (your daily reference) + - Copy-paste commands for iOS/Android reproduction + - Instrumentation templates + - UI test checklist and templates + - Checkpoint templates (MANDATORY before certain steps) + +3. **Reference other files as needed during workflow**: + - [README.md](../instructions/issue-resolver-agent/README.md) - Navigation hub, find files by scenario + - [core-workflow.md](../instructions/issue-resolver-agent/core-workflow.md) - Deep dive on workflow + - [reproduction.md](../instructions/issue-resolver-agent/reproduction.md) - Reproduction patterns + - [solution-development.md](../instructions/issue-resolver-agent/solution-development.md) - Fix implementation guidance + - [pr-submission.md](../instructions/issue-resolver-agent/pr-submission.md) - PR requirements + - [error-handling.md](../instructions/issue-resolver-agent/error-handling.md) - Troubleshooting + +2. **Fetch and Analyze Issue Information**: + - **Retrieve the issue from GitHub**: `https://github.com/dotnet/maui/issues/XXXXX` (replace `XXXXX` with actual issue number) + - **Read the entire issue thread**: Don't just read the initial description - review ALL comments for: + - Additional reproduction steps discovered by community + - Workarounds or partial fixes attempted + - Platform-specific details (iOS version, Android API level, device type) + - Related issues mentioned by others + - Screenshots or code samples shared in comments + - **Check for existing work**: + - Search for open PRs that reference this issue (use GitHub search: `is:pr is:open "fixes #XXXXX"`) + - Look for closed/rejected PRs that attempted to fix this previously + - Review linked issues and duplicates for additional context + - **Extract key details**: + - Affected platforms (iOS, Android, Windows, Mac, All) + - Minimum reproduction steps + - Expected vs actual behavior + - When the issue started (specific MAUI version if mentioned) + - Priority/severity indicators (how many users affected, thumbs up count) + +3. **Understand Mandatory Checkpoints**: + - 🛑 **Checkpoint 1**: After reproduction, STOP and show user (template in quick-ref.md) + - 🛑 **Checkpoint 2**: Before implementation, STOP and show user (template in quick-ref.md) + - **Never skip these** - they prevent wasted time on wrong approaches + +**If you skip any of these steps, your issue resolution is incomplete.** + +## Quick Reference + +**Core Principle**: Reproduce first, understand deeply, fix correctly, test thoroughly. + +**App Selection**: +- ✅ **Sandbox app** (`src/Controls/samples/Controls.Sample.Sandbox/`) - DEFAULT for issue reproduction +- ✅ **TestCases.HostApp** - When writing UI tests for the fix + +**Workflow**: Analyze issue → Reproduce → 🛑 CHECKPOINT 1 → Investigate root cause → 🛑 CHECKPOINT 2 → Implement fix → Test thoroughly → Create PR with tests + +**See instruction files above for complete details.** + +--- + +## 🛑 Mandatory Checkpoints + +**You MUST stop and get user approval at these points:** + +### Checkpoint 1: After Reproduction (MANDATORY) +- **When**: After successfully reproducing the issue +- **Show**: Reproduction steps, observed behavior, evidence +- **Template**: [quick-ref.md#checkpoint-1](../instructions/issue-resolver-agent/quick-ref.md#checkpoint-1-after-reproduction) +- **Why**: Ensures you're fixing the right issue before investigating +- **Do NOT proceed without approval** + +### Checkpoint 2: Before Implementation (MANDATORY) +- **When**: After root cause analysis, before writing fix code +- **Show**: Root cause explanation, fix design, alternatives, risks, edge cases +- **Template**: [quick-ref.md#checkpoint-2](../instructions/issue-resolver-agent/quick-ref.md#checkpoint-2-before-implementation) +- **Why**: Saves hours if approach is wrong +- **Do NOT implement without approval** + +**Checkpoint violations waste time.** Always show your work before expensive operations. + +--- + +## ⏱️ Time Budgets + +Set expectations for issue complexity: + +| Issue Type | Expected Time | Examples | +|------------|---------------|----------| +| **Simple** | 1-2 hours | Typo fixes, obvious null checks, simple property bugs | +| **Medium** | 3-6 hours | Single-file bug fixes, handler issues, basic layout problems | +| **Complex** | 6-12 hours | Multi-file changes, architecture issues, platform-specific edge cases | + +**If exceeding these times**: +- Use mandatory checkpoints to validate approach +- Check [error-handling.md](../instructions/issue-resolver-agent/error-handling.md) +- Ask for help rather than continuing on wrong path + +**Note**: Time includes reproduction, investigation, fix, tests, and PR submission. + +--- + +## Critical Principles + +- **Retry 2-3 times, then ask** - Don't get stuck indefinitely on the same problem +- **Read logs before rebuilding** - Crashes need investigation, not immediate rebuilds +- **Focus on code issues** - Ask for help with environment/SDK/dependency problems + +See [Error Handling](../instructions/issue-resolver-agent/error-handling.md) for detailed troubleshooting guidance. diff --git a/.github/agents/pr-reviewer.md b/.github/agents/pr-reviewer.md index c791a23253b1..7b7aaa833195 100644 --- a/.github/agents/pr-reviewer.md +++ b/.github/agents/pr-reviewer.md @@ -7,38 +7,73 @@ description: Specialized agent for conducting thorough, constructive code review You are a specialized PR review agent for the .NET MAUI repository. -## Core Instructions - -**🚨 CRITICAL WORKFLOW RULE** +## 🚨 CRITICAL: Mandatory Pre-Work (Do These First) -**YOU MUST DO THESE BEFORE ANYTHING ELSE (including creating plans or todos):** +**BEFORE creating any plans or todos:** -1. Check current state: `git branch --show-current` -2. Read instruction files IN THIS EXACT ORDER: - 1. `.github/instructions/pr-reviewer-agent/core-guidelines.md` - Core philosophy, workflow, code analysis patterns - 2. `.github/instructions/pr-reviewer-agent/testing-guidelines.md` - Which app to use (Sandbox vs HostApp), fetch PR, build/deploy, edge cases, SafeArea testing - 3. `.github/instructions/pr-reviewer-agent/sandbox-setup.md` - Sandbox modification, instrumentation, validation checkpoint - 4. `.github/instructions/pr-reviewer-agent/error-handling.md` - Handling build errors and unexpected results - 5. `.github/instructions/pr-reviewer-agent/checkpoint-resume.md` - Checkpoint/resume system for environment limitations - 6. `.github/instructions/pr-reviewer-agent/output-format.md` - Review structure, redundancy elimination -3. Fetch and analyze PR details +1. ✅ Check current state: `git branch --show-current` +2. ✅ Read [quick-start.md](.github/instructions/pr-reviewer-agent/quick-start.md) (5 min) - **STOP after "Essential Reading" section** +3. ✅ Fetch and analyze PR details -**ONLY AFTER completing steps 1-3 above may you:** -- Create a todo list +**ONLY AFTER completing these steps may you:** +- Create initial assessment +- Plan testing approach - Start modifying code -- Begin testing **Why this order matters:** -- Instructions contain critical context you MUST understand first -- Creating plans before reading instructions = wrong assumptions -- You may already be on the PR branch - check first! - -**ALSO READ** (context-specific): -- `.github/copilot-instructions.md` - General coding standards -- `.github/instructions/common-testing-patterns.md` - Command patterns with error checking -- `.github/instructions/instrumentation.instructions.md` - Testing patterns -- `.github/instructions/safearea-testing.instructions.md` - If SafeArea-related PR -- `.github/instructions/uitests.instructions.md` - If PR adds/modifies UI tests +- You need to know which app to use (Sandbox vs HostApp) +- You may already be on the PR branch +- Instructions prevent common mistakes that waste time + +--- + +## Reading Order & Stopping Points + +**Phase 1: Mandatory Pre-Work (Do NOT skip)** +1. ✅ Check current branch: `git branch --show-current` +2. ✅ Read quick-start.md (5 min) - **STOP after "Essential Reading" section** +3. ✅ Fetch PR and analyze code changes + +**Phase 2: Create Initial Plan** +- Based ONLY on what you've read so far +- Reference other files DURING work, not BEFORE planning + +**Phase 3: Just-In-Time Reading** +- Read additional files ONLY when you encounter that specific scenario +- Don't read everything upfront - it creates cognitive overload + +--- + +## Core Instructions + +### Progressive Learning Approach + +**Step 1: Quick Start (5 minutes - READ THIS FIRST)** + +Read **[quick-start.md](.github/instructions/pr-reviewer-agent/quick-start.md)** which covers: +- ✅ Which app to use (Sandbox vs HostApp) +- ✅ Basic workflow with mandatory checkpoints +- ✅ Where to find detailed instructions +- ✅ Common mistakes to avoid + +**Step 2: Context-Specific (Read as needed during work)** + +- **CollectionView/CarouselView PR?** → Read [collectionview-handler-detection.md](.github/instructions/pr-reviewer-agent/collectionview-handler-detection.md) +- **SafeArea changes?** → Read [safearea-testing.instructions.md](.github/instructions/safearea-testing.instructions.md) +- **UI test files in PR?** → Read [uitests.instructions.md](.github/instructions/uitests.instructions.md) +- **Need test code examples?** → See [sandbox-setup.md](.github/instructions/pr-reviewer-agent/sandbox-setup.md) +- **Build/deploy commands?** → Use [quick-ref.md](.github/instructions/pr-reviewer-agent/quick-ref.md) +- **Hit an error?** → Check [error-handling.md](.github/instructions/pr-reviewer-agent/error-handling.md) +- **Can't complete testing?** → Use [checkpoint-resume.md](.github/instructions/pr-reviewer-agent/checkpoint-resume.md) + +**Step 3: Before Final Review (Always)** + +- **Writing review?** → Read [output-format.md](.github/instructions/pr-reviewer-agent/output-format.md) to eliminate redundancy + +**Step 4: Deep Understanding (Optional - for complex PRs)** + +- **Why test deeply?** → [core-guidelines.md](.github/instructions/pr-reviewer-agent/core-guidelines.md) +- **Complete workflow details?** → [testing-guidelines.md](.github/instructions/pr-reviewer-agent/testing-guidelines.md) ## Quick Reference diff --git a/.github/instructions/appium-control.instructions.md b/.github/instructions/appium-control.instructions.md index 4b815f48e7ab..308860e424c2 100644 --- a/.github/instructions/appium-control.instructions.md +++ b/.github/instructions/appium-control.instructions.md @@ -251,7 +251,7 @@ catch (Exception ex) **🚨 CRITICAL: .NET 10 Native Scripting (NOT dotnet-script)** - ✅ **DO**: Use `dotnet run yourscript.cs` (.NET 10 native scripting) -- ✅ **DO**: Use `#:package Appium.WebDriver@8.0.1` directive +- ✅ **DO**: Use `#:package Appium.WebDriver@8.0.1` directive (latest stable version) - ❌ **DON'T**: Use `dotnet script` or `dotnet-script` commands - ❌ **DON'T**: Use `#r` directive syntax (that's for dotnet-script, not .NET 10) - ❌ **DON'T**: Create scripts in `/tmp` or repository root @@ -420,8 +420,10 @@ For Shell-specific testing patterns (e.g., opening flyouts), see [UI Tests Instr - Start Appium: `appium &` (or `appium --log-level error &` for less noise) **"App crashes on launch" or "Cannot launch application"** -- Rebuild with `--no-incremental` flag (incremental builds can leave app bundles inconsistent) -- See [UI Testing Guide](../../docs/UITesting-Guide.md) for detailed troubleshooting steps +- **Read the crash logs** to find the exception (iOS: `xcrun simctl spawn booted log stream`, Android: `adb logcat`) +- **Investigate the root cause** from the exception stack trace +- **If you can't fix it**, ask for guidance with the full exception details +- See [Common Testing Patterns: App Crashes](../common-testing-patterns.md#error-app-crashes-on-launch) for detailed log capture commands **"Device not found" (iOS)** - Verify DEVICE_UDID is set: `echo $DEVICE_UDID` diff --git a/.github/instructions/common-testing-patterns.md b/.github/instructions/common-testing-patterns.md index 36a3b15e6218..d46dcb9fe3b7 100644 --- a/.github/instructions/common-testing-patterns.md +++ b/.github/instructions/common-testing-patterns.md @@ -201,7 +201,7 @@ cd $ANDROID_HOME/emulator && (./emulator -avd Pixel_9 ... &) **Pattern**: ```bash -# Build (use --no-incremental only if app crashes on launch) +# Build dotnet build src/Controls/samples/Controls.Sample.Sandbox/Maui.Controls.Sample.Sandbox.csproj -f net10.0-ios # Check build succeeded @@ -215,8 +215,6 @@ echo "Build successful" **When to use**: Building Sandbox app for iOS testing -**Troubleshooting**: If app crashes on launch, rebuild with `--no-incremental` flag - ### Sandbox App Build and Deploy (Android) **Used in**: `appium-control.instructions.md` @@ -249,8 +247,6 @@ fi **Why `-t:Run`**: On Android, use the `Run` target which builds, installs, and launches the app in one command -**Troubleshooting**: If app crashes on launch, rebuild with `--no-incremental` flag - --- ### Sandbox App Build (Android) @@ -259,7 +255,7 @@ fi **Pattern**: ```bash -# Build and deploy (use --no-incremental only if app crashes on launch) +# Build and deploy dotnet build src/Controls/samples/Controls.Sample.Sandbox/Maui.Controls.Sample.Sandbox.csproj -f net10.0-android -t:Run # Check build/deploy succeeded @@ -302,8 +298,6 @@ echo "Build successful" **When to use**: Building TestCases.HostApp for automated UI tests -**Troubleshooting**: If app crashes on launch, rebuild with `--no-incremental` flag - --- ### TestCases.HostApp Build (Android) @@ -449,11 +443,8 @@ git branch -D test-pr-* baseline-test pr-*-temp 2>/dev/null || true **Pattern**: ```bash -# Clean and retry build -dotnet clean [project-path] -rm -rf bin/ obj/ -dotnet tool restore --force -dotnet build [project-path] --verbosity normal --no-incremental +# Retry build with verbose output +dotnet build [project-path] --verbosity normal # If still failing, check for: # - Wrong .NET SDK version (check global.json) @@ -479,6 +470,428 @@ This keeps instructions DRY while maintaining readability. --- -**Last Updated**: 2025-11-12 +**Last Updated**: 2025-11-21 **Note**: These patterns include error checking at every critical step to ensure AI agents can detect and handle failures early. + +--- + +## 9. Common Error Handling Patterns + +### Build Errors + +#### Error: Build Tasks Not Found + +**Symptom**: +``` +error: Could not find Microsoft.Maui.Resizetizer.BuildTasks +``` + +**Solution**: +```bash +# Rebuild build tasks +dotnet build ./Microsoft.Maui.BuildTasks.slnf + +# Check build succeeded +if [ $? -ne 0 ]; then + echo "❌ ERROR: Build tasks compilation failed" + exit 1 +fi +``` + +--- + +#### Error: Dependency Version Conflicts + +**Symptom**: +``` +error: Package version conflict detected +error: Unable to resolve dependencies +``` + +**Solution**: +```bash +# Remove artifacts and restore +rm -rf bin/ obj/ +dotnet restore Microsoft.Maui.sln --force + +# Check restore succeeded +if [ $? -ne 0 ]; then + echo "❌ ERROR: Dependency restore failed" + exit 1 +fi +``` + +--- + +#### Error: PublicAPI Analyzer Failures + +**Symptom**: +``` +error RS0016: Symbol 'X' is not marked as public API +``` + +**Solution - Use dotnet format analyzers**: +```bash +# Let analyzers fix PublicAPI.Unshipped.txt files +dotnet format analyzers Microsoft.Maui.sln + +# Check if it fixed the issue +dotnet build [project-path] +``` + +**Solution - Manual fix if needed**: +```bash +# If dotnet format doesn't work, revert and re-add +git checkout -- **/PublicAPI.Unshipped.txt +dotnet format analyzers Microsoft.Maui.sln + +# NEVER disable the analyzer or add #pragma warning disable +``` + +**Why**: See `.github/copilot-instructions.md` section "PublicAPI.Unshipped.txt File Management" + +--- + +#### Error: Platform SDK Not Found + +**iOS - Xcode Not Found**: +```bash +# Check Xcode installation +xcode-select --print-path + +# If not found, install Xcode command line tools +xcode-select --install +``` + +**Android - SDK Not Found**: +```bash +# Check ANDROID_HOME environment variable +echo $ANDROID_HOME + +# If not set, find Android SDK location +android # Opens Android SDK Manager + +# Or manually set (adjust path to your SDK location) +export ANDROID_HOME=/path/to/android-sdk +``` + +--- + +### Deployment Errors + +#### Error: App Installation Failed (iOS) + +**Symptom**: +``` +error: Failed to install app to simulator +``` + +**Solution**: +```bash +# Ensure simulator is booted +xcrun simctl boot $UDID 2>/dev/null || true + +# Wait a moment for boot +sleep 3 + +# Verify booted +STATE=$(xcrun simctl list devices --json | jq -r --arg udid "$UDID" '.devices[][] | select(.udid == $udid) | .state') +if [ "$STATE" != "Booted" ]; then + echo "❌ ERROR: Simulator not booted. State: $STATE" + exit 1 +fi + +# Retry install +xcrun simctl install $UDID [path-to-.app] +``` + +--- + +#### Error: App Installation Failed (Android) + +**Symptom**: +``` +adb: failed to install [app]: INSTALL_FAILED_UPDATE_INCOMPATIBLE +``` + +**Solution**: +```bash +# Uninstall previous version +adb uninstall com.microsoft.maui.sandbox + +# Check uninstall succeeded +if [ $? -eq 0 ]; then + echo "✅ Uninstalled previous version" +fi + +# Retry installation +adb install artifacts/path/to/app.apk +``` + +--- + +#### Error: App Crashes on Launch + +**Solution: Read the crash logs to find the exception** + +**iOS**: +```bash +# Capture crash logs +xcrun simctl spawn booted log stream --predicate 'processImagePath contains "[AppName]"' --level=debug > /tmp/ios_crash.log 2>&1 & +LOG_PID=$! + +# Try to launch the app +xcrun simctl launch $UDID [bundle-id] + +# Wait for crash +sleep 3 + +# Stop log capture +kill $LOG_PID + +# Find the exception +cat /tmp/ios_crash.log | grep -A 20 -B 5 "Exception" +``` + +**Android**: +```bash +# Monitor crash logs in real-time +adb logcat | grep -E "(FATAL|AndroidRuntime|Exception|Error|Crash)" + +# Or capture to file +adb logcat > /tmp/android_crash.log 2>&1 & +LOGCAT_PID=$! + +# Launch app +dotnet build [project-path] -f net10.0-android -t:Run + +# Wait for crash +sleep 3 + +# Stop logcat +kill $LOGCAT_PID + +# Review crash +cat /tmp/android_crash.log | grep -A 30 "FATAL EXCEPTION" +``` + +**Next steps after finding the exception**: +1. **Investigate the root cause** - What line is throwing? Why? +2. **Check for null references** - Are required objects initialized? +3. **Verify resources exist** - Are all needed files/IDs present? +4. **Check platform compatibility** - Is the code compatible with the target OS version? +5. **If you can't fix it**, ask for help with the full exception details + +**Why not clean/rebuild**: Crashes are caused by actual code issues, not build artifacts. Reading the exception tells you exactly what's wrong. + +--- + +### Runtime Errors + +#### Error: Console Output Not Captured + +**iOS**: +```bash +# Ensure using --console-pty flag +xcrun simctl launch --console-pty $UDID com.microsoft.maui.sandbox > /tmp/output.log 2>&1 & + +# Wait for app to start +sleep 5 + +# Check log file has content +if [ ! -s /tmp/output.log ]; then + echo "⚠️ WARNING: Log file is empty" +fi + +cat /tmp/output.log +``` + +**Android**: +```bash +# Ensure logcat is running before app launch +adb logcat > /tmp/android.log 2>&1 & +LOGCAT_PID=$! + +# Launch app (dotnet build -t:Run) + +# Wait for logging +sleep 5 + +# Stop logcat +kill $LOGCAT_PID + +# Check log +cat /tmp/android.log | grep "YourMarker" +``` + +--- + +#### Error: Measurements Show Zero or Null + +**Symptom**: Layout measurements are 0x0 or null + +**Cause**: Measuring before layout completes + +**Solution**: +```csharp +// DON'T measure immediately +private void OnLoaded(object sender, EventArgs e) +{ + // Layout not complete yet! + Console.WriteLine($"Bounds: {TestElement.Bounds}"); // Might be 0x0 +} + +// DO wait for layout +private void OnLoaded(object sender, EventArgs e) +{ + // Wait for layout to complete + Dispatcher.DispatchDelayed(TimeSpan.FromMilliseconds(500), () => + { + Console.WriteLine($"Bounds: {TestElement.Bounds}"); // Actual values + }); +} +``` + +**See**: [Instrumentation Instructions](instrumentation.instructions.md#timing-when-to-measure) for proper timing patterns + +--- + +### Testing Errors + +#### Error: Appium Server Not Starting + +**Symptom**: +``` +AppiumServerHasNotBeenStartedLocallyException +``` + +**Solution**: +```bash +# Kill existing Appium processes +lsof -i :4723 | grep LISTEN | awk '{print $2}' | xargs kill -9 2>/dev/null + +# Verify port is free +lsof -i :4723 +# Should show nothing + +# Start Appium fresh +appium --log-level error & + +# Wait for startup +sleep 3 + +# Verify it's running +curl http://localhost:4723/status +``` + +**Why**: UITest framework needs to start its own Appium server. Existing processes block it. + +**Reference**: `.github/instructions/uitests.instructions.md` - "Prerequisites: Kill Existing Appium Processes" + +--- + +#### Error: DEVICE_UDID Not Set + +**Symptom**: +``` +ERROR: DEVICE_UDID environment variable not set +``` + +**Solution**: +```bash +# iOS - Set DEVICE_UDID before running tests +export DEVICE_UDID=$(xcrun simctl list devices available --json | jq -r '.devices | to_entries | map(select(.key | startswith("com.apple.CoreSimulator.SimRuntime.iOS"))) | map({key: .key, version: (.key | sub("com.apple.CoreSimulator.SimRuntime.iOS-"; "") | split("-") | map(tonumber)), devices: .value}) | sort_by(.version) | reverse | map(select(.devices | any(.name == "iPhone Xs"))) | first | .devices[] | select(.name == "iPhone Xs") | .udid') + +# Android - Set DEVICE_UDID before running tests +export DEVICE_UDID=$(adb devices | grep -v "List" | grep "device" | awk '{print $1}' | head -1) + +# Verify it's set +echo "DEVICE_UDID: $DEVICE_UDID" + +# Now run tests +dotnet test [test-project] +``` + +--- + +### Decision Tree: What to Do When Errors Occur + +``` +Error occurs during testing + │ + ├─ Build error? + │ ├─ Build tasks missing → Rebuild Microsoft.Maui.BuildTasks.slnf + │ ├─ Dependency conflict → Clean + restore --force + │ ├─ PublicAPI error → dotnet format analyzers + │ └─ Platform SDK missing → Install/configure SDK + │ + ├─ Deployment error? + │ ├─ Install failed → Check device booted, uninstall old version + │ ├─ App crashes → Check crash logs for exception + │ └─ App won't launch → Check console logs for error + │ + ├─ Runtime error? + │ ├─ No console output → Check --console-pty flag or logcat running + │ ├─ Zero measurements → Add Dispatcher.DispatchDelayed delay + │ └─ Unexpected behavior → Add more instrumentation + │ + └─ Testing error? + ├─ Appium won't start → Kill existing Appium processes + ├─ DEVICE_UDID missing → Set environment variable + └─ Test crashes → Check app is installed and running +``` + +--- + +### When to Ask for Help + +**Stop and ask for guidance if:** + +1. **Error persists after 2-3 fix attempts** - Don't waste hours debugging +2. **Error message is cryptic** - "Unknown error" or stack traces without context +3. **Platform-specific issue you're unfamiliar with** - E.g., iOS code signing issues +4. **Suspected infrastructure problem** - .NET SDK corruption, simulator issues +5. **Multiple errors compound** - Fixing one reveals another, chain of failures + +**How to ask**: +```markdown +## Error Encountered + +**Command that failed**: +```bash +[exact command] +``` + +**Error output**: +``` +[relevant error message] +``` + +**What I've tried**: +1. [First attempt] - [result] +2. [Second attempt] - [result] + +**Environment**: +- Platform: [iOS/Android/Windows/Mac] +- .NET SDK: [version from `dotnet --version`] +- Device: [simulator/emulator/physical] + +**Request**: [What you need help with] +``` + +--- + +## Summary of Error Handling Principles + +1. **Check success after every critical step** - Don't assume commands succeed +2. **Exit early on errors** - Use `if [ $? -ne 0 ]; then exit 1; fi` +3. **Provide clear error messages** - Explain what failed and why +4. **Try simple fixes first** - Clean/rebuild before complex debugging +5. **Know when to ask for help** - 2-3 attempts maximum before escalating +6. **Document errors and solutions** - Help others avoid same issues + +**Related Documentation**: +- `.github/instructions/issue-resolver-agent/error-handling.md` - Issue resolution specific errors +- `.github/instructions/pr-reviewer-agent/error-handling.md` - PR review specific errors +- `.github/copilot-instructions.md` - Troubleshooting section diff --git a/.github/instructions/edge-case-testing.md b/.github/instructions/edge-case-testing.md new file mode 100644 index 000000000000..a81518b00fe9 --- /dev/null +++ b/.github/instructions/edge-case-testing.md @@ -0,0 +1,639 @@ +--- +description: "Standard edge cases to test for .NET MAUI UI and layout changes" +--- + +# Edge Case Testing Guide + +This guide provides a comprehensive list of edge cases to test when validating fixes, new features, or reviewing PRs in .NET MAUI. + +## Why Test Edge Cases + +**Testing only the "happy path" misses:** +- Null reference exceptions +- Empty state rendering issues +- Performance problems with large data +- Race conditions with rapid changes +- Layout problems with extreme sizes +- Platform-specific quirks + +**Edge case testing:** +- Reveals bugs before users encounter them +- Validates robustness of implementations +- Proves fixes don't introduce regressions +- Tests assumptions in code + +## When to Test Edge Cases + +### For Issue Resolution +✅ Test edge cases **after** verifying the main fix works +✅ Document which edge cases were tested in PR description + +### For PR Review +✅ Test edge cases **beyond** what PR author tested +✅ Report any edge cases that fail or behave unexpectedly + +### For New Features +✅ Test edge cases **during** development, not just at the end +✅ Add automated tests for critical edge cases + +## Standard Edge Cases by Category + +### 1. Data/Content Edge Cases + +Test with various data states: + +#### Empty State +```csharp +// Empty collections +TestCollectionView.ItemsSource = new List(); +TestCollectionView.ItemsSource = Array.Empty(); + +// Null values +TestCollectionView.ItemsSource = null; +TestLabel.Text = null; +TestEntry.Text = null; + +// Empty strings +TestLabel.Text = string.Empty; +TestLabel.Text = ""; +``` + +**What to verify:** +- No crashes +- Appropriate empty state display +- Layout still correct +- No visual glitches + +#### Single Item +```csharp +// Single item in collection +TestCollectionView.ItemsSource = new[] { "Single Item" }; + +// Minimal text +TestLabel.Text = "A"; +``` + +**What to verify:** +- Proper sizing with one item +- No layout issues +- Scrolling disabled/enabled appropriately + +#### Large Data Sets +```csharp +// Many items for scrolling/virtualization test +TestCollectionView.ItemsSource = Enumerable.Range(1, 100).Select(i => $"Item {i}").ToList(); + +// Very long text +TestLabel.Text = new string('A', 10000); +``` + +**What to verify:** +- Performance is acceptable +- Scrolling works smoothly +- Virtualization works correctly +- Memory usage is reasonable + +#### Extreme Values +```csharp +// Very large numbers +TestLabel.Text = int.MaxValue.ToString(); +TestLabel.Text = double.MaxValue.ToString(); + +// Unicode and special characters +TestLabel.Text = "🎉 Emoji 日本語 العربية"; +TestLabel.Text = "Line1\nLine2\nLine3"; // Multiline +``` + +**What to verify:** +- Text renders correctly +- No truncation issues +- Layout handles content size + +### 2. Property Change Edge Cases + +#### Rapid Property Changes +```csharp +// Toggle property rapidly (test race conditions) +private async void TestRapidChanges() +{ + for (int i = 0; i < 10; i++) + { + TestElement.FlowDirection = FlowDirection.RightToLeft; + await Task.Delay(50); + TestElement.FlowDirection = FlowDirection.LeftToRight; + await Task.Delay(50); + } +} +``` + +**What to verify:** +- No crashes or exceptions +- Final state is correct +- No flickering or visual glitches +- Properties settle to correct values + +#### Property Order Dependency +```csharp +// Set properties in different orders +// Order 1: +TestElement.Width = 200; +TestElement.Height = 100; +TestElement.BackgroundColor = Colors.Red; + +// Order 2: +TestElement.BackgroundColor = Colors.Red; +TestElement.Height = 100; +TestElement.Width = 200; +``` + +**What to verify:** +- Order doesn't matter (unless it should) +- Final result is same regardless of order + +#### Default vs Explicit Values +```csharp +// Test default values +var element = new Label(); // All defaults + +// Test explicit same-as-default +var element2 = new Label { TextColor = Colors.Black }; // Explicit default +``` + +**What to verify:** +- Default behavior works +- Explicit defaults behave same as implicit + +### 3. Layout Edge Cases + +#### Extreme Sizes +```csharp +// Very large +TestElement.WidthRequest = 10000; +TestElement.HeightRequest = 10000; + +// Very small +TestElement.WidthRequest = 1; +TestElement.HeightRequest = 1; + +// Zero size +TestElement.WidthRequest = 0; +TestElement.HeightRequest = 0; +``` + +**What to verify:** +- No crashes with extreme sizes +- Layout algorithm handles edge cases +- Clipping works correctly + +#### Nested Layouts +```xml + + + + + + + + + +``` + +**What to verify:** +- Layout calculation correct at all levels +- Performance acceptable +- No stack overflow or recursion issues + +#### Constrained vs Unconstrained +```csharp +// Parent with constraints + + + +// Parent without constraints (fill available space) + + +``` + +**What to verify:** +- Child respects parent constraints +- Filling behavior works correctly + +#### Margin and Padding Combinations +```csharp +// Various margin/padding combinations +TestElement.Margin = new Thickness(10); +TestElement.Padding = new Thickness(5); + +TestElement.Margin = new Thickness(0); +TestElement.Padding = new Thickness(0); + +TestElement.Margin = new Thickness(10, 5, 15, 20); // Different values +TestElement.Padding = new Thickness(5, 10, 15, 20); +``` + +**What to verify:** +- Spacing calculated correctly +- No overlap or gaps +- RTL properly handled + +### 4. Timing and Lifecycle Edge Cases + +#### Before Layout Complete +```csharp +public MainPage() +{ + InitializeComponent(); + + // Immediately query before layout runs + var bounds = TestElement.Bounds; // Might be 0,0,0,0 + Console.WriteLine($"Immediate bounds: {bounds}"); +} +``` + +**What to verify:** +- Code handles pre-layout state gracefully +- No crashes from uninitialized values + +#### During Navigation +```csharp +// Change properties while navigating +private async void OnButtonClicked(object sender, EventArgs e) +{ + TestElement.Text = "Changing..."; + await Navigation.PushAsync(new NewPage()); +} +``` + +**What to verify:** +- No crashes during navigation +- State is preserved correctly +- Visual updates don't cause issues + +#### Page Appearing/Disappearing +```csharp +protected override void OnAppearing() +{ + base.OnAppearing(); + // Test behavior when page appears +} + +protected override void OnDisappearing() +{ + base.OnDisappearing(); + // Test behavior when page disappears +} +``` + +**What to verify:** +- Lifecycle methods work correctly +- State is managed properly +- Resources are cleaned up + +### 5. Platform-Specific Edge Cases + +#### Orientation Changes +```csharp +// Test in both orientations +// Portrait (default) +// Landscape (rotate device/simulator) +``` + +**What to verify:** +- Layout adapts correctly +- Content remains visible +- No crashes on rotation +- State preserved across rotation + +#### Different Screen Sizes +Test on: +- Small phones (iPhone SE, small Android) +- Large phones (iPhone Pro Max, large Android) +- Tablets (iPad, Android tablets) +- Foldables (if applicable) + +**What to verify:** +- Layout scales appropriately +- Text is readable +- Touch targets are adequate size + +#### Dark Mode / Light Mode +```csharp +// Test in both modes +Application.Current.UserAppTheme = AppTheme.Dark; +Application.Current.UserAppTheme = AppTheme.Light; +``` + +**What to verify:** +- Colors visible in both modes +- Contrast is sufficient +- Custom colors adapt correctly + +#### RTL (Right-to-Left) Languages +```csharp +// Test RTL layout +TestElement.FlowDirection = FlowDirection.RightToLeft; + +// Test with RTL text +TestLabel.Text = "مرحبا" + " Hello"; // Mixed RTL/LTR +``` + +**What to verify:** +- Layout mirrors correctly +- Padding/margins swap sides +- Text alignment correct +- Icons/images positioned correctly + +#### Safe Areas (iOS/Android) +```csharp +// Test with different SafeAreaEdges +TestElement.SafeAreaEdges = SafeAreaEdges.Top; +TestElement.SafeAreaEdges = SafeAreaEdges.Bottom; +TestElement.SafeAreaEdges = SafeAreaEdges.All; +TestElement.SafeAreaEdges = SafeAreaEdges.None; +``` + +**What to verify:** +- Content respects safe areas +- Notches/home indicators avoided +- Status bar area handled + +See `.github/instructions/safearea-testing.instructions.md` for detailed SafeArea testing patterns. + +### 6. Interaction Edge Cases + +#### Rapid User Input +```csharp +// Rapid button taps +private int _tapCount = 0; +private void OnButtonTapped(object sender, EventArgs e) +{ + _tapCount++; + Console.WriteLine($"Tap #{_tapCount}"); +} +// Test: Tap button 10+ times rapidly +``` + +**What to verify:** +- No crashes from rapid input +- All events handled correctly +- No duplicate processing +- UI remains responsive + +#### Simultaneous Gestures +```csharp +// Multiple touch points (if applicable) +// Pinch to zoom while scrolling +// Two-finger gestures +``` + +**What to verify:** +- Gestures don't conflict +- Priority is handled correctly +- No erratic behavior + +#### Focus and Keyboard +```csharp +// Tab through Entry controls rapidly +// Show/hide keyboard quickly +TestEntry1.Focus(); +await Task.Delay(100); +TestEntry2.Focus(); +await Task.Delay(100); +TestEntry1.Focus(); +``` + +**What to verify:** +- Focus moves correctly +- Keyboard shows/hides appropriately +- No visual glitches +- ScrollView adjusts for keyboard + +### 7. State Management Edge Cases + +#### Property Value Boundaries +```csharp +// Test at boundaries +TestElement.Opacity = 0; // Fully transparent +TestElement.Opacity = 1; // Fully opaque +TestElement.Opacity = 0.5; // Semi-transparent + +TestElement.Rotation = 0; +TestElement.Rotation = 360; +TestElement.Rotation = -180; +``` + +**What to verify:** +- Boundary values work correctly +- No visual artifacts +- Negative values handled (if applicable) + +#### Conditional Property Sets +```csharp +// If/else property setting +if (condition) + TestElement.BackgroundColor = Colors.Red; +else + TestElement.BackgroundColor = Colors.Blue; + +// Test with condition true and false +``` + +**What to verify:** +- Both branches work +- No leftover state from previous value + +### 8. Collection-Specific Edge Cases + +#### Selection +```csharp +// Single selection +TestCollectionView.SelectionMode = SelectionMode.Single; +TestCollectionView.SelectedItem = items[0]; + +// Multiple selection +TestCollectionView.SelectionMode = SelectionMode.Multiple; +TestCollectionView.SelectedItems = new ObservableCollection { items[0], items[2] }; + +// No selection +TestCollectionView.SelectedItem = null; +``` + +**What to verify:** +- Selection visual state correct +- Selection events fire +- Changing selection mode works + +#### Scrolling Edge Cases +```csharp +// Scroll to specific positions +TestCollectionView.ScrollTo(0, position: ScrollToPosition.Start); +TestCollectionView.ScrollTo(items.Count - 1, position: ScrollToPosition.End); +TestCollectionView.ScrollTo(50, position: ScrollToPosition.Center); +``` + +**What to verify:** +- Scrolling works smoothly +- Virtualization doesn't break +- End of list handled correctly + +#### Dynamic Data Changes +```csharp +// Add items +items.Add("New Item"); + +// Remove items +items.RemoveAt(0); + +// Clear all +items.Clear(); + +// Replace items +items[0] = "Updated Item"; +``` + +**What to verify:** +- UI updates correctly +- No visual glitches during updates +- Performance is acceptable + +### 9. Error Condition Edge Cases + +#### Exception Handling +```csharp +try +{ + // Operation that might throw + TestElement.SomeProperty = someValue; +} +catch (Exception ex) +{ + Console.WriteLine($"Exception: {ex.Message}"); +} +``` + +**What to verify:** +- Exceptions are caught appropriately +- Error messages are helpful +- App doesn't crash +- Recovery is possible + +#### Invalid Values +```csharp +// Test with invalid but non-crashing values +TestElement.WidthRequest = -100; // Negative size +TestElement.Opacity = 5; // > 1.0 +TestElement.Rotation = 1000000; // Very large +``` + +**What to verify:** +- Invalid values are clamped/rejected +- No crashes +- Reasonable default used + +## Edge Case Testing Checklist + +When testing a fix or new feature, go through this checklist: + +### Data States +- [ ] Null values +- [ ] Empty collections/strings +- [ ] Single item +- [ ] Large data sets (100+ items) +- [ ] Very long text +- [ ] Special characters/emoji + +### Property Changes +- [ ] Rapid property toggling (10+ times) +- [ ] Different property set orders +- [ ] Default vs explicit values +- [ ] Extreme values + +### Layout +- [ ] Very large sizes +- [ ] Very small/zero sizes +- [ ] Deeply nested layouts +- [ ] Different margin/padding combinations + +### Timing +- [ ] Before layout complete +- [ ] During navigation +- [ ] During page lifecycle events + +### Platform-Specific +- [ ] Portrait and landscape +- [ ] Different screen sizes +- [ ] Dark mode and light mode +- [ ] RTL layout +- [ ] Safe area variations + +### Interaction +- [ ] Rapid user input +- [ ] Multiple simultaneous gestures (if applicable) +- [ ] Focus changes +- [ ] Keyboard show/hide + +### Collections (if applicable) +- [ ] Empty collection +- [ ] Single item +- [ ] Large collection (100+ items) +- [ ] Selection modes +- [ ] Dynamic data changes (add/remove/update) + +### Error Handling +- [ ] Invalid values +- [ ] Exception scenarios +- [ ] Recovery from errors + +## Documenting Edge Case Testing + +When testing edge cases, document your findings: + +```markdown +## Edge Cases Tested + +### Data States +- ✅ Empty ItemsSource - No crash, shows empty state correctly +- ✅ Null ItemsSource - Handles gracefully, no exception +- ✅ Single item - Layout correct, no scrolling +- ✅ 100 items - Scrolling smooth, virtualization working +- ❌ Very long text (10000 chars) - Layout breaks, text truncated [BUG FOUND] + +### Property Changes +- ✅ Rapid FlowDirection toggle (10x) - No flicker, final state correct +- ✅ Different property order - Results identical +- ✅ Extreme sizes (10000x10000) - Clipping works, no crash + +### Platform-Specific +- ✅ Portrait mode - Works correctly +- ✅ Landscape mode - Works correctly +- ✅ Dark mode - Visible and correct contrast +- ✅ RTL layout - Mirrors correctly, padding swapped +- ✅ SafeAreaEdges.All - Content respects safe areas + +### Interaction +- ✅ Rapid tapping (20+ taps) - All handled, no duplicate events +- ✅ Quick focus changes - Focus moves correctly, no crash + +### Recommendation +Fix works well overall. Found one issue with very long text that should be addressed before merging. +``` + +## Related Documentation + +- `.github/instructions/instrumentation.instructions.md` - How to instrument test code +- `.github/instructions/safearea-testing.instructions.md` - SafeArea-specific edge cases +- `.github/instructions/uitests.instructions.md` - Creating automated edge case tests +- `.github/copilot-instructions.md` - Platform-specific considerations + +## Quick Reference + +**Start with data edge cases**: Null, empty, single, large +**Then test behavior edge cases**: Rapid changes, extreme values +**Finally test platform edge cases**: Orientation, screen sizes, RTL, dark mode + +**Document what you test**: Both what works AND what doesn't + +**Goal**: Find issues before users do! diff --git a/.github/instructions/instrumentation.instructions.md b/.github/instructions/instrumentation.instructions.md index 00919a97dee2..c5d6ae95aa30 100644 --- a/.github/instructions/instrumentation.instructions.md +++ b/.github/instructions/instrumentation.instructions.md @@ -1,157 +1,458 @@ --- -description: "Guidelines for instrumenting .NET MAUI source code for debugging, testing, and validating changes" +description: "Comprehensive guide for instrumenting .NET MAUI apps for debugging, testing, and validation" --- # .NET MAUI Instrumentation Guide -This guide provides patterns and techniques for adding instrumentation to .NET MAUI source code to debug issues, validate PR changes, and understand runtime behavior. +This guide provides comprehensive patterns and techniques for adding instrumentation to .NET MAUI apps to debug issues, validate changes, and understand runtime behavior. -**Common Command Patterns**: For UDID extraction, device boot, builds, and error checking patterns, see [Common Testing Patterns](common-testing-patterns.md). +**Target App**: Use the Sandbox app (`src/Controls/samples/Controls.Sample.Sandbox/`) for all testing and instrumentation. -**Guiding Principle: Use cross-platform MAUI APIs for all instrumentation.** +**Common Commands**: For build, deploy, and error handling patterns, see [Common Testing Patterns](common-testing-patterns.md). + +--- + +## Table of Contents + +1. [When to Use Instrumentation](#when-to-use-instrumentation) +2. [Quick Start: Basic Pattern](#quick-start-basic-pattern) +3. [Key Instrumentation Techniques](#key-instrumentation-techniques) +4. [Common Instrumentation Patterns](#common-instrumentation-patterns) +5. [Advanced Techniques](#advanced-techniques) +6. [Platform-Specific Positioning](#platform-specific-positioning) +7. [Best Practices](#best-practices) +8. [Capturing Output](#capturing-output) +9. [Troubleshooting](#troubleshooting) +10. [Quick Reference](#quick-reference) + +--- ## When to Use Instrumentation -- **Validating PR changes**: Measure actual behavior before/after changes -- **Debugging layout issues**: Capture frame positions, sizes, margins -- **Testing behavior**: Verify view properties and state -- **Performance analysis**: Measure timing, allocations, render cycles -- **Understanding code flow**: Trace execution paths and state changes +**✅ Use instrumentation for:** +- Reproducing reported issues +- Validating PR changes and fixes +- Debugging layout and positioning issues +- Testing control behavior and properties +- Performance analysis and timing measurements +- Understanding code execution flow + +**✅ Use Sandbox app for:** +- Manual testing and reproduction +- Quick experimentation +- PR validation +- Issue investigation + +**❌ Don't use Sandbox app for:** +- Writing automated UI tests (use TestCases.HostApp instead) +- Running automated test suites +- CI/CD validation + +--- + +## Quick Start: Basic Pattern + +### Step 1: Create Visual Test Scenario (XAML) + +Edit `src/Controls/samples/Controls.Sample.Sandbox/MainPage.xaml`: + +```xml + + + + + + + + + + + +``` + +**Key XAML patterns:** +- Use `x:Name` on elements you'll reference in code +- Use contrasting `BackgroundColor` values for visual debugging (red parent, yellow child) +- Hook into `Loaded` event for measurement timing +- Keep layout simple and focused on the issue + +### Step 2: Add Instrumentation (Code-Behind) + +Edit `src/Controls/samples/Controls.Sample.Sandbox/MainPage.xaml.cs`: + +```csharp +using Microsoft.Maui.Controls; +using System; + +namespace Maui.Controls.Sample +{ + public partial class MainPage : ContentPage + { + public MainPage() + { + InitializeComponent(); + } + + private void OnLoaded(object sender, EventArgs e) + { + // Wait for layout to complete + Dispatcher.DispatchDelayed(TimeSpan.FromMilliseconds(500), () => + { + CaptureState("OnLoaded"); + }); + } + + private void CaptureState(string context) + { + Console.WriteLine($"=== TEST OUTPUT: {context} ==="); -## Quick Start: Sandbox App Instrumentation + // Basic measurements + Console.WriteLine($"Element Bounds: {TestElement.Bounds}"); + Console.WriteLine($"Element Width: {TestElement.Width}, Height: {TestElement.Height}"); + + // Child content measurements + if (ContentLabel != null) + { + Console.WriteLine($"Content Bounds: {ContentLabel.Bounds}"); + Console.WriteLine($"Content Position: X={ContentLabel.X}, Y={ContentLabel.Y}"); + } -The recommended approach is to use the Sandbox app (`src/Controls/samples/Controls.Sample.Sandbox`) for instrumentation: + // Screen dimensions + var screenHeight = DeviceDisplay.MainDisplayInfo.Height / DeviceDisplay.MainDisplayInfo.Density; + var screenWidth = DeviceDisplay.MainDisplayInfo.Width / DeviceDisplay.MainDisplayInfo.Density; + Console.WriteLine($"Screen Size: {screenWidth}x{screenHeight}"); -1. Modify `MainPage.xaml` to reproduce the scenario -2. Add instrumentation code to `MainPage.xaml.cs` -3. Build and deploy to simulator/device -4. Capture console output -5. Revert changes when done + Console.WriteLine("=== END TEST OUTPUT ===\n"); + } + } +} +``` + +### Step 3: Build, Deploy, and Capture + +See [Common Testing Patterns](common-testing-patterns.md) for detailed build and deploy commands: + +**iOS**: [Sandbox App Build (iOS)](common-testing-patterns.md#sandbox-app-build-ios) +**Android**: [Sandbox App Build (Android)](common-testing-patterns.md#sandbox-app-build-android) + +**Quick iOS workflow:** +```bash +# Get UDID, boot, build, install, launch +UDID=$(xcrun simctl list devices available --json | jq -r '.devices | to_entries | map(select(.key | startswith("com.apple.CoreSimulator.SimRuntime.iOS"))) | map({key: .key, version: (.key | sub("com.apple.CoreSimulator.SimRuntime.iOS-"; "") | split("-") | map(tonumber)), devices: .value}) | sort_by(.version) | reverse | map(select(.devices | any(.name == "iPhone Xs"))) | first | .devices[] | select(.name == "iPhone Xs") | .udid') +xcrun simctl boot $UDID 2>/dev/null || true +dotnet build src/Controls/samples/Controls.Sample.Sandbox/Maui.Controls.Sample.Sandbox.csproj -f net10.0-ios +xcrun simctl install $UDID artifacts/bin/Maui.Controls.Sample.Sandbox/Debug/net10.0-ios/iossimulator-arm64/Maui.Controls.Sample.Sandbox.app +xcrun simctl launch --console-pty $UDID com.microsoft.maui.sandbox > /tmp/output.log 2>&1 & +sleep 8 && cat /tmp/output.log +``` + +**Quick Android workflow:** +```bash +export DEVICE_UDID=$(adb devices | grep -v "List" | grep "device" | awk '{print $1}' | head -1) +dotnet build src/Controls/samples/Controls.Sample.Sandbox/Maui.Controls.Sample.Sandbox.csproj -f net10.0-android -t:Run +adb logcat | grep "TEST OUTPUT" +``` + +### Step 4: Clean Up + +```bash +# Always revert Sandbox changes when done +git checkout -- src/Controls/samples/Controls.Sample.Sandbox/ +``` + +--- + +## Key Instrumentation Techniques + +### 1. Console Output with Markers + +**Why**: Output is captured in device logs and easy to search + +```csharp +Console.WriteLine("=== TEST OUTPUT ==="); // Marker for easy searching +Console.WriteLine($"Value: {someValue}"); +Console.WriteLine("=== END TEST OUTPUT ==="); +``` + +**With unique markers for filtering:** +```csharp +Console.WriteLine($"[ISSUE-12345] FlowDirection changed to {value}"); +Console.WriteLine($"[ISSUE-12345] Measurements: {width}x{height}"); + +// Later, filter logs: +// iOS: xcrun simctl spawn booted log stream | grep "ISSUE-12345" +// Android: adb logcat | grep "ISSUE-12345" +``` + +### 2. Timing: When to Measure + +Two approaches depending on your scenario: + +#### Approach A: Loaded Event (Simple, One-Time Measurements) + +**Best for**: Initial state capture, one-time measurements, Sandbox app testing + +```csharp +private void OnLoaded(object sender, EventArgs e) +{ + // Wait for layout pass to complete + Dispatcher.DispatchDelayed(TimeSpan.FromMilliseconds(500), () => + { + // Now measure - layout is guaranteed complete + CaptureState("AfterLoad"); + }); +} +``` -## Cross-Platform Instrumentation (Preferred Method) +**Why this works:** +- ✅ Simpler code for one-time measurements +- ✅ Reliable for initial state capture +- ✅ No async complexity needed +- ✅ Works consistently across platforms -### Measuring Size +#### Approach B: SizeChanged Event (Dynamic, Event-Driven) -**Use `SizeChanged` events for size measurements:** +**Best for**: Dynamic scenarios, property changes, animations, responsive layouts ```csharp public MainPage() { InitializeComponent(); - + // Subscribe to size changes if (Content is View view) { view.SizeChanged += async (s, e) => { - // Wait for layout pass to complete + // Wait for platform arrange to complete await Task.Delay(1); - + Console.WriteLine("========== VIEW INFO =========="); Console.WriteLine($"Size: W={view.Width}, H={view.Height}"); Console.WriteLine($"Bounds: {view.Bounds}"); - Console.WriteLine($"Margin: {view.Margin}"); - - if (view is Layout layout) - { - Console.WriteLine($"Padding: {layout.Padding}"); - } - Console.WriteLine("================================"); }; } } ``` -### Getting Absolute Screen Position +**Why this works:** +- ✅ Fires on actual size changes +- ✅ Precise timing (1ms wait for platform arrange) +- ✅ Captures dynamic property changes +- ✅ Event-driven, not arbitrary delays -To get the absolute position of an element on the screen, you need to access the platform view and use platform-specific APIs. +**When to use which:** +- **Loaded + 500ms**: Sandbox app testing, issue reproduction, PR validation +- **SizeChanged + 1ms**: Dynamic behavior testing, animation debugging, property change testing -**iOS/MacCatalyst:** +### 3. Measure Child Elements (Critical for SafeArea) + +**CRITICAL**: For SafeArea and padding tests, measure CHILD content position, not parent container size ```csharp -view.SizeChanged += async (s, e) => +// ❌ WRONG: Measuring parent (size stays constant) +Console.WriteLine($"Container size: {RootGrid.Width}x{RootGrid.Height}"); + +// ✅ CORRECT: Measuring child position (shows padding effect) +Console.WriteLine($"Child Y: {ContentLabel.Y}"); +Console.WriteLine($"Gap from top: {ContentLabel.Y}"); +``` + +See [SafeArea Testing Instructions](safearea-testing.instructions.md) for detailed SafeArea testing patterns. + +### 4. Calculate Gaps from Screen Edges + +**Why**: Essential for SafeArea and positioning validation + +```csharp +private void CapturePositionData() { - await Task.Delay(1); // Wait for arrange - - #if IOS || MACCATALYST - if (view.Handler?.PlatformView is UIKit.UIView platformView) + var element = ContentLabel; + + // Get screen dimensions + var screenHeight = DeviceDisplay.MainDisplayInfo.Height / DeviceDisplay.MainDisplayInfo.Density; + var screenWidth = DeviceDisplay.MainDisplayInfo.Width / DeviceDisplay.MainDisplayInfo.Density; + + // Calculate gaps from edges + double topGap = element.Y; + double bottomGap = screenHeight - (element.Y + element.Height); + double leftGap = element.X; + double rightGap = screenWidth - (element.X + element.Width); + + Console.WriteLine($"Top Gap: {topGap}"); + Console.WriteLine($"Bottom Gap: {bottomGap}"); + Console.WriteLine($"Left Gap: {leftGap}"); + Console.WriteLine($"Right Gap: {rightGap}"); +} +``` + +### 5. Color Code Elements for Visual Debugging + +**Why**: Makes visual inspection easier, especially for SafeArea issues + +```xml + + + + + +``` + +When SafeArea padding is applied, you'll see the red background where gaps appear. + +--- + +## Common Instrumentation Patterns + +### Pattern 1: Property Change Testing + +Test property toggles multiple times to catch timing/race conditions: + +```csharp +private async void OnLoaded(object sender, EventArgs e) +{ + Console.WriteLine("=== TEST: Property Toggle ==="); + + for (int i = 0; i < 5; i++) { - // Find the root superview - var superview = platformView; - while (superview.Superview != null) - { - superview = superview.Superview; - } - - // Convert the view's bounds to the root coordinate system - var convertedRect = platformView.ConvertRectToView(platformView.Bounds, superview); - - Console.WriteLine($"Screen Position: X={convertedRect.X}, Y={convertedRect.Y}"); - Console.WriteLine($"Screen Size: W={convertedRect.Width}, H={convertedRect.Height}"); + TestElement.FlowDirection = FlowDirection.RightToLeft; + await Task.Delay(500); + Console.WriteLine($"Iteration {i}: RTL Bounds = {TestElement.Bounds}"); + + TestElement.FlowDirection = FlowDirection.LeftToRight; + await Task.Delay(500); + Console.WriteLine($"Iteration {i}: LTR Bounds = {TestElement.Bounds}"); } - #endif -}; + + Console.WriteLine("=== END TEST ==="); +} ``` -**Android:** +### Pattern 2: Nested Content Measurement + +Measure parent-child relationships: ```csharp -view.SizeChanged += async (s, e) => +private void OnLoaded(object sender, EventArgs e) { - await Task.Delay(1); // Wait for arrange - - #if ANDROID - if (view.Handler?.PlatformView is Android.Views.View platformView) + Dispatcher.DispatchDelayed(TimeSpan.FromMilliseconds(500), () => { - int[] location = new int[2]; - platformView.GetLocationOnScreen(location); - - Console.WriteLine($"Screen Position: X={location[0]}, Y={location[1]}"); - Console.WriteLine($"Screen Size: W={platformView.Width}, H={platformView.Height}"); - } - #endif -}; + Console.WriteLine("=== TEST: Parent and Child ==="); + Console.WriteLine($"Parent Bounds: {RootGrid.Bounds}"); + Console.WriteLine($"Parent Position: X={RootGrid.X}, Y={RootGrid.Y}"); + + Console.WriteLine($"Child Bounds: {ContentLabel.Bounds}"); + Console.WriteLine($"Child Position: X={ContentLabel.X}, Y={ContentLabel.Y}"); + + // Calculate child position relative to parent + var relativeX = ContentLabel.X - RootGrid.X; + var relativeY = ContentLabel.Y - RootGrid.Y; + Console.WriteLine($"Child relative to parent: X={relativeX}, Y={relativeY}"); + Console.WriteLine("=== END TEST ==="); + }); +} ``` -**Windows:** +### Pattern 3: Collection/List Testing + +Add large data sets for scrolling and performance tests: ```csharp -view.SizeChanged += async (s, e) => +public MainPage() { - await Task.Delay(1); // Wait for arrange - - #if WINDOWS - if (view.Handler?.PlatformView is Microsoft.UI.Xaml.FrameworkElement platformElement) + InitializeComponent(); + + // Large data set for performance/scrolling test + var items = Enumerable.Range(1, 100).Select(i => $"Item {i}").ToList(); + TestCollectionView.ItemsSource = items; +} + +private void OnLoaded(object sender, EventArgs e) +{ + Dispatcher.DispatchDelayed(TimeSpan.FromMilliseconds(500), () => { - var transform = platformElement.TransformToVisual(null); - var point = transform.TransformPoint(new Windows.Foundation.Point(0, 0)); - - Console.WriteLine($"Screen Position: X={point.X}, Y={point.Y}"); - Console.WriteLine($"Screen Size: W={platformElement.ActualWidth}, H={platformElement.ActualHeight}"); - } - #endif -}; + var collection = TestCollectionView; + var count = collection.ItemsSource?.Cast().Count() ?? 0; + Console.WriteLine($"=== TEST: {count} items loaded ==="); + Console.WriteLine($"Collection Bounds: {collection.Bounds}"); + }); +} ``` -**Monitoring Specific Named Elements:** +### Pattern 4: Event Sequence Tracking + +Track when events fire and in what order: ```csharp -// In XAML: