diff --git a/.github/README-AI.md b/.github/README-AI.md index 9835279f6f7e..5fd40c573579 100644 --- a/.github/README-AI.md +++ b/.github/README-AI.md @@ -64,9 +64,11 @@ This is by design - it's better to pause and get guidance than provide incomplet ### Instruction Files These files provide specialized guidance for specific scenarios: +- **`instructions/common-testing-patterns.md`** - Common testing patterns for 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 ### General Guidelines diff --git a/.github/agents/pr-reviewer.md b/.github/agents/pr-reviewer.md index 11c8b5246e16..fafd5e2d2629 100644 --- a/.github/agents/pr-reviewer.md +++ b/.github/agents/pr-reviewer.md @@ -27,6 +27,7 @@ You are a specialized PR review agent for the .NET MAUI repository. Your role is 1. **Read Required Files**: - `.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 @@ -178,7 +179,15 @@ When testing is required, use the Sandbox app to validate PR changes: ### Fetch PR Changes (Without Checking Out) -**CRITICAL**: Stay on the current branch (pr-reviewer) to preserve all instruction files and context. Apply PR changes on top of the current branch instead of checking out the PR branch. +**CRITICAL**: Stay on your current branch (wherever you are when starting the review) to preserve context. Apply PR changes on top of the current branch instead of checking out the PR branch. + +**FIRST STEP - Record Your Starting Branch:** +```bash +# Record what branch you're currently on - you'll need this for cleanup +ORIGINAL_BRANCH=$(git branch --show-current) +echo "Starting review from branch: $ORIGINAL_BRANCH" +# Remember this value for cleanup at the end! +``` ```bash # Get the PR number from the user's request @@ -187,11 +196,30 @@ PR_NUMBER=XXXXX # Replace with actual PR number # Fetch the PR into a temporary branch git fetch origin pull/$PR_NUMBER/head:pr-$PR_NUMBER-temp +# Check fetch succeeded +if [ $? -ne 0 ]; then + echo "❌ ERROR: Failed to fetch PR #$PR_NUMBER" + exit 1 +fi + # Create a test branch from current branch (preserves instruction files) git checkout -b test-pr-$PR_NUMBER +# Check branch creation succeeded +if [ $? -ne 0 ]; then + echo "❌ ERROR: Failed to create test branch" + exit 1 +fi + # Merge the PR changes into the test branch git merge pr-$PR_NUMBER-temp -m "Test PR #$PR_NUMBER" --no-edit + +# Check merge succeeded (will error if conflicts) +if [ $? -ne 0 ]; then + echo "❌ ERROR: Merge failed with conflicts" + echo "See section below on handling merge conflicts" + exit 1 +fi ``` **If merge conflicts occur:** @@ -248,10 +276,10 @@ I need help resolving this merge issue before I can test the PR. **Why this matters**: If you can't cleanly merge the PR, you can't accurately test it. Testing with incorrect code leads to misleading results. It's better to pause and get help than to provide an incomplete or incorrect review. **Why this approach:** -- ✅ Preserves all instruction files from pr-reviewer branch -- ✅ Tests PR changes on top of latest guidelines -- ✅ Simple and reliable (standard git merge) -- ✅ Easy to clean up (just delete test branch) +- ✅ Preserves your current working context and branch state +- ✅ Tests PR changes on top of wherever you currently are +- ✅ Allows agent to maintain proper context across review +- ✅ Easy to clean up (just delete test branch and return to original branch) - ✅ Can compare before/after easily - ✅ Handles most conflicts gracefully @@ -259,17 +287,36 @@ I need help resolving this merge issue before I can test the PR. **iOS Testing**: ```bash -# Find iOS 26 simulator (or specify version based on issue) -UDID=$(xcrun simctl list devices available --json | jq -r '.devices["com.apple.CoreSimulator.SimRuntime.iOS-26-0"] | first | .udid') +# Find iPhone Xs with highest iOS version +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') + +# Check UDID was found +if [ -z "$UDID" ] || [ "$UDID" = "null" ]; then + echo "❌ ERROR: No iPhone Xs simulator found. Please create one." + exit 1 +fi # Boot simulator xcrun simctl boot $UDID 2>/dev/null || true + +# Check simulator is booted +STATE=$(xcrun simctl list devices --json | jq -r --arg udid "$UDID" '.devices[][] | select(.udid == $udid) | .state') +if [ "$STATE" != "Booted" ]; then + echo "❌ ERROR: Simulator failed to boot. Current state: $STATE" + exit 1 +fi ``` **Android Testing**: ```bash # Get connected device/emulator export DEVICE_UDID=$(adb devices | grep -v "List" | grep "device" | awk '{print $1}' | head -1) + +# Check device was found +if [ -z "$DEVICE_UDID" ]; then + echo "❌ ERROR: No Android device/emulator found. Start an emulator or connect a device." + exit 1 +fi ``` ### Modify Sandbox App for Testing @@ -382,11 +429,30 @@ Does this test approach look correct before I build and deploy? # Build dotnet build src/Controls/samples/Controls.Sample.Sandbox/Maui.Controls.Sample.Sandbox.csproj -f net10.0-ios +# Check build succeeded +if [ $? -ne 0 ]; then + echo "❌ ERROR: Build failed" + exit 1 +fi + # Install xcrun simctl install $UDID artifacts/bin/Maui.Controls.Sample.Sandbox/Debug/net10.0-ios/iossimulator-arm64/Maui.Controls.Sample.Sandbox.app +# Check install succeeded +if [ $? -ne 0 ]; then + echo "❌ ERROR: App installation failed" + exit 1 +fi + # Launch with console capture xcrun simctl launch --console-pty $UDID com.microsoft.maui.sandbox > /tmp/ios_test.log 2>&1 & + +# Check launch didn't immediately fail +if [ $? -ne 0 ]; then + echo "❌ ERROR: App launch failed" + exit 1 +fi + sleep 8 cat /tmp/ios_test.log ``` @@ -396,6 +462,12 @@ cat /tmp/ios_test.log # 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 +if [ $? -ne 0 ]; then + echo "❌ ERROR: Build or deployment failed" + exit 1 +fi + # Monitor logs adb logcat | grep -E "(YourMarker|Frame|Console)" ``` @@ -614,12 +686,14 @@ See `.github/instructions/safearea-testing.instructions.md` for comprehensive gu 6. **Clean up test branches** ```bash - # Return to pr-reviewer branch - git checkout pr-reviewer + # Return to original branch (whatever branch you started on) + git checkout $ORIGINAL_BRANCH # Delete test branches git branch -D test-pr-XXXXX baseline-test pr-XXXXX-temp ``` + + **Note**: Uses `$ORIGINAL_BRANCH` variable you set at the beginning. If you didn't save it, replace with whatever branch you were on when you started the review (e.g., `main`, `pr-reviewer`, etc.) ### Include Test Results in Review @@ -682,8 +756,8 @@ For each edge case tested, document: After testing, clean up all test artifacts: ```bash -# Return to pr-reviewer branch -git checkout pr-reviewer +# Return to your original branch (use the variable from the beginning) +git checkout $ORIGINAL_BRANCH # Or manually specify: main, pr-reviewer, etc. # Revert any changes to Sandbox app git checkout -- src/Controls/samples/Controls.Sample.Sandbox/ @@ -695,6 +769,8 @@ git branch -D test-pr-XXXXX baseline-test pr-XXXXX-temp 2>/dev/null || true dotnet clean ``` +**Important**: If you didn't save `$ORIGINAL_BRANCH` at the start, replace it with whatever branch you were on when you began the review. This ensures you return to your starting state. + ## Core Responsibilities 1. **Code Quality Review**: Analyze code for correctness, performance, maintainability, and adherence to .NET MAUI coding standards diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index d34c7a55d758..62ffad4a1200 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -19,7 +19,7 @@ When updating this file, you MUST also update `AGENTS.md` (in repository root) t - **.NET SDK** - Version is **ALWAYS** defined in `global.json` at repository root - **main branch**: Use the latest stable version of .NET to build (currently .NET 10) - **net10.0 branch**: Use the latest .NET 10 SDK - - **etc.**: Each feature branch correlates to its respective .NET version + - **Feature branches**: Each feature branch (e.g., `net11.0`, `net12.0`) correlates to its respective .NET version - **C#** and **XAML** for application development - **Cake build system** for compilation and packaging - **MSBuild** with custom build tasks @@ -91,10 +91,18 @@ dotnet build ./Microsoft.Maui.BuildTasks.slnf --verbosity normal - **Windows** specific code is inside folders named `Windows` ### Platform-Specific File Extensions -- Files with `.windows.cs` will only compile for the Windows TFM -- Files with `.android.cs` will only compile for the Android TFM -- Files with `.ios.cs` will only compile for the iOS and MacCatalyst TFM -- Files with `MacCatalyst.cs` will only compile for the MacCatalyst TFM + +Platform-specific files use naming conventions to control compilation: + +**File extension patterns**: +- `.windows.cs` - Windows TFM only +- `.android.cs` - Android TFM only +- `.ios.cs` - iOS and MacCatalyst TFMs (both) +- `.maccatalyst.cs` - MacCatalyst TFM only (does NOT compile for iOS) + +**Important**: Both `.ios.cs` and `.maccatalyst.cs` files compile for MacCatalyst. There is no precedence mechanism that excludes one when the other exists. + +**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. ### Sample Projects ``` @@ -133,12 +141,12 @@ dotnet cake --target=dotnet-pack #### Testing Guidelines - Add tests for new functionality -- Ensure existing tests pass: - - `src/Core/tests/UnitTests/Core.UnitTests.csproj` - - `src/Essentials/test/UnitTests/Essentials.UnitTests.csproj` - - `src/Compatibility/Core/tests/Compatibility.UnitTests/Compatibility.Core.UnitTests.csproj` - - `src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj` - - `src/Controls/tests/Xaml.UnitTests/Controls.Xaml.UnitTests.csproj` +- Ensure existing tests pass for modified areas (major test projects): + - **Core tests**: `src/Core/tests/UnitTests/Core.UnitTests.csproj` + - **Essentials tests**: `src/Essentials/test/UnitTests/Essentials.UnitTests.csproj` + - **Controls tests**: `src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj` + - **XAML tests**: `src/Controls/tests/Xaml.UnitTests/Controls.Xaml.UnitTests.csproj` + - **To find other test projects**: `find . -name "*.UnitTests.csproj"` or check the solution file #### UI Testing @@ -305,11 +313,13 @@ dotnet format analyzers Microsoft.Maui.slnx ## Additional Resources +- [Common Testing Patterns](/.github/instructions/common-testing-patterns.md) - Common command patterns for UDID extraction, builds, deploys, and error checking - [UI Testing Guide](../docs/UITesting-Guide.md) - [UI Testing Architecture](../docs/design/UITesting-Architecture.md) - [PR Test Validation Guide](../docs/PR-Test-Validation-Guide.md) - Procedures for validating UI tests in PRs - [SafeArea Testing Guide](/.github/instructions/safearea-testing.instructions.md) - Specialized guide for testing SafeArea changes (measure children, not parents) - [Instrumentation Guide](/.github/instructions/instrumentation.instructions.md) - Patterns for instrumenting MAUI code for debugging and testing +- [Appium Control Scripts](/.github/instructions/appium-control.instructions.md) - Create standalone scripts for manual Appium-based debugging and exploration - [Development Guide](/.github/DEVELOPMENT.md) - [Development Tips](/docs/DevelopmentTips.md) - [Contributing Guidelines](/.github/CONTRIBUTING.md) diff --git a/.github/instructions/appium-control.instructions.md b/.github/instructions/appium-control.instructions.md new file mode 100644 index 000000000000..714706dfc372 --- /dev/null +++ b/.github/instructions/appium-control.instructions.md @@ -0,0 +1,369 @@ +--- +description: "Quick reference for creating standalone Appium control scripts for manual debugging and exploration of .NET MAUI applications." +--- + +# Appium Control Script Instructions + +Create standalone C# scripts for manual Appium-based debugging and exploration of .NET MAUI apps. Use these when you need direct control outside of automated tests. + +**Common Command Patterns**: For UDID extraction, device boot, and build patterns, see [Common Testing Patterns](common-testing-patterns.md). + +## When to Use + +- **Manual debugging** - Interactive exploration of app behavior +- **Quick experiments** - Test UI interactions without writing full tests +- **Investigation** - Reproduce issues or explore edge cases +- **Learning** - Understand how Appium interacts with MAUI apps +- **Prototyping** - Test Appium interactions before creating full UI tests + +**Not for automated testing** - For automated UI tests, use the established test infrastructure in `src/Controls/tests/`. + +## Quick Start with Sandbox App + +The fastest way to experiment with Appium is using the Sandbox app (`src/Controls/samples/Controls.Sample.Sandbox`): + +1. **Modify `MainPage.xaml`** to add controls with `AutomationId` attributes + +2. **Build and deploy**: + + **iOS:** + ```bash + # Build + dotnet build src/Controls/samples/Controls.Sample.Sandbox/Maui.Controls.Sample.Sandbox.csproj -f net10.0-ios + + # Get device UDID + 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') + + # Verify UDID was found + if [ -z "$UDID" ]; then + echo "❌ ERROR: No iPhone Xs simulator found" + exit 1 + fi + echo "Using iPhone Xs with UDID: $UDID" + + # Boot and install + xcrun simctl boot $UDID 2>/dev/null || true + xcrun simctl install $UDID artifacts/bin/Maui.Controls.Sample.Sandbox/Debug/net10.0-ios/iossimulator-arm64/Maui.Controls.Sample.Sandbox.app + + # Set environment variable + export DEVICE_UDID=$UDID + ``` + + **Android:** + ```bash + # Get device UDID + UDID=$(adb devices | grep -v "List" | grep "device" | awk '{print $1}' | head -1) + + # Verify UDID was found + if [ -z "$UDID" ]; then + echo "❌ ERROR: No Android device/emulator found" + exit 1 + fi + echo "Using Android device: $UDID" + + # Set environment variable + export DEVICE_UDID=$UDID + + # Build and deploy (this handles install + launch) + dotnet build src/Controls/samples/Controls.Sample.Sandbox/Maui.Controls.Sample.Sandbox.csproj -f net10.0-android -t:Run + + # Verify app is running + sleep 3 + if adb -s $UDID shell pidof com.microsoft.maui.sandbox > /dev/null; then + echo "✅ App is running" + else + echo "❌ App failed to start" + exit 1 + fi + ``` + +3. **Start Appium and run your control script**: + ```bash + # Start Appium in background + appium --log-level error & + + # Wait for Appium to be ready + sleep 3 + + # Or verify it's ready (optional): + # curl http://localhost:4723/status + + # Run your script (from SandboxAppium/ directory) + cd SandboxAppium + dotnet run yourscript.cs + ``` + +**Note**: The Sandbox app and SandboxAppium folder are set up for iterative development. You can modify your script and re-run it multiple times without cleaning up. Only clean up when you're completely done with your debugging session. + +## Cleanup (Optional) + +When you're finished with your debugging session and ready to clean up: + +```bash +# Revert Sandbox changes +git checkout -- src/Controls/samples/Controls.Sample.Sandbox/ + +# Remove SandboxAppium folder (it's gitignored) +rm -rf SandboxAppium + +# Kill Appium server +lsof -i :4723 | grep LISTEN | awk '{print $2}' | xargs kill -9 +``` + +## Prerequisites + +Ensure Appium server is running on `http://localhost:4723` before running your script. + +## Basic Template + +**IMPORTANT: Create script files inside project directory (e.g., `SandboxAppium/`), not in repository root or `/tmp`.** + +```csharp +#:package Appium.WebDriver@8.0.1 + +using System; +using System.Threading; +using OpenQA.Selenium; +using OpenQA.Selenium.Appium; +using OpenQA.Selenium.Appium.iOS; +using OpenQA.Selenium.Appium.Android; +using OpenQA.Selenium.Appium.Enums; + +// CRITICAL: Get device UDID from environment variable +// Without this, Appium will randomly select a device +var udid = Environment.GetEnvironmentVariable("DEVICE_UDID"); +if (string.IsNullOrEmpty(udid)) +{ + Console.WriteLine("❌ ERROR: DEVICE_UDID environment variable not set!"); + Console.WriteLine("Set it with: export DEVICE_UDID="); + Environment.Exit(1); +} + +Console.WriteLine($"Target device UDID: {udid}"); + +// iOS Configuration +var iOSOptions = new AppiumOptions(); +iOSOptions.PlatformName = "iOS"; +iOSOptions.AutomationName = "XCUITest"; +iOSOptions.AddAdditionalAppiumOption("appium:bundleId", "com.microsoft.maui.sandbox"); +iOSOptions.AddAdditionalAppiumOption(MobileCapabilityType.Udid, udid); // CRITICAL: Target specific device +iOSOptions.AddAdditionalAppiumOption("appium:newCommandTimeout", 300); + +// Android Configuration (alternative - comment/uncomment as needed) +/* +var androidOptions = new AppiumOptions(); +androidOptions.PlatformName = "Android"; +androidOptions.AutomationName = "UIAutomator2"; +androidOptions.AddAdditionalAppiumOption("appium:appPackage", "com.microsoft.maui.sandbox"); +androidOptions.AddAdditionalAppiumOption("appium:appActivity", "com.microsoft.maui.sandbox.MainActivity"); +androidOptions.AddAdditionalAppiumOption("appium:noReset", true); +androidOptions.AddAdditionalAppiumOption(MobileCapabilityType.Udid, udid); // CRITICAL: Target specific device +androidOptions.AddAdditionalAppiumOption("appium:newCommandTimeout", 300); +*/ + +// Connect to Appium server +var serverUri = new Uri("http://localhost:4723"); + +Console.WriteLine("Connecting to Appium server..."); + +try +{ + // For iOS: + using var driver = new IOSDriver(serverUri, iOSOptions); + + // For Android (alternative): + // using var driver = new AndroidDriver(serverUri, androidOptions); + + Console.WriteLine("✅ Connected to app! Starting interaction..."); + + // CRITICAL PLATFORM DIFFERENCE: Element locators differ between iOS and Android + // iOS: AutomationId maps to accessibility identifier → use MobileBy.AccessibilityId() + // Android: AutomationId maps to resource-id → use MobileBy.Id() + + // Wait for app to fully load + Thread.Sleep(3000); + + // Your interactive code here + // Example for iOS: Find and tap a button + var button = driver.FindElement(MobileBy.AccessibilityId("ClickButton")); + // Example for Android: Find and tap a button + // var button = driver.FindElement(MobileBy.Id("ClickButton")); + + button.Click(); + Console.WriteLine("Button clicked!"); + + // Keep session alive for manual exploration + Console.WriteLine("Press Enter to quit..."); + Console.ReadLine(); +} +catch (Exception ex) +{ + Console.WriteLine($"❌ ERROR: Failed to connect to Appium or launch app"); + Console.WriteLine($"Error: {ex.Message}"); + Console.WriteLine("\nTroubleshooting steps:"); + Console.WriteLine("1. Verify Appium is running: curl http://localhost:4723/status"); + Console.WriteLine("2. Check device UDID is correct: echo $DEVICE_UDID"); + Console.WriteLine("3. Verify app is installed on device"); + Console.WriteLine("4. Check Appium logs for detailed error information"); + Environment.Exit(1); +} +``` + +## Key Differences Between Platforms + +### iOS +- **Driver**: `IOSDriver` +- **Automation**: `AutomationName = "XCUITest"` +- **App Path**: `.app` bundle in `iossimulator-arm64/` folder +- **Device Name**: Find with `xcrun simctl list devices` +- **Element Locator**: `MobileBy.AccessibilityId("AutomationId")` - AutomationIds map to accessibility identifiers +- **App Options**: Use `appium:bundleId` to specify the app + +### Android +- **Driver**: `AndroidDriver` +- **Automation**: `AutomationName = "UIAutomator2"` +- **App Path**: `.apk` file with `-Signed` suffix +- **Device Name**: Get from `adb devices` command +- **Element Locator**: `MobileBy.Id("AutomationId")` - AutomationIds map to `resource-id` attributes +- **App Options**: Use `appium:appPackage` and `appium:appActivity` (format: `{packageId}.MainActivity`) +- **Deployment**: Use `dotnet build -t:Run` to build, install, and launch the app in one command + +## Running the Script + +**⚠️ Important: Create scripts inside project directory (e.g., `SandboxAppium/`), not in `/tmp` or repository root.** + +```bash +# 1. Create script folder +mkdir -p SandboxAppium && cd SandboxAppium + +# 2. Copy the Basic Template above into a .cs file (or use the Quick Start example) + +# 3. Set DEVICE_UDID environment variable +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') + +# 4. Start Appium (separate terminal or background) +appium --log-level error & + +# Wait for Appium to be ready +sleep 3 + +# 5. Run your script +dotnet run yourscript.cs +``` + +**For Android:** Replace iOS UDID command with: `adb devices | grep -v "List" | grep "device" | awk '{print $1}' | head -1` + +**Complete workflow:** See [Quick Start with Sandbox App](#quick-start-with-sandbox-app) above for full end-to-end example. + +## Common Appium Operations + +### Finding and Interacting with Elements + +**CRITICAL**: Element locators differ by platform! + +```csharp +// Find elements by AutomationId +// iOS: Use MobileBy.AccessibilityId() +var element = driver.FindElement(MobileBy.AccessibilityId("AutomationId")); + +// Android: Use MobileBy.Id() +// var element = driver.FindElement(MobileBy.Id("AutomationId")); + +// Find by visible text (works on both platforms) +var byText = driver.FindElement(MobileBy.Name("Button Text")); + +// Interact +element.Click(); +element.SendKeys("text input"); +element.Clear(); + +// Query +var text = element.Text; +var isDisplayed = element.Displayed; +var location = element.Location; +var size = element.Size; + +// Wait for element +var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10)); +wait.Until(d => d.FindElement(MobileBy.AccessibilityId("ElementId"))); +``` + +### Device Control + +**⚠️ Important: Use Appium APIs for device operations, not platform tools.** + +```csharp +// Rotate device and validate +driver.Orientation = ScreenOrientation.Landscape; +Thread.Sleep(2000); // Wait for animation + +if (driver.Orientation != ScreenOrientation.Landscape) + Console.WriteLine("❌ Rotation failed!"); +``` + +**Why:** `driver.Orientation` actually rotates the device. Platform tools like `xcrun simctl status_bar --orientation` only change the status bar appearance, not the device state. + +### Screenshots and Other Operations + +```csharp +// Take screenshot +var screenshot = driver.GetScreenshot(); +screenshot.SaveAsFile("/tmp/screenshot.png"); + +// Navigate +driver.Navigate().Back(); + +// Background app +driver.BackgroundApp(TimeSpan.FromSeconds(5)); +``` + +## Advanced Patterns + +For Shell-specific testing patterns (e.g., opening flyouts), see [UI Tests Instructions](uitests.instructions.md). + +## Troubleshooting + +**"#: directives can be only used in file-based programs"** +- ❌ **Problem**: Script file is in `/tmp` or outside project directory +- ✅ **Solution**: Move script into project folder (e.g., `SandboxAppium/`) +- **Why**: The `#:package` directive only works inside a project context in .NET 10 + +**"DEVICE_UDID environment variable not set"** +- Set it before running: `export DEVICE_UDID=` +- For iOS: Use the UDID from `xcrun simctl list devices` +- For Android: Use the device ID from `adb devices` + +**"Could not connect to Appium server"** +- Verify Appium is running: `curl http://localhost:4723/status` +- 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 + +**"Device not found" (iOS)** +- Verify DEVICE_UDID is set: `echo $DEVICE_UDID` +- List devices: `xcrun simctl list devices available | grep "iPhone"` +- Boot simulator: `xcrun simctl boot $DEVICE_UDID` + +**"Device not found" (Android)** +- Verify DEVICE_UDID is set: `echo $DEVICE_UDID` +- List devices: `adb devices` +- Start emulator via Android Studio or: `emulator -avd [avd-name]` + +**"Appium server keeps stopping"** +- Check if port 4723 is already in use: `lsof -i :4723` +- Kill existing process: `kill -9 ` +- Restart Appium: `appium &` + +**"Rotation doesn't work" or "Device stays in same orientation"** +- ✅ **Use Appium API**: `driver.Orientation = ScreenOrientation.Landscape` +- ❌ **Don't use**: `xcrun simctl status_bar --orientation` (only changes status bar, not device) +- ✅ **Always validate**: Check `driver.Orientation` after rotation +- ✅ **Wait for animation**: `Thread.Sleep(2000)` after rotation command + +## Additional Resources + +- [Appium Documentation](http://appium.io/docs) - Complete API reference and guides +- [UI Testing Guide](../../docs/UITesting-Guide.md) - Full automated testing documentation +- [Selenium WebDriver Docs](https://www.selenium.dev/documentation/webdriver/) - Core WebDriver concepts diff --git a/.github/instructions/common-testing-patterns.md b/.github/instructions/common-testing-patterns.md new file mode 100644 index 000000000000..8c7c7aa0c3ad --- /dev/null +++ b/.github/instructions/common-testing-patterns.md @@ -0,0 +1,397 @@ +--- +description: "Common testing patterns for command sequences used across .NET MAUI AI agent instructions" +--- + +# Common Testing Patterns + +This document consolidates recurring command patterns used across multiple instruction files. Reference these patterns instead of duplicating them. + +## 1. UDID Extraction Patterns + +### iOS Simulator UDID (iPhone Xs, Highest iOS Version) + +**Used in**: `pr-reviewer.md`, `uitests.instructions.md`, `instrumentation.instructions.md`, `appium-control.instructions.md` + +**Pattern**: +```bash +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') + +# Check UDID was found +if [ -z "$UDID" ] || [ "$UDID" = "null" ]; then + echo "❌ ERROR: No iPhone Xs simulator found. Please create one." + exit 1 +fi + +echo "Using simulator: iPhone Xs (UDID: $UDID)" +``` + +**When to use**: Default iOS testing on iPhone Xs with latest iOS version + +--- + +### iOS Simulator UDID (Specific iOS Version) + +**Used in**: `uitests.instructions.md` + +**Pattern**: +```bash +# User specifies iOS version (e.g., "26.0") and device name +IOS_VERSION="${IOS_VERSION:-}" +DEVICE_NAME="${DEVICE_NAME:-iPhone Xs}" + +if [ -z "$IOS_VERSION" ]; then + echo "❌ ERROR: IOS_VERSION not set" + exit 1 +fi + +IOS_VERSION_FILTER="iOS-${IOS_VERSION//./-}" +UDID=$(xcrun simctl list devices available --json | jq -r --arg filter "$IOS_VERSION_FILTER" --arg device "$DEVICE_NAME" '.devices | to_entries | map(select(.key | contains($filter))) | map(.value) | flatten | map(select(.name == $device)) | first | .udid') + +if [ -z "$UDID" ] || [ "$UDID" = "null" ]; then + echo "❌ ERROR: No $DEVICE_NAME simulator found for iOS $IOS_VERSION" + exit 1 +fi + +echo "Using simulator: $DEVICE_NAME iOS $IOS_VERSION (UDID: $UDID)" +``` + +**When to use**: Testing on specific iOS version + +--- + +### Android Device UDID + +**Used in**: `pr-reviewer.md`, `uitests.instructions.md`, `instrumentation.instructions.md`, `appium-control.instructions.md` + +**Pattern**: +```bash +export DEVICE_UDID=$(adb devices | grep -v "List" | grep "device" | awk '{print $1}' | head -1) + +# Check device was found +if [ -z "$DEVICE_UDID" ]; then + echo "❌ ERROR: No Android device/emulator found. Start an emulator or connect a device." + exit 1 +fi + +echo "Using Android device: $DEVICE_UDID" +``` + +**When to use**: Any Android testing + +--- + +## 2. Device Boot Patterns + +### iOS Simulator Boot with Error Checking + +**Used in**: `pr-reviewer.md`, `uitests.instructions.md`, `instrumentation.instructions.md` + +**Pattern**: +```bash +# Requires: $UDID already set +# Boot simulator (error if already booted is OK) +xcrun simctl boot $UDID 2>/dev/null || true + +# Check simulator is booted +STATE=$(xcrun simctl list devices --json | jq -r --arg udid "$UDID" '.devices[][] | select(.udid == $udid) | .state') +if [ "$STATE" != "Booted" ]; then + echo "❌ ERROR: Simulator failed to boot. Current state: $STATE" + exit 1 +fi + +echo "Simulator is booted and ready" +``` + +**When to use**: Before installing/launching iOS apps + +--- + +## 3. Build Patterns + +### Sandbox App Build (iOS) + +**Used in**: `pr-reviewer.md`, `instrumentation.instructions.md`, `appium-control.instructions.md` + +**Pattern**: +```bash +# Build (use --no-incremental only if app crashes on launch) +dotnet build src/Controls/samples/Controls.Sample.Sandbox/Maui.Controls.Sample.Sandbox.csproj -f net10.0-ios + +# Check build succeeded +if [ $? -ne 0 ]; then + echo "❌ ERROR: Build failed" + exit 1 +fi + +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` + +**Pattern**: +```bash +# Requires: $DEVICE_UDID already set +# Build, install, and launch (the -t:Run target does all three) +dotnet build src/Controls/samples/Controls.Sample.Sandbox/Maui.Controls.Sample.Sandbox.csproj -f net10.0-android -t:Run + +# Check build succeeded +if [ $? -ne 0 ]; then + echo "❌ ERROR: Build/deploy failed" + exit 1 +fi + +echo "Build and deploy successful" + +# Verify app is running +sleep 3 +if adb -s $DEVICE_UDID shell pidof com.microsoft.maui.sandbox > /dev/null; then + echo "✅ App is running" +else + echo "❌ App failed to start" + exit 1 +fi +``` + +**When to use**: Building and deploying Sandbox app for Android testing + +**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) + +**Used in**: `pr-reviewer.md`, `instrumentation.instructions.md` + +**Pattern**: +```bash +# Build and deploy (use --no-incremental only if app crashes on launch) +dotnet build src/Controls/samples/Controls.Sample.Sandbox/Maui.Controls.Sample.Sandbox.csproj -f net10.0-android -t:Run + +# Check build/deploy succeeded +if [ $? -ne 0 ]; then + echo "❌ ERROR: Build or deployment failed" + exit 1 +fi + +echo "Build successful and deployed" +``` + +**When to use**: Building and deploying Sandbox app for Android testing + +**Troubleshooting**: If app crashes on launch, rebuild with `--no-incremental` flag + +--- + +### TestCases.HostApp Build (iOS) + +**Used in**: `uitests.instructions.md` + +**Pattern**: +```bash +# Use local dotnet if available, otherwise global +DOTNET_CMD="./bin/dotnet/dotnet" +if [ ! -f "$DOTNET_CMD" ]; then + DOTNET_CMD="dotnet" +fi + +$DOTNET_CMD build src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj -f net10.0-ios + +# Check build succeeded +if [ $? -ne 0 ]; then + echo "❌ ERROR: Build failed" + exit 1 +fi + +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) + +**Used in**: `uitests.instructions.md` + +**Pattern**: +```bash +# Use local dotnet if available, otherwise global +DOTNET_CMD="./bin/dotnet/dotnet" +if [ ! -f "$DOTNET_CMD" ]; then + DOTNET_CMD="dotnet" +fi + +$DOTNET_CMD build src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj -f net10.0-android -t:Run + +# Check build/deploy succeeded +if [ $? -ne 0 ]; then + echo "❌ ERROR: Build or deployment failed" + exit 1 +fi + +echo "Build successful and deployed" +``` + +**When to use**: Building and deploying TestCases.HostApp for Android automated UI tests + +--- + +## 4. App Installation Patterns + +### iOS App Install with Error Checking + +**Used in**: `pr-reviewer.md`, `uitests.instructions.md`, `instrumentation.instructions.md` + +**Pattern**: +```bash +# Requires: $UDID already set, simulator booted +APP_PATH="${APP_PATH:-artifacts/bin/Maui.Controls.Sample.Sandbox/Debug/net10.0-ios/iossimulator-arm64/Maui.Controls.Sample.Sandbox.app}" + +# Install +xcrun simctl install $UDID "$APP_PATH" + +# Check install succeeded +if [ $? -ne 0 ]; then + echo "❌ ERROR: App installation failed" + exit 1 +fi + +echo "App installed successfully" +``` + +**When to use**: Installing iOS apps to simulator + +--- + +## 5. App Launch Patterns + +### iOS App Launch with Console Capture + +**Used in**: `pr-reviewer.md`, `instrumentation.instructions.md` + +**Pattern**: +```bash +# Requires: $UDID, $BUNDLE_ID set, app already installed +LOG_FILE="${LOG_FILE:-/tmp/ios_test.log}" +BUNDLE_ID="${BUNDLE_ID:-com.microsoft.maui.sandbox}" + +# Launch with console capture +xcrun simctl launch --console-pty $UDID "$BUNDLE_ID" > "$LOG_FILE" 2>&1 & + +# Check launch didn't immediately fail +if [ $? -ne 0 ]; then + echo "❌ ERROR: App launch failed" + exit 1 +fi + +# Wait for app to start +sleep 8 + +# Show output +cat "$LOG_FILE" +``` + +**When to use**: Launching iOS app and capturing console output for instrumentation + +--- + +### Android Logcat Monitoring + +**Used in**: `pr-reviewer.md`, `instrumentation.instructions.md` + +**Pattern**: +```bash +# Monitor logs for specific markers +MARKER="${MARKER:-TEST OUTPUT}" +adb logcat | grep -E "($MARKER|Console|FATAL|AndroidRuntime)" +``` + +**When to use**: Monitoring Android console output during testing + +--- + +## 6. Cleanup Patterns + +### Sandbox App Cleanup + +**Used in**: `pr-reviewer.md`, `instrumentation.instructions.md` + +**Pattern**: +```bash +# Revert all changes to Sandbox app +git checkout -- src/Controls/samples/Controls.Sample.Sandbox/ +``` + +**When to use**: After testing with Sandbox app + +--- + +### Test Branch Cleanup + +**Used in**: `pr-reviewer.md` + +**Pattern**: +```bash +# Requires: $ORIGINAL_BRANCH set at start of review +# Return to original branch +git checkout $ORIGINAL_BRANCH + +# Delete test branches +git branch -D test-pr-* baseline-test pr-*-temp 2>/dev/null || true +``` + +**When to use**: After completing PR review testing + +--- + +## 7. Error Recovery Patterns + +### Build Failure Recovery + +**Used in**: `copilot-instructions.md`, `pr-reviewer.md` + +**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 + +# If still failing, check for: +# - Wrong .NET SDK version (check global.json) +# - Missing build tasks (build Microsoft.Maui.BuildTasks.slnf first) +# - Platform SDK issues (check Android SDK, Xcode, etc.) +``` + +**When to use**: When initial build fails + +--- + +## 8. References in Other Files + +When these patterns are used in instruction files, reference them like this: + +```markdown +**iOS Simulator Setup**: See [Common Testing Patterns: UDID Extraction (iOS)](common-testing-patterns.md#ios-simulator-udid-iphone-xs-highest-ios-version) + +**Device Boot**: See [Common Testing Patterns: Device Boot](common-testing-patterns.md#ios-simulator-boot-with-error-checking) +``` + +This keeps instructions DRY while maintaining readability. + +--- + +**Last Updated**: 2025-11-12 + +**Note**: These patterns include error checking at every critical step to ensure AI agents can detect and handle failures early. diff --git a/.github/instructions/instrumentation.instructions.md b/.github/instructions/instrumentation.instructions.md index 6be340524c69..00919a97dee2 100644 --- a/.github/instructions/instrumentation.instructions.md +++ b/.github/instructions/instrumentation.instructions.md @@ -6,6 +6,8 @@ description: "Guidelines for instrumenting .NET MAUI source code for debugging, This guide provides patterns and techniques for adding instrumentation to .NET MAUI source code to debug issues, validate PR 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). + **Guiding Principle: Use cross-platform MAUI APIs for all instrumentation.** ## When to Use Instrumentation @@ -287,15 +289,48 @@ private async void LogLayoutInfo(object? sender, EventArgs e) # Build dotnet build src/Controls/samples/Controls.Sample.Sandbox/Maui.Controls.Sample.Sandbox.csproj -f net10.0-ios -# Find simulator -UDID=$(xcrun simctl list devices available --json | jq -r '.devices["com.apple.CoreSimulator.SimRuntime.iOS-26-0"] | first | .udid') +# Check build succeeded +if [ $? -ne 0 ]; then + echo "❌ ERROR: Build failed" + exit 1 +fi + +# Find iPhone Xs with highest iOS version +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') + +# Check UDID was found +if [ -z "$UDID" ] || [ "$UDID" = "null" ]; then + echo "❌ ERROR: No iPhone Xs simulator found. Please create one." + exit 1 +fi # Boot and install -xcrun simctl boot $UDID +xcrun simctl boot $UDID 2>/dev/null || true + +# Check simulator is booted +STATE=$(xcrun simctl list devices --json | jq -r --arg udid "$UDID" '.devices[][] | select(.udid == $udid) | .state') +if [ "$STATE" != "Booted" ]; then + echo "❌ ERROR: Simulator failed to boot. Current state: $STATE" + exit 1 +fi + xcrun simctl install $UDID artifacts/bin/Maui.Controls.Sample.Sandbox/Debug/net10.0-ios/iossimulator-arm64/Maui.Controls.Sample.Sandbox.app +# Check install succeeded +if [ $? -ne 0 ]; then + echo "❌ ERROR: App installation failed" + exit 1 +fi + # Launch with console capture xcrun simctl launch --console-pty $UDID com.microsoft.maui.sandbox > /tmp/ios_output.log 2>&1 & + +# Check launch didn't immediately fail +if [ $? -ne 0 ]; then + echo "❌ ERROR: App launch failed" + exit 1 +fi + sleep 5 cat /tmp/ios_output.log ``` @@ -305,6 +340,12 @@ cat /tmp/ios_output.log # 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 +if [ $? -ne 0 ]; then + echo "❌ ERROR: Build or deployment failed" + exit 1 +fi + # Monitor logcat adb logcat | grep "LAYOUT INFO" ``` diff --git a/.github/instructions/uitests.instructions.md b/.github/instructions/uitests.instructions.md index 69b6d0a6a76f..f8e96ccde5a5 100644 --- a/.github/instructions/uitests.instructions.md +++ b/.github/instructions/uitests.instructions.md @@ -8,6 +8,8 @@ applyTo: "src/Controls/tests/TestCases.Shared.Tests/**,src/Controls/tests/TestCa This document provides specific guidance for GitHub Copilot when writing UI tests for the .NET MAUI repository. +**Common Command Patterns**: For UDID extraction, device boot, builds, and error checking patterns, see [Common Testing Patterns](common-testing-patterns.md). + **Critical Principle**: UI tests should run on all applicable platforms (iOS, Android, Windows, MacCatalyst) by default unless there is a specific technical limitation. ## UI Test Structure @@ -139,17 +141,25 @@ App.Screenshot("TestStep1"); - **Only ONE** `[Category]` attribute per test - Pick the most specific category that applies -### Common Categories -See [UITestCategories.cs](../../src/Controls/tests/TestCases.Shared.Tests/UITestCategories.cs) for the complete list. +### Test Categories + +**CRITICAL**: Always check [UITestCategories.cs](../../src/Controls/tests/TestCases.Shared.Tests/UITestCategories.cs) for the authoritative, complete list of categories. + +**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. -Examples: -- `SafeAreaEdges` - Safe area and padding tests -- `Button`, `Label`, `Entry`, `Editor` - Specific control tests -- `CollectionView`, `ListView`, `CarouselView` - Collection control tests -- `Layout` - Layout-related tests -- `Shell`, `Navigation`, `TabbedPage` - Navigation tests -- `Gestures`, `Focus`, `Accessibility` - Interaction tests -- `Window`, `Page`, `LifeCycle` - Page lifecycle tests +**Common categories** (examples only - not exhaustive): +- **SafeArea**: `SafeAreaEdges` - Safe area and padding tests +- **Basic controls**: `Button`, `Label`, `Entry`, `Editor` - Specific control tests +- **Collection controls**: `CollectionView`, `ListView`, `CarouselView` - Collection control tests +- **Layout**: `Layout` - Layout-related tests +- **Navigation**: `Shell`, `Navigation`, `TabbedPage` - Navigation tests +- **Interaction**: `Gestures`, `Focus`, `Accessibility` - Interaction tests +- **Lifecycle**: `Window`, `Page`, `LifeCycle` - Page lifecycle tests + +**List all categories programmatically**: +```bash +grep -E "public const string [A-Za-z]+ = " src/Controls/tests/TestCases.Shared.Tests/UITestCategories.cs +``` **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. @@ -200,17 +210,44 @@ public void SoftInputBehaviorTest() ## Running UI Tests Locally +### Prerequisites: Kill Existing Appium Processes + +**CRITICAL**: Before running UITests, 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: + +``` +AppiumServerHasNotBeenStartedLocallyException: The local appium server has not been started. +Time 120000 ms for the service starting has been expired! +``` + +**Solution: Always kill existing Appium processes before running tests:** + +```bash +# Kill any Appium processes on port 4723 +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" +``` + +**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. + ### Quick Test Execution (for rapid development) When developing and debugging a specific test: **Android:** + +**IMPORTANT**: Like iOS, Android tests also require the `DEVICE_UDID` environment variable to be set to target the correct device/emulator. + 1. Deploy the TestCases.HostApp: ```bash # Use local dotnet if available, otherwise use global dotnet ./bin/dotnet/dotnet build src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj -f net10.0-android -t:Run # OR: dotnet build src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj -f net10.0-android -t:Run + + # Check build/deploy succeeded + if [ $? -ne 0 ]; then + echo "❌ ERROR: Build or deployment failed" + exit 1 + fi ``` 2. Run your specific test: @@ -218,7 +255,16 @@ When developing and debugging a specific test: # Set DEVICE_UDID environment variable so Appium tests know which device to use # Get the device ID from: adb devices export DEVICE_UDID=$(adb devices | grep -v "List" | grep "device" | awk '{print $1}' | head -1) - + + # Check device was found + if [ -z "$DEVICE_UDID" ]; then + echo "❌ ERROR: No Android device/emulator found. Start an emulator or connect a device." + exit 1 + fi + + # Verify device is set + echo "Using Android device: $DEVICE_UDID" + # Run the test dotnet test src/Controls/tests/TestCases.Android.Tests/Controls.TestCases.Android.Tests.csproj --filter "FullyQualifiedName~Issue12345" ``` @@ -348,6 +394,15 @@ export IOS_VERSION="18.0" ./bin/dotnet/dotnet build src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj -f net10.0-ios # OR: dotnet build src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj -f net10.0-ios + +# Check build succeeded +if [ $? -ne 0 ]; then + echo "❌ ERROR: Build failed" + exit 1 +fi + +# If the app crashes on launch, rebuild with --no-incremental: +# dotnet build src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj -f net10.0-ios --no-incremental ``` **Step 3: Boot simulator and install app** @@ -358,13 +413,19 @@ xcrun simctl boot $UDID 2>/dev/null || true # Verify simulator is booted STATE=$(xcrun simctl list devices --json | jq -r --arg udid "$UDID" '.devices[][] | select(.udid == $udid) | .state') if [ "$STATE" != "Booted" ]; then - echo "ERROR: Simulator failed to boot. Current state: $STATE" + echo "❌ ERROR: Simulator failed to boot. Current state: $STATE" exit 1 fi echo "Simulator is booted and ready" # Install the app to the simulator xcrun simctl install $UDID artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-ios/iossimulator-arm64/Controls.TestCases.HostApp.app + +# Check install succeeded +if [ $? -ne 0 ]; then + echo "❌ ERROR: App installation failed" + exit 1 +fi ``` **Step 4: Run your specific test** @@ -376,6 +437,20 @@ export DEVICE_UDID=$UDID dotnet test src/Controls/tests/TestCases.iOS.Tests/Controls.TestCases.iOS.Tests.csproj --filter "FullyQualifiedName~Issue12345" ``` +**Important Note on Device Targeting:** + +The UI test infrastructure automatically reads the `DEVICE_UDID` environment variable to target the correct iOS simulator. You must set this before running tests: + +```bash +# Set the device UDID (from Step 1) +export DEVICE_UDID=$UDID + +# Run the test +dotnet test src/Controls/tests/TestCases.iOS.Tests/Controls.TestCases.iOS.Tests.csproj --filter "FullyQualifiedName~Issue12345" +``` + +Without `DEVICE_UDID` set, Appium will randomly select a device, which may not have your app installed. + **MacCatalyst:** **Step 1: Deploy TestCases.HostApp to MacCatalyst** @@ -410,6 +485,25 @@ dotnet build src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csp 2. Try clean build: `dotnet clean src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj` 3. Check emulator: `adb devices` +**iOS App Crashes on Launch or Won't Start with Appium:** + +If the iOS app crashes when launched by Appium or manually with `xcrun simctl launch`, the issue is often related to incremental builds: + +**Solution:** Clean and rebuild with `--no-incremental`: +```bash +# Clean first +dotnet clean src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj + +# Rebuild with no-incremental +dotnet build src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj -f net10.0-ios --no-incremental + +# Reinstall to simulator +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 install $UDID artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-ios/iossimulator-arm64/Controls.TestCases.HostApp.app +``` + +**Why this happens:** Incremental builds can sometimes leave the app bundle in an inconsistent state, especially after making changes to XAML files or project structure. The `--no-incremental` flag forces a complete rebuild. + ## Before Committing Verify the following checklist before committing UI tests: diff --git a/.gitignore b/.gitignore index 5a048174d6c3..5fe19d20f014 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ templatesTest/ *.userosscache *.sln.docstates /src/Controls/tests/TestCases.HostApp/MauiProgram.user.cs +SandboxAppium/ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 65832185af32..000000000000 --- a/AGENTS.md +++ /dev/null @@ -1,521 +0,0 @@ -# AGENTS.md - -This file provides guidance to AI coding assistants when working with code in this repository. - -**🔄 IMPORTANT: Synchronization with Copilot Instructions** - -When updating this file, you MUST: -1. Analyze ALL copilot instruction files: - - `.github/copilot-instructions.md` (repository-wide instructions) - - `.github/instructions/*.instructions.md` (path-specific instructions) -2. Determine which information is useful for AI coding assistants and should be added to AGENTS.md -3. Update `.github/copilot-instructions.md` with corresponding changes to keep both files synchronized -4. Ensure no duplication - AGENTS.md should contain universal guidance, while copilot-instructions.md may have GitHub Copilot-specific details - -## Project Overview - -.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. - -### Key Technologies - -- **.NET SDK** - Version always defined in `global.json` at repository root -- **C#** and **XAML** for application development -- **Cake build system** for compilation and packaging -- **MSBuild** with custom build tasks -- **xUnit + NUnit** for testing (xUnit for unit tests, NUnit for UI tests) -- **Appium WebDriver** for UI test automation - -## Essential Setup Commands - -### Initial Repository Setup - -**🚨 CRITICAL**: Before any build operation, verify .NET SDK version: -```bash -# Check required version (always defined in global.json) -cat global.json | grep -A 1 '"dotnet"' - -# Verify installed version matches -dotnet --version -``` - -**Before opening the solution in any IDE**, you MUST build the build tasks first: - -```bash -dotnet tool restore -dotnet build ./Microsoft.Maui.BuildTasks.slnf -``` - -**⚠️ FAILURE RECOVERY**: If restore or build fails: -```bash -# Clean and retry -dotnet clean -rm -rf bin/ obj/ -dotnet tool restore --force -dotnet build ./Microsoft.Maui.BuildTasks.slnf --verbosity normal -``` - -### Primary Build Commands - -```bash -# Build everything using Cake (recommended) -dotnet cake - -# Pack NuGet packages -dotnet cake --target=dotnet-pack - -# Clean incremental builds when switching branches -dotnet cake --clean - -# Build for specific platforms -dotnet cake --target=VS --android --ios -``` - -### Code Formatting - -Before committing any changes: - -```bash -dotnet format Microsoft.Maui.slnx --no-restore --exclude Templates/src --exclude-diagnostics CA1822 -``` - -### PublicAPI Management - -When adding new public APIs: - -```bash -# For a specific project -dotnet build ./src/Controls/src/Core/Controls.Core.csproj /p:PublicApiType=Generate - -# Or use Cake to regenerate all PublicAPI files -dotnet cake --target=publicapi -``` - -## Project Architecture - -### Handler-Based Architecture - -MAUI uses a Handler-based architecture that replaces Xamarin.Forms renderers. Key concepts: - -- **Handlers**: Map cross-platform controls to platform-specific implementations -- **Mappers**: Define property and command mappings between virtual and platform views -- **Handler files**: Located in `src/Core/src/Handlers/[ControlName]/` - - Interface: `I[ControlName]Handler.cs` - - Platform-specific: `[ControlName]Handler.Android.cs`, `[ControlName]Handler.iOS.cs`, etc. - - Shared logic: `[ControlName]Handler.cs` - -### Platform-Specific Code Organization - -**File Extensions** (automatic platform targeting): -- `.android.cs` → Android only -- `.ios.cs` → iOS and MacCatalyst -- `.windows.cs` → Windows only -- `.MacCatalyst.cs` → MacCatalyst only - -**Folder Organization**: -- Platform-specific code in `Android/`, `iOS/`, `MacCatalyst/`, `Windows/` folders -- Shared code at the folder root or in `Shared/` - -### Major Components - -- **`src/Core/`** - Core framework, handlers, platform abstractions -- **`src/Controls/`** - UI controls, XAML infrastructure - - `src/Core/` - Core controls (Button, Label, etc.) - - `src/Xaml/` - XAML loader and compilation - - `src/Build.Tasks/` - MSBuild tasks for XAML compilation -- **`src/Essentials/`** - Platform APIs (GPS, accelerometer, etc.) -- **`src/Graphics/`** - Cross-platform graphics abstractions -- **`src/Compatibility/`** - Xamarin.Forms compatibility layer - -### Solution Files - -- `Microsoft.Maui-windows.slnf` - Windows development -- `Microsoft.Maui-mac.slnf` - Mac development -- `Microsoft.Maui.BuildTasks.slnf` - Build tasks only (must build first) - -### Sample Projects for Testing - -- `src/Controls/samples/Controls.Sample.Sandbox` - **Empty project for testing/reproduction** (preferred for debugging) -- `src/Controls/samples/Controls.Sample` - Full gallery sample -- `src/Essentials/samples/Essentials.Sample` - Essentials API demonstrations - -**Important**: Do not commit changes to the Sandbox project in PRs. - -## Testing - -### Unit Tests - -Key test projects to ensure pass: -- `src/Core/tests/UnitTests/Core.UnitTests.csproj` -- `src/Essentials/test/UnitTests/Essentials.UnitTests.csproj` -- `src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj` -- `src/Controls/tests/Xaml.UnitTests/Controls.Xaml.UnitTests.csproj` - -### Running UI Tests - -**Option 1: Using Cake build system (for full CI-like test runs):** - -```bash -# Android -./build.ps1 -Script eng/devices/android.cake --target=uitest - -# iOS -./build.ps1 -Script eng/devices/ios.cake --target=uitest - -# Windows -./build.ps1 -Script eng/devices/windows.cake --target=uitest - -# MacCatalyst -./build.ps1 -Script eng/devices/catalyst.cake --target=uitest - -# Filter by category -dotnet cake eng/devices/android.cake --target=uitest --test-filter="TestCategory=Button" - -# Filter by test name -dotnet cake eng/devices/android.cake --target=uitest --test-filter="FullyQualifiedName~Issue12345" -``` - -**Option 2: Running specific tests directly (for rapid development):** - -**Android:** - -1. Deploy the TestCases.HostApp: - ```bash - # Use local dotnet if available, otherwise use global dotnet - ./bin/dotnet/dotnet build src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj -f net10.0-android -t:Run - # OR: - dotnet build src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj -f net10.0-android -t:Run - ``` - -2. Run your specific test: - ```bash - # Set DEVICE_UDID environment variable so Appium tests know which device to use - # Get the device ID from: adb devices - export DEVICE_UDID=$(adb devices | grep -v "List" | grep "device" | awk '{print $1}' | head -1) - - # Run the test - dotnet test src/Controls/tests/TestCases.Android.Tests/Controls.TestCases.Android.Tests.csproj --filter "FullyQualifiedName~Issue12345" - ``` - -**iOS (4-step process):** - -**Important: Device and iOS Version Selection** - -When the user requests to run tests on iOS: -- **Default behavior (no device, no iOS version specified)**: Use iPhone Xs with the highest available iOS version -- **User specifies iOS version only** (e.g., "iOS 26.0", "iOS 18.4"): - - First, try to find iPhone Xs with that iOS version - - If iPhone Xs not available for that iOS version, use ANY available device with that iOS version - - Set `IOS_VERSION` variable, leave `DEVICE_NAME` empty to allow fallback -- **User specifies device only** (e.g., "iPhone 16 Pro", "iPhone 15"): - - Set `DEVICE_NAME` variable with the specified device - - Use highest available iOS version for that device -- **User specifies both device AND iOS version**: Set both `IOS_VERSION` and `DEVICE_NAME` variables - -Examples of interpreting user requests: -- "Run on iOS 26.0" → Set `IOS_VERSION="26.0"`, leave `DEVICE_NAME` empty (will try iPhone Xs first, then fallback to any iOS 26.0 device) -- "Run on iPhone 16 Pro" → Set `DEVICE_NAME="iPhone 16 Pro"`, use highest iOS version -- "Run on iPhone 15 with iOS 18.0" → Set both `DEVICE_NAME="iPhone 15"` and `IOS_VERSION="18.0"` -- No specific request → Use defaults (iPhone Xs with highest iOS) - -**Step 1: Find iOS Simulator** - -```bash -# Set device name and iOS version based on user request -# Leave DEVICE_NAME empty when user only specifies iOS version (to allow fallback) -DEVICE_NAME="${DEVICE_NAME:-}" -IOS_VERSION="${IOS_VERSION:-}" - -# Determine search strategy -if [ -z "$IOS_VERSION" ]; then - # No iOS version specified - use iPhone Xs with highest available iOS - DEVICE_NAME="${DEVICE_NAME:-iPhone Xs}" - JQ_FILTER=' - .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 == "'"$DEVICE_NAME"'"))) - | first - | .devices[] - | select(.name == "'"$DEVICE_NAME"'") - | .udid' -else - # Specific iOS version requested - IOS_VERSION_FILTER="iOS-${IOS_VERSION//./-}" - - if [ -z "$DEVICE_NAME" ]; then - # iOS version specified, but no device - try iPhone Xs first, then fallback to any device - JQ_FILTER=' - .devices - | to_entries - | map(select(.key | contains("'"$IOS_VERSION_FILTER"'"))) - | first - | .value - | (map(select(.name == "iPhone Xs")) + .)[0] - | .udid' - else - # Both iOS version and device specified - JQ_FILTER=' - .devices - | to_entries - | map(select(.key | contains("'"$IOS_VERSION_FILTER"'"))) - | map(.value) - | flatten - | map(select(.name == "'"$DEVICE_NAME"'")) - | first - | .udid' - fi -fi - -# Extract UDID using the constructed filter -UDID=$(xcrun simctl list devices available --json | jq -r "$JQ_FILTER") - -# Get the actual device name that was found -if [ ! -z "$UDID" ] && [ "$UDID" != "null" ]; then - FOUND_DEVICE=$(xcrun simctl list devices available --json | jq -r --arg udid "$UDID" '.devices[][] | select(.udid == $udid) | .name') -fi - -# Verify UDID was found and is not empty -if [ -z "$UDID" ] || [ "$UDID" = "null" ]; then - if [ -z "$IOS_VERSION" ]; then - DEVICE_NAME="${DEVICE_NAME:-iPhone Xs}" - echo "ERROR: No $DEVICE_NAME simulator found. Please create a $DEVICE_NAME simulator before running iOS tests." - elif [ -z "$DEVICE_NAME" ]; then - echo "ERROR: No simulator found for iOS $IOS_VERSION. Please install iOS $IOS_VERSION runtime." - else - echo "ERROR: No $DEVICE_NAME simulator found for iOS $IOS_VERSION. Please create one before running iOS tests." - fi - exit 1 -fi - -echo "Using $FOUND_DEVICE with UDID: $UDID" -``` - -**Examples of device/version selection:** -```bash -# Default: iPhone Xs with highest iOS version -# (no environment variables needed) - -# Specific iOS version (will try iPhone Xs first, then any device): -export IOS_VERSION="26.0" -# Leave DEVICE_NAME unset - -# Specific device with highest iOS version: -export DEVICE_NAME="iPhone 16 Pro" -# Leave IOS_VERSION unset - -# Specific device AND specific iOS version: -export DEVICE_NAME="iPhone 15" -export IOS_VERSION="18.0" -``` - -**Step 2: Build the iOS app** -```bash -# Use local dotnet if available, otherwise use global dotnet -./bin/dotnet/dotnet build src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj -f net10.0-ios -# OR: -dotnet build src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj -f net10.0-ios -``` - -**Step 3: Boot simulator and install app** -```bash -# Boot the simulator (will error if already booted, which is fine) -xcrun simctl boot $UDID 2>/dev/null || true - -# Verify simulator is booted -STATE=$(xcrun simctl list devices --json | jq -r --arg udid "$UDID" '.devices[][] | select(.udid == $udid) | .state') -if [ "$STATE" != "Booted" ]; then - echo "ERROR: Simulator failed to boot. Current state: $STATE" - exit 1 -fi -echo "Simulator is booted and ready" - -# Install the app to the simulator -xcrun simctl install $UDID artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-ios/iossimulator-arm64/Controls.TestCases.HostApp.app -``` - -**Step 4: Run your specific test** -```bash -# Set DEVICE_UDID environment variable so Appium tests know which device to use -export DEVICE_UDID=$UDID - -# Run the test -dotnet test src/Controls/tests/TestCases.iOS.Tests/Controls.TestCases.iOS.Tests.csproj --filter "FullyQualifiedName~Issue12345" -``` - -**MacCatalyst:** - -**Step 1: Deploy TestCases.HostApp to MacCatalyst** -```bash -# Use local dotnet if available, otherwise use global dotnet -./bin/dotnet/dotnet build src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj -f net10.0-maccatalyst -t:Run -# OR: -dotnet build src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj -f net10.0-maccatalyst -t:Run -``` - -**Step 2: Run your specific test** -```bash -dotnet test src/Controls/tests/TestCases.Mac.Tests/Controls.TestCases.Mac.Tests.csproj --filter "FullyQualifiedName~Issue12345" -``` - -**Troubleshooting UI Tests:** - -**Android App Crashes on Launch:** - -If you encounter navigation fragment errors or resource ID issues: -``` -java.lang.IllegalArgumentException: No view found for id 0x7f0800f8 (com.microsoft.maui.uitests:id/inward) for fragment NavigationRootManager_ElementBasedFragment -``` - -**Solution:** Build with `--no-incremental` to force a clean build: -```bash -dotnet build src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj -f net10.0-android -t:Run --no-incremental -``` - -**Other debugging steps:** -1. Monitor logcat: `adb logcat | grep -E "(FATAL|AndroidRuntime|Exception|Error|Crash)"` -2. Try clean build: `dotnet clean src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj` -3. Check emulator: `adb devices` - -### UI Tests - -**CRITICAL**: UI tests require code in TWO separate projects: - -1. **HostApp UI Test Page** (`src/Controls/tests/TestCases.HostApp/Issues/`) - - Create XAML page with `AutomationId` attributes - - Naming: `IssueXXXXX.xaml` and `IssueXXXXX.xaml.cs` - -2. **NUnit Test Implementation** (`src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/`) - - Inherit from `_IssuesUITest` - - Naming: `IssueXXXXX.cs` (matches HostApp file) - - Include ONE `[Category(UITestCategories.XYZ)]` attribute - -**Platform Coverage**: Avoid platform-specific directives (`#if ANDROID`, `#if IOS`, etc.) unless absolutely necessary. Tests should run on all applicable platforms (iOS, Android, Windows, MacCatalyst) by default. - -**Important**: When adding a new UI test category to `UITestCategories.cs`, also update `eng/pipelines/common/ui-tests.yml`. - -**For comprehensive UI testing documentation**, see [docs/UITesting-Guide.md](docs/UITesting-Guide.md). - -## Branching Strategy - -- **`main`** - Bug fixes without API changes (pinned to latest stable .NET SDK) -- **`net10.0`** - New features and API changes (next version development) - -## Critical File Management Rules - -### Files to NEVER Commit - -- `cgmanifest.json` - Auto-generated during CI builds -- `templatestrings.json` - Auto-generated localization files - -### File Reset Guidelines for AI Agents - -Since coding agents function as both CI and pair programmers, they need to handle CI-generated files appropriately: - -- **Always reset changes to `cgmanifest.json` files** - These are generated during CI builds and should not be committed by coding agents -- **Always reset changes to `templatestrings.json` files** - These localization files are auto-generated and should not be committed by coding agents - -### PublicAPI.Unshipped.txt Management - -- Never disable analyzers or set nowarn to bypass PublicAPI errors -- Use `dotnet format analyzers` if having trouble -- **Revert and re-add approach**: Revert all changes to PublicAPI.Unshipped.txt files, then make only necessary additions - -## PR Requirements - -All PRs must include this note at the top of the description: - -```markdown -> [!NOTE] -> Are you waiting for the changes in this PR to be merged? -> 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! -``` - -## Handling Existing PRs - -**CRITICAL REQUIREMENT**: Always develop your own solution first, then compare with existing PRs. - -1. Develop your own solution independently -2. Search for existing PRs addressing the same issue -3. Compare solutions thoroughly -4. Document your decision in PR description -5. Explain why you chose your approach over alternatives - -## Code Style - -Follow [.NET Foundation coding style](https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/coding-style.md) with exceptions: -- Do not use the `private` keyword (default accessibility) -- Use hard tabs over spaces - -## Platform-Specific Development - -### Android -- Requires Android SDK and OpenJDK 17 -- Set `ANDROID_HOME` environment variable -- Install Android SDK components via Android SDK Manager - -### iOS (requires macOS) -- Requires current stable Xcode installation -- Xcode command-line tools must be installed -- Pair to Mac required when developing on Windows - -### Windows -- Requires Windows SDK -- Visual Studio workloads must include .NET MAUI development - -### macOS/Mac Catalyst -- Requires Xcode installation - -## Troubleshooting - -### Common Build Issues - -**Issue: "Build tasks not found"** -```bash -# Solution: Rebuild build tasks -dotnet clean ./Microsoft.Maui.BuildTasks.slnf -dotnet build ./Microsoft.Maui.BuildTasks.slnf -``` - -**Issue: "Dependency version conflicts"** -```bash -# Solution: Full clean and restore -dotnet clean Microsoft.Maui.slnx -rm -rf bin/ obj/ -dotnet restore Microsoft.Maui.slnx --force -``` - -**Issue: "PublicAPI analyzer failures"** -```bash -# Solution: Use format analyzers first -dotnet format analyzers Microsoft.Maui.slnx -# If still failing, check build output for required API entries -``` - -### Platform-Specific Troubleshooting - -**iOS/Mac Build Issues:** -- Verify Xcode command line tools: `xcode-select --install` -- Check Xcode version compatibility with .NET MAUI version -- Ensure provisioning profiles are current (for device testing) - -**Android Build Issues:** -- Verify OpenJDK 17 installation: `java -version` -- Check ANDROID_HOME environment variable -- Update Android SDK components via Android SDK Manager - -**Windows Build Issues:** -- Ensure Windows SDK is installed -- Verify Visual Studio workloads include .NET MAUI development -- Check for missing NuGet packages: `dotnet restore --force` - -## Additional Resources - -- [UI Testing Guide](docs/UITesting-Guide.md) -- [UI Testing Architecture](docs/design/UITesting-Architecture.md) diff --git a/docs/PR-Test-Validation-Guide.md b/docs/PR-Test-Validation-Guide.md index e0bd1cf9c6dc..2b26b0d245e0 100644 --- a/docs/PR-Test-Validation-Guide.md +++ b/docs/PR-Test-Validation-Guide.md @@ -71,9 +71,8 @@ dotnet test src/Controls/tests/TestCases.Android.Tests/Controls.TestCases.Androi **iOS:** ```bash -# Find iPhone simulator -UDID=$(xcrun simctl list devices available | grep "iPhone Xs" | tail -1 | \ - sed -n 's/.*(\([A-F0-9-]*\)).*/\1/p') +# Find iPhone Xs with highest iOS version +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') # Build app dotnet build src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj \ diff --git a/docs/UITesting-Guide.md b/docs/UITesting-Guide.md index 3d2ed50c2e51..90f1ac7eb419 100644 --- a/docs/UITesting-Guide.md +++ b/docs/UITesting-Guide.md @@ -325,11 +325,8 @@ For rapid development and debugging, you can run specific tests directly: 1. **Find iPhone Xs with highest API level:** ```bash - # View all iPhone Xs devices with their iOS versions - xcrun simctl list devices available | awk '/^--.*iOS/ {version=$0} /iPhone Xs/ {print version " -> " $0}' - - # Extract UDID of iPhone Xs with highest iOS version (last in list) - UDID=$(xcrun simctl list devices available | grep "iPhone Xs" | tail -1 | sed -n 's/.*(\([A-F0-9-]*\)).*/\1/p') + # Extract UDID of iPhone Xs with highest iOS version + 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') # Verify UDID was found if [ -z "$UDID" ]; then @@ -620,6 +617,7 @@ If migrating from Xamarin.UITest: ## Additional Resources - [UITesting-Architecture.md](design/UITesting-Architecture.md) - CI/CD integration, advanced patterns, and architecture decisions +- [Appium Control Scripts](../.github/instructions/appium-control.instructions.md) - Create standalone scripts for manual Appium-based debugging and exploration - [Appium Documentation](http://appium.io/docs/en/about-appium/intro/) - [NUnit Documentation](https://docs.nunit.org/) - [.NET MAUI Testing Wiki](https://github.com/dotnet/maui/wiki/UITests) diff --git a/src/Controls/samples/Controls.Sample.Sandbox/Platforms/Android/MainActivity.cs b/src/Controls/samples/Controls.Sample.Sandbox/Platforms/Android/MainActivity.cs index be015c14658d..93c53ee683b1 100644 --- a/src/Controls/samples/Controls.Sample.Sandbox/Platforms/Android/MainActivity.cs +++ b/src/Controls/samples/Controls.Sample.Sandbox/Platforms/Android/MainActivity.cs @@ -1,5 +1,6 @@ using Android.App; using Android.Content.PM; +using Android.Runtime; using Microsoft.Maui; namespace Maui.Controls.Sample.Platform @@ -11,6 +12,7 @@ namespace Maui.Controls.Sample.Platform [IntentFilter( new[] { Microsoft.Maui.ApplicationModel.Platform.Intent.ActionAppAction }, Categories = new[] { Android.Content.Intent.CategoryDefault })] + [Register("com.microsoft.maui.sandbox.MainActivity")] public class MainActivity : MauiAppCompatActivity { }