Skip to content

Commit

Permalink
[Feature] Application management functionality +semver: feature (#40)
Browse files Browse the repository at this point in the history
* [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
  • Loading branch information
mialeska authored May 9, 2024
1 parent 813b040 commit 8b8a996
Show file tree
Hide file tree
Showing 16 changed files with 327 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
using Aquality.Selenium.Core.Applications;
using Aquality.Selenium.Core.Configurations;
using Aquality.Selenium.Core.Localization;
using Aquality.Selenium.Core.Utilities;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Interfaces;
using OpenQA.Selenium.Appium.Android;
using OpenQA.Selenium.Appium.Enums;
using OpenQA.Selenium.Appium.Service;
using System;
using System.Collections.Generic;

namespace Aquality.Appium.Mobile.Applications
{
// Ignore Spelling: app
public class Application : IMobileApplication
{
private readonly ILocalizedLogger localizedLogger;
Expand Down Expand Up @@ -42,6 +46,22 @@ private void SetImplicitlyWaitToDriver(TimeSpan timeout)

public PlatformName PlatformName => applicationProfile.PlatformName;

protected virtual T DoWithRetry<T>(Func<T> function) => AqualityServices.Get<IActionRetrier>()
.DoWithRetry(function, new[] { typeof(WebDriverException) });

protected virtual void DoWithRetry(Action action) => AqualityServices.Get<IActionRetrier>()
.DoWithRetry(action, new[] { typeof(WebDriverException) });

public string Id
{
get
{
return DoWithRetry(() => PlatformName.Android == PlatformName
? ((AndroidDriver)Driver).CurrentPackage
: ((Dictionary<string, object>)Driver.ExecuteScript("mobile: activeAppInfo"))["bundleId"].ToString());
}
}

public void SetImplicitWaitTimeout(TimeSpan timeout)
{
if (timeout != timeoutImplicit)
Expand All @@ -58,9 +78,73 @@ public void Quit()
DriverService?.Dispose();
}

public bool TerminateApp(string bundleId)
public bool Terminate(TimeSpan? timeout = null)
{
return Terminate(Id, timeout);
}

public bool Terminate(string appId, TimeSpan? timeout = null)
{
localizedLogger.Info("loc.application.terminate", appId);
return DoWithRetry(() => Driver.TerminateApp(appId,
timeout ?? AqualityServices.Get<ITimeoutConfiguration>().Condition));
}

public void Install(string appPath)
{
localizedLogger.Info("loc.application.install", appPath);
DoWithRetry(() => Driver.InstallApp(appPath));
}

public void Install()
{
Install(applicationProfile.DriverSettings.ApplicationPath);
}

public void Background(TimeSpan? timeout = null)
{
if (timeout.HasValue)
{
localizedLogger.Info("loc.application.background.with.timeout", timeout.Value.TotalSeconds);
DoWithRetry(() => Driver.BackgroundApp(timeout.Value));
}
else
{
localizedLogger.Info("loc.application.background");
DoWithRetry(() => Driver.BackgroundApp());
}
}

public void Remove(string appId)
{
localizedLogger.Info("loc.application.remove", appId);
DoWithRetry(() => Driver.RemoveApp(appId));
}

public void Remove()
{
Remove(Id);
}

public void Activate(string appId, TimeSpan? timeout = null)
{
localizedLogger.Info("loc.application.activate", appId);
if (timeout.HasValue)
{
DoWithRetry(() => Driver.ActivateApp(appId, timeout.Value));
}
else
{
DoWithRetry(() => Driver.ActivateApp(appId));
}
}

public AppState GetState(string appId)
{
return ((IInteractsWithApps)Driver).TerminateApp(bundleId);
localizedLogger.Info("loc.application.get.state", appId);
var state = DoWithRetry(() => Driver.GetAppState(appId));
localizedLogger.Info("loc.application.state", state);
return state;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Aquality.Selenium.Core.Applications;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Enums;
using OpenQA.Selenium.Appium.Service;
using System;

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

/// <summary>
/// Closes application and disposes <see cref="DriverService"/> if it not null.
/// Quits application driver and disposes <see cref="DriverService"/> if it not null.
/// </summary>
void Quit();

/// <summary>
/// Installs an application.
/// </summary>
/// <param name="appPath">a string containing the file path or url of the application.</param>
void Install(string appPath);

/// <summary>
/// Installs an application defined in settings.
/// Note that path to the application must be defined as 'app' capability.
/// </summary>
void Install();

/// <summary>
/// Send the currently active application to the background,
/// and either return after a certain amount of time, or leave the application deactivated
/// (as "Home" button does).
/// <param name="timeout">How long to background the application for. If null, application would be deactivated entirely.</param>
/// </summary>
void Background(TimeSpan? timeout = null);

/// <summary>
/// Removes an application.
/// </summary>
/// <param name="appId">the bundle identifier (or appId) of the application to be removed.</param>
void Remove(string appId);

/// <summary>
/// Removes currently running application.
/// </summary>
void Remove();

/// <summary>
/// Activates the given application by moving to the foreground if it is running in the background
/// or starting it if it is not running yet.
/// </summary>
/// <param name="appId">the bundle identifier (or appId) of the application.</param>
/// <param name="timeout">command timeout.</param>
void Activate(string appId, TimeSpan? timeout = null);

/// <summary>
/// Terminate the particular application if it is running.
/// </summary>
/// <param name="bundleId">the bundle identifier (or app id) of the app to be terminated.</param>
/// <returns>true if the app was running before and has been successfully stopped.</returns>
bool TerminateApp(string bundleId);
/// <param name="appId">the bundle identifier (or appId) of the application to be terminated.</param>
/// <param name="timeout">If not null, defines for how long to wait until the application is terminated.</param>
/// <returns>true if the application was running before and has been successfully stopped.</returns>
bool Terminate(string appId, TimeSpan? timeout = null);

/// <summary>
/// Terminates currently running application.
/// </summary>
/// <param name="timeout">If not null, defines for how long to wait until the application is terminated.</param>
/// <returns>true if the application was running before and has been successfully stopped.</returns>
bool Terminate(TimeSpan? timeout = null);

/// <summary>
/// Gets the state of the application.
/// </summary>
/// <param name="appId">the bundle identifier (or appId) of the application.</param>
/// <returns>an enumeration of the application state.</returns>
AppState GetState(string appId);

/// <summary>
/// Provides current AppiumDriver service instance (would be null if driver is not local).
Expand All @@ -35,5 +91,10 @@ public interface IMobileApplication : IApplication
/// Provides name of current platform.
/// </summary>
PlatformName PlatformName { get; }

/// <summary>
/// Gets the bundle identifier (or appId) of the currently running application.
/// </summary>
string Id { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@
</ItemGroup>

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

</Project>

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public DriverSettings(ISettingsFile settingsFile, PlatformName platformName)

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

private string BundleIdCapabilityKey => platformName == PlatformName.Android ? "appPackage" : "bundleId";

protected virtual IReadOnlyDictionary<string, object> Capabilities => settingsFile.GetValueDictionaryOrEmpty<object>($"{DriverSettingsPath}.capabilities");

/// <summary>
Expand All @@ -42,7 +44,7 @@ public virtual AppiumOptions AppiumOptions
Capabilities.ToList().ForEach(capability => SetCapability(options, capability));
if (HasApplicationPath && ApplicationPath != null)
{
SetCapability(options, new KeyValuePair<string, object> (AppCapabilityKey, ApplicationPath));
SetCapability(options, new KeyValuePair<string, object>(AppCapabilityKey, ApplicationPath));
}
DeviceCapabilities.ToList().ForEach(capability => SetCapability(options, capability));
return options;
Expand All @@ -59,6 +61,15 @@ public virtual string ApplicationPath
}
}

public virtual string BundleId
{
get
{
DeviceCapabilities.TryGetValue(BundleIdCapabilityKey, out var bundleId);
return bundleId?.ToString() ?? settingsFile.GetValue<string>($"{DriverSettingsPath}.capabilities.{BundleIdCapabilityKey}");
}
}

private IReadOnlyDictionary<string, object> DeviceCapabilities
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,10 @@ public interface IDriverSettings
/// Provides a path to the application.
/// </summary>
string ApplicationPath { get; }

/// <summary>
/// The bundleId/appPackage of the application.
/// </summary>
string BundleId { get; }
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
{
"loc.action.swipe": "Праводзім па экране з каардынатаў (x:{0}; y:{1}) у (x:{2}; y:{3})",
"loc.action.swipeLongPress": "Праводзім па экране доўгім націскам з каардынатаў (x:{0}; y:{1}) у (x:{2}; y:{3})",
"loc.application.quit": "Закрываем праграму",
"loc.application.quit": "Закрываем драйвер праграмы",
"loc.application.terminate": "Закрываем праграму '{0}'",
"loc.application.install": "Усталёўваем праграму '{0}'",
"loc.application.background": "Адпраўляем праграму ў фонавы рэжым",
"loc.application.background.with.timeout": "Адпраўляем праграму ў фонавы рэжым на {0} сек.",
"loc.application.remove": "Выдаляем праграму '{0}'",
"loc.application.activate": "Актывуем праграму '{0}'",
"loc.application.get.state": "Атрымліваем стан праграмы '{0}'",
"loc.application.state": "Стан праграмы: [{0}]",
"loc.application.driver.remote": "Усталёўваем драйвер праграммы з выкарыстаннем сервера па адрасе '{0}'",
"loc.application.ready": "Праграма на платформе '{0}' гатовая...",
"loc.application.implicit.timeout": "Задаем таймаўт implicit(няяўнага) чакання: '{0}' сек.",
Expand Down
Loading

0 comments on commit 8b8a996

Please sign in to comment.