Skip to content

Commit 3f9596b

Browse files
committed
Add some UI tests
Done: iOS/macOS/Android. TODO: Windows
1 parent 68c930f commit 3f9596b

File tree

11 files changed

+507
-5
lines changed

11 files changed

+507
-5
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Microsoft.Maui.Controls;
4+
5+
namespace Maui.Controls.Sample
6+
{
7+
internal class AlertsGalleryPage : CoreGalleryBasePage
8+
{
9+
protected override void Build()
10+
{
11+
// ALERTS
12+
13+
// Test with a single button alert that can be dismissed by tapping the button
14+
Add(Test.Alerts.AlertCancel, async t =>
15+
{
16+
await DisplayAlert(
17+
"Alert Title Here",
18+
"Alert Message Here",
19+
"CANCEL");
20+
t.ReportSuccessEvent();
21+
});
22+
23+
// Test alert with options to Accept or Cancel, Accept is the correct option
24+
Add(Test.Alerts.AlertAcceptCancelClickAccept, async t =>
25+
{
26+
var result = await DisplayAlert(
27+
"Alert Title Here",
28+
"Alert Message Here",
29+
"ACCEPT", "CANCEL");
30+
if (result)
31+
t.ReportSuccessEvent();
32+
else
33+
t.ReportFailEvent();
34+
});
35+
36+
// Test alert with options to Accept or Cancel, Cancel is the correct option
37+
Add(Test.Alerts.AlertAcceptCancelClickCancel, async t =>
38+
{
39+
var result = await DisplayAlert(
40+
"Alert Title Here",
41+
"Alert Message Here",
42+
"ACCEPT", "CANCEL");
43+
if (result)
44+
t.ReportFailEvent();
45+
else
46+
t.ReportSuccessEvent();
47+
});
48+
49+
// ACTION SHEETS
50+
51+
// Test action sheet with items and Cancel, Item 2 is the correct option
52+
Add(Test.Alerts.ActionSheetClickItem, async t =>
53+
{
54+
var result = await DisplayActionSheet(
55+
"Action Sheet Title Here",
56+
"CANCEL", "DESTROY",
57+
"ITEM 1", "ITEM 2", "ITEM 3");
58+
if (result == "ITEM 2")
59+
t.ReportSuccessEvent();
60+
else
61+
t.ReportFailEvent();
62+
});
63+
64+
// Test action sheet with items and Cancel, Cancel is the correct option
65+
Add(Test.Alerts.ActionSheetClickCancel, async t =>
66+
{
67+
var result = await DisplayActionSheet(
68+
"Action Sheet Title Here",
69+
"CANCEL", "DESTROY",
70+
"ITEM 1", "ITEM 2", "ITEM 3");
71+
if (result == "CANCEL")
72+
t.ReportSuccessEvent();
73+
else
74+
t.ReportFailEvent();
75+
});
76+
77+
// Test action sheet with items and Cancel, Destroy is the correct option
78+
Add(Test.Alerts.ActionSheetClickDestroy, async t =>
79+
{
80+
var result = await DisplayActionSheet(
81+
"Action Sheet Title Here",
82+
"CANCEL", "DESTROY",
83+
"ITEM 1", "ITEM 2", "ITEM 3");
84+
if (result == "DESTROY")
85+
t.ReportSuccessEvent();
86+
else
87+
t.ReportFailEvent();
88+
});
89+
}
90+
91+
ExpectedEventViewContainer<Button>
92+
Add(Test.Alerts test, Func<ExpectedEventViewContainer<Button>, Task> action) =>
93+
Add(new ExpectedEventViewContainer<Button>(test, new Button { Text = "Click Me!" }))
94+
.With(t => t.View.Clicked += (_, _) => action(t));
95+
}
96+
}

src/Controls/samples/Controls.Sample.UITests/CoreViews/CorePageView.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public override string ToString()
4949
new GalleryPageFactory(() => new GestureRecognizerGallery(), "Gesture Recognizer Gallery"),
5050
new GalleryPageFactory(() => new InputTransparencyGalleryPage(), "Input Transparency Gallery"),
5151
new GalleryPageFactory(() => new ImageLoadingGalleryPage(), "Image Loading Gallery"),
52+
new GalleryPageFactory(() => new AlertsGalleryPage(), "Alerts Gallery"),
5253
// Elements
5354
new GalleryPageFactory(() => new ActivityIndicatorCoreGalleryPage(), "ActivityIndicator Gallery"),
5455
new GalleryPageFactory(() => new BoxViewCoreGalleryPage(), "Box Gallery"),

src/Controls/samples/Controls.Sample.UITests/Test.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,16 @@ public enum InputTransparency
731731
CascadeTransLayoutOverlayWithButton,
732732
}
733733

734+
public enum Alerts
735+
{
736+
AlertCancel,
737+
AlertAcceptCancelClickAccept,
738+
AlertAcceptCancelClickCancel,
739+
ActionSheetClickItem,
740+
ActionSheetClickCancel,
741+
ActionSheetClickDestroy,
742+
}
743+
734744
public static class InputTransparencyMatrix
735745
{
736746
// this is both for color diff and cols

src/Controls/src/Core/Page/Page.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ public Task<string> DisplayActionSheet(string title, string cancel, string destr
241241
/// Displays a platform action sheet, allowing the application user to choose from several buttons.
242242
/// </summary>
243243
/// <param name="title">Title of the displayed action sheet. Can be <see langword="null"/> to hide the title.</param>
244-
/// <param name="cancel">Text to be displayed in the 'Cancel' button. Can be null to hide the <see langword="null"/> action.</param>
244+
/// <param name="cancel">Text to be displayed in the 'Cancel' button. Can be null to hide the cancel action.</param>
245245
/// <param name="destruction">Text to be displayed in the 'Destruct' button. Can be <see langword="null"/> to hide the destructive option.</param>
246246
/// <param name="flowDirection">The flow direction to be used by the action sheet.</param>
247247
/// <param name="buttons">Text labels for additional buttons.</param>
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
using Maui.Controls.Sample;
2+
using NUnit.Framework;
3+
using OpenQA.Selenium.Appium;
4+
using UITest.Appium;
5+
using UITest.Core;
6+
7+
namespace Microsoft.Maui.AppiumTests
8+
{
9+
public class AlertsGalleryTests : CoreGalleryBasePageTest
10+
{
11+
public AlertsGalleryTests(TestDevice device)
12+
: base(device)
13+
{
14+
}
15+
16+
protected override void NavigateToGallery()
17+
{
18+
App.NavigateToGallery("Alerts Gallery");
19+
}
20+
21+
[Test]
22+
public void AlertCancel()
23+
{
24+
if (Device == TestDevice.Windows)
25+
Assert.Ignore("UI testing alert code is not yet implemented on Windows.");
26+
27+
var test = Test.Alerts.AlertCancel;
28+
29+
var remote = new EventViewContainerRemote(UITestContext, test);
30+
remote.GoTo(test.ToString());
31+
32+
var textBeforeClick = remote.GetEventLabel().GetText();
33+
Assert.AreEqual($"Event: {test} (none)", textBeforeClick);
34+
35+
remote.TapView();
36+
37+
var alert = App.WaitForElement(() => App.GetAlert());
38+
Assert.NotNull(alert);
39+
40+
var alertText = alert.GetAlertText();
41+
CollectionAssert.Contains(alertText, "Alert Title Here");
42+
CollectionAssert.Contains(alertText, "Alert Message Here");
43+
44+
var buttons = alert.GetAlertButtons();
45+
CollectionAssert.IsNotEmpty(buttons);
46+
Assert.True(buttons.Count == 1, $"Expected 1 buttonText, found {buttons.Count}.");
47+
48+
var cancel = buttons.First();
49+
Assert.AreEqual("CANCEL", cancel.GetText());
50+
51+
cancel.Click();
52+
53+
App.WaitForNoElement(() => App.GetAlert());
54+
55+
var textAfterClick = remote.GetEventLabel().GetText();
56+
Assert.AreEqual($"Event: {test} (SUCCESS 1)", textAfterClick);
57+
}
58+
59+
[Test]
60+
[TestCase(Test.Alerts.AlertAcceptCancelClickAccept, "ACCEPT")]
61+
[TestCase(Test.Alerts.AlertAcceptCancelClickCancel, "CANCEL")]
62+
public void AlertAcceptCancel(Test.Alerts test, string buttonText)
63+
{
64+
if (Device == TestDevice.Windows)
65+
Assert.Ignore("UI testing alert code is not yet implemented on Windows.");
66+
67+
var remote = new EventViewContainerRemote(UITestContext, test);
68+
remote.GoTo(test.ToString());
69+
70+
var textBeforeClick = remote.GetEventLabel().GetText();
71+
Assert.AreEqual($"Event: {test} (none)", textBeforeClick);
72+
73+
remote.TapView();
74+
75+
var alert = App.WaitForElement(() => App.GetAlert());
76+
Assert.NotNull(alert);
77+
78+
var alertText = alert.GetAlertText();
79+
CollectionAssert.Contains(alertText, "Alert Title Here");
80+
CollectionAssert.Contains(alertText, "Alert Message Here");
81+
82+
var buttons = alert.GetAlertButtons()
83+
.Select(b => (Element: b, Text: b.GetText()))
84+
.ToList();
85+
CollectionAssert.IsNotEmpty(buttons);
86+
Assert.True(buttons.Count == 2, $"Expected 2 buttons, found {buttons.Count}.");
87+
CollectionAssert.Contains(buttons.Select(b => b.Text), "ACCEPT");
88+
CollectionAssert.Contains(buttons.Select(b => b.Text), "CANCEL");
89+
90+
var button = buttons.Single(b => b.Text == buttonText);
91+
button.Element.Click();
92+
93+
App.WaitForNoElement(() => App.GetAlert());
94+
95+
var textAfterClick = remote.GetEventLabel().GetText();
96+
Assert.AreEqual($"Event: {test} (SUCCESS 1)", textAfterClick);
97+
}
98+
99+
[Test]
100+
[TestCase(Test.Alerts.ActionSheetClickItem, "ITEM 2")]
101+
[TestCase(Test.Alerts.ActionSheetClickCancel, "CANCEL")]
102+
[TestCase(Test.Alerts.ActionSheetClickDestroy, "DESTROY")]
103+
public void ActionSheetClickItem(Test.Alerts test, string itemText)
104+
{
105+
if (Device == TestDevice.Windows)
106+
Assert.Ignore("UI testing alert code is not yet implemented on Windows.");
107+
108+
var remote = new EventViewContainerRemote(UITestContext, test);
109+
remote.GoTo(test.ToString());
110+
111+
var textBeforeClick = remote.GetEventLabel().GetText();
112+
Assert.AreEqual($"Event: {test} (none)", textBeforeClick);
113+
114+
remote.TapView();
115+
116+
var alert = App.WaitForElement(() => App.GetAlert());
117+
Assert.NotNull(alert);
118+
119+
var alertText = alert.GetAlertText();
120+
CollectionAssert.Contains(alertText, "Action Sheet Title Here");
121+
122+
var buttons = alert.GetAlertButtons()
123+
.Select(b => (Element: b, Text: b.GetText()))
124+
.ToList();
125+
CollectionAssert.IsNotEmpty(buttons);
126+
Assert.True(buttons.Count == 5, $"Expected 5 buttons, found {buttons.Count}.");
127+
CollectionAssert.Contains(buttons.Select(b => b.Text), "CANCEL");
128+
CollectionAssert.Contains(buttons.Select(b => b.Text), "DESTROY");
129+
CollectionAssert.Contains(buttons.Select(b => b.Text), "ITEM 1");
130+
CollectionAssert.Contains(buttons.Select(b => b.Text), "ITEM 2");
131+
CollectionAssert.Contains(buttons.Select(b => b.Text), "ITEM 3");
132+
133+
var button = buttons.Single(b => b.Text == itemText);
134+
button.Element.Click();
135+
136+
App.WaitForNoElement(() => App.GetAlert());
137+
138+
var textAfterClick = remote.GetEventLabel().GetText();
139+
Assert.AreEqual($"Event: {test} (SUCCESS 1)", textAfterClick);
140+
}
141+
}
142+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
using System.Collections.ObjectModel;
2+
using OpenQA.Selenium;
3+
using OpenQA.Selenium.Appium;
4+
using UITest.Core;
5+
6+
namespace UITest.Appium
7+
{
8+
public class AppiumAndroidAlertActions : ICommandExecutionGroup
9+
{
10+
const string GetAlertsCommand = "getAlerts";
11+
const string GetAlertButtonsCommand = "getAlertButtons";
12+
const string GetAlertTextCommand = "getAlertText";
13+
14+
readonly List<string> _commands = new()
15+
{
16+
GetAlertsCommand,
17+
GetAlertButtonsCommand,
18+
GetAlertTextCommand,
19+
};
20+
readonly AppiumApp _appiumApp;
21+
22+
public AppiumAndroidAlertActions(AppiumApp appiumApp)
23+
{
24+
_appiumApp = appiumApp;
25+
}
26+
27+
public bool IsCommandSupported(string commandName)
28+
{
29+
return _commands.Contains(commandName, StringComparer.OrdinalIgnoreCase);
30+
}
31+
32+
public CommandResponse Execute(string commandName, IDictionary<string, object> parameters)
33+
{
34+
return commandName switch
35+
{
36+
GetAlertsCommand => GetAlerts(parameters),
37+
GetAlertButtonsCommand => GetAlertButtons(parameters),
38+
GetAlertTextCommand => GetAlertText(parameters),
39+
_ => CommandResponse.FailedEmptyResponse,
40+
};
41+
}
42+
43+
CommandResponse GetAlerts(IDictionary<string, object> parameters)
44+
{
45+
var alerts = _appiumApp.Query.ById("parentPanel");
46+
47+
if (alerts is null || alerts.Count == 0)
48+
return CommandResponse.FailedEmptyResponse;
49+
50+
return new CommandResponse(alerts, CommandResponseResult.Success);
51+
}
52+
53+
CommandResponse GetAlertButtons(IDictionary<string, object> parameters)
54+
{
55+
var alert = GetAppiumElement(parameters["element"]);
56+
if (alert is null)
57+
return CommandResponse.FailedEmptyResponse;
58+
59+
var items = AppiumQuery.ByClass("android.widget.ListView")
60+
.FindElements(alert, _appiumApp)
61+
.FirstOrDefault()
62+
?.ByClass("android.widget.TextView");
63+
64+
var buttons = AppiumQuery.ByClass("android.widget.Button")
65+
.FindElements(alert, _appiumApp);
66+
67+
var all = new List<IUIElement>();
68+
if (items is not null)
69+
all.AddRange(items);
70+
all.AddRange(buttons);
71+
72+
return new CommandResponse(all, CommandResponseResult.Success);
73+
}
74+
75+
CommandResponse GetAlertText(IDictionary<string, object> parameters)
76+
{
77+
var alert = GetAppiumElement(parameters["element"]);
78+
if (alert is null)
79+
return CommandResponse.FailedEmptyResponse;
80+
81+
var text = AppiumQuery.ByClass("android.widget.TextView").FindElements(alert, _appiumApp);
82+
var strings = text.Select(t => t.GetText()).ToList();
83+
84+
return new CommandResponse(strings, CommandResponseResult.Success);
85+
}
86+
87+
static AppiumElement? GetAppiumElement(object element) =>
88+
element switch
89+
{
90+
AppiumElement appiumElement => appiumElement,
91+
AppiumDriverElement driverElement => driverElement.AppiumElement,
92+
_ => null
93+
};
94+
}
95+
}

0 commit comments

Comments
 (0)