Skip to content

Commit a11555b

Browse files
authored
[Testing] Implement ContextMenu UITest extension methods (#25340)
* Add ContextMenu UITest extension methods * Simplified test
1 parent 6e56d11 commit a11555b

File tree

9 files changed

+368
-15
lines changed

9 files changed

+368
-15
lines changed
Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using NUnit.Framework;
1+
#if ANDROID
2+
using NUnit.Framework;
23
using UITest.Appium;
34
using UITest.Core;
45

@@ -12,17 +13,13 @@ public Bugzilla59580(TestDevice testDevice) : base(testDevice)
1213

1314
public override string Issue => "Raising Command.CanExecutChanged causes crash on Android";
1415

15-
// [Test]
16-
// [Category(UITestCategories.TableView)]
17-
// [FailsOnIOSWhenRunningOnXamarinUITest]
18-
// public void RaisingCommandCanExecuteChangedCausesCrashOnAndroid()
19-
// {
20-
// App.WaitForElement(c => c.Marked("Cell"));
21-
22-
// App.ActivateContextMenu("Cell");
23-
24-
// App.WaitForElement(c => c.Marked("Fire CanExecuteChanged"));
25-
// App.Tap(c => c.Marked("Fire CanExecuteChanged"));
26-
// App.WaitForElement("Cell");
27-
// }
28-
}
16+
[Test]
17+
[Category(UITestCategories.TableView)]
18+
[FailsOnIOSWhenRunningOnXamarinUITest]
19+
public void RaisingCommandCanExecuteChangedCausesCrashOnAndroid()
20+
{
21+
App.ActivateContextMenu("Cell");
22+
App.Tap("Fire CanExecuteChanged");
23+
}
24+
}
25+
#endif
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using OpenQA.Selenium.Appium;
2+
using OpenQA.Selenium.Appium.Interactions;
3+
using OpenQA.Selenium.Interactions;
4+
using UITest.Core;
5+
6+
namespace UITest.Appium
7+
{
8+
public class AppiumAndroidContextMenuActions : ICommandExecutionGroup
9+
{
10+
const string ActivateContextMenuCommand = "activateContextMenu";
11+
const string DismissContextMenuCommand = "dismissContextMenu";
12+
13+
protected readonly AppiumApp _app;
14+
readonly List<string> _commands = new()
15+
{
16+
ActivateContextMenuCommand,
17+
DismissContextMenuCommand,
18+
};
19+
20+
public AppiumAndroidContextMenuActions(AppiumApp app)
21+
{
22+
_app = app;
23+
}
24+
25+
public bool IsCommandSupported(string commandName)
26+
{
27+
return _commands.Contains(commandName, StringComparer.OrdinalIgnoreCase);
28+
}
29+
30+
public CommandResponse Execute(string commandName, IDictionary<string, object> parameters)
31+
{
32+
return commandName switch
33+
{
34+
ActivateContextMenuCommand => ActivateContextMenu(parameters),
35+
DismissContextMenuCommand => DismissContextMenu(parameters),
36+
_ => CommandResponse.FailedEmptyResponse,
37+
};
38+
}
39+
40+
protected CommandResponse ActivateContextMenu(IDictionary<string, object> parameters)
41+
{
42+
parameters.TryGetValue("element", out var value);
43+
44+
if (value is null)
45+
return CommandResponse.FailedEmptyResponse;
46+
47+
string elementString = (string)value;
48+
var element = GetAppiumElement(elementString);
49+
50+
// If cannot find an element by Id, just try to find using the text.
51+
if (element is null)
52+
element = _app.Driver.FindElement(OpenQA.Selenium.By.XPath("//*[@text='" + elementString + "']"));
53+
54+
if (element is not null)
55+
{
56+
OpenQA.Selenium.Appium.Interactions.PointerInputDevice touchDevice = new OpenQA.Selenium.Appium.Interactions.PointerInputDevice(PointerKind.Touch);
57+
var longPress = new ActionSequence(touchDevice, 0);
58+
59+
longPress.AddAction(touchDevice.CreatePointerMove(element, 0, 0, TimeSpan.FromMilliseconds(0)));
60+
longPress.AddAction(touchDevice.CreatePointerDown(PointerButton.TouchContact));
61+
longPress.AddAction(touchDevice.CreatePointerMove(element, 0, 0, TimeSpan.FromMilliseconds(2000)));
62+
longPress.AddAction(touchDevice.CreatePointerUp(PointerButton.TouchContact));
63+
_app.Driver.PerformActions(new List<ActionSequence> { longPress });
64+
65+
return CommandResponse.SuccessEmptyResponse;
66+
}
67+
68+
return CommandResponse.FailedEmptyResponse;
69+
}
70+
71+
protected CommandResponse DismissContextMenu(IDictionary<string, object> parameters)
72+
{
73+
_app.Back();
74+
75+
return CommandResponse.SuccessEmptyResponse;
76+
}
77+
78+
static AppiumElement? GetAppiumElement(object element)
79+
{
80+
if (element is AppiumElement appiumElement)
81+
{
82+
return appiumElement;
83+
}
84+
else if (element is AppiumDriverElement driverElement)
85+
{
86+
return driverElement.AppiumElement;
87+
}
88+
89+
return null;
90+
}
91+
}
92+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
using System.Drawing;
2+
using OpenQA.Selenium.Appium;
3+
using OpenQA.Selenium.Appium.Interactions;
4+
using OpenQA.Selenium.Interactions;
5+
using UITest.Core;
6+
7+
namespace UITest.Appium
8+
{
9+
public class AppiumAppleContextMenuActions : ICommandExecutionGroup
10+
{
11+
const string ActivateContextMenuCommand = "activateContextMenu";
12+
const string DismissContextMenuCommand = "dismissContextMenu";
13+
14+
protected readonly AppiumApp _app;
15+
readonly List<string> _commands = new()
16+
{
17+
ActivateContextMenuCommand,
18+
DismissContextMenuCommand,
19+
};
20+
21+
public AppiumAppleContextMenuActions(AppiumApp app)
22+
{
23+
_app = app;
24+
}
25+
26+
public bool IsCommandSupported(string commandName)
27+
{
28+
return _commands.Contains(commandName, StringComparer.OrdinalIgnoreCase);
29+
}
30+
31+
public CommandResponse Execute(string commandName, IDictionary<string, object> parameters)
32+
{
33+
return commandName switch
34+
{
35+
ActivateContextMenuCommand => ActivateContextMenu(parameters),
36+
DismissContextMenuCommand => DismissContextMenu(parameters),
37+
_ => CommandResponse.FailedEmptyResponse,
38+
};
39+
}
40+
41+
protected CommandResponse ActivateContextMenu(IDictionary<string, object> parameters)
42+
{
43+
parameters.TryGetValue("element", out var value);
44+
45+
if (value is null)
46+
return CommandResponse.FailedEmptyResponse;
47+
48+
string elementString = (string)value;
49+
var element = GetAppiumElement(elementString);
50+
51+
// If cannot find an element by Id, just try to find using the text.
52+
if (element is null)
53+
element = _app.Driver.FindElement(OpenQA.Selenium.By.XPath("//*[@text='" + elementString + "']"));
54+
55+
if (element is not null)
56+
{
57+
var target = _app.WaitForElement(element.Id);
58+
var rect = target.GetRect();
59+
var rootViewRect = GetRootViewRect(_app);
60+
61+
var centerX = rect.Width / 2;
62+
var centerY = rect.Height / 2;
63+
var width = Math.Max(250, rect.Width);
64+
65+
if ((rect.X + width) > rootViewRect.Width)
66+
{
67+
width = rootViewRect.Width - rect.X;
68+
}
69+
70+
int fromX = (int)(rect.X + (0.95f * width));
71+
int fromY = centerY;
72+
int toX = (int)(rect.X + (0.05f * width));
73+
int toY = centerY;
74+
75+
OpenQA.Selenium.Appium.Interactions.PointerInputDevice touchDevice = new OpenQA.Selenium.Appium.Interactions.PointerInputDevice(PointerKind.Touch);
76+
var dragSequence = new ActionSequence(touchDevice, 0);
77+
dragSequence.AddAction(touchDevice.CreatePointerMove(CoordinateOrigin.Viewport, fromX, fromY, TimeSpan.Zero));
78+
dragSequence.AddAction(touchDevice.CreatePointerDown(PointerButton.TouchContact));
79+
dragSequence.AddAction(touchDevice.CreatePointerMove(CoordinateOrigin.Viewport, toX, toY, TimeSpan.FromMilliseconds(250)));
80+
dragSequence.AddAction(touchDevice.CreatePointerUp(PointerButton.TouchContact));
81+
_app.Driver.PerformActions([dragSequence]);
82+
83+
return CommandResponse.SuccessEmptyResponse;
84+
}
85+
86+
return CommandResponse.FailedEmptyResponse;
87+
}
88+
89+
protected CommandResponse DismissContextMenu(IDictionary<string, object> parameters)
90+
{
91+
try
92+
{
93+
var rootViewRect = GetRootViewRect(_app);
94+
95+
var centerX = rootViewRect.Width / 2;
96+
var centerY = rootViewRect.Height / 2;
97+
98+
_app.TapCoordinates(centerX, centerY);
99+
100+
return CommandResponse.SuccessEmptyResponse;
101+
}
102+
catch
103+
{
104+
return CommandResponse.FailedEmptyResponse;
105+
}
106+
}
107+
108+
static AppiumElement? GetAppiumElement(object element)
109+
{
110+
if (element is AppiumElement appiumElement)
111+
{
112+
return appiumElement;
113+
}
114+
else if (element is AppiumDriverElement driverElement)
115+
{
116+
return driverElement.AppiumElement;
117+
}
118+
119+
return null;
120+
}
121+
122+
static Rectangle GetRootViewRect(AppiumApp app)
123+
{
124+
var rootElement = app.FindElement(AppiumQuery.ByXPath("/*"));
125+
var rootViewRect = rootElement.GetRect();
126+
127+
return rootViewRect;
128+
}
129+
}
130+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
using System.Drawing;
2+
using OpenQA.Selenium.Appium;
3+
using UITest.Core;
4+
5+
namespace UITest.Appium
6+
{
7+
public class AppiumWindowsContextMenuActions : ICommandExecutionGroup
8+
{
9+
const string ActivateContextMenuCommand = "activateContextMenu";
10+
const string DismissContextMenuCommand = "dismissContextMenu";
11+
12+
protected readonly AppiumApp _app;
13+
readonly List<string> _commands = new()
14+
{
15+
ActivateContextMenuCommand,
16+
DismissContextMenuCommand,
17+
};
18+
19+
public AppiumWindowsContextMenuActions(AppiumApp app)
20+
{
21+
_app = app;
22+
}
23+
24+
public bool IsCommandSupported(string commandName)
25+
{
26+
return _commands.Contains(commandName, StringComparer.OrdinalIgnoreCase);
27+
}
28+
29+
public CommandResponse Execute(string commandName, IDictionary<string, object> parameters)
30+
{
31+
return commandName switch
32+
{
33+
ActivateContextMenuCommand => ActivateContextMenu(parameters),
34+
DismissContextMenuCommand => DismissContextMenu(parameters),
35+
_ => CommandResponse.FailedEmptyResponse,
36+
};
37+
}
38+
39+
protected CommandResponse ActivateContextMenu(IDictionary<string, object> parameters)
40+
{
41+
parameters.TryGetValue("element", out var value);
42+
43+
if (value is null)
44+
return CommandResponse.FailedEmptyResponse;
45+
46+
string elementString = (string)value;
47+
var element = GetAppiumElement(elementString);
48+
49+
// If cannot find an element by Id, just try to find using the text.
50+
if (element is null)
51+
element = _app.Driver.FindElement(OpenQA.Selenium.By.XPath("//*[@text='" + elementString + "']"));
52+
53+
if (element is not null)
54+
{
55+
_app.Driver.ExecuteScript("windows: click", new Dictionary<string, object>
56+
{
57+
{ "elementId", element.Id },
58+
{ "button", "right" },
59+
});
60+
61+
return CommandResponse.SuccessEmptyResponse;
62+
}
63+
64+
return CommandResponse.FailedEmptyResponse;
65+
}
66+
67+
protected CommandResponse DismissContextMenu(IDictionary<string, object> parameters)
68+
{
69+
try
70+
{
71+
var screenbounds = GetRootViewRect(_app);
72+
73+
var centerX = screenbounds.Width / 2;
74+
var centerY = screenbounds.Height / 2;
75+
76+
_app.TapCoordinates(centerX, centerY);
77+
78+
return CommandResponse.SuccessEmptyResponse;
79+
}
80+
catch
81+
{
82+
return CommandResponse.FailedEmptyResponse;
83+
}
84+
}
85+
86+
static AppiumElement? GetAppiumElement(object element)
87+
{
88+
if (element is AppiumElement appiumElement)
89+
{
90+
return appiumElement;
91+
}
92+
else if (element is AppiumDriverElement driverElement)
93+
{
94+
return driverElement.AppiumElement;
95+
}
96+
97+
return null;
98+
}
99+
100+
static Rectangle GetRootViewRect(AppiumApp app)
101+
{
102+
var rootElement = app.FindElement(AppiumQuery.ByXPath("/*"));
103+
var rootViewRect = rootElement.GetRect();
104+
105+
return rootViewRect;
106+
}
107+
}
108+
}

src/TestUtils/src/UITest.Appium/AppiumAndroidApp.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ private AppiumAndroidApp(Uri remoteAddress, IConfig config)
1414
_commandExecutor.AddCommandGroup(new AppiumAndroidSpecificActions(this));
1515
_commandExecutor.AddCommandGroup(new AppiumAndroidVirtualKeyboardActions(this));
1616
_commandExecutor.AddCommandGroup(new AppiumAndroidAlertActions(this));
17+
_commandExecutor.AddCommandGroup(new AppiumAndroidContextMenuActions(this));
1718
_commandExecutor.AddCommandGroup(new AppiumAndroidStepperActions(this));
1819
}
1920

src/TestUtils/src/UITest.Appium/AppiumCatalystApp.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public AppiumCatalystApp(Uri remoteAddress, IConfig config)
1313
_commandExecutor.AddCommandGroup(new AppiumCatalystMouseActions(this));
1414
_commandExecutor.AddCommandGroup(new AppiumCatalystTouchActions(this));
1515
_commandExecutor.AddCommandGroup(new AppiumCatalystAlertActions(this));
16+
_commandExecutor.AddCommandGroup(new AppiumAppleContextMenuActions(this));
1617
}
1718

1819
public override ApplicationState AppState

src/TestUtils/src/UITest.Appium/AppiumIOSApp.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public AppiumIOSApp(Uri remoteAddress, IConfig config)
1717
_commandExecutor.AddCommandGroup(new AppiumIOSVirtualKeyboardActions(this));
1818
_commandExecutor.AddCommandGroup(new AppiumIOSThemeChangeAction(this));
1919
_commandExecutor.AddCommandGroup(new AppiumIOSAlertActions(this));
20+
_commandExecutor.AddCommandGroup(new AppiumAppleContextMenuActions(this));
2021
_commandExecutor.AddCommandGroup(new AppiumIOSThemeChangeAction(this));
2122
_commandExecutor.AddCommandGroup(new AppiumIOSStepperActions(this));
2223
}

0 commit comments

Comments
 (0)