Skip to content

Commit

Permalink
Add changes from MAUI PR 19629
Browse files Browse the repository at this point in the history
  • Loading branch information
jfversluis committed Jul 3, 2024
1 parent e9fcfb1 commit 2d6ffef
Show file tree
Hide file tree
Showing 10 changed files with 359 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using OpenQA.Selenium.Appium;
using Plugin.Maui.UITestHelpers.Core;

namespace Plugin.Maui.UITestHelpers.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
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using OpenQA.Selenium.Appium;
using Plugin.Maui.UITestHelpers.Core;

namespace Plugin.Maui.UITestHelpers.Appium;

public abstract class AppiumAppleAlertActions : ICommandExecutionGroup
{
const string GetAlertsCommand = "getAlerts";
const string GetAlertButtonsCommand = "getAlertButtons";
const string GetAlertTextCommand = "getAlertText";

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

protected readonly AppiumApp _appiumApp;

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

public virtual bool IsCommandSupported(string commandName) =>
_commands.Contains(commandName, StringComparer.OrdinalIgnoreCase);

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

protected abstract IReadOnlyCollection<IUIElement> OnGetAlerts(AppiumApp appiumApp, IDictionary<string, object> parameters);

CommandResponse GetAlerts(IDictionary<string, object> parameters)
{
var alerts = OnGetAlerts(_appiumApp, parameters);

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 buttons = AppiumQuery.ByClass("XCUIElementTypeButton").FindElements(alert, _appiumApp);

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

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

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

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

protected static AppiumElement? GetAppiumElement(object element) =>
element switch
{
AppiumElement appiumElement => appiumElement,
AppiumDriverElement driverElement => driverElement.AppiumElement,
_ => null
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using Plugin.Maui.UITestHelpers.Core;

namespace Plugin.Maui.UITestHelpers.Appium;

public class AppiumCatalystAlertActions : AppiumAppleAlertActions
{
// Selects the inner "popover contents" of a popover window.
const string PossibleActionSheetXPath =
"/XCUIElementTypeApplication/XCUIElementTypeWindow/XCUIElementTypePopover";

const string DismissAlertCommand = "dismissAlert";

readonly List<string> _commands = new()
{
DismissAlertCommand,
};

public AppiumCatalystAlertActions(AppiumApp appiumApp)
: base(appiumApp)
{
}

public override bool IsCommandSupported(string commandName) =>
_commands.Contains(commandName, StringComparer.OrdinalIgnoreCase) || base.IsCommandSupported(commandName);

public override CommandResponse Execute(string commandName, IDictionary<string, object> parameters) =>
commandName switch
{
DismissAlertCommand => DismissAlert(parameters),
_ => base.Execute(commandName, parameters),
};

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

// XCUIElementTypePopover == 18
if (!"18".Equals(alert.GetAttribute("elementType"), StringComparison.OrdinalIgnoreCase))
return CommandResponse.FailedEmptyResponse;

var dismissRegions = AppiumQuery.ById("PopoverDismissRegion").FindElements(_appiumApp).ToList();
for (var i = dismissRegions.Count - 1; i >= 0; i--)
{
var region = GetAppiumElement(dismissRegions[i])!;
if ("true".Equals(region.GetAttribute("enabled"), StringComparison.OrdinalIgnoreCase))
{
region.Click();
return CommandResponse.SuccessEmptyResponse;
}
}

return CommandResponse.FailedEmptyResponse;
}

protected override IReadOnlyCollection<IUIElement> OnGetAlerts(AppiumApp appiumApp, IDictionary<string, object> parameters)
{
// Catalyst uses action sheets for alerts and macOS 14
var alerts = appiumApp.FindElements(AppiumQuery.ByClass("XCUIElementTypeSheet"));

// But it also uses popovers for action sheets on macOS 13
if (alerts is null || alerts.Count == 0)
alerts = appiumApp.FindElements(AppiumQuery.ByXPath(PossibleActionSheetXPath));

return alerts;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Plugin.Maui.UITestHelpers.Core;

namespace Plugin.Maui.UITestHelpers.Appium;

public class AppiumIOSAlertActions : AppiumAppleAlertActions
{
// Selects VISIBLE "Other" elements that are the direct child of
// a VISIBLE window AND are OVERLAYED on top of the first window.
const string PossibleAlertXPath =
"//XCUIElementTypeWindow[@visible='true']/XCUIElementTypeOther[@visible='true' and @index > 0]";

public AppiumIOSAlertActions(AppiumApp appiumApp)
: base(appiumApp)
{
}

protected override IReadOnlyCollection<IUIElement> OnGetAlerts(AppiumApp appiumApp, IDictionary<string, object> parameters)
{
// First try the type used on iOS.
var alerts = appiumApp.FindElements(AppiumQuery.ByClass("XCUIElementTypeAlert"));

// It appears iOS sometimes uses the XCUIElementTypeOther class for action sheets
// so we need a way to do a more fuzzy check.
if (alerts is null || alerts.Count == 0)
alerts = appiumApp.FindElements(AppiumQuery.ByXPath(PossibleAlertXPath));

return alerts;
}
}
1 change: 1 addition & 0 deletions src/Plugin.Maui.UITestHelpers.Appium/AppiumAndroidApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ private AppiumAndroidApp(Uri remoteAddress, IConfig config)
: base(new AndroidDriver(remoteAddress, GetOptions(config)), config)
{
_commandExecutor.AddCommandGroup(new AppiumAndroidVirtualKeyboardActions(this));
_commandExecutor.AddCommandGroup(new AppiumAndroidAlertActions(this));
}

public static AppiumAndroidApp CreateAndroidApp(Uri remoteAddress, IConfig config)
Expand Down
1 change: 1 addition & 0 deletions src/Plugin.Maui.UITestHelpers.Appium/AppiumCatalystApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public AppiumCatalystApp(Uri remoteAddress, IConfig config)
{
_commandExecutor.AddCommandGroup(new AppiumCatalystMouseActions(this));
_commandExecutor.AddCommandGroup(new AppiumCatalystTouchActions(this));
_commandExecutor.AddCommandGroup(new AppiumCatalystAlertActions(this));
}

public override ApplicationState AppState
Expand Down
1 change: 1 addition & 0 deletions src/Plugin.Maui.UITestHelpers.Appium/AppiumIOSApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public AppiumIOSApp(Uri remoteAddress, IConfig config)
_commandExecutor.AddCommandGroup(new AppiumIOSMouseActions(this));
_commandExecutor.AddCommandGroup(new AppiumIOSTouchActions(this));
_commandExecutor.AddCommandGroup(new AppiumIOSVirtualKeyboardActions(this));
_commandExecutor.AddCommandGroup(new AppiumIOSAlertActions(this));
}

public override ApplicationState AppState
Expand Down
13 changes: 13 additions & 0 deletions src/Plugin.Maui.UITestHelpers.Appium/AppiumQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ public class AppiumQuery : IQuery
const string IdQuery = IdToken + "={0}";
const string NameQuery = NameToken + "={0}";
const string AccessibilityQuery = AccessibilityToken + "={0}";
const string XPathToken = "xpath";
const string ClassQuery = ClassToken + "={0}";
const string XPathQuery = XPathToken + "={0}";
readonly string _queryStr;

public AppiumQuery(string queryStr)
Expand Down Expand Up @@ -48,6 +50,11 @@ IQuery IQuery.ByName(string nameQuery)
return new AppiumQuery(this, string.Format(NameQuery, nameQuery));
}

IQuery IQuery.ByXPath(string xpath)
{
return new AppiumQuery(this, string.Format(XPathQuery, Uri.EscapeDataString(xpath)));
}

public static AppiumQuery ById(string id)
{
return new AppiumQuery(string.Format(IdQuery, id));
Expand All @@ -58,6 +65,11 @@ public static AppiumQuery ByName(string nameQuery)
return new AppiumQuery(string.Format(NameQuery, nameQuery));
}

public static AppiumQuery ByXPath(string xpath)
{
return new AppiumQuery(string.Format(XPathQuery, Uri.EscapeDataString(xpath)));
}

public static AppiumQuery ByAccessibilityId(string id)
{
return new AppiumQuery(string.Format(AccessibilityQuery, id));
Expand Down Expand Up @@ -184,6 +196,7 @@ private static By GetQueryBy(string token, string value)
NameToken => MobileBy.Name(value),
AccessibilityToken => MobileBy.AccessibilityId(value),
IdToken => MobileBy.Id(value),
XPathToken => MobileBy.XPath(Uri.UnescapeDataString(value)),
_ => throw new ArgumentException("Unknown query type"),
};
}
Expand Down
Loading

0 comments on commit 2d6ffef

Please sign in to comment.