Skip to content

Commit

Permalink
Merge branch 'master' into feature/Swipe-Actions
Browse files Browse the repository at this point in the history
  • Loading branch information
n-verbitsky committed May 24, 2020
2 parents 997b2a9 + 9f5dba2 commit 9526b6b
Show file tree
Hide file tree
Showing 29 changed files with 179 additions and 216 deletions.
85 changes: 57 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,29 @@ We use interfaces where is possible, so you can implement your own version of ta
<dependency>
<groupId>com.github.aquality-automation</groupId>
<artifactId>aquality-appium-mobile</artifactId>
<version>LATEST</version>
<version>${LATEST_VERSION}</version>
</dependency>
```

2. Configure path to your application at settings.json:
2. Configure the path to your application at settings.json:
- Copy [settings.json](./src/main/resources/settings.json) into the resources directory of your project.
- Open settings.json and find `applicationPath` option under the `driverSettings` section of desired platform. Replace the value with full or relative path to your app, e.g. `./src/test/resources/apps/ApiDemos-debug.apk`.

3. Ensure that [Appium server](https://appium.io) is set up at your machine where the code would be executed, and the address/port match to set in your `settings.json` in `remoteConnectionUrl` parameter.
If the parameter `isRemote` in your settings.json is set to `false`, this means that AppiumDriverLocalService would be used to setup Appium server using Node.js. This option requires specific version of node.js to be preinstalled on your machine (Please read more [here](http://appium.io/docs/en/contributing-to-appium/appium-from-source/#nodejs) )

4. (optional) Launch an application directly by calling `AqualityServices.getApplication();`.

> Note:
If you don't start an Application directly, it would be started with the first call of any Aquality service or class requiring interacting with the Application.

5. That's it! Now you are able work with Application via AqualityServices or via element services.
Please take a look at our example tests [here](./src/test/java/samples/)

6. To interact with Application's forms and elements, we recommend following the Page/Screen Objects pattern. This approach is fully integrated into our package.
To start with that, you will need to create a separate class for each window/form of your application, and inherit this class from the [AndroidScreen](./src/main/java/aquality/appium/mobile/screens/AndroidScreen.java) or [IOSScreen](./src/main/java/aquality/appium/mobile/screens/IOSScreen.java) respectively.

> We recommend to use separate Screen class for each form of your application. You can take advantage of inheritance and composition pattern. We also suggest not to mix app different platforms in single class: take advantage of interfaces instead, adding the default implementation to them if is needed.
To start with that, you will need to create a separate class for each window/form of your application, and inherit this class from the [Screen](./src/main/java/aquality/appium/mobile/screens/Screen.java).

> We recommend to use separate Screen class for each form of your application. You can take advantage of inheritance and composition pattern. We also suggest not to mix app different platforms in single class: take advantage of interfaces instead, adding the default implementation to them if it is needed.
7. From the Screen Object perspective, each Screen consists of elements on it (e.g. Buttons, TextBox, Labels and so on).
To interact with elements, on your form class create fields of type IButton, ITextBox, ILabel, and initialize them using the `getElementFactory()`. Created elements have a various methods to interact with them. We recommend combining actions into a business-level methods:
Expand Down Expand Up @@ -77,72 +77,101 @@ public class InvokeSearchScreen extends AndroidScreen {
return lblSearchResult.getText();
}
}

```

8. We use DI Guice to inject dependencies, so you can simply implement your MobileModule extended from [MobileModule](./src/main/java/aquality/appium/mobile/application/MobileModule.java) and inject it to `AqualityServices.initInjector(yourModule)`.

### ScreenFactory

When you automate tests for both iOS and Android platforms it is good to have only one set of tests and different implementations of screens. `ScreenFactory` allows to do this. You can define interfaces for your screens and have different implementations for iOS and Android platforms. And then use `ScreenFactory` to resolve necessary screen depending on the chosen platform.
When you automate tests for both iOS and Android platforms it is good to have only one set of tests and different implementations of screens. `ScreenFactory` allows to do this. You can define abstract classes for your screens and have different implementations for iOS and Android platforms. After that you can use `ScreenFactory` to resolve a necessary screen depending on the chosen platform.

1. Set `screensLocation` property in `settings.json`. It is a name of package where you define screens.

2. Define interfaces for the screens:
2. Define abstract classes for the screens:

```java
package aquality.appium.mobile.template.screens.interfaces;
package aquality.appium.mobile.template.screens.login;

import aquality.appium.mobile.elements.interfaces.IButton;
import aquality.appium.mobile.elements.interfaces.ITextBox;
import aquality.appium.mobile.screens.Screen;
import org.openqa.selenium.By;

public abstract class LoginScreen extends Screen {

private final ITextBox usernameTxb;
private final ITextBox passwordTxb;
private final IButton loginBtn;

protected LoginScreen(By locator) {
super(locator, "Login");
usernameTxb = getElementFactory().getTextBox(getUsernameTxbLoc(), "Username");
passwordTxb = getElementFactory().getTextBox(getPasswordTxbLoc(), "Password");
loginBtn = getElementFactory().getButton(getLoginBtnLoc(), "Login");
}

protected abstract By getUsernameTxbLoc();

public interface ILoginScreen extends IScreen {
protected abstract By getPasswordTxbLoc();

ILoginScreen setUsername(final String username);
protected abstract By getLoginBtnLoc();

ILoginScreen setPassword(final String password);
public LoginScreen setUsername(final String username) {
usernameTxb.sendKeys(username);
return this;
}

public LoginScreen setPassword(final String password) {
passwordTxb.typeSecret(password);
return this;
}

void tapLogin();
public void tapLogin() {
loginBtn.click();
}
}
```

3. Implement interface (Android example):

```java
package aquality.appium.mobile.template.screens.android;
package aquality.appium.mobile.template.screens.login;

import aquality.appium.mobile.screens.AndroidScreen;
import aquality.appium.mobile.template.screens.interfaces.ILoginScreen;
import aquality.appium.mobile.application.PlatformName;
import aquality.appium.mobile.screens.screenfactory.ScreenType;
import org.openqa.selenium.By;

import static io.appium.java_client.MobileBy.AccessibilityId;
import static org.openqa.selenium.By.xpath;

public class LoginScreen extends AndroidScreen implements ILoginScreen {
@ScreenType(platform = PlatformName.ANDROID)
public class AndroidLoginScreen extends LoginScreen {

public LoginScreen() {
super(xpath("//android.widget.TextView[@text='Login']"), "Login");
public AndroidLoginScreen() {
super(xpath("//android.widget.TextView[@text='Login']"));
}

@Override
public ILoginScreen setUsername(final String username) {
getElementFactory().getTextBox(AccessibilityId("username"), "Username").sendKeys(username);
return this;
protected By getUsernameTxbLoc() {
return AccessibilityId("username");
}

@Override
public ILoginScreen setPassword(final String password) {
getElementFactory().getTextBox(AccessibilityId("password"), "Password").typeSecret(password);
return this;
protected By getPasswordTxbLoc() {
return AccessibilityId("password");
}

@Override
public void tapLogin() {
getElementFactory().getButton(AccessibilityId("loginBtn"), "Login").click();
protected By getLoginBtnLoc() {
return AccessibilityId("loginBtn");
}
}
```

4. Resolve screen in test:

```java
ILoginScreen loginScreen = AqualityServices.getScreenFactory().getScreen(ILoginScreen.class);
LoginScreen loginScreen = AqualityServices.getScreenFactory().getScreen(LoginScreen.class);
```

You can find an example in [aquality-appium-mobile-java-template](https://github.com/aquality-automation/aquality-appium-mobile-java-template) repository.
Expand Down
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.github.aquality-automation</groupId>
<artifactId>aquality-appium-mobile</artifactId>
<version>1.2.0</version>
<version>2.0.0</version>

<packaging>jar</packaging>
<name>Aquality Appium Mobile</name>
Expand Down Expand Up @@ -176,7 +176,7 @@
<dependency>
<groupId>com.github.aquality-automation</groupId>
<artifactId>aquality-selenium-core</artifactId>
<version>1.0.0</version>
<version>1.0.1</version>
</dependency>

<dependency>
Expand All @@ -198,4 +198,4 @@
<version>0.9.12</version>
</dependency>
</dependencies>
</project>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import aquality.appium.mobile.configuration.ILocalServiceSettings;
import aquality.appium.mobile.elements.interfaces.IElementFactory;
import aquality.appium.mobile.screens.screenfactory.IScreenFactory;
import aquality.appium.mobile.screens.screenfactory.IScreenFactoryProvider;
import aquality.selenium.core.localization.ILocalizedLogger;
import aquality.selenium.core.logging.Logger;
import aquality.selenium.core.waitings.IConditionalWait;
Expand Down Expand Up @@ -163,7 +162,7 @@ public static IElementFactory getElementFactory() {
* @return Factory of screens.
*/
public static IScreenFactory getScreenFactory() {
return get(IScreenFactoryProvider.class).getScreenFactory();
return get(IScreenFactory.class);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import aquality.appium.mobile.configuration.ITouchActionsConfiguration;
import aquality.appium.mobile.elements.IElementsModule;
import aquality.appium.mobile.elements.interfaces.IElementFactory;
import aquality.appium.mobile.screens.screenfactory.IScreenFactoryProvider;
import aquality.appium.mobile.screens.screenfactory.IScreenFactory;
import aquality.appium.mobile.screens.screenfactory.IScreensModule;
import aquality.selenium.core.applications.AqualityModule;
import com.google.inject.Provider;
Expand All @@ -28,7 +28,7 @@ protected void configure() {
bind(ILocalServiceSettings.class).to(getLocalServiceSettingsImplementation()).in(Singleton.class);
bind(IConfiguration.class).to(getConfigurationImplementation());
bind(IElementFactory.class).to(getElementFactoryImplementation());
bind(IScreenFactoryProvider.class).to(getScreenFactoryProviderImplementation());
bind(IScreenFactory.class).to(getScreenFactoryImplementation());
bind(ITouchActionsConfiguration.class).to(getTouchActionsConfigurationImplementation()).in(Singleton.class);
bind(ITouchActions.class).to(getTouchActionsImplementation()).in(Singleton.class);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ public enum ElementType {
RADIOBUTTON(IRadioButton.class),
TEXTBOX(ITextBox.class);

private Class<? extends IElement> clazz;
private final Class<? extends IElement> clazz;

<T extends IElement> ElementType(Class<T> clazz){
this.clazz = clazz;
}

@SuppressWarnings("unchecked")
public <T extends IElement> Class<T> getClazz() {
return (Class<T>) clazz;
}
Expand Down
33 changes: 0 additions & 33 deletions src/main/java/aquality/appium/mobile/screens/AndroidScreen.java

This file was deleted.

24 changes: 0 additions & 24 deletions src/main/java/aquality/appium/mobile/screens/IOSScreen.java

This file was deleted.

30 changes: 5 additions & 25 deletions src/main/java/aquality/appium/mobile/screens/Screen.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package aquality.appium.mobile.screens;

import aquality.appium.mobile.application.AqualityServices;
import aquality.appium.mobile.application.PlatformName;
import aquality.appium.mobile.elements.interfaces.IElementFactory;
import aquality.appium.mobile.elements.interfaces.ILabel;
import aquality.selenium.core.elements.interfaces.IElementStateProvider;
import io.appium.java_client.AppiumDriver;
import org.openqa.selenium.By;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.InvalidArgumentException;

public abstract class Screen<T extends AppiumDriver> implements IScreen {
public abstract class Screen implements IScreen {

private final By locator;
private final String name;
Expand All @@ -25,27 +22,6 @@ protected Screen(By locator, String name) {
this.screenLabel = getElementFactory().getLabel(locator, name);
}

protected IElementFactory getElementFactory(){
return AqualityServices.getElementFactory();
}

@SuppressWarnings("unchecked")
protected T getDriver() {
ensureApplicationPlatformCorrect(getPlatform());
return (T) AqualityServices.getApplication().getDriver();
}

protected abstract PlatformName getPlatform();

private void ensureApplicationPlatformCorrect(PlatformName targetPlatform) {
PlatformName currentPlatform = AqualityServices.getApplication().getPlatformName();
if (targetPlatform != currentPlatform) {
throw new InvalidArgumentException(String.format(
"Cannot perform this operation. Current platform %s don't match to target %s",
currentPlatform, targetPlatform));
}
}

public By getLocator() {
return locator;
}
Expand All @@ -61,4 +37,8 @@ public Dimension getSize() {
public IElementStateProvider state() {
return screenLabel.state();
}

protected IElementFactory getElementFactory(){
return AqualityServices.getElementFactory();
}
}

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
public interface IScreensModule {

/**
* @return class which implements {@link IScreenFactoryProvider}
* @return class which implements {@link IScreenFactory}
*/
default Class<? extends IScreenFactoryProvider> getScreenFactoryProviderImplementation() {
return ScreenFactoryProvider.class;
default Class<? extends IScreenFactory> getScreenFactoryImplementation() {
return ScreenFactory.class;
}
}
Loading

0 comments on commit 9526b6b

Please sign in to comment.