Skip to content

Commit

Permalink
Release 2.0 (#54)
Browse files Browse the repository at this point in the history
* Started on release 2.0

* Continue to work with the release

* Created seperated interfaces for AndroidDevice

* Changed name on interfaces

* Fixed screen recording so we wait for process to finish before pulling the recording.

* Some name changes and comment fixes

* Fixed all comments

* Final fixes

* Renamed class and minor fixes

* Fixed problem when having a single device

* Changed name on attribute

* Changed from with to by

* Reworked a lot of the design

* Cleaned up the where class and updated the readme

* minor name changes

* Fixed new exception

* Added nuspec file

* Changed index to string

* Updated change log in nuget spec
  • Loading branch information
MilleBo authored Jul 17, 2018
1 parent 6370154 commit 96da746
Show file tree
Hide file tree
Showing 96 changed files with 2,243 additions and 2,375 deletions.
216 changes: 139 additions & 77 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ Testura.Android is a lightweight test automation framework for Android built in

- Quick and easy to set up
- Designed to be used with the page object pattern
- Designed to be flexible so you can change the behavior of individual services and/or extend the UI dump functionallity.
- Interact with multiple objects at the same time
- Add custom extensions to handle pop-ups, error dialogs, loading bars, etc without any overhead.
- Interact with multiple objects at the same time
- Makes it easy to run tests with multiple devices
- Add custom extensions to handle pop-ups, error dialogs, loading bars, etc without any overhead.
- Help tools to create your page objects ([seperate project](https://github.com/Testura/Testura.Android.PageObjectCreator))


Expand All @@ -24,21 +24,9 @@ Testura.Android is a lightweight test automation framework for Android built in
[https://www.nuget.org/packages/Testura.Android](https://www.nuget.org/packages/Testura.Android)

PM> Install-Package Testura.Android

## Nuget with page object helpers (optional) [![NuGet Status](https://img.shields.io/nuget/v/Testura.Android.PageObject.svg?style=flat)](https://www.nuget.org/packages/Testura.Android.PageObject)

[https://www.nuget.org/packages/Testura.Android.PageObject](https://www.nuget.org/packages/Testura.Android.PageObject)

PM> Install-Package Testura.Android.PageObject

## Usage

[Short introduction - Youtube](https://www.youtube.com/watch?v=x-U2F6mzcyc)

[How to create a test automation project with testura - Youtube](https://www.youtube.com/watch?v=0QhAcGdx65E)

[Blog with short tutorials](https://testura.wordpress.com/)

Testura.Android has been designed for the page object pattern. Here is a short example of a login view:

```c#
Expand All @@ -49,23 +37,22 @@ using Testura.Android.Device.Ui.Search;

namespace Testura.Android.Tests.Device
{
public class ExampleView
public class ExampleView : View
{
private IAndroidDevice _androidDevice;

// All UiObjects with the "MapUiObject" attribute will be automatically initialized as long as your class
// inherit from the "View" class or if you call on "ViewFactory.MapUiNodes(..)"
[MapUiObject(ResourceId = "usernameTextbox")]
private readonly UiObject _usernameTextbox;

[MapUiObject(ContentDesc = "passwordTextbox"]
private readonly UiObject _passwordTextbox;

private readonly UiObject _logInButton;

public ExampleView()
public ExampleView(IAndroidDevice device) : base(device)
{
_androidDevice = new AndroidDevice();

// The device won't look for the node/UI object before we interact with it,
// so it's perfectly safe to set up everything inside the constructor.
_usernameTextbox = _androidDevice.Ui.CreateUiObject(With.ResourceId("usernameTextbox"));
_passwordTextbox = _androidDevice.Ui.CreateUiObject(With.ContentDesc("passwordTextbox"));
_logInButton = _androidDevice.Ui.CreateUiObject(With.Lambda(n => n.Text == "Login"));
// It is also possible to skip the attribute and initialize the object from the constructor (this is also required for the "lambda" mapping)
_logInButton = device.MapUiObject(Where.Lambda(node => node.Text == "Login"));
}

public void Login(string username, string password)
Expand All @@ -78,75 +65,54 @@ namespace Testura.Android.Tests.Device
}
```

In the example we can see how we map different nodes on the screen to "UI objects". We can then interact with the nodes through the mapped UI object.
In the example we can see how we map different nodes on the screen to "UI objects".

It it also possible to create objects through attributes (required the PageObject package):

```c#
using Testura.Android.Device;
using Testura.Android.Device.Ui.Objects;
using Testura.Android.PageObject;
using Testura.Android.PageObject.Attributes;
using Testura.Android.Util;
## Map `UiObject` with `Where`

namespace Testura.Android.Tests.Device
{
public class ExampleView : View
{
[Create(with: AttributeTags.ResourceId, value: "usernameTextbox")]
private readonly UiObject _usernameTextbox;
If you have used Selenium, Appium or any other big test automation framework I'm sure you are familiar with the `By` keyword. Testura.Android has something similiar called `Where`.

[Create(with: AttributeTags.ContentDesc, value: "passwordTextbox")]
private readonly UiObject _passwordTextbox;
When mapping up a new UI object, you first have to decide how to find it, using one or more `Where`:

[Create(with: AttributeTags.Text, value: "Login")]
private readonly UiObject _logInButton;
- `Where.Text` - Find nodes that match the exact text
- `Where.ContainsText` - Find nodes that contain just a part of the text
- `Where.ResourceId` - Find nodes that contains the exact resource ID
- `Where.ContentDesc` - Find nodes that contains the exact content description
- `Where.Class` - Find nodes that contains the exact class
- `Where.Index` - Find nodes that have this index
- `Where.Package` - Find nodes that contains the exact package
- `Where.Lamba` - Find nodes with a lamba expression

public ExampleView(IAndroidDevice device)
: base(device)
{
}
`Where.Lamba` is a powerful method to find nodes and you can access both the node's parent and children:

public void Login(string username, string password)
{
_usernameTextbox.InputText(username);
_passwordTextbox.InputText(password);
_logInButton.Tap();
}
}
}
```c#
Where.Lambda(n =>
n.Text == "Some text"
&& n.Parent != null
&& n.Children.First().ContentDesc == "My child");
```

## Selecting nodes with `With`
### Advanced mapping with wildcards

If you have used Selenium, Appium or any other big test automation framework I'm sure you are familiar with the `By` keyword. Testura.Android has something similiar called `With`.
In some cases we don't know the whole value when mapping so to get passed this Testura.Android provide "wildcards":

When mapping up a new UI object, you first have to decide how to find it, using one or more `With`:

- `With.Text` - Find nodes that match the exact text
- `With.ContainsText` - Find nodes that contain just a part of the text
- `With.ResourceId` - Find nodes that contains the exact resource ID
- `With.ContentDesc` - Find nodes that contains the exact content description
- `With.Class` - Find nodes that contains the exact class
- `With.Index` - Find nodes that have this index
- `With.Package` - Find nodes that contains the exact package
- `With.Lamba` - Find nodes with a lamba expression
```c#
[MapUiObject(Class = "textbox", ResourceId = Where.Wildcard)]
private readonly UiObject _usernameTextbox;
```

`With.Lamba` is a powerful method to find nodes and you can access both the node's parent and children:
and later on we provide the wildcard value:

```c#
With.Lambda(n =>
n.Text == "Some text"
&& n.Parent != null
&& n.Children.First().ContentDesc == "My child");
// This will input text into textbox where class = "textbox" and resourceId = "user"
_usernameTextbox["user"].InputText("..");
```

## Services

The Android device class consists of multiple "services" that handle different parts of the device:

- ADB service - Send shell commands, push and pull files, install APKs etc.
- UI service - Look for nodes or map UI objects (handles screen dumping)
- Settings service - Enable/disable different settings, for example wifi and airplane mode
- Activity service - Start activity or get the name of the current one
- Interaction service - Handle interaction with the device, for example swipe and click
Expand All @@ -165,13 +131,109 @@ device.Settings.Wifi(State.Enable);

```c#
var device = new AndroidDevice();
var uiObject = device.Ui.CreateUiObject(With.ContentDesc(".."));
var uiObject = device.MapUiObject(Where.ContentDesc(".."));
uiObject.Tap();
uiObject.IsVisible();
uiObject.IsHidden();
uiObject.InputText("..");
uiObject.WaitForValue(n => n.Enabled);
var node = uiObject.Values();
uiObject.WaitUntil(node => node.Enabled);
var node = uiObject.First();
```

## Run tests in parallel with multiple devices

In many cases you want to run your tests on multiple devices in parallel to save time and Testura.Android can help you with that. Simply use the `AndroidDeviceFactory` class.

In your setup:

```c#
var deviceFactory = new AndroidDeviceFactory();
var device = deviceFactory.GetDevice(new DeviceConfiguration { Serial = ".." }, new DeviceConfiguration { Serial = ".." });
```

After your test:

```c#
deviceFactory.DisposeDevice(device);
```

Here we simply provide all possible devices to the `GetDevice` method and let Testura.Android keep track of the queue. When we finished our test we call on `DisposeDevice` so next test in the queue can run.

## Other features

### Logcat watcher

Testura.Android provides functionally to watch the logcat for different tags. To use it you simply have to extend the `LogcatWatcher` class or use the `EventLogcatWatcher`.

#### `EventLogcatWatcher`

`EventLogcatWatcher` will fire an event every time it find something that matches the provided tag(s).

```c#
public void StartLogcatWatcher(IAndroidDevice device)
{
var eventLogcatWatcher = new EventLogcatWatcher(device, new[] {"myTag "}, flushLogcat: true);
eventLogcatWatcher.NewOutputEvent += EventLogcatWatcherOnNewOutputEvent;
}

private void EventLogcatWatcherOnNewOutputEvent(object sender, string line)
{
// Found a new line with the tag, handle it.
}
```

### UI Extensions

Ui extensions is a way to hijack the UIService so *before* we look for element x we do y. An example of this could be looking for dialogs, error message or loading bars.

#### Example when we wait for a loading bar to disappear

```c#
public class UiLoadingExtension : IUiExtension
{
// A flag to temporary disable this extension
private bool _isWaiting;
private readonly UiObject _loadingBar;

private UiLoadingExtension(IAndroidUiMapper device)
{
_loadingBar = device.MapUiObject(With.ResourceId("loadingId"));
}

public bool CheckNodes(IList<Node> nodes)
{
if (nodes.Any(n => With.ResourceId("loadingId").NodeMatch(n, null)) && !_isWaiting)
{
WaitForLoading();
// Reset the wait timer for the actual object we're looking for.
return true;
}
return false;
}

private void WaitForLoading()
{
_isWaiting = true;
_loadingBar.IsHidden(180);
_isWaiting = false;
}
}
```

And then you add this to your device ui service

```c#
var device = new AndroidDevice();
device.Ui.Extensions.Add(new UiLoadingExtension());
```

### Record screen

```c#

var screenRecording = device.Adb.RecordScreen();
.. Perform actions ...
screenRecording.StopRecording("path/to/save");
```

## Page object helpers
Expand Down
9 changes: 3 additions & 6 deletions Testura.Android.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testura.Android", "src\Test
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testura.Android.Tests", "src\Testura.Android.Tests\Testura.Android.Tests.csproj", "{2A3D8906-EDF1-4215-8DF9-5BCACF69DC55}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testura.Android.PageObject", "src\Testura.Android.PageObject\Testura.Android.PageObject.csproj", "{8FADCEA7-D70C-49D6-B017-EBBAB5A709E4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -23,12 +21,11 @@ Global
{2A3D8906-EDF1-4215-8DF9-5BCACF69DC55}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2A3D8906-EDF1-4215-8DF9-5BCACF69DC55}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2A3D8906-EDF1-4215-8DF9-5BCACF69DC55}.Release|Any CPU.Build.0 = Release|Any CPU
{8FADCEA7-D70C-49D6-B017-EBBAB5A709E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8FADCEA7-D70C-49D6-B017-EBBAB5A709E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8FADCEA7-D70C-49D6-B017-EBBAB5A709E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8FADCEA7-D70C-49D6-B017-EBBAB5A709E4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D2167462-5F35-455B-A92F-B8174F7AF848}
EndGlobalSection
EndGlobal
53 changes: 0 additions & 53 deletions src/Testura.Android.PageObject/Attributes/CreateAttribute.cs

This file was deleted.

35 changes: 0 additions & 35 deletions src/Testura.Android.PageObject/Properties/AssemblyInfo.cs

This file was deleted.

Loading

0 comments on commit 96da746

Please sign in to comment.