Skip to content

Commit 8b8a996

Browse files
authored
[Feature] Application management functionality +semver: feature (#40)
* [Feature] Application management functionality * update nuget packages and docs * Update BundleId acquiring for iOS, stabilize Terminate() method, update interface * Add retrying to app management actions
1 parent 813b040 commit 8b8a996

File tree

16 files changed

+327
-30
lines changed

16 files changed

+327
-30
lines changed

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

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22
using Aquality.Selenium.Core.Applications;
33
using Aquality.Selenium.Core.Configurations;
44
using Aquality.Selenium.Core.Localization;
5+
using Aquality.Selenium.Core.Utilities;
56
using OpenQA.Selenium;
67
using OpenQA.Selenium.Appium;
7-
using OpenQA.Selenium.Appium.Interfaces;
8+
using OpenQA.Selenium.Appium.Android;
9+
using OpenQA.Selenium.Appium.Enums;
810
using OpenQA.Selenium.Appium.Service;
911
using System;
12+
using System.Collections.Generic;
1013

1114
namespace Aquality.Appium.Mobile.Applications
1215
{
16+
// Ignore Spelling: app
1317
public class Application : IMobileApplication
1418
{
1519
private readonly ILocalizedLogger localizedLogger;
@@ -42,6 +46,22 @@ private void SetImplicitlyWaitToDriver(TimeSpan timeout)
4246

4347
public PlatformName PlatformName => applicationProfile.PlatformName;
4448

49+
protected virtual T DoWithRetry<T>(Func<T> function) => AqualityServices.Get<IActionRetrier>()
50+
.DoWithRetry(function, new[] { typeof(WebDriverException) });
51+
52+
protected virtual void DoWithRetry(Action action) => AqualityServices.Get<IActionRetrier>()
53+
.DoWithRetry(action, new[] { typeof(WebDriverException) });
54+
55+
public string Id
56+
{
57+
get
58+
{
59+
return DoWithRetry(() => PlatformName.Android == PlatformName
60+
? ((AndroidDriver)Driver).CurrentPackage
61+
: ((Dictionary<string, object>)Driver.ExecuteScript("mobile: activeAppInfo"))["bundleId"].ToString());
62+
}
63+
}
64+
4565
public void SetImplicitWaitTimeout(TimeSpan timeout)
4666
{
4767
if (timeout != timeoutImplicit)
@@ -58,9 +78,73 @@ public void Quit()
5878
DriverService?.Dispose();
5979
}
6080

61-
public bool TerminateApp(string bundleId)
81+
public bool Terminate(TimeSpan? timeout = null)
82+
{
83+
return Terminate(Id, timeout);
84+
}
85+
86+
public bool Terminate(string appId, TimeSpan? timeout = null)
87+
{
88+
localizedLogger.Info("loc.application.terminate", appId);
89+
return DoWithRetry(() => Driver.TerminateApp(appId,
90+
timeout ?? AqualityServices.Get<ITimeoutConfiguration>().Condition));
91+
}
92+
93+
public void Install(string appPath)
94+
{
95+
localizedLogger.Info("loc.application.install", appPath);
96+
DoWithRetry(() => Driver.InstallApp(appPath));
97+
}
98+
99+
public void Install()
100+
{
101+
Install(applicationProfile.DriverSettings.ApplicationPath);
102+
}
103+
104+
public void Background(TimeSpan? timeout = null)
105+
{
106+
if (timeout.HasValue)
107+
{
108+
localizedLogger.Info("loc.application.background.with.timeout", timeout.Value.TotalSeconds);
109+
DoWithRetry(() => Driver.BackgroundApp(timeout.Value));
110+
}
111+
else
112+
{
113+
localizedLogger.Info("loc.application.background");
114+
DoWithRetry(() => Driver.BackgroundApp());
115+
}
116+
}
117+
118+
public void Remove(string appId)
119+
{
120+
localizedLogger.Info("loc.application.remove", appId);
121+
DoWithRetry(() => Driver.RemoveApp(appId));
122+
}
123+
124+
public void Remove()
125+
{
126+
Remove(Id);
127+
}
128+
129+
public void Activate(string appId, TimeSpan? timeout = null)
130+
{
131+
localizedLogger.Info("loc.application.activate", appId);
132+
if (timeout.HasValue)
133+
{
134+
DoWithRetry(() => Driver.ActivateApp(appId, timeout.Value));
135+
}
136+
else
137+
{
138+
DoWithRetry(() => Driver.ActivateApp(appId));
139+
}
140+
}
141+
142+
public AppState GetState(string appId)
62143
{
63-
return ((IInteractsWithApps)Driver).TerminateApp(bundleId);
144+
localizedLogger.Info("loc.application.get.state", appId);
145+
var state = DoWithRetry(() => Driver.GetAppState(appId));
146+
localizedLogger.Info("loc.application.state", state);
147+
return state;
64148
}
65149
}
66150
}

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

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using Aquality.Selenium.Core.Applications;
22
using OpenQA.Selenium.Appium;
3+
using OpenQA.Selenium.Appium.Enums;
34
using OpenQA.Selenium.Appium.Service;
5+
using System;
46

57
namespace Aquality.Appium.Mobile.Applications
68
{
@@ -15,16 +17,70 @@ public interface IMobileApplication : IApplication
1517
new AppiumDriver Driver { get; }
1618

1719
/// <summary>
18-
/// Closes application and disposes <see cref="DriverService"/> if it not null.
20+
/// Quits application driver and disposes <see cref="DriverService"/> if it not null.
1921
/// </summary>
2022
void Quit();
2123

24+
/// <summary>
25+
/// Installs an application.
26+
/// </summary>
27+
/// <param name="appPath">a string containing the file path or url of the application.</param>
28+
void Install(string appPath);
29+
30+
/// <summary>
31+
/// Installs an application defined in settings.
32+
/// Note that path to the application must be defined as 'app' capability.
33+
/// </summary>
34+
void Install();
35+
36+
/// <summary>
37+
/// Send the currently active application to the background,
38+
/// and either return after a certain amount of time, or leave the application deactivated
39+
/// (as "Home" button does).
40+
/// <param name="timeout">How long to background the application for. If null, application would be deactivated entirely.</param>
41+
/// </summary>
42+
void Background(TimeSpan? timeout = null);
43+
44+
/// <summary>
45+
/// Removes an application.
46+
/// </summary>
47+
/// <param name="appId">the bundle identifier (or appId) of the application to be removed.</param>
48+
void Remove(string appId);
49+
50+
/// <summary>
51+
/// Removes currently running application.
52+
/// </summary>
53+
void Remove();
54+
55+
/// <summary>
56+
/// Activates the given application by moving to the foreground if it is running in the background
57+
/// or starting it if it is not running yet.
58+
/// </summary>
59+
/// <param name="appId">the bundle identifier (or appId) of the application.</param>
60+
/// <param name="timeout">command timeout.</param>
61+
void Activate(string appId, TimeSpan? timeout = null);
62+
2263
/// <summary>
2364
/// Terminate the particular application if it is running.
2465
/// </summary>
25-
/// <param name="bundleId">the bundle identifier (or app id) of the app to be terminated.</param>
26-
/// <returns>true if the app was running before and has been successfully stopped.</returns>
27-
bool TerminateApp(string bundleId);
66+
/// <param name="appId">the bundle identifier (or appId) of the application to be terminated.</param>
67+
/// <param name="timeout">If not null, defines for how long to wait until the application is terminated.</param>
68+
/// <returns>true if the application was running before and has been successfully stopped.</returns>
69+
bool Terminate(string appId, TimeSpan? timeout = null);
70+
71+
/// <summary>
72+
/// Terminates currently running application.
73+
/// </summary>
74+
/// <param name="timeout">If not null, defines for how long to wait until the application is terminated.</param>
75+
/// <returns>true if the application was running before and has been successfully stopped.</returns>
76+
bool Terminate(TimeSpan? timeout = null);
77+
78+
/// <summary>
79+
/// Gets the state of the application.
80+
/// </summary>
81+
/// <param name="appId">the bundle identifier (or appId) of the application.</param>
82+
/// <returns>an enumeration of the application state.</returns>
83+
AppState GetState(string appId);
2884

2985
/// <summary>
3086
/// Provides current AppiumDriver service instance (would be null if driver is not local).
@@ -35,5 +91,10 @@ public interface IMobileApplication : IApplication
3591
/// Provides name of current platform.
3692
/// </summary>
3793
PlatformName PlatformName { get; }
94+
95+
/// <summary>
96+
/// Gets the bundle identifier (or appId) of the currently running application.
97+
/// </summary>
98+
string Id { get; }
3899
}
39100
}

Aquality.Appium.Mobile/src/Aquality.Appium.Mobile/Aquality.Appium.Mobile.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@
4949
</ItemGroup>
5050

5151
<ItemGroup>
52-
<PackageReference Include="Appium.WebDriver" Version="[5.0.0-rc.7, 5.0.0]" />
53-
<PackageReference Include="Aquality.Selenium.Core" Version="[3.0.7, 4.0.0)" />
52+
<PackageReference Include="Appium.WebDriver" Version="5.0.0-rc.8" />
53+
<PackageReference Include="Aquality.Selenium.Core" Version="3.0.8" />
5454
</ItemGroup>
5555

5656
</Project>

Aquality.Appium.Mobile/src/Aquality.Appium.Mobile/Aquality.Appium.Mobile.xml

Lines changed: 68 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Aquality.Appium.Mobile/src/Aquality.Appium.Mobile/Configurations/DriverSettings.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public DriverSettings(ISettingsFile settingsFile, PlatformName platformName)
2727

2828
private string ApplicationPathJPath => $"{DriverSettingsPath}.{ApplicationPathKey}";
2929

30+
private string BundleIdCapabilityKey => platformName == PlatformName.Android ? "appPackage" : "bundleId";
31+
3032
protected virtual IReadOnlyDictionary<string, object> Capabilities => settingsFile.GetValueDictionaryOrEmpty<object>($"{DriverSettingsPath}.capabilities");
3133

3234
/// <summary>
@@ -42,7 +44,7 @@ public virtual AppiumOptions AppiumOptions
4244
Capabilities.ToList().ForEach(capability => SetCapability(options, capability));
4345
if (HasApplicationPath && ApplicationPath != null)
4446
{
45-
SetCapability(options, new KeyValuePair<string, object> (AppCapabilityKey, ApplicationPath));
47+
SetCapability(options, new KeyValuePair<string, object>(AppCapabilityKey, ApplicationPath));
4648
}
4749
DeviceCapabilities.ToList().ForEach(capability => SetCapability(options, capability));
4850
return options;
@@ -59,6 +61,15 @@ public virtual string ApplicationPath
5961
}
6062
}
6163

64+
public virtual string BundleId
65+
{
66+
get
67+
{
68+
DeviceCapabilities.TryGetValue(BundleIdCapabilityKey, out var bundleId);
69+
return bundleId?.ToString() ?? settingsFile.GetValue<string>($"{DriverSettingsPath}.capabilities.{BundleIdCapabilityKey}");
70+
}
71+
}
72+
6273
private IReadOnlyDictionary<string, object> DeviceCapabilities
6374
{
6475
get

Aquality.Appium.Mobile/src/Aquality.Appium.Mobile/Configurations/IDriverSettings.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,10 @@ public interface IDriverSettings
1616
/// Provides a path to the application.
1717
/// </summary>
1818
string ApplicationPath { get; }
19+
20+
/// <summary>
21+
/// The bundleId/appPackage of the application.
22+
/// </summary>
23+
string BundleId { get; }
1924
}
2025
}

Aquality.Appium.Mobile/src/Aquality.Appium.Mobile/Resources/Localization/be.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
{
22
"loc.action.swipe": "Праводзім па экране з каардынатаў (x:{0}; y:{1}) у (x:{2}; y:{3})",
33
"loc.action.swipeLongPress": "Праводзім па экране доўгім націскам з каардынатаў (x:{0}; y:{1}) у (x:{2}; y:{3})",
4-
"loc.application.quit": "Закрываем праграму",
4+
"loc.application.quit": "Закрываем драйвер праграмы",
5+
"loc.application.terminate": "Закрываем праграму '{0}'",
6+
"loc.application.install": "Усталёўваем праграму '{0}'",
7+
"loc.application.background": "Адпраўляем праграму ў фонавы рэжым",
8+
"loc.application.background.with.timeout": "Адпраўляем праграму ў фонавы рэжым на {0} сек.",
9+
"loc.application.remove": "Выдаляем праграму '{0}'",
10+
"loc.application.activate": "Актывуем праграму '{0}'",
11+
"loc.application.get.state": "Атрымліваем стан праграмы '{0}'",
12+
"loc.application.state": "Стан праграмы: [{0}]",
513
"loc.application.driver.remote": "Усталёўваем драйвер праграммы з выкарыстаннем сервера па адрасе '{0}'",
614
"loc.application.ready": "Праграма на платформе '{0}' гатовая...",
715
"loc.application.implicit.timeout": "Задаем таймаўт implicit(няяўнага) чакання: '{0}' сек.",

0 commit comments

Comments
 (0)