Skip to content

Commit d8e76f4

Browse files
Merge pull request #10 from aquality-automation/Documentation/Create-Readme
Documentation/create readme
2 parents 92dc6af + 26d11bd commit d8e76f4

File tree

9 files changed

+139
-15
lines changed

9 files changed

+139
-15
lines changed

README.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Aquality Appium Mobile for Java
2+
3+
[![Build Status](https://dev.azure.com/aquality-automation/aquality-automation/_apis/build/status/aquality-automation.aquality-appium-mobile-java?branchName=master)](https://dev.azure.com/aquality-automation/aquality-automation/_build/latest?definitionId=6&branchName=master)
4+
[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=aquality-automation_aquality-appium-mobile-java&metric=alert_status)](https://sonarcloud.io/dashboard?id=aquality-automation_aquality-appium-mobile-java)
5+
6+
### Overview
7+
8+
This package is a library designed to simplify automation of Android and iOS mobile applications using Appium.
9+
10+
You've got to use this set of methods, related to most common actions performed with web elements.
11+
12+
Most of performed methods are logged using LOG4J, so you can easily see a history of performed actions in your log.
13+
14+
We use interfaces where is possible, so you can implement your own version of target interface with no need to rewrite other classes.
15+
16+
### Quick start
17+
18+
1. To start work with this package, simply add the dependency to your pom.xml:
19+
```
20+
<dependency>
21+
<groupId>com.github.aquality-automation</groupId>
22+
<artifactId>aquality-selenium</artifactId>
23+
<version>1.0.0</version>
24+
</dependency>
25+
```
26+
27+
2. Configure path to your application at settings.json:
28+
- Copy [settings.json](./src/main/resources/settings.json) into the resources directory of your project.
29+
- 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`.
30+
31+
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.
32+
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) )
33+
34+
4. (optional) Launch an application directly by calling `AqualityServices.getApplication();`.
35+
> Note:
36+
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.
37+
38+
5. That's it! Now you are able work with Application via AqualityServices or via element services.
39+
Please take a look at our example tests [here](./src/test/java/samples/android/AndroidBasicInteractionsTest.java)
40+
41+
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.
42+
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.
43+
44+
> 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.
45+
46+
47+
7. From the Screen Object perspective, each Screen consists of elements on it (e.g. Buttons, TextBox, Labels and so on).
48+
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:
49+
50+
```java
51+
package samples.android.apidemos.screens;
52+
53+
import aquality.appium.mobile.elements.interfaces.IButton;
54+
import aquality.appium.mobile.elements.interfaces.ILabel;
55+
import aquality.appium.mobile.elements.interfaces.ITextBox;
56+
import aquality.appium.mobile.screens.AndroidScreen;
57+
import org.openqa.selenium.By;
58+
59+
public class InvokeSearchScreen extends AndroidScreen {
60+
61+
private final ITextBox txbSearch = getElementFactory().getTextBox(By.id("txt_query_prefill"), "Search");
62+
private final IButton btnStartSearch = getElementFactory().getButton(By.id("btn_start_search"), "Start search");
63+
private final ILabel lblSearchResult = getElementFactory().getLabel(By.id("android:id/search_src_text"), "Search results");
64+
65+
public InvokeSearchScreen() {
66+
super(By.xpath("//android.widget.TextView[@text='App/Search/Invoke Search']"), "Invoke Search");
67+
}
68+
69+
public void submitSearch(String query) {
70+
txbSearch.clearAndType(query);
71+
btnStartSearch.click();
72+
}
73+
74+
public String getSearchResult() {
75+
return lblSearchResult.getText();
76+
}
77+
}
78+
79+
```
80+
81+
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)`.
82+

azure-pipelines.yml

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,23 +104,39 @@ steps:
104104
105105
#sudo xcode-select -s /Applications/Xcode_$(XCODE_VERSION).app/Contents/Developer
106106
#xcrun simctl list
107+
108+
#appium --allow-insecure chromedriver_autodownload &
109+
#echo "Appium server started"
107110
111+
- task: Maven@3
112+
displayName: 'Run tests using appium local service'
113+
inputs:
114+
mavenPomFile: 'pom.xml'
115+
goals: 'test -DisRemote=false'
116+
publishJUnitResults: true
117+
testResultsFiles: '**/surefire-reports/TEST-*.xml'
118+
javaHomeOption: 'JDKVersion'
119+
mavenVersionOption: 'Default'
120+
mavenAuthenticateFeed: false
121+
effectivePomSkip: false
122+
sonarQubeRunAnalysis: false
123+
124+
- script: |
108125
appium --allow-insecure chromedriver_autodownload &
109126
echo "Appium server started"
110127
111128
- task: Maven@3
112-
displayName: 'Run tests'
129+
displayName: 'Run tests using manually started (remote) server'
113130
inputs:
114131
mavenPomFile: 'pom.xml'
115-
goals: 'test'
132+
goals: 'test -DisRemote=true'
116133
publishJUnitResults: true
117134
testResultsFiles: '**/surefire-reports/TEST-*.xml'
118135
javaHomeOption: 'JDKVersion'
119136
mavenVersionOption: 'Default'
120137
mavenAuthenticateFeed: false
121138
effectivePomSkip: false
122139
sonarQubeRunAnalysis: false
123-
continueOnError: true
124140

125141
- task: CopyFiles@2
126142
displayName: 'Copy failure screenshots and test logs'
@@ -130,10 +146,12 @@ steps:
130146
surefire-reports/failure_screenshots/*.png
131147
log/*.log
132148
TargetFolder: '$(Build.ArtifactStagingDirectory)'
149+
condition: succeededOrFailed()
133150

134151
- task: PublishBuildArtifacts@1
135152
displayName: 'Publish copied artifacts'
136153
inputs:
137154
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
138155
ArtifactName: 'drop'
139-
publishLocation: 'Container'
156+
publishLocation: 'Container'
157+
condition: succeededOrFailed()

src/main/java/aquality/appium/mobile/application/ApplicationFactory.java

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,44 @@
22

33
import aquality.selenium.core.configurations.ITimeoutConfiguration;
44
import aquality.selenium.core.localization.ILocalizationManager;
5+
import aquality.selenium.core.utilities.ElementActionRetrier;
56
import io.appium.java_client.AppiumDriver;
67
import io.appium.java_client.android.AndroidDriver;
78
import io.appium.java_client.android.AndroidElement;
89
import io.appium.java_client.ios.IOSDriver;
910
import io.appium.java_client.ios.IOSElement;
1011
import org.openqa.selenium.Capabilities;
12+
import org.openqa.selenium.SessionNotCreatedException;
1113
import org.openqa.selenium.remote.http.HttpClient;
1214
import org.openqa.selenium.remote.http.HttpClient.Builder;
1315
import org.openqa.selenium.remote.http.HttpClient.Factory;
1416

1517
import java.net.URL;
1618
import java.time.Duration;
19+
import java.util.Collections;
20+
import java.util.List;
1721

18-
abstract class ApplicationFactory implements IApplicationFactory {
22+
public abstract class ApplicationFactory implements IApplicationFactory {
1923

20-
private IllegalArgumentException getLoggedWrongPlatformNameException(String actualPlatform) {
24+
protected IllegalArgumentException getLoggedWrongPlatformNameException(String actualPlatform) {
2125
String message = AqualityServices.get(ILocalizationManager.class)
2226
.getLocalizedMessage("loc.platform.name.wrong", actualPlatform);
2327
IllegalArgumentException exception = new IllegalArgumentException(message);
2428
AqualityServices.getLogger().fatal(message, exception);
2529
return exception;
2630
}
2731

28-
AppiumDriver getDriver(URL serviceUrl) {
32+
protected AppiumDriver getDriver(URL serviceUrl) {
2933
PlatformName platformName = AqualityServices.getApplicationProfile().getPlatformName();
3034
Capabilities capabilities = AqualityServices.getApplicationProfile().getDriverSettings().getCapabilities();
3135
Factory httpClientFactory = new ClientFactory();
36+
return new CustomActionRetrier(Collections.singletonList(SessionNotCreatedException.class))
37+
.doWithRetry(() -> createSession(platformName, serviceUrl, httpClientFactory, capabilities));
38+
}
39+
40+
protected AppiumDriver createSession(PlatformName platformName, URL serviceUrl, Factory httpClientFactory,
41+
Capabilities capabilities) {
42+
AqualityServices.getLocalizedLogger().info("loc.application.driver.remote", serviceUrl);
3243
AppiumDriver driver;
3344
switch (platformName) {
3445
case ANDROID:
@@ -43,7 +54,21 @@ AppiumDriver getDriver(URL serviceUrl) {
4354
return driver;
4455
}
4556

46-
class ClientFactory implements Factory{
57+
protected class CustomActionRetrier extends ElementActionRetrier {
58+
private final List<Class<? extends Exception>> handledExceptions;
59+
60+
CustomActionRetrier(List<Class<? extends Exception>> handledExceptions) {
61+
super(AqualityServices.getConfiguration().getRetryConfiguration());
62+
this.handledExceptions = handledExceptions;
63+
}
64+
65+
@Override
66+
public List<Class<? extends Exception>> getHandledExceptions() {
67+
return handledExceptions;
68+
}
69+
}
70+
71+
protected class ClientFactory implements Factory {
4772

4873
private final Factory defaultClientFactory = Factory.createDefault();
4974
private final Duration timeoutCommand = AqualityServices.get(ITimeoutConfiguration.class).getCommand();
@@ -64,7 +89,7 @@ public void cleanupIdleClients() {
6489
}
6590
}
6691

67-
void logApplicationIsReady() {
92+
protected void logApplicationIsReady() {
6893
AqualityServices.getLocalizedLogger().info("loc.application.ready", AqualityServices.getApplicationProfile().getPlatformName());
6994
}
7095
}

src/main/java/aquality/appium/mobile/application/LocalApplicationFactory.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
import io.appium.java_client.AppiumDriver;
44
import io.appium.java_client.service.local.AppiumDriverLocalService;
5+
import io.appium.java_client.service.local.AppiumServiceBuilder;
56

67
public class LocalApplicationFactory extends ApplicationFactory {
78

89
@Override
910
public Application getApplication() {
10-
AppiumDriverLocalService service = AppiumDriverLocalService.buildDefaultService();
11+
AppiumServiceBuilder builder = new AppiumServiceBuilder()
12+
.withArgument(() -> "--allow-insecure", "chromedriver_autodownload");
13+
AppiumDriverLocalService service = AppiumDriverLocalService.buildService(builder);
1114
service.start();
1215
AppiumDriver driver = getDriver(service.getUrl());
1316
logApplicationIsReady();

src/main/java/aquality/appium/mobile/application/RemoteApplicationFactory.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ public class RemoteApplicationFactory extends ApplicationFactory {
1010
@Override
1111
public Application getApplication() {
1212
URL serverUrl = AqualityServices.getApplicationProfile().getRemoteConnectionUrl();
13-
AqualityServices.getLocalizedLogger().info("loc.application.driver.remote", serverUrl);
1413
AppiumDriver driver = getDriver(serverUrl);
1514
driver.setFileDetector(new LocalFileDetector());
1615
logApplicationIsReady();

src/main/java/aquality/appium/mobile/screens/Screen.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ protected Screen(By locator, String name) {
3636
* False - screen is not opened
3737
*/
3838
public boolean isDisplayed() {
39-
return screenLabel.state().waitForDisplayed();
39+
return isDisplayed(null);
4040
}
4141

4242
/**

src/main/resources/settings.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
"platformName" : "android",
33
"isRemote": false,
44
"remoteConnectionUrl": "http://127.0.0.1:4723/wd/hub",
5-
"isElementHighlightEnabled" : true,
65

76
"driverSettings": {
87
"android": {

src/test/resources/settings.androidwebsession.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
"platformName" : "android",
33
"isRemote": true,
44
"remoteConnectionUrl": "http://127.0.0.1:4723/wd/hub",
5-
"isElementHighlightEnabled" : true,
65

76
"driverSettings": {
87
"android": {

src/test/resources/settings.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
"platformName" : "android",
33
"isRemote": true,
44
"remoteConnectionUrl": "http://127.0.0.1:4723/wd/hub",
5-
"isElementHighlightEnabled" : true,
65

76
"driverSettings": {
87
"android": {

0 commit comments

Comments
 (0)