Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 8 additions & 41 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,47 +112,14 @@ dotnet cake --target=dotnet-pack

#### UI Testing Guidelines

When adding UI tests to validate visual behavior and user interactions, follow this two-part pattern:

**CRITICAL: UITests require code in TWO separate projects that must BOTH be implemented:**

1. **HostApp UI Test Page** (`src/Controls/tests/TestCases.HostApp/Issues/`)
- Create the actual UI page that demonstrates the feature or reproduces the issue
- Use XAML with proper `AutomationId` attributes on interactive controls for test automation
- Follow naming convention: `IssueXXXXX.xaml` and `IssueXXXXX.xaml.cs`
- Ensure the UI provides clear visual feedback for the behavior being tested

2. **NUnit Test Implementation** (`src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/`)
- Create corresponding Appium-based NUnit tests that inherit from `_IssuesUITest`
- Use the `AutomationId` values to locate and interact with UI elements
- Follow naming convention: `IssueXXXXX.cs` (matches the HostApp file)
- Include appropriate `[Category(UITestCategories.XYZ)]` attributes
- Test should validate expected behavior through UI interactions and assertions

**UI Test Pattern Example:**
```csharp
// In TestCases.Shared.Tests/Tests/Issues/IssueXXXXX.cs
public class IssueXXXXX : _IssuesUITest
{
public override string Issue => "Description of the issue being tested";

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

[Test]
[Category(UITestCategories.Layout)] // or appropriate category
public void TestMethodName()
{
App.WaitForElement("AutomationId");
App.Tap("AutomationId");
// Add assertions to verify expected behavior
}
}
```

**Before committing UI tests:**
- Compile both the HostApp project and TestCases.Shared.Tests project to ensure no build errors
- Verify AutomationId references match between XAML and test code
- Ensure tests follow the established naming and inheritance patterns
For comprehensive UI testing guidelines and patterns, see the dedicated instructions file:
`.github/instructions/ui-testing.instructions.md`

This includes:
- Two-part testing pattern (HostApp + NUnit tests)
- UI test code examples and patterns
- AutomationId requirements and naming conventions
- Complete API reference and best practices

### Code Formatting

Expand Down
68 changes: 68 additions & 0 deletions .github/instructions/ui-testing.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
applyTo:
- "src/Controls/tests/TestCases.HostApp/**/*.cs"
- "src/Controls/tests/TestCases.iOS.Tests/**/*.cs"
- "src/Controls/tests/TestCases.Android.Tests/**/*.cs"
- "src/Controls/tests/TestCases.Shared.Tests/**/*.cs"
- "src/Controls/tests/TestCases.Mac.Tests/**/*.cs"
- "src/Controls/tests/TestCases.WinUI.Tests/**/*.cs"
---

# UI Testing Guidelines for .NET MAUI

When adding UI tests to validate visual behavior and user interactions, follow this two-part pattern:

**CRITICAL: UITests require code in TWO separate projects that must BOTH be implemented:**

1. **HostApp UI Test Page** (`src/Controls/tests/TestCases.HostApp/Issues/`)
- Create the actual UI page that demonstrates the feature or reproduces the issue
- Use XAML with proper `AutomationId` attributes on interactive controls for test automation
- Follow naming convention: `IssueXXXXX.xaml` and `IssueXXXXX.xaml.cs`
- Ensure the UI provides clear visual feedback for the behavior being tested

2. **NUnit Test Implementation** (`src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/`)
- Create corresponding Appium-based NUnit tests that inherit from `_IssuesUITest`
- Use the `AutomationId` values to locate and interact with UI elements
- Follow naming convention: `IssueXXXXX.cs` (matches the HostApp file)
- Include appropriate `[Category(UITestCategories.XYZ)]` attributes
- Test should validate expected behavior through UI interactions and assertions

## UI Test Pattern Example

```csharp
// In TestCases.Shared.Tests/Tests/Issues/IssueXXXXX.cs
public class IssueXXXXX : _IssuesUITest
{
public override string Issue => "Description of the issue being tested";

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

[Test]
[Category(UITestCategories.Layout)] // or appropriate category
public void TestMethodName()
{
App.WaitForElement("AutomationId");
App.Tap("AutomationId");
// Add assertions to verify expected behavior
}
}
```

## Before committing UI tests

- Compile both the HostApp project and TestCases.Shared.Tests project to ensure no build errors
- Verify AutomationId references match between XAML and test code
- Ensure tests follow the established naming and inheritance patterns

## UI Test API Reference

For comprehensive API documentation and examples, see the detailed guide at:
`src/Controls/tests/TestCases.Shared.Tests/AppiumTestGuide.md`

This guide includes:
- Complete IApp and IUIElement API reference
- All test base classes (_IssuesUITest, UITest, etc.)
- Visual regression testing patterns
- Platform-specific testing strategies
- Step-by-step instructions for creating new tests
- Best practices and common pitfalls
38 changes: 38 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue2708.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue2708">

<ContentPage Title="Tab 1" AutomationId="Tab1">
<StackLayout Padding="20">
<Label Text="This is Tab 1" FontSize="18" HorizontalOptions="Center" />
<Button
x:Name="OpenModalButton"
AutomationId="OpenModalButton"
Text="Open Modal"
Clicked="OnOpenModalClicked" />
<Label
x:Name="StatusLabel"
AutomationId="StatusLabel"
Text="Tabs should remain visible during modal navigation"
FontSize="14"
HorizontalOptions="Center" />
</StackLayout>
</ContentPage>

<ContentPage Title="Tab 2" AutomationId="Tab2">
<StackLayout Padding="20">
<Label Text="This is Tab 2" FontSize="18" HorizontalOptions="Center" />
<Label Text="Switch to this tab to verify tab functionality" FontSize="14" />
</StackLayout>
</ContentPage>

<ContentPage Title="Tab 3" AutomationId="Tab3">
<StackLayout Padding="20">
<Label Text="This is Tab 3" FontSize="18" HorizontalOptions="Center" />
<Label Text="This tab confirms tab navigation works" FontSize="14" />
</StackLayout>
</ContentPage>

</TabbedPage>
57 changes: 57 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue2708.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
namespace Maui.Controls.Sample.Issues
{
[Issue(IssueTracker.Github, 2708, "Prevent tabs from being removed during modal navigation", PlatformAffected.Android)]
public partial class Issue2708 : TabbedPage
{
public Issue2708()
{
InitializeComponent();
}

async void OnOpenModalClicked(object sender, EventArgs e)
{
await Navigation.PushModalAsync(new Issue2708Modal());
}
}

public class Issue2708Modal : ContentPage
{
public Issue2708Modal()
{
Title = "Modal Page";

var layout = new StackLayout
{
Padding = new Thickness(20),
Children =
{
new Label
{
Text = "This is a modal page",
FontSize = 18,
HorizontalOptions = LayoutOptions.Center
},
new Label
{
Text = "The tabs should still be visible behind this modal on Android",
FontSize = 14,
HorizontalOptions = LayoutOptions.Center
},
new Button
{
Text = "Close Modal",
AutomationId = "CloseModalButton"
}
}
};

var closeButton = layout.Children.OfType<Button>().First();
closeButton.Clicked += async (sender, e) =>
{
await Navigation.PopModalAsync();
};

Content = layout;
}
}
}
134 changes: 134 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue30337.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue30337"
Title="SafeArea Per-Edge Control">

<ScrollView>
<VerticalStackLayout Spacing="10" Padding="20">

<Label Text="SafeAreaEdges Per-Edge Control Test"
FontSize="18"
FontAttributes="Bold"
HorizontalOptions="Center" />

<Label Text="This test validates SafeAreaEdges property functionality on different controls with various SafeAreaRegions values. This test page demonstrates the UI layout for the SafeArea feature."
FontSize="12"
TextColor="Gray"
HorizontalOptions="Center"
HorizontalTextAlignment="Center" />

<!-- Test Layout with SafeAreaEdges.All -->
<Border BackgroundColor="LightBlue"
Padding="10">
<VerticalStackLayout>
<Label Text="Layout: SafeAreaEdges.All"
FontAttributes="Bold" />
<Label Text="This layout would obey all safe area insets"
FontSize="12" />
<Button Text="Test Layout All"
AutomationId="ButtonLayoutAll"
Clicked="OnLayoutAllClicked" />
</VerticalStackLayout>
</Border>

<!-- Test ContentView with SafeAreaEdges.None -->
<ContentView BackgroundColor="LightGreen"
Padding="10">
<VerticalStackLayout>
<Label Text="ContentView: SafeAreaEdges.None"
FontAttributes="Bold" />
<Label Text="This content view would extend edge-to-edge"
FontSize="12" />
<Button Text="Test ContentView None"
AutomationId="ButtonContentViewNone"
Clicked="OnContentViewNoneClicked" />
</VerticalStackLayout>
</ContentView>

<!-- Test Border with mixed edges -->
<Border BackgroundColor="LightCoral"
Padding="10">
<VerticalStackLayout>
<Label Text="Border: Mixed Edges (All,None,All,None)"
FontAttributes="Bold" />
<Label Text="Left=All, Top=None, Right=All, Bottom=None"
FontSize="12" />
<Button Text="Test Border Mixed"
AutomationId="ButtonBorderMixed"
Clicked="OnBorderMixedClicked" />
</VerticalStackLayout>
</Border>

<!-- Test Grid with Container region -->
<Grid BackgroundColor="LightYellow"
Padding="10">
<VerticalStackLayout>
<Label Text="Grid: SafeAreaEdges.Container"
FontAttributes="Bold" />
<Label Text="Container region - flows under keyboard but stays out of bars/notch"
FontSize="12" />
<Button Text="Test Grid Container"
AutomationId="ButtonGridContainer"
Clicked="OnGridContainerClicked" />
</VerticalStackLayout>
</Grid>

<!-- Test ScrollView with SoftInput region -->
<ScrollView BackgroundColor="LightPink"
HeightRequest="100">
<VerticalStackLayout Padding="10">
<Label Text="ScrollView: SafeAreaEdges.SoftInput"
FontAttributes="Bold" />
<Label Text="SoftInput region - always pads to avoid keyboard"
FontSize="12" />
<Button Text="Test ScrollView SoftInput"
AutomationId="ButtonScrollViewSoftInput"
Clicked="OnScrollViewSoftInputClicked" />
</VerticalStackLayout>
</ScrollView>

<!-- Dynamic property change test -->
<VerticalStackLayout BackgroundColor="Lavender"
Padding="10"
x:Name="DynamicLayout">
<Label Text="Dynamic Property Change Test"
FontAttributes="Bold" />
<Label x:Name="DynamicLabel"
Text="Current: SafeAreaEdges.Default"
FontSize="12" />
<HorizontalStackLayout Spacing="5">
<Button Text="None"
AutomationId="ButtonSetNone"
Clicked="OnSetNoneClicked" />
<Button Text="All"
AutomationId="ButtonSetAll"
Clicked="OnSetAllClicked" />
<Button Text="Container"
AutomationId="ButtonSetContainer"
Clicked="OnSetContainerClicked" />
<Button Text="SoftInput"
AutomationId="ButtonSetSoftInput"
Clicked="OnSetSoftInputClicked" />
</HorizontalStackLayout>
</VerticalStackLayout>

<!-- Status display -->
<Border BackgroundColor="WhiteSmoke"
Padding="10"
Stroke="Gray"
StrokeThickness="1">
<VerticalStackLayout>
<Label Text="Test Status"
FontAttributes="Bold" />
<Label x:Name="StatusLabel"
Text="Ready to test SafeAreaEdges functionality"
AutomationId="StatusLabel"
FontSize="12" />
</VerticalStackLayout>
</Border>

</VerticalStackLayout>
</ScrollView>

</ContentPage>
Loading