Skip to content

Commit

Permalink
Add some UI tests
Browse files Browse the repository at this point in the history
Done: iOS/macOS/Android. TODO: Windows
  • Loading branch information
mattleibow committed Feb 15, 2024
1 parent 8b0bb34 commit 09c8152
Show file tree
Hide file tree
Showing 11 changed files with 507 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System;
using System.Threading.Tasks;
using Microsoft.Maui.Controls;

namespace Maui.Controls.Sample
{
internal class AlertsGalleryPage : CoreGalleryBasePage
{
protected override void Build()
{
// ALERTS

// Test with a single button alert that can be dismissed by tapping the button
Add(Test.Alerts.AlertCancel, async t =>
{
await DisplayAlert(
"Alert Title Here",
"Alert Message Here",
"CANCEL");
t.ReportSuccessEvent();
});

// Test alert with options to Accept or Cancel, Accept is the correct option
Add(Test.Alerts.AlertAcceptCancelClickAccept, async t =>
{
var result = await DisplayAlert(
"Alert Title Here",
"Alert Message Here",
"ACCEPT", "CANCEL");
if (result)
t.ReportSuccessEvent();
else
t.ReportFailEvent();
});

// Test alert with options to Accept or Cancel, Cancel is the correct option
Add(Test.Alerts.AlertAcceptCancelClickCancel, async t =>
{
var result = await DisplayAlert(
"Alert Title Here",
"Alert Message Here",
"ACCEPT", "CANCEL");
if (result)
t.ReportFailEvent();
else
t.ReportSuccessEvent();
});

// ACTION SHEETS

// Test action sheet with items and Cancel, Item 2 is the correct option
Add(Test.Alerts.ActionSheetClickItem, async t =>
{
var result = await DisplayActionSheet(
"Action Sheet Title Here",
"CANCEL", "DESTROY",
"ITEM 1", "ITEM 2", "ITEM 3");
if (result == "ITEM 2")
t.ReportSuccessEvent();
else
t.ReportFailEvent();
});

// Test action sheet with items and Cancel, Cancel is the correct option
Add(Test.Alerts.ActionSheetClickCancel, async t =>
{
var result = await DisplayActionSheet(
"Action Sheet Title Here",
"CANCEL", "DESTROY",
"ITEM 1", "ITEM 2", "ITEM 3");
if (result == "CANCEL")
t.ReportSuccessEvent();
else
t.ReportFailEvent();
});

// Test action sheet with items and Cancel, Destroy is the correct option
Add(Test.Alerts.ActionSheetClickDestroy, async t =>
{
var result = await DisplayActionSheet(
"Action Sheet Title Here",
"CANCEL", "DESTROY",
"ITEM 1", "ITEM 2", "ITEM 3");
if (result == "DESTROY")
t.ReportSuccessEvent();
else
t.ReportFailEvent();
});
}

ExpectedEventViewContainer<Button>
Add(Test.Alerts test, Func<ExpectedEventViewContainer<Button>, Task> action) =>
Add(new ExpectedEventViewContainer<Button>(test, new Button { Text = "Click Me!" }))
.With(t => t.View.Clicked += (_, _) => action(t));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public override string ToString()
new GalleryPageFactory(() => new GestureRecognizerGallery(), "Gesture Recognizer Gallery"),
new GalleryPageFactory(() => new InputTransparencyGalleryPage(), "Input Transparency Gallery"),
new GalleryPageFactory(() => new ImageLoadingGalleryPage(), "Image Loading Gallery"),
new GalleryPageFactory(() => new AlertsGalleryPage(), "Alerts Gallery"),
// Elements
new GalleryPageFactory(() => new ActivityIndicatorCoreGalleryPage(), "ActivityIndicator Gallery"),
new GalleryPageFactory(() => new BoxViewCoreGalleryPage(), "Box Gallery"),
Expand Down
10 changes: 10 additions & 0 deletions src/Controls/samples/Controls.Sample.UITests/Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,16 @@ public enum InputTransparency
CascadeTransLayoutOverlayWithButton,
}

public enum Alerts
{
AlertCancel,
AlertAcceptCancelClickAccept,
AlertAcceptCancelClickCancel,
ActionSheetClickItem,
ActionSheetClickCancel,
ActionSheetClickDestroy,
}

public static class InputTransparencyMatrix
{
// this is both for color diff and cols
Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/Core/Page/Page.cs
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ public Task<string> DisplayActionSheet(string title, string cancel, string destr
/// Displays a platform action sheet, allowing the application user to choose from several buttons.
/// </summary>
/// <param name="title">Title of the displayed action sheet. Can be <see langword="null"/> to hide the title.</param>
/// <param name="cancel">Text to be displayed in the 'Cancel' button. Can be null to hide the <see langword="null"/> action.</param>
/// <param name="cancel">Text to be displayed in the 'Cancel' button. Can be null to hide the cancel action.</param>
/// <param name="destruction">Text to be displayed in the 'Destruct' button. Can be <see langword="null"/> to hide the destructive option.</param>
/// <param name="flowDirection">The flow direction to be used by the action sheet.</param>
/// <param name="buttons">Text labels for additional buttons.</param>
Expand Down
142 changes: 142 additions & 0 deletions src/Controls/tests/UITests/Tests/Concepts/AlertsGalleryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
using Maui.Controls.Sample;
using NUnit.Framework;
using OpenQA.Selenium.Appium;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.AppiumTests
{
public class AlertsGalleryTests : CoreGalleryBasePageTest
{
public AlertsGalleryTests(TestDevice device)
: base(device)
{
}

protected override void NavigateToGallery()
{
App.NavigateToGallery("Alerts Gallery");
}

[Test]
public void AlertCancel()
{
if (Device == TestDevice.Windows)
Assert.Ignore("UI testing alert code is not yet implemented on Windows.");

var test = Test.Alerts.AlertCancel;

var remote = new EventViewContainerRemote(UITestContext, test);
remote.GoTo(test.ToString());

var textBeforeClick = remote.GetEventLabel().GetText();
Assert.AreEqual($"Event: {test} (none)", textBeforeClick);

remote.TapView();

var alert = App.WaitForElement(() => App.GetAlert());
Assert.NotNull(alert);

var alertText = alert.GetAlertText();
CollectionAssert.Contains(alertText, "Alert Title Here");
CollectionAssert.Contains(alertText, "Alert Message Here");

var buttons = alert.GetAlertButtons();
CollectionAssert.IsNotEmpty(buttons);
Assert.True(buttons.Count == 1, $"Expected 1 buttonText, found {buttons.Count}.");

var cancel = buttons.First();
Assert.AreEqual("CANCEL", cancel.GetText());

cancel.Click();

App.WaitForNoElement(() => App.GetAlert());

var textAfterClick = remote.GetEventLabel().GetText();
Assert.AreEqual($"Event: {test} (SUCCESS 1)", textAfterClick);
}

[Test]
[TestCase(Test.Alerts.AlertAcceptCancelClickAccept, "ACCEPT")]
[TestCase(Test.Alerts.AlertAcceptCancelClickCancel, "CANCEL")]
public void AlertAcceptCancel(Test.Alerts test, string buttonText)
{
if (Device == TestDevice.Windows)
Assert.Ignore("UI testing alert code is not yet implemented on Windows.");

var remote = new EventViewContainerRemote(UITestContext, test);
remote.GoTo(test.ToString());

var textBeforeClick = remote.GetEventLabel().GetText();
Assert.AreEqual($"Event: {test} (none)", textBeforeClick);

remote.TapView();

var alert = App.WaitForElement(() => App.GetAlert());
Assert.NotNull(alert);

var alertText = alert.GetAlertText();
CollectionAssert.Contains(alertText, "Alert Title Here");
CollectionAssert.Contains(alertText, "Alert Message Here");

var buttons = alert.GetAlertButtons()
.Select(b => (Element: b, Text: b.GetText()))
.ToList();
CollectionAssert.IsNotEmpty(buttons);
Assert.True(buttons.Count == 2, $"Expected 2 buttons, found {buttons.Count}.");
CollectionAssert.Contains(buttons.Select(b => b.Text), "ACCEPT");
CollectionAssert.Contains(buttons.Select(b => b.Text), "CANCEL");

var button = buttons.Single(b => b.Text == buttonText);
button.Element.Click();

App.WaitForNoElement(() => App.GetAlert());

var textAfterClick = remote.GetEventLabel().GetText();
Assert.AreEqual($"Event: {test} (SUCCESS 1)", textAfterClick);
}

[Test]
[TestCase(Test.Alerts.ActionSheetClickItem, "ITEM 2")]
[TestCase(Test.Alerts.ActionSheetClickCancel, "CANCEL")]
[TestCase(Test.Alerts.ActionSheetClickDestroy, "DESTROY")]
public void ActionSheetClickItem(Test.Alerts test, string itemText)
{
if (Device == TestDevice.Windows)
Assert.Ignore("UI testing alert code is not yet implemented on Windows.");

var remote = new EventViewContainerRemote(UITestContext, test);
remote.GoTo(test.ToString());

var textBeforeClick = remote.GetEventLabel().GetText();
Assert.AreEqual($"Event: {test} (none)", textBeforeClick);

remote.TapView();

var alert = App.WaitForElement(() => App.GetAlert());
Assert.NotNull(alert);

var alertText = alert.GetAlertText();
CollectionAssert.Contains(alertText, "Action Sheet Title Here");

var buttons = alert.GetAlertButtons()
.Select(b => (Element: b, Text: b.GetText()))
.ToList();
CollectionAssert.IsNotEmpty(buttons);
Assert.True(buttons.Count == 5, $"Expected 5 buttons, found {buttons.Count}.");
CollectionAssert.Contains(buttons.Select(b => b.Text), "CANCEL");
CollectionAssert.Contains(buttons.Select(b => b.Text), "DESTROY");
CollectionAssert.Contains(buttons.Select(b => b.Text), "ITEM 1");
CollectionAssert.Contains(buttons.Select(b => b.Text), "ITEM 2");
CollectionAssert.Contains(buttons.Select(b => b.Text), "ITEM 3");

var button = buttons.Single(b => b.Text == itemText);
button.Element.Click();

App.WaitForNoElement(() => App.GetAlert());

var textAfterClick = remote.GetEventLabel().GetText();
Assert.AreEqual($"Event: {test} (SUCCESS 1)", textAfterClick);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System.Collections.ObjectModel;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using UITest.Core;

namespace UITest.Appium
{
public class AppiumAndroidAlertActions : ICommandExecutionGroup
{
const string GetAlertsCommand = "getAlerts";
const string GetAlertButtonsCommand = "getAlertButtons";
const string GetAlertTextCommand = "getAlertText";

readonly List<string> _commands = new()
{
GetAlertsCommand,
GetAlertButtonsCommand,
GetAlertTextCommand,
};
readonly AppiumApp _appiumApp;

public AppiumAndroidAlertActions(AppiumApp appiumApp)
{
_appiumApp = appiumApp;
}

public bool IsCommandSupported(string commandName)
{
return _commands.Contains(commandName, StringComparer.OrdinalIgnoreCase);
}

public CommandResponse Execute(string commandName, IDictionary<string, object> parameters)
{
return commandName switch
{
GetAlertsCommand => GetAlerts(parameters),
GetAlertButtonsCommand => GetAlertButtons(parameters),
GetAlertTextCommand => GetAlertText(parameters),
_ => CommandResponse.FailedEmptyResponse,
};
}

CommandResponse GetAlerts(IDictionary<string, object> parameters)
{
var alerts = _appiumApp.Query.ById("parentPanel");

if (alerts is null || alerts.Count == 0)
return CommandResponse.FailedEmptyResponse;

return new CommandResponse(alerts, CommandResponseResult.Success);
}

CommandResponse GetAlertButtons(IDictionary<string, object> parameters)
{
var alert = GetAppiumElement(parameters["element"]);
if (alert is null)
return CommandResponse.FailedEmptyResponse;

var items = AppiumQuery.ByClass("android.widget.ListView")
.FindElements(alert, _appiumApp)
.FirstOrDefault()
?.ByClass("android.widget.TextView");

var buttons = AppiumQuery.ByClass("android.widget.Button")
.FindElements(alert, _appiumApp);

var all = new List<IUIElement>();
if (items is not null)
all.AddRange(items);
all.AddRange(buttons);

return new CommandResponse(all, CommandResponseResult.Success);
}

CommandResponse GetAlertText(IDictionary<string, object> parameters)
{
var alert = GetAppiumElement(parameters["element"]);
if (alert is null)
return CommandResponse.FailedEmptyResponse;

var text = AppiumQuery.ByClass("android.widget.TextView").FindElements(alert, _appiumApp);
var strings = text.Select(t => t.GetText()).ToList();

return new CommandResponse(strings, CommandResponseResult.Success);
}

static AppiumElement? GetAppiumElement(object element) =>
element switch
{
AppiumElement appiumElement => appiumElement,
AppiumDriverElement driverElement => driverElement.AppiumElement,
_ => null
};
}
}
Loading

0 comments on commit 09c8152

Please sign in to comment.