Official agent skill

MAUI UI Test Writer

by dotnet in dotnet/maui

Writes UI tests that reproduce a GitHub issue in .NET MAUI and keeps iterating until the tests actually fail, proving they catch the bug.

OfficialMITAuto-check passedTesting & QA

Install MAUI UI Test Writer

skills CLI
$ npx skills add dotnet/maui --skill write-ui-tests -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install dotnet/maui write-ui-tests --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/dotnet/maui.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.github/skills/write-ui-tests .claude/skills/write-ui-tests && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
write-ui-tests
GitHub stars
23k
Token cost
~3k tokens
SKILL.md length
826 words
Files
1
Skills in repo
27
Repo updated
First seen
Licence
MIT

At a glance

Writes UI tests that reproduce a GitHub issue in .NET MAUI and keeps iterating until the tests actually fail, proving they catch the bug.

  • Works in 5 steps: Read the UI Test Guidelines → Create HostApp Page → Create NUnit Test → …
  • A pull request has no tests and needs a UI test for the bug it fixes
  • SKILL.md covers 🛑 BLOCKING REQUIREMENT, When to Use, Required Input and Workflow, plus 5 more sections
  • Calls dotnet, pwsh and xcrun

What it does

Starting from an issue number, a description or repro steps and the affected platforms, the agent reads the repository's UI test guidelines, creates a HostApp page for the issue and writes the matching test following .NET MAUI conventions, such as file names built from the issue number and the Issue and Category attributes. The excerpt shows these conventions and file locations but is cut off before the remaining test steps.

The defining rule is that the skill cannot finish until the tests fail. A passing test does not show that it catches the bug, so the agent iterates on the test code, tries other platforms and asks you before going further if tests still pass after three iterations. For platform choice it starts with the one named in the issue, or Android when all platforms are affected because the emulator boots faster. Appium is needed to execute UI tests.

When your agent uses it

  • A pull request has no tests and needs a UI test for the bug it fixes
  • An issue needs a reproduction test before anyone starts on a fix
  • Existing tests do not adequately cover a reported bug

Example prompts

  • “Write a UI test that reproduces issue 33331 on Android and confirm it fails.”
  • “This PR has no tests. Create a UI test for the bug and iterate until it fails.”
  • “The new test passes on iOS even though the issue is still open. Try another platform.”

Requirements

  • git, PowerShell and the .NET SDK
  • Appium for running UI tests
  • Compatibility (from SKILL.md): Requires git, PowerShell, .NET SDK, and Appium for UI test execution.

Workflow steps

5 steps, taken from the step headings in SKILL.md.

  1. Read the UI Test Guidelines
  2. Create HostApp Page
  3. Create NUnit Test
  4. Verify Files Compile
  5. Verify Tests Reproduce the Bug ⚠️ CRITICAL

What it can do on your machine

Read from SKILL.md and the folder at commit 7d38fd0. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • dotnet
    • pwsh
    • xcrun
    • jq

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

  • Compatibility

    Requires git, PowerShell, .NET SDK, and Appium for UI test execution.

    From compatibility in the SKILL.md frontmatter.

Context cost

MAUI UI Test Writer loads about 3k tokens when it runs. Until then it costs about 55 tokens; SKILL.md has 826 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~55
When it runs · the whole SKILL.md, loaded when a task matches
~3k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from dotnet/maui at commit 7d38fd0, republished under its MIT licence (© dotnet). 826 words, ~2,965 tokens.

Download SKILL.mdSave it as .claude/skills/write-ui-tests/SKILL.md (or your agent's skills folder).
name
write-ui-tests
description
Creates UI tests for a GitHub issue and verifies they reproduce the bug. Iterates until tests actually fail (proving they catch the issue). Use when PR lacks tests or tests need to be created for an issue.
compatibility
Requires git, PowerShell, .NET SDK, and Appium for UI test execution.
metadata.author
dotnet-maui
metadata.version
1.1

Write UI Tests Skill

Creates UI tests that reproduce a GitHub issue, following .NET MAUI conventions. Verifies the tests actually fail before completing.

🛑 BLOCKING REQUIREMENT

YOU CANNOT COMPLETE THIS SKILL UNTIL TESTS FAIL.

A test that passes does NOT prove it catches the bug. You MUST:

  1. Run tests and observe them FAIL
  2. If tests pass, iterate on test code until they fail
  3. Never report "done" with passing tests

If tests keep passing after 3 iterations:

  • STOP and ask user: "Tests are passing but they should fail to prove they catch the bug. The test scenario may not correctly reproduce the issue. Should I try a different approach?"

Common mistakes that lead to passing tests:

  • Test scenario doesn't match issue reproduction steps
  • Checking wrong element or property
  • Bug only manifests on specific platform (try different platform)
  • Bug requires specific timing or async behavior not captured
  • Issue description is incomplete - may need to ask user for clarification

When to Use

  • ✅ PR has no tests and needs them
  • ✅ Issue needs a reproduction test before fixing
  • ✅ Existing tests don't adequately cover the bug

Required Input

Before invoking, ensure you have:

  • Issue number (e.g., 33331)
  • Issue description or reproduction steps
  • Platforms affected (iOS, Android, Windows, MacCatalyst)

Platform selection guidance:

  • Start with the platform mentioned in the issue (often in title or labels)
  • If issue says "iOS" or has platform/iOS label → test on iOS first
  • If issue says "Android" or has platform/Android label → test on Android first
  • If issue affects "All" platforms → start with Android (faster emulator boot)
  • If test passes on one platform, try another before concluding test is wrong

Workflow

Step 1: Read the UI Test Guidelines
bash
cat .github/instructions/uitests.instructions.md

This contains the authoritative conventions for:

  • File naming (IssueXXXXX.cs for C#-only, or IssueXXXXX.xaml/.xaml.cs for XAML)
  • File locations (TestCases.HostApp/Issues/, TestCases.Shared.Tests/Tests/Issues/)
  • Required attributes ([Issue()], [Category()])
  • Test patterns and assertions
Step 2: Create HostApp Page

Location: src/Controls/tests/TestCases.HostApp/Issues/IssueXXXXX.cs

csharp
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, XXXXX, "Brief description of issue", PlatformAffected.All)]
public partial class IssueXXXXX : ContentPage
{
    public IssueXXXXX()
    {
        // Create UI that reproduces the issue
        var button = new Button 
        { 
            Text = "Test Button",
            AutomationId = "TestButton"  // Required for Appium
        };
        
        var resultLabel = new Label
        {
            Text = "Waiting...",
            AutomationId = "ResultLabel"
        };
        
        button.Clicked += (s, e) => 
        {
            resultLabel.Text = "Success";
        };
        
        Content = new VerticalStackLayout
        {
            Children = { button, resultLabel }
        };
    }
}

Key requirements:

  • Add AutomationId to all interactive elements
  • Use [Issue()] attribute with tracker, number, description, platform
  • Keep UI minimal - just enough to reproduce the bug

Note: XAML is optional. C#-only pages (as shown above) are simpler and preferred for most test scenarios. Use XAML only when the bug specifically relates to XAML parsing or markup behavior.

Step 3: Create NUnit Test

Location: src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/IssueXXXXX.cs

csharp
namespace Microsoft.Maui.TestCases.Tests.Issues;

public class IssueXXXXX : _IssuesUITest
{
    public override string Issue => "Brief description matching HostApp";

    public IssueXXXXX(TestDevice device) : base(device) { }

    [Test]
    [Category(UITestCategories.Button)]  // Pick ONE appropriate category
    public void ButtonClickUpdatesLabel()
    {
        // Wait for element to be ready
        App.WaitForElement("TestButton");

        // Interact with the UI
        App.Tap("TestButton");

        // Verify expected behavior
        var labelText = App.FindElement("ResultLabel").GetText();
        Assert.That(labelText, Is.EqualTo("Success"));
    }
}

Key requirements:

  • Inherit from _IssuesUITest
  • Use same AutomationId values as HostApp
  • Add ONE [Category()] attribute (check UITestCategories.cs for options)
  • Use App.WaitForElement() before interactions
Step 4: Verify Files Compile
bash
# For Android
dotnet build src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj -c Debug -f net10.0-android --no-restore -v q

# For iOS
dotnet build src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj -c Debug -f net10.0-ios --no-restore -v q

# Test project (platform-independent)
dotnet build src/Controls/tests/TestCases.Shared.Tests/Controls.TestCases.Shared.Tests.csproj -c Debug --no-restore -v q
Show full SKILL.md (425 more words)Show less
Step 5: Verify Tests Reproduce the Bug ⚠️ CRITICAL

Tests must FAIL to prove they catch the bug. Run verification:

bash
pwsh .github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1 -Platform <platform> -TestFilter "IssueXXXXX"

Replace <platform> with android, ios, or maccatalyst based on the issue's affected platforms.

The script auto-detects that only test files exist (no fix files) and runs in "verify failure only" mode.

Why FAIL = success? The test must fail NOW (before the fix) to prove it catches the bug. After the fix is applied, it should pass. A test that passes now proves nothing.

If tests FAIL → ✅ Success! Tests correctly reproduce the bug. Proceed to Output.

If tests PASS → ❌ STOP. Test doesn't catch the bug. Iterate:

  1. Re-read the issue reproduction steps - Is your test doing exactly what the issue describes?
  2. Check if you're testing the right thing - Are you asserting on the correct element/property?
  3. Try a different platform - Bug may only manifest on iOS vs Android
  4. Add debug output - Use Console.WriteLine in HostApp to trace execution
  5. Simplify - Remove complexity until you isolate the bug behavior
  6. After 3 failed iterations, STOP and ask user:

    "Tests are passing after 3 iterations. This means either: (a) my test scenario doesn't correctly reproduce the bug, (b) the bug may already be fixed on this branch, or (c) I'm missing something from the issue description. How would you like me to proceed?"

Common reasons tests pass when they shouldn't:

SymptomLikely CauseFix
Test passes on all attemptsTest scenario doesn't match bugRe-read issue reproduction steps carefully
Test asserts pass but bug existsAsserting wrong property/elementCheck what exactly the bug affects
Works on Android, fails on iOSBug is platform-specificTry both platforms
Bug involves timingRace condition not capturedAdd delays or event handlers
Bug involves navigationPage lifecycle not exercisedEnsure pages are actually pushed/popped

Do NOT mark this skill complete until tests FAIL.

Output

⚠️ ONLY use this output format if tests FAIL. If tests pass, you have not completed this skill.

After completion (tests verified to fail), report:

markdown
✅ Tests created and verified for Issue #XXXXX

**Files:**
- `src/Controls/tests/TestCases.HostApp/Issues/IssueXXXXX.cs`
- `src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/IssueXXXXX.cs`

**Test method:** `ButtonClickUpdatesLabel`
**Category:** `UITestCategories.Button`
**Verification:** Tests FAIL as expected (bug reproduced)
**Failure message:** `Expected "X" but got "Y"` (include actual assertion failure)

If tests PASS after multiple iterations, report instead:

markdown
⚠️ Tests created but NOT verified for Issue #XXXXX

**Files:** [list files]
**Status:** Tests PASS when they should FAIL
**Iterations tried:** 3
**Problem:** [describe why test may not be catching the bug]
**Next steps:** Need guidance on reproduction steps

Common Patterns

Testing Property Changes
csharp
// HostApp: Add a way to trigger and observe the property
var picker = new Picker { AutomationId = "TestPicker" };
var statusLabel = new Label { AutomationId = "StatusLabel" };
picker.PropertyChanged += (s, e) => {
    if (e.PropertyName == nameof(Picker.IsOpen))
        statusLabel.Text = $"IsOpen={picker.IsOpen}";
};

// Test: Verify the property changes correctly
App.Tap("TestPicker");
App.WaitForElement("StatusLabel");
var status = App.FindElement("StatusLabel").GetText();
Assert.That(status, Does.Contain("IsOpen=True"));
Testing Layout/Positioning
csharp
// Test: Use GetRect() for position/size assertions
var rect = App.WaitForElement("TestElement").GetRect();
Assert.That(rect.Height, Is.GreaterThan(0));
Assert.That(rect.Y, Is.GreaterThanOrEqualTo(safeAreaTop));
Testing Visual State (Screenshots)
csharp
// Use retryTimeout for animations - keeps retrying until success
App.Tap("AnimatedButton");
VerifyScreenshot(retryTimeout: TimeSpan.FromSeconds(2));

// retryTimeout handles timing variance, small tolerance for cross-machine rendering
VerifyScreenshot(tolerance: 0.5, retryTimeout: TimeSpan.FromSeconds(2));
Testing Platform-Specific Behavior
csharp
// Only limit platforms when NECESSARY
[Test]
[Category(UITestCategories.Picker)]
public void PickerDismissResetsIsOpen()
{
    // This test should run on all platforms unless there's
    // a specific technical reason it can't
    App.WaitForElement("TestPicker");
    // ...
}

iOS Device Selection

When running tests on iOS, you may need to target a specific device or iOS version:

bash
# Default: iPhone Xs with iOS 18.5
pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "Issue12345"

# Find iPhone Xs with iOS 18.5 and get its UDID
UDID=$(xcrun simctl list devices available --json | jq -r '
  .devices | to_entries 
  | map(select(.key | contains("iOS-18-5"))) 
  | map(.value) | flatten 
  | map(select(.name == "iPhone Xs")) | first | .udid')

# Run with specific device
pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "Issue12345" -DeviceUdid "$UDID"

Finding different device/version combinations:

bash
# iPhone 16 Pro with any iOS version
UDID=$(xcrun simctl list devices available --json | jq -r '
  .devices[][] | select(.name == "iPhone 16 Pro") | .udid' | head -1)

# Any device with iOS 18.0
UDID=$(xcrun simctl list devices available --json | jq -r '
  .devices | to_entries 
  | map(select(.key | contains("iOS-18-0"))) 
  | map(.value) | flatten | .[0].udid')

Pre-Run Checklist

Before running verify-tests-fail.ps1, confirm:

  • HostApp file exists: TestCases.HostApp/Issues/IssueXXXXX.cs
  • NUnit test file exists: TestCases.Shared.Tests/Tests/Issues/IssueXXXXX.cs
  • [Issue()] attribute present with all parameters
  • All AutomationId values match between HostApp and test
  • Test inherits from _IssuesUITest
  • ONE [Category()] attribute from UITestCategories.cs

References

  • Full conventions: .github/instructions/uitests.instructions.md
  • Category list: src/Controls/tests/TestCases.Shared.Tests/UITestCategories.cs
  • Example tests: src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/

© dotnet, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in .github/skills/write-ui-tests of dotnet/maui.

Open the folder on GitHubat commit 7d38fd0

Compare with similar skills

MAUI UI Test Writer next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

MAUI UI Test Writer compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
MAUI UI Test Writer this skilldotnet/maui23k—~3kAutomated safety check: PassMIT
Kane CLI Browser TestingLambdaTest/kane-cli247—~8.4kAutomated safety check: PassApache-2.0
Releaselycorp-jp/sim-use1.4k—~1.7kAutomated safety check: PassApache-2.0
Fix Random CI Test Failuredotnet/macios2.9k—~1.3kAutomated safety check: PassCustom licence
Argent QA Flowsbbplayer-app/BBPlayer1.1k—~3.2kAutomated safety check: PassMIT
Simulator Audio E2Ehyochan/react-native-nitro-sound961—~1.1kAutomated safety check: PassMIT

Similar skills

  • Kane CLI Browser Testing

    LambdaTest/kane-cli

    Drives a real browser through the kane-cli tool and designs requirement-linked test suites from a PRD or a plain description, with mobile and cloud-grid runs.

    247 GitHub stars~8.4k tokensUpdated yesterday
    Testing & QAAuto-check passed
  • Release

    lycorp-jp/sim-use

    Cut a sim-use release end-to-end. An agent skill from lycorp-jp/sim-use.

    1.4k GitHub stars~1.7k tokensUpdated yesterday
    Testing & QAAuto-check passed
  • Official

    Investigate and fix flaky/random CI test failures in dotnet/macios.

    2.9k GitHub stars~1.3k tokensUpdated today
    Testing & QAAuto-check passed
  • Argent QA Flows

    bbplayer-app/BBPlayer

    Create repeatable QA regression E2E tests as Argent flows from test cases, tickets, or acceptance criteria.

    1.1k GitHub stars~3.2k tokensUpdated today
    Testing & QAAuto-check passed
  • Simulator Audio E2E

    hyochan/react-native-nitro-sound

    Build and run repeatable react-native-nitro-sound recorder/player regression tests on an iOS Simulator or Android emulator, with explicit virtual-device selection, microphone permission, Maestro…

    961 GitHub stars~1.1k tokensUpdated 7 days ago
    MobileAuto-check passed
  • Maui AI Debugging

    Redth/Maui.Gtk

    End-to-end workflow for building, deploying, inspecting, and debugging .NET MAUI and MAUI Blazor Hybrid apps as an AI agent.

    101 GitHub stars~4.1k tokensUpdated 5 mo ago
    MobileAuto-check passed

More from dotnet/maui

All 27 skills in this repo
  • Mines local Copilot CLI session logs for dotnet/maui to rank costly or failing runs, tag recurring failure modes, propose repo edits and emit guard evals.

    23k GitHub stars~3.4k tokensUpdated today
    Auto-check passed
  • Official

    Reviews the tests added in a pull request for fix coverage, quality, edge cases and test type, and recommends lighter test types where they would do.

    23k GitHub stars~2.9k tokensUpdated today
    Auto-check passed
  • Official

    Produces evidence-backed ship-readiness verdicts for .NET MAUI Servicing Releases and Previews, and drafts public-safe release handoff pages from the result.

    23k GitHub stars~15k tokensUpdated today
    Auto-check passed
  • Official

    Interprets pinned managed benchmark evidence for a dotnet/maui pull request and writes a narrative for the performance review workflow, without running or publishing anything.

    23k GitHub stars~2.4k tokensUpdated today
    Auto-check passed
  • PR Finalize

    dotnet/maui

    Official

    Checks that a pull request's title and description match its implementation and reviews the code for best practices before merge, without posting anything.

    23k GitHub stars~3.1k tokensUpdated today
    Auto-check passed
  • Official

    Adds MAUI-specific guardrails on top of the maestro-cli skill and Maestro MCP tools for darc, BAR, and channel or feed lookups in dotnet/maui.

    23k GitHub stars~10k tokensUpdated today
    Auto-check passed

Questions about MAUI UI Test Writer

What does MAUI UI Test Writer do?

Writes UI tests that reproduce a GitHub issue in .NET MAUI and keeps iterating until the tests actually fail, proving they catch the bug. NET MAUI conventions, such as file names built from the issue number and the Issue and Category attributes. The excerpt shows these conventions and file locations but is cut off before the remaining test steps.

When should I use MAUI UI Test Writer?

MAUI UI Test Writer fits situations like: A pull request has no tests and needs a UI test for the bug it fixes; an issue needs a reproduction test before anyone starts on a fix; existing tests do not adequately cover a reported bug.

How do I install MAUI UI Test Writer in Claude Code?

Run `npx skills add dotnet/maui --skill write-ui-tests -a claude-code`. Or copy the skill folder (.github/skills/write-ui-tests in dotnet/maui) into .claude/skills/write-ui-tests in your project. Claude Code loads it when a task matches its description.

How do I install MAUI UI Test Writer in Codex?

Run `npx skills add dotnet/maui --skill write-ui-tests -a codex`. Or copy the skill folder (.github/skills/write-ui-tests in dotnet/maui) into .agents/skills/write-ui-tests in your project. Codex loads it when a task matches its description.

Can I use MAUI UI Test Writer in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add dotnet/maui --skill write-ui-tests -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/write-ui-tests, .gemini/skills/write-ui-tests, .github/skills/write-ui-tests and .opencode/skills/write-ui-tests in your project.

What does MAUI UI Test Writer need to run?

Going by SKILL.md and its folder, MAUI UI Test Writer needs the command-line tools its instructions call (dotnet, pwsh, xcrun and jq). Our summary lists: git, PowerShell and the .NET SDK; Appium for running UI tests. Compatibility (from SKILL.md): Requires git, PowerShell, .NET SDK, and Appium for UI test execution..

Does MAUI UI Test Writer access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is MAUI UI Test Writer safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does MAUI UI Test Writer use?

MAUI UI Test Writer is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does MAUI UI Test Writer use?

About 3k tokens (SKILL.md is roughly 12k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to MAUI UI Test Writer?

Skills that share tags, products or a category with MAUI UI Test Writer: Kane CLI Browser Testing (LambdaTest/kane-cli, 247 stars), Release (lycorp-jp/sim-use, 1.4k stars), Fix Random CI Test Failure (dotnet/macios, 2.9k stars) and Argent QA Flows (bbplayer-app/BBPlayer, 1.1k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains MAUI UI Test Writer?

dotnet (a GitHub organization, an official publisher) maintains it in dotnet/maui, which has 23,322 GitHub stars. The repository holds 27 skills in this directory. The repository was last updated on October 7, 2026.

Source: dotnet/maui on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.