Skip to content

Commit

Permalink
[Testing] Implement Pinch gestures in UITest extensions methods (#25316)
Browse files Browse the repository at this point in the history
* Implement Pinch gestures in UITest extensions methods

* Updated test

* Updated test

* More changes

* Apply suggestions from code review

---------

Co-authored-by: Gerald Versluis <gerald.versluis@microsoft.com>
  • Loading branch information
jsuarezruiz and jfversluis authored Oct 31, 2024
1 parent 2077463 commit d91ba8e
Show file tree
Hide file tree
Showing 4 changed files with 356 additions and 12 deletions.
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 @@ -24,6 +24,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 @@ -490,6 +490,76 @@ internal static void DragAndDrop(this IApp app, IUIElement? dragSourceElement, I
}
}

/// <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

0 comments on commit d91ba8e

Please sign in to comment.