Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Testing] Implement Pinch gestures in UITest extensions methods #25316

Merged
merged 8 commits into from
Oct 31, 2024
Merged
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
Original file line number Diff line number Diff line change
@@ -1,25 +1,37 @@
using NUnit.Framework;
#if ANDROID || IOS // TODO: Cannot find the element ZoomContainer on Desktop. Investigate the reason.
using NUnit.Framework;
using NUnit.Framework.Legacy;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Bugzilla57515 : _IssuesUITest
{
const string ZoomContainer = "zoomContainer";

public Bugzilla57515(TestDevice testDevice) : base(testDevice)
{
}

public override string Issue => "PinchGestureRecognizer not getting called on Android ";

// [Test]
// [Category(UITestCategories.Gestures)]
// [FailsOnIOSWhenRunningOnXamarinUITest]
// public void Bugzilla57515Test()
// {
// App.WaitForElement(ZoomContainer);
// App.WaitForElement("1");
// App.PinchToZoomIn(ZoomContainer);
// App.WaitForNoElement("1"); // The scale should have changed during the zoom
// }
}
[Test]
[Category(UITestCategories.Gestures)]
[FailsOnIOSWhenRunningOnXamarinUITest]
public void Bugzilla57515Test()
{
if (App is not AppiumApp app2 || app2 is null || app2.Driver is null)
{
throw new InvalidOperationException("Cannot run test. Missing driver to run quick tap actions.");
}

App.WaitForElement(ZoomContainer);
var zoomScale1 = app2.Driver.FindElement(OpenQA.Selenium.By.XPath("//*[@text='" + "1" + "']"));
ClassicAssert.AreEqual("1", zoomScale1.Text);
App.PinchToZoomIn(ZoomContainer);
var elements = app2.Driver.FindElements(OpenQA.Selenium.By.XPath("//*[@text='" + "1" + "']"));
ClassicAssert.AreEqual(0, elements.Count); // The scale should have changed during the zoom
}
}
#endif
261 changes: 261 additions & 0 deletions src/TestUtils/src/UITest.Appium/Actions/AppiumPinchToZoomActions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Interactions;
using OpenQA.Selenium.Interactions;
using UITest.Core;

namespace UITest.Appium
{
class AppiumPinchToZoomActions : ICommandExecutionGroup
{
const string PinchToZoomInCommand = "pinchToZoomIn";
const string PinchToZoomInCoordinatesCommand = "pinchToZoomInCoordinates";
const string PinchToZoomOutCommand = "pinchToZoomOut";
const string PinchToZoomOutCoordinatesCommand = "pinchToZoomOutCoordinates";

protected readonly AppiumApp _app;

readonly List<string> _commands = new()
{
PinchToZoomInCommand,
PinchToZoomInCoordinatesCommand,
PinchToZoomOutCommand,
PinchToZoomOutCoordinatesCommand,
};

public AppiumPinchToZoomActions(AppiumApp app)
{
_app = app;
}

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

public CommandResponse Execute(string commandName, IDictionary<string, object> parameters)
{
return commandName switch
{
PinchToZoomInCommand => PinchToZoomIn(parameters),
PinchToZoomInCoordinatesCommand => PinchToZoomInCoordinates(parameters),
PinchToZoomOutCommand => PinchToZoomOut(parameters),
PinchToZoomOutCoordinatesCommand => PinchToZoomOutCoordinates(parameters),
_ => CommandResponse.FailedEmptyResponse,
};
}

CommandResponse PinchToZoomIn(IDictionary<string, object> parameters)
{
try
{
parameters.TryGetValue("element", out var value);
var element = GetAppiumElement(value);

TimeSpan duration = (TimeSpan)parameters["duration"];

PinchToZoomIn(_app.Driver, element, duration);

return CommandResponse.SuccessEmptyResponse;
}
catch
{
return CommandResponse.FailedEmptyResponse;
}
}

CommandResponse PinchToZoomInCoordinates(IDictionary<string, object> parameters)
{
try
{
int x = (int)parameters["x"];
int y = (int)parameters["y"];
TimeSpan duration = (TimeSpan)parameters["duration"];

PinchToZoomInCoordinates(_app.Driver, x, y, duration);

return CommandResponse.SuccessEmptyResponse;
}
catch
{
return CommandResponse.FailedEmptyResponse;
}
}

CommandResponse PinchToZoomOut(IDictionary<string, object> parameters)
{
try
{
parameters.TryGetValue("element", out var value);
var element = GetAppiumElement(value);

TimeSpan duration = (TimeSpan)parameters["duration"];

PinchToZoomOut(_app.Driver, element, duration);

return CommandResponse.SuccessEmptyResponse;
}
catch
{
return CommandResponse.FailedEmptyResponse;
}
}

CommandResponse PinchToZoomOutCoordinates(IDictionary<string, object> parameters)
{
try
{
int x = (int)parameters["x"];
int y = (int)parameters["y"];
TimeSpan duration = (TimeSpan)parameters["duration"];

PinchToZoomOutCoordinates(_app.Driver, x, y, duration);

return CommandResponse.SuccessEmptyResponse;
}
catch
{
return CommandResponse.FailedEmptyResponse;
}
}

static void PinchToZoomIn(AppiumDriver driver, AppiumElement? element, TimeSpan duration)
{
var position = element is not null ? element.Location : System.Drawing.Point.Empty;
var size = element is not null ? element.Size : driver.Manage().Window.Size;

int touch1StartX = position.X + size.Width / 2;
int touch1StartY = position.Y + size.Height / 2;
int touch1EndX = position.X;
int touch1EndY = position.Y;

OpenQA.Selenium.Appium.Interactions.PointerInputDevice touch1 = new OpenQA.Selenium.Appium.Interactions.PointerInputDevice(PointerKind.Touch);
ActionSequence touch1Sequence = new ActionSequence(touch1, 0);
touch1Sequence.AddAction(touch1.CreatePointerMove(CoordinateOrigin.Viewport, touch1StartX, touch1StartY, TimeSpan.Zero));
touch1Sequence.AddAction(touch1.CreatePointerDown(PointerButton.TouchContact));
touch1Sequence.AddAction(touch1.CreatePointerMove(CoordinateOrigin.Viewport, touch1EndX, touch1EndY, duration));
touch1Sequence.AddAction(touch1.CreatePointerUp(PointerButton.TouchContact));

int touch2StartX = position.X + size.Width / 2;
int touch2StartY = position.Y + size.Height / 2;
int touch2EndX = position.X + size.Width;
int touch2EndY = position.Y + size.Height;

OpenQA.Selenium.Appium.Interactions.PointerInputDevice touch2 = new OpenQA.Selenium.Appium.Interactions.PointerInputDevice(PointerKind.Touch);
ActionSequence touch2Sequence = new ActionSequence(touch2, 0);
touch2Sequence.AddAction(touch2.CreatePointerMove(CoordinateOrigin.Viewport, touch2StartX, touch2StartY, TimeSpan.Zero));
touch2Sequence.AddAction(touch2.CreatePointerDown(PointerButton.TouchContact));
touch2Sequence.AddAction(touch2.CreatePointerMove(CoordinateOrigin.Viewport, touch2EndX, touch2EndY, duration));
touch2Sequence.AddAction(touch2.CreatePointerUp(PointerButton.TouchContact));

driver.PerformActions([touch1Sequence, touch2Sequence]);
}

static void PinchToZoomInCoordinates(AppiumDriver driver, int x, int y, TimeSpan duration)
{
const int distance = 50;

int touch1StartX = x;
int touch1StartY = y;
int touch1EndX = x - distance;
int touch1EndY = y - distance;

OpenQA.Selenium.Appium.Interactions.PointerInputDevice touch1 = new OpenQA.Selenium.Appium.Interactions.PointerInputDevice(PointerKind.Touch);
ActionSequence touch1Sequence = new ActionSequence(touch1, 0);
touch1Sequence.AddAction(touch1.CreatePointerMove(CoordinateOrigin.Viewport, touch1StartX, touch1StartY, TimeSpan.Zero));
touch1Sequence.AddAction(touch1.CreatePointerDown(PointerButton.TouchContact));
touch1Sequence.AddAction(touch1.CreatePointerMove(CoordinateOrigin.Viewport, touch1EndX, touch1EndY, duration));
touch1Sequence.AddAction(touch1.CreatePointerUp(PointerButton.TouchContact));

int touch2StartX = x;
int touch2StartY = y;
int touch2EndX = x + distance;
int touch2EndY = y + distance;

OpenQA.Selenium.Appium.Interactions.PointerInputDevice touch2 = new OpenQA.Selenium.Appium.Interactions.PointerInputDevice(PointerKind.Touch);
ActionSequence touch2Sequence = new ActionSequence(touch2, 0);
touch2Sequence.AddAction(touch2.CreatePointerMove(CoordinateOrigin.Viewport, touch2StartX, touch2StartY, TimeSpan.Zero));
touch2Sequence.AddAction(touch2.CreatePointerDown(PointerButton.TouchContact));
touch2Sequence.AddAction(touch2.CreatePointerMove(CoordinateOrigin.Viewport, touch2EndX, touch2EndY, duration));
touch2Sequence.AddAction(touch2.CreatePointerUp(PointerButton.TouchContact));

driver.PerformActions([touch1Sequence, touch2Sequence]);
}

static void PinchToZoomOut(AppiumDriver driver, AppiumElement? element, TimeSpan duration)
{
var position = element is not null ? element.Location : System.Drawing.Point.Empty;
var size = element is not null ? element.Size : driver.Manage().Window.Size;

int touch1StartX = position.X;
int touch1StartY = position.Y;
int touch1EndX = position.X + size.Width / 2;
int touch1EndY = position.Y + size.Height / 2;

OpenQA.Selenium.Appium.Interactions.PointerInputDevice touch1 = new OpenQA.Selenium.Appium.Interactions.PointerInputDevice(PointerKind.Touch);
ActionSequence touch1Sequence = new ActionSequence(touch1, 0);
touch1Sequence.AddAction(touch1.CreatePointerMove(CoordinateOrigin.Viewport, touch1StartX, touch1StartY, TimeSpan.Zero));
touch1Sequence.AddAction(touch1.CreatePointerDown(PointerButton.TouchContact));
touch1Sequence.AddAction(touch1.CreatePointerMove(CoordinateOrigin.Viewport, touch1EndX, touch1EndY, duration));
touch1Sequence.AddAction(touch1.CreatePointerUp(PointerButton.TouchContact));

int touch2StartX = position.X + size.Width;
int touch2StartY = position.Y + size.Height;
int touch2EndX = position.X + size.Width / 2;
int touch2EndY = position.Y + size.Height / 2;

OpenQA.Selenium.Appium.Interactions.PointerInputDevice touch2 = new OpenQA.Selenium.Appium.Interactions.PointerInputDevice(PointerKind.Touch);
ActionSequence touch2Sequence = new ActionSequence(touch2, 0);
touch2Sequence.AddAction(touch2.CreatePointerMove(CoordinateOrigin.Viewport, touch2StartX, touch2StartY, TimeSpan.Zero));
touch2Sequence.AddAction(touch2.CreatePointerDown(PointerButton.TouchContact));
touch2Sequence.AddAction(touch2.CreatePointerMove(CoordinateOrigin.Viewport, touch2EndX, touch2EndY, duration));
touch2Sequence.AddAction(touch2.CreatePointerUp(PointerButton.TouchContact));

driver.PerformActions([touch1Sequence, touch2Sequence]);
}

static void PinchToZoomOutCoordinates(AppiumDriver driver, int x, int y, TimeSpan duration)
{
const int distance = 50;

int touch1StartX = x - distance;
int touch1StartY = y - distance;
int touch1EndX = x;
int touch1EndY = y;

OpenQA.Selenium.Appium.Interactions.PointerInputDevice touch1 = new OpenQA.Selenium.Appium.Interactions.PointerInputDevice(PointerKind.Touch);
ActionSequence touch1Sequence = new ActionSequence(touch1, 0);
touch1Sequence.AddAction(touch1.CreatePointerMove(CoordinateOrigin.Viewport, touch1StartX, touch1StartY, TimeSpan.Zero));
touch1Sequence.AddAction(touch1.CreatePointerDown(PointerButton.TouchContact));
touch1Sequence.AddAction(touch1.CreatePointerMove(CoordinateOrigin.Viewport, touch1EndX, touch1EndY, duration));
touch1Sequence.AddAction(touch1.CreatePointerUp(PointerButton.TouchContact));

int touch2StartX = x + distance;
int touch2StartY = y + distance;
int touch2EndX = x;
int touch2EndY = y;

OpenQA.Selenium.Appium.Interactions.PointerInputDevice touch2 = new OpenQA.Selenium.Appium.Interactions.PointerInputDevice(PointerKind.Touch);
ActionSequence touch2Sequence = new ActionSequence(touch2, 0);
touch2Sequence.AddAction(touch2.CreatePointerMove(CoordinateOrigin.Viewport, touch2StartX, touch2StartY, TimeSpan.Zero));
touch2Sequence.AddAction(touch2.CreatePointerDown(PointerButton.TouchContact));
touch2Sequence.AddAction(touch2.CreatePointerMove(CoordinateOrigin.Viewport, touch2EndX, touch2EndY, duration));
touch2Sequence.AddAction(touch2.CreatePointerUp(PointerButton.TouchContact));

driver.PerformActions([touch1Sequence, touch2Sequence]);
}

static AppiumElement? GetAppiumElement(object? element)
{
if (element is AppiumElement appiumElement)
{
return appiumElement;
}
else if (element is AppiumDriverElement driverElement)
{
return driverElement.AppiumElement;
}

return null;
}
}
}
1 change: 1 addition & 0 deletions src/TestUtils/src/UITest.Appium/AppiumApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public AppiumApp(AppiumDriver driver, IConfig config)
_commandExecutor.AddCommandGroup(new AppiumGeneralActions());
_commandExecutor.AddCommandGroup(new AppiumDeviceActions(this));
_commandExecutor.AddCommandGroup(new AppiumVirtualKeyboardActions(this));
_commandExecutor.AddCommandGroup(new AppiumPinchToZoomActions(this));
_commandExecutor.AddCommandGroup(new AppiumSliderActions(this));
_commandExecutor.AddCommandGroup(new AppiumSwipeActions(this));
_commandExecutor.AddCommandGroup(new AppiumScrollActions(this));
Expand Down
70 changes: 70 additions & 0 deletions src/TestUtils/src/UITest.Appium/HelperExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,76 @@ public static void DragAndDrop(this IApp app, string dragSource, string dragTarg
});
}

/// <summary>
/// Performs a pinch gestures on the matched element to zoom the view in.
/// If multiple elements are matched, the first one will be used.
/// </summary>
/// <param name="app">Represents the main gateway to interact with an app.</param>
/// <param name="element">Element to zoom in.</param>
/// <param name="duration">The TimeSpan duration of the pinch gesture.</param>
public static void PinchToZoomIn(this IApp app, string element, TimeSpan? duration = null)
{
var elementToPinchToZoomIn = app.FindElement(element);

app.CommandExecutor.Execute("pinchToZoomIn", new Dictionary<string, object>
{
{ "element", elementToPinchToZoomIn },
{ "duration", duration ?? TimeSpan.FromSeconds(1) }
});
}

/// <summary>
/// Performs a pinch gestures to zoom the view in on the given coordinates.
/// </summary>
/// <param name="app">Represents the main gateway to interact with an app.</param>
/// <param name="x">The x coordinate of the center of the pinch.</param>
/// <param name="y">The y coordinate of the center of the pinch.</param>
/// <param name="duration">The TimeSpan duration of the pinch gesture.</param>
public static void PinchToZoomInCoordinates(this IApp app, float x, float y, TimeSpan? duration = null)
{
app.CommandExecutor.Execute("pinchToZoomInCoordinates", new Dictionary<string, object>
{
{ "x", x },
{ "y", y },
{ "duration", duration ?? TimeSpan.FromSeconds(1) }
});
}

/// <summary>
/// Performs a pinch gestures on the matched element to zoom the view out.
/// If multiple elements are matched, the first one will be used.
/// </summary>
/// <param name="app">Represents the main gateway to interact with an app.</param>
/// <param name="element">Element to zoom in.</param>
/// <param name="duration">The TimeSpan duration of the pinch gesture.</param>
public static void PinchToZoomOut(this IApp app, string element, TimeSpan? duration = null)
{
var elementToPinchToZoomOut = app.FindElement(element);

app.CommandExecutor.Execute("pinchToZoomOut", new Dictionary<string, object>
{
{ "element", elementToPinchToZoomOut },
{ "duration", duration ?? TimeSpan.FromSeconds(1) }
});
}

/// <summary>
/// Performs a pinch gestures to zoom the view out on the given coordinates.
/// </summary>
/// <param name="app">Represents the main gateway to interact with an app.</param>
/// <param name="x">The x coordinate of the center of the pinch.</param>
/// <param name="y">The y coordinate of the center of the pinch.</param>
/// <param name="duration">The TimeSpan duration of the pinch gesture.</param>
public static void PinchToZoomOutCoordinates(this IApp app, float x, float y, TimeSpan? duration = null)
{
app.CommandExecutor.Execute("pinchToZoomOutCoordinates", new Dictionary<string, object>
{
{ "x", x },
{ "y", y },
{ "duration", duration ?? TimeSpan.FromSeconds(1) }
});
}

/// <summary>
/// Scroll until an element that matches the toElementId is shown on the screen.
/// </summary>
Expand Down
Loading