Skip to content

Commit 9fb6340

Browse files
authored
[#26] Implementing TouchActions
* [#26] Implementing TouchActions - Adding TouchActions - Adding ElementTouchActions - Updating Element - Adding tests - Updating Localization Settings - Updating settings - Updating project dependencies
1 parent 3273eea commit 9fb6340

File tree

22 files changed

+462
-25
lines changed

22 files changed

+462
-25
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.Drawing;
2+
3+
namespace Aquality.Appium.Mobile.Actions
4+
{
5+
public interface ITouchActions
6+
{
7+
/// <summary>
8+
/// Swipes from start point to end point using TouchAction.
9+
/// </summary>
10+
/// <param name="startPoint">Point on screen to swipe from.</param>
11+
/// <param name="endPoint">Point on screen to swipe to.</param>
12+
void Swipe(Point startPoint, Point endPoint);
13+
14+
/// <summary>
15+
/// Performs long press action and moves cursor from a start point to an end point imitating the swipe action.
16+
/// </summary>
17+
/// <param name="startPoint">Point on screen to swipe from.</param>
18+
/// <param name="endPoint">Point on screen to swipe to.</param>
19+
void SwipeWithLongPress(Point startPoint, Point endPoint);
20+
}
21+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace Aquality.Appium.Mobile.Actions
2+
{
3+
public enum SwipeDirection
4+
{
5+
Up,
6+
Down,
7+
Left,
8+
Right
9+
}
10+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using Aquality.Appium.Mobile.Applications;
2+
using Aquality.Appium.Mobile.Configurations;
3+
using Aquality.Selenium.Core.Utilities;
4+
using OpenQA.Selenium.Appium.Interfaces;
5+
using OpenQA.Selenium.Appium.MultiTouch;
6+
using System;
7+
using System.Drawing;
8+
9+
namespace Aquality.Appium.Mobile.Actions
10+
{
11+
public class TouchActions : ITouchActions
12+
{
13+
public void Swipe(Point startPoint, Point endPoint)
14+
{
15+
AqualityServices.LocalizedLogger.Info(
16+
"loc.action.swipe",
17+
startPoint.X,
18+
startPoint.Y,
19+
endPoint.X,
20+
endPoint.Y);
21+
PerformTouchAction(
22+
touchAction => touchAction
23+
.Press(startPoint.X, startPoint.Y)
24+
.Wait(GetLongSwipeDuration()),
25+
endPoint);
26+
}
27+
28+
public void SwipeWithLongPress(Point startPoint, Point endPoint)
29+
{
30+
AqualityServices.LocalizedLogger.Info(
31+
"loc.action.swipeLongPress",
32+
startPoint.X,
33+
startPoint.Y,
34+
endPoint.X,
35+
endPoint.Y);
36+
PerformTouchAction(touchAction => touchAction.LongPress(startPoint.X, startPoint.Y), endPoint);
37+
}
38+
39+
protected void PerformTouchAction(Func<ITouchAction, ITouchAction> function, Point endPoint)
40+
{
41+
var actionRetrier = AqualityServices.Get<IElementActionRetrier>();
42+
var touchAction = new TouchAction(AqualityServices.Application.Driver);
43+
actionRetrier.DoWithRetry(() =>
44+
function(touchAction).MoveTo(endPoint.X, endPoint.Y).Release().Perform());
45+
}
46+
47+
private long GetLongSwipeDuration()
48+
{
49+
var swipeDuration = AqualityServices.Get<ITouchActionsSettings>().SwipeDuration;
50+
return Convert.ToInt64(swipeDuration.TotalMilliseconds);
51+
}
52+
}
53+
}

Aquality.Appium.Mobile/src/Aquality.Appium.Mobile/Applications/AqualityServices.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Aquality.Appium.Mobile.Configurations;
1+
using Aquality.Appium.Mobile.Actions;
2+
using Aquality.Appium.Mobile.Configurations;
23
using Aquality.Appium.Mobile.Elements.Interfaces;
34
using Aquality.Appium.Mobile.Screens.ScreenFactory;
45
using Aquality.Selenium.Core.Applications;
@@ -60,11 +61,16 @@ public class AqualityServices : AqualityServices<IMobileApplication>
6061
/// </summary>
6162
public static IScreenFactory ScreenFactory => Get<IScreenFactory>();
6263

64+
/// <summary>
65+
/// Gets the the utility used to perform touch actions.
66+
/// </summary>
67+
public static ITouchActions TouchActions => Get<ITouchActions>();
68+
6369
/// <summary>
6470
/// Resolves required service from <see cref="ServiceProvider"/>
6571
/// </summary>
6672
/// <typeparam name="T">type of required service</typeparam>
67-
/// <exception cref="InvalidOperationException">Thrown if there is no service of the required type.</exception>
73+
/// <exception cref="InvalidOperationException">Thrown if there is no service of the required type.</exception>
6874
/// <returns></returns>
6975
public static T Get<T>()
7076
{
@@ -119,15 +125,15 @@ public static IApplicationFactory ApplicationFactory
119125
/// </summary>
120126
public static void SetDefaultFactory()
121127
{
122-
ApplicationFactory = ApplicationProfile.IsRemote ? new RemoteApplicationFactory() : (IApplicationFactory) new LocalApplicationFactory();
128+
ApplicationFactory = ApplicationProfile.IsRemote ? new RemoteApplicationFactory() : new LocalApplicationFactory() as IApplicationFactory;
123129
}
124130

125131
private static IServiceProvider ServiceProvider => GetServiceProvider(services => Application, ConfigureServices);
126132

127133
private static Func<IServiceProvider, IMobileApplication> StartApplicationFunction => (services) => ApplicationFactory.Application;
128134

129135
private static IServiceCollection ConfigureServices()
130-
{
136+
{
131137
return ApplicationStartupContainer.Value.ConfigureServices(new ServiceCollection(), serviceProvider => Application);
132138
}
133139
}

Aquality.Appium.Mobile/src/Aquality.Appium.Mobile/Applications/MobileStartup.cs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
using Aquality.Selenium.Core.Applications;
1+
using Aquality.Appium.Mobile.Actions;
2+
using Aquality.Appium.Mobile.Configurations;
3+
using Aquality.Appium.Mobile.Elements;
4+
using Aquality.Appium.Mobile.Elements.Interfaces;
5+
using Aquality.Appium.Mobile.Screens.ScreenFactory;
6+
using Aquality.Selenium.Core.Applications;
27
using Aquality.Selenium.Core.Configurations;
3-
using CoreElementFactory = Aquality.Selenium.Core.Elements.Interfaces.IElementFactory;
4-
using Microsoft.Extensions.DependencyInjection;
5-
using System;
68
using Aquality.Selenium.Core.Localization;
79
using Aquality.Selenium.Core.Logging;
10+
using Microsoft.Extensions.DependencyInjection;
11+
using System;
812
using System.Reflection;
9-
using Aquality.Appium.Mobile.Elements.Interfaces;
10-
using Aquality.Appium.Mobile.Elements;
11-
using Aquality.Appium.Mobile.Configurations;
12-
using Aquality.Appium.Mobile.Screens.ScreenFactory;
13+
using CoreElementFactory = Aquality.Selenium.Core.Elements.Interfaces.IElementFactory;
1314

1415
namespace Aquality.Appium.Mobile.Applications
1516
{
@@ -25,9 +26,11 @@ public override IServiceCollection ConfigureServices(IServiceCollection services
2526
base.ConfigureServices(services, applicationProvider, settings);
2627
services.AddTransient<IElementFactory, ElementFactory>();
2728
services.AddTransient<CoreElementFactory, ElementFactory>();
28-
services.AddSingleton<IApplicationProfile, ApplicationProfile>();
29+
services.AddSingleton<IApplicationProfile, ApplicationProfile>();
2930
services.AddSingleton<ILocalServiceSettings, LocalServiceSettings>();
31+
services.AddSingleton<ITouchActionsSettings, TouchActionsSettings>();
3032
services.AddSingleton<IScreenFactory, ScreenFactory>();
33+
services.AddSingleton<ITouchActions, TouchActions>();
3134
services.AddSingleton<ILocalizationManager>(serviceProvider => new LocalizationManager(serviceProvider.GetRequiredService<ILoggerConfiguration>(), serviceProvider.GetRequiredService<Logger>(), Assembly.GetExecutingAssembly()));
3235
services.AddTransient(serviceProvider => AqualityServices.ApplicationFactory);
3336
services.AddScoped(serviceProvider => applicationProvider(serviceProvider) as IMobileApplication);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System;
2+
3+
namespace Aquality.Appium.Mobile.Configurations
4+
{
5+
/// <summary>
6+
/// Describes Touch Actions settings.
7+
/// </summary>
8+
public interface ITouchActionsSettings
9+
{
10+
/// <summary>
11+
/// Gets number of retries to perform swipe.
12+
/// </summary>
13+
int SwipeRetries { get; }
14+
15+
/// <summary>
16+
/// Gets the number of seconds required to perform a swipe action.
17+
/// </summary>
18+
TimeSpan SwipeDuration { get; }
19+
20+
/// <summary>
21+
/// Gets the offset coefficient to adjust the start/end point for swipe action relatively to the parallel screen edge.
22+
/// Example for swipe down action:
23+
/// The offset coefficient is used to calculate start/end point's Y coordinate.
24+
/// If offset coefficient == 0.2, then the start point's Y coordinate == screen's length * (1 - 0.2).
25+
/// If offset coefficient == 0.2, then the end point's Y coordinate == screen's length * 0.2.
26+
/// The vice versa for swipe up action.
27+
/// Example for swipe left action:
28+
/// The offset coefficient is used to calculate start/end point's X coordinate.
29+
/// If offset coefficient == 0.2, then the start point's X coordinate == screen's width * (1 - 0.2).
30+
/// If offset coefficient == 0.2, then the end point's X coordinate == screen's width * 0.2.
31+
/// The vice versa for swipe right action.
32+
/// </summary>
33+
double SwipeVerticalOffset { get; }
34+
35+
/// <summary>
36+
/// Gets the offset coefficient to adjust the start/end point for swipe action relatively to the perpendicular screen edge.
37+
/// Example for swipe down action:
38+
/// The offset coefficient is used to calculate start/end point's X coordinate.
39+
/// If offset coefficient == 0.5, then the start point's X coordinate == screen's width * (1 - 0.5).
40+
/// If offset coefficient == 0.5, then the end point's X coordinate == screen's width * 0.5.
41+
/// The vice versa for swipe up action.
42+
/// Example for swipe left action:
43+
/// The offset coefficient is used to calculate start/end point's X coordinate.
44+
/// If offset coefficient == 0.5, then the start point's Y coordinate == screen's length * (1 - 0.5).
45+
/// If offset coefficient == 0.5, then the end point's Y coordinate == screen's length * 0.5.
46+
/// The vice versa for swipe right action.
47+
/// </summary>
48+
double SwipeHorizontalOffset { get; }
49+
}
50+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using Aquality.Selenium.Core.Configurations;
2+
using System;
3+
4+
namespace Aquality.Appium.Mobile.Configurations
5+
{
6+
public class TouchActionsSettings : ITouchActionsSettings
7+
{
8+
private readonly ISettingsFile settingsFile;
9+
private readonly string swipeSettingsPath = ".touchActions.swipe";
10+
11+
public TouchActionsSettings(ISettingsFile settingsFile)
12+
{
13+
this.settingsFile = settingsFile;
14+
}
15+
16+
public int SwipeRetries => settingsFile.GetValue<int>($"{swipeSettingsPath}.retries");
17+
18+
public TimeSpan SwipeDuration => TimeSpan.FromSeconds(settingsFile.GetValue<long>($"{swipeSettingsPath}.duration"));
19+
20+
public double SwipeVerticalOffset => settingsFile.GetValue<double>($"{swipeSettingsPath}.verticalOffset");
21+
22+
public double SwipeHorizontalOffset => settingsFile.GetValue<double>($"{swipeSettingsPath}.horizontalOffset");
23+
}
24+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
using Aquality.Appium.Mobile.Actions;
2+
using Aquality.Appium.Mobile.Applications;
3+
using Aquality.Appium.Mobile.Configurations;
4+
using Aquality.Selenium.Core.Elements.Interfaces;
5+
using System.ComponentModel;
6+
using System.Drawing;
7+
8+
namespace Aquality.Appium.Mobile.Elements.Actions
9+
{
10+
public class ElementTouchActions : IElementTouchActions
11+
{
12+
private readonly IElement element;
13+
private readonly ITouchActionsSettings touchActionsSettings = AqualityServices.Get<ITouchActionsSettings>();
14+
private readonly Point scrollDownStartPoint;
15+
private readonly Point scrollDownEndPoint;
16+
private readonly Point swipeLeftStartPoint;
17+
private readonly Point swipeLeftEndPoint;
18+
private readonly Point scrollUpStartPoint;
19+
private readonly Point scrollUpEndPoint;
20+
private readonly Point swipeRightStartPoint;
21+
private readonly Point swipeRightEndPoint;
22+
23+
public ElementTouchActions(IElement element)
24+
{
25+
this.element = element;
26+
var horizontalOffset = touchActionsSettings.SwipeHorizontalOffset;
27+
var verticalOffset = touchActionsSettings.SwipeVerticalOffset;
28+
scrollDownStartPoint = RecalculatePointCoordinates(
29+
GetBottomRightCornerPoint(),
30+
1 - horizontalOffset,
31+
1 - verticalOffset);
32+
scrollDownEndPoint = RecalculatePointCoordinates(
33+
GetBottomRightCornerPoint(),
34+
horizontalOffset,
35+
verticalOffset);
36+
swipeLeftStartPoint = RecalculatePointCoordinates(
37+
GetBottomRightCornerPoint(),
38+
1 - verticalOffset,
39+
1 - horizontalOffset);
40+
swipeLeftEndPoint = RecalculatePointCoordinates(
41+
GetBottomRightCornerPoint(),
42+
horizontalOffset,
43+
verticalOffset);
44+
scrollUpStartPoint = scrollDownEndPoint;
45+
scrollUpEndPoint = scrollDownStartPoint;
46+
swipeRightStartPoint = swipeLeftEndPoint;
47+
swipeRightEndPoint = swipeLeftStartPoint;
48+
}
49+
50+
public void Swipe(Point endPoint)
51+
{
52+
AqualityServices.TouchActions.Swipe(GetElementLocation(), endPoint);
53+
}
54+
55+
public void SwipeWithLongPress(Point endPoint)
56+
{
57+
AqualityServices.TouchActions.SwipeWithLongPress(GetElementLocation(), endPoint);
58+
}
59+
60+
public void ScrollToElement(SwipeDirection direction)
61+
{
62+
var numberOfRetries = touchActionsSettings.SwipeRetries;
63+
var touchActions = AqualityServices.TouchActions;
64+
while (numberOfRetries-- > 0 && !element.State.IsDisplayed)
65+
{
66+
switch (direction)
67+
{
68+
case SwipeDirection.Down:
69+
touchActions.Swipe(scrollDownStartPoint, scrollDownEndPoint);
70+
break;
71+
case SwipeDirection.Up:
72+
touchActions.Swipe(scrollUpStartPoint, scrollUpEndPoint);
73+
break;
74+
case SwipeDirection.Left:
75+
touchActions.Swipe(swipeLeftStartPoint, swipeLeftEndPoint);
76+
break;
77+
case SwipeDirection.Right:
78+
touchActions.Swipe(swipeRightStartPoint, swipeRightEndPoint);
79+
break;
80+
default:
81+
throw new InvalidEnumArgumentException($"'{direction}' direction does not exist");
82+
}
83+
}
84+
}
85+
86+
private Point GetElementLocation() => element.GetElement().Location;
87+
88+
private Point RecalculatePointCoordinates(
89+
Point point,
90+
double horizontalOffset,
91+
double verticalOffset)
92+
{
93+
return new Point((int)(point.X * horizontalOffset),
94+
(int)(point.Y * verticalOffset));
95+
}
96+
97+
private Point GetBottomRightCornerPoint()
98+
{
99+
var screnSize = AqualityServices.Application.Driver.Manage().Window.Size;
100+
return new Point(screnSize.Width, screnSize.Height);
101+
}
102+
}
103+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using Aquality.Appium.Mobile.Actions;
2+
using System.Drawing;
3+
4+
namespace Aquality.Appium.Mobile.Elements.Actions
5+
{
6+
/// <summary>
7+
/// Describes Touch Actions for elements.
8+
/// </summary>
9+
public interface IElementTouchActions
10+
{
11+
/// <summary>
12+
/// Swipes from element to end point using TouchAction.
13+
/// </summary>
14+
/// <param name="endPoint"> Point on screen to swipe to.</param>
15+
/// <exception cref="OpenQA.Selenium.NoSuchElementException">Thrown if element was not found.</exception>
16+
void Swipe(Point endPoint);
17+
18+
/// <summary>
19+
/// Swipes from element to end point using LongPress.
20+
/// <param name="endPoint"> Point on screen to swipe to.</param>
21+
/// </summary>
22+
/// <exception cref="OpenQA.Selenium.NoSuchElementException">Thrown if element was not found.</exception>
23+
void SwipeWithLongPress(Point endPoint);
24+
25+
/// <summary>
26+
/// Scrolls current screen in provided direction until element is displayed.
27+
/// <param name="direction"> Direction to swipe.</param>
28+
/// </summary>
29+
/// <exception cref="OpenQA.Selenium.NoSuchElementException">Thrown if element was not found.</exception>
30+
/// <exception cref="System.ComponentModel.InvalidEnumArgumentException">Thrown if incorrect swipe direction was provided.</exception>
31+
void ScrollToElement(SwipeDirection direction);
32+
}
33+
}

Aquality.Appium.Mobile/src/Aquality.Appium.Mobile/Elements/CheckableElement.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ protected virtual bool GetState()
2828
return string.IsNullOrEmpty(checkedAttribute)
2929
? GetElement().Selected
3030
: checkedAttribute == "true";
31-
3231
});
3332
}
3433
}
35-
}
34+
}

0 commit comments

Comments
 (0)