Skip to content

Commit

Permalink
Release 1.4.0 (#44)
Browse files Browse the repository at this point in the history
* Improved the overall speed of interactions (tap, swipe, input text, key events) by going through the server instead of adb 
* Dependency handling "InstallIfMissing" now also check if the installed package version is outdated. 
* New method in ActivityService to check for package version 
* Improved logging when dumping UI.
  • Loading branch information
MilleBo authored Oct 2, 2017
1 parent 5cd2e7a commit f3636d6
Show file tree
Hide file tree
Showing 15 changed files with 292 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,21 @@ public void GetCurrent_WhenCantParseActvity_ShouldReturnUnkownActivity()
_adbServiceMock.Setup(s => s.Shell(It.IsAny<string>())).Returns("223232ddad");
Assert.AreEqual("Unknown activity", _activityService.GetCurrent());
}

[Test]
public void GetPackageVersion_WhenGetVersionFromPackageThatExist_ShouldReturnVersion()
{
_adbServiceMock.Setup(s => s.Shell(It.IsAny<string>())).Returns("versionName=1.1");
var version = _activityService.GetPackageVersion("test");
Assert.AreEqual(new Version(1, 1), version);
}

[Test]
public void GetPackageVersion_WhenGetVersionFromPackageThatDontExist_ShouldReturnVersion0()
{
_adbServiceMock.Setup(s => s.Shell(It.IsAny<string>())).Returns("");
var version = _activityService.GetPackageVersion("test");
Assert.AreEqual(new Version(0, 0), version);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Testura.Android.Device;
using Testura.Android.Device.Services;
using Testura.Android.Device.Services.Default;
using Testura.Android.Device.Ui.Server;

namespace Testura.Android.Tests.Device.Services.Default
{
Expand All @@ -22,7 +23,7 @@ public void SetUp()
_adbServiceMock = new Mock<IAdbService>();
_androidMock.Setup(a => a.Adb).Returns(_adbServiceMock.Object);

_interactionService = new InteractionService();
_interactionService = new InteractionService(new Mock<IInteractionUiAutomatorServer>().Object);
}

[Test]
Expand Down
Binary file not shown.
Binary file modified src/Testura.Android/Dependencies/Testura.Android.Server.apk
Binary file not shown.
5 changes: 3 additions & 2 deletions src/Testura.Android/Device/AndroidDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,15 @@ public AndroidDevice(
public AndroidDevice(DeviceConfiguration configuration)
{
Configuration = configuration;
var server = new UiAutomatorServer(new Terminal(configuration), configuration.Port);
Adb = new AdbService(new Terminal(configuration));
Ui = new UiService(
new ScreenDumper(new UiAutomatorServer(new Terminal(configuration), configuration.Port), configuration.DumpTries),
new ScreenDumper(server, configuration.DumpTries),
new NodeParser(),
new NodeFinder());
Settings = new SettingsService();
Activity = new ActivityService();
Interaction = new InteractionService();
Interaction = new InteractionService(server);
SetOwner();
InstallHelperApks();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ public class DeviceConfiguration
/// </summary>
public const string ServerUiAutomatorApkName = "Testura.Android.Server-UiAutomator.apk";

/// <summary>
/// Latest Testura server apk version
/// </summary>
public const string ServerApkVersion = "1.1";

/// <summary>
/// Initializes a new instance of the <see cref="DeviceConfiguration"/> class.
/// </summary>
Expand Down
22 changes: 22 additions & 0 deletions src/Testura.Android/Device/Services/Default/ActivityService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,5 +92,27 @@ public bool IsPackagedInstalled(string packageName)
var packages = GetPackages();
return packages.Contains(packageName);
}

/// <summary>
/// Get the package name.
/// </summary>
/// <param name="packageName">Name of package.</param>
/// <returns>The package version if package exist, otherwise version 0.</returns>
public Version GetPackageVersion(string packageName)
{
var version = Device.Adb.Shell($"dumpsys package {packageName} | grep versionName");
var versionSplit = version
.Trim()
.Replace("\n", string.Empty)
.Replace("\r", string.Empty)
.Split('=');

if (versionSplit.Length != 2)
{
return new Version(0, 0);
}

return Version.Parse(versionSplit[1]);
}
}
}
45 changes: 41 additions & 4 deletions src/Testura.Android/Device/Services/Default/InteractionService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using Testura.Android.Device.Ui.Nodes.Data;
using Testura.Android.Device.Ui.Server;
using Testura.Android.Util;
using Testura.Android.Util.Exceptions;
using Testura.Android.Util.Logging;
Expand All @@ -11,8 +12,24 @@ namespace Testura.Android.Device.Services.Default
/// </summary>
public class InteractionService : Service, IInteractionService
{
private readonly IInteractionUiAutomatorServer _interactionServer;
private NodeBounds _screenBounds;

/// <summary>
/// Initializes a new instance of the <see cref="InteractionService"/> class.
/// </summary>
/// <param name="interactionServer">An implementation of the interaction server interface.</param>
/// <exception cref="ArgumentNullException">Thrown if interaction service is null.</exception>
public InteractionService(IInteractionUiAutomatorServer interactionServer)
{
if (interactionServer == null)
{
throw new ArgumentNullException(nameof(interactionServer));
}

_interactionServer = interactionServer;
}

/// <summary>
/// Perform a swipe motion on the screen.
/// </summary>
Expand All @@ -23,7 +40,12 @@ public class InteractionService : Service, IInteractionService
/// <param name="duration">Duration of the swipe in milliseconds</param>
public void Swipe(int fromX, int fromY, int toX, int toY, int duration)
{
Device.Adb.Shell($"input swipe {fromX} {fromY} {toX} {toY} {duration}");
if (!_interactionServer.Swipe(fromX, fromY, toX, toY, duration))
{
DeviceLogger.Log("Failed to swipe through server, trying through adb.");
Device.Adb.Shell($"input swipe {fromX} {fromY} {toX} {toY} {duration}");
}

Device.Ui.ClearCache();
}

Expand Down Expand Up @@ -83,7 +105,12 @@ public void Tap(Node node)
/// <param name="y">The y position</param>
public void Tap(int x, int y)
{
Device.Adb.Shell($"input tap {x} {y}");
if (!_interactionServer.Tap(x, y))
{
DeviceLogger.Log("Failed to tap through server, trying through adb.");
Device.Adb.Shell($"input tap {x} {y}");
}

Device.Ui.ClearCache();
}

Expand All @@ -98,7 +125,12 @@ public void InputText(string text)
throw new ArgumentNullException(nameof(text));
}

Device.Adb.Shell($"input text {text.Replace(" ", "%s")}");
if (!_interactionServer.InputText(text))
{
DeviceLogger.Log("Failed to input text through server, trying through adb.");
Device.Adb.Shell($"input text {text.Replace(" ", "%s")}");
}

Device.Ui.ClearCache();
}

Expand All @@ -108,7 +140,12 @@ public void InputText(string text)
/// <param name="keyEvent">Key event to send to the device</param>
public void InputKeyEvent(KeyEvents keyEvent)
{
Device.Adb.Shell($"input keyevent {(int)keyEvent}");
if (!_interactionServer.InputKeyEvent(keyEvent))
{
DeviceLogger.Log("Failed to input key event through server, trying through adb.");
Device.Adb.Shell($"input keyevent {(int)keyEvent}");
}

Device.Ui.ClearCache();
}

Expand Down
8 changes: 8 additions & 0 deletions src/Testura.Android/Device/Services/IActivityService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#pragma warning disable IDE0005 // Using directive is unnecessary.
using System;
using System.Collections;
using System.Collections.Generic;
using Testura.Android.Util.Exceptions;
Expand Down Expand Up @@ -39,5 +40,12 @@ public interface IActivityService
/// <param name="packageName">Name of the package.</param>
/// <returns>True if package are installed, otherwise false</returns>
bool IsPackagedInstalled(string packageName);

/// <summary>
/// Get the package name.
/// </summary>
/// <param name="packageName">Name of package.</param>
/// <returns>The package version</returns>
Version GetPackageVersion(string packageName);
}
}
69 changes: 35 additions & 34 deletions src/Testura.Android/Device/Ui/Nodes/ScreenDumper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Threading;
using System.Xml;
using System.Xml.Linq;
using Testura.Android.Device.Ui.Server;
Expand Down Expand Up @@ -57,54 +56,56 @@ public void StopUiServer()
/// <returns>An xmldocument containing all information about the current android screen</returns>
public XDocument DumpUi()
{
var dump = GetDump();
try
{
return XDocument.Parse(dump);
}
catch (XmlException ex)
lock (_dumpLock)
{
throw new UiNodeNotFoundException("Could not parse nodes from dump", ex);
var dump = GetDump();

try
{
return XDocument.Parse(dump);
}
catch (XmlException ex)
{
throw new UiNodeNotFoundException("Could not parse nodes from dump", ex);
}
}
}

private string GetDump()
{
lock (_dumpLock)
int tries = _dumpTries;
while (true)
{
int tries = _dumpTries;
while (true)
try
{
try
if (!_server.Alive(2))
{
if (!_server.Alive(2))
{
_server.Start();
}

return _server.DumpUi();
_server.Start();
}
catch (UiAutomatorServerException)
{
if (tries > 0)
{
DeviceLogger.Log($"Failed to dump UI, trying {tries} more times");
Thread.Sleep(500);
tries--;

if (tries == 0)
{
/* In some cases we get stuck and the server is alive
but we can't dump the UI. So lets stop it once to be safe. */
_server.Stop();
}
return _server.DumpUi();
}
catch (UiAutomatorServerException)
{
if (tries > 0)
{
DeviceLogger.Log($"Failed to dump UI, trying {tries} more times");
tries--;

continue;
/* In some cases we get stuck and the server is alive
but we can't dump the UI. So try a reboot */
if (_server.Alive(2))
{
DeviceLogger.Log("Server alive but we can't dump.. trying a reboot.");
_server.Stop();
_server.Start();
}

DeviceLogger.Log("Failed to dump UI!");
throw;
continue;
}

DeviceLogger.Log("Tried everything but still can't dump the screen. Glitch in the matrix or did your device freeze?");
throw;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Testura.Android.Util;

namespace Testura.Android.Device.Ui.Server
{
/// <summary>
/// Defines methods to send interaction requests to the ui automator server.
/// </summary>
public interface IInteractionUiAutomatorServer
{
/// <summary>
/// Send a tap request to the ui automator server on the android device.
/// </summary>
/// <param name="x">The x coordinate</param>
/// <param name="y">The y coordinate</param>
/// <returns>True if we successfully tapped, otherwise false.</returns>
bool Tap(int x, int y);

/// <summary>
/// Send a swipe request to the ui automator server on the android device.
/// </summary>
/// <param name="fromX">Swipe from this x coordinate</param>
/// <param name="fromY">Swipe from this y coordinate</param>
/// <param name="toX">Swipe to this x coordinate</param>
/// <param name="toY">Swipe to this y coordinate</param>
/// <param name="duration">Swipe duration in miliseconds</param>
/// <returns>True if we successfully swiped, otherwise false.</returns>
bool Swipe(int fromX, int fromY, int toX, int toY, int duration);

/// <summary>
/// Send a key event request to the ui automator server on the android device.
/// </summary>
/// <param name="keyEvent">Key event to send to the device</param>
/// <returns>True if we successfully input key event, otherwise false.</returns>
bool InputKeyEvent(KeyEvents keyEvent);

/// <summary>
/// Send a input text request to the ui automator server on the android device.
/// </summary>
/// <param name="text">Text to send</param>
/// <returns>True if we successfully input text, otherwise false.</returns>
bool InputText(string text);
}
}
Loading

0 comments on commit f3636d6

Please sign in to comment.