This is an easy to use Mobile Automation Framework build on top of Appium. It is completely configurable thru the config file. It can be used to test native Mobile app for iOS and Android platform. This framework introduces AOM (Activity Object Model) concept similar to Page Object Model and promotes fluent coding style. Main aim behind this framework is to minimize the line of codes that is needed to write tests as all of the heavy weight lifting is done by the framework itself. Also, it is possible to do Automation with multiple devices in a single test if needed.
You need to use following in your pom.xml in order to use this framework.
<dependency>
<groupId>com.github.wasiqb.coteafs</groupId>
<artifactId>appium</artifactId>
<version>0.0.1</version>
</dependency>
You will also need following supporting dependencies which is also required to be added in your pom file.
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>io.appium</groupId>
<artifactId>java-client</artifactId>
<version>4.1.2</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
This the heart of this framework. It is a yaml file which will have all the settings needed for your tests. The framework will try to find System property coteafs.appium.config
which will have the path of the config file. If it is not specified, by default, it will search the file at src/test/resources/test-config.yaml
. If this file is not found, then it will throw AppiumConfigFileNotFoundException
.
Following is the server config list:
Key | Sample Value | Default Value | Description |
---|---|---|---|
ip |
127.0.0.1 | null | IP Address |
port |
4723 | 0 | Port Number |
start_up_time_out_seconds |
60 | 60 | Wait timeout for Server to start. |
session_timeout |
120 | 120 | New command timeout value. |
external |
true | false | true, if using external server run from terminal, else, if required for framework to start the server. |
appium_js_path |
path\to\appiumJS | null | Path to main.js file in appium installation directory. |
no_reset |
true | false | true, if app reset is not required, else, can be omitted. |
full_reset |
true | false | true, if full reset of app is required, else, can be omitted. |
Following is the device config list:
Key | Platform | Allowed Values | Description |
---|---|---|---|
device_name |
Both | Name of the device. | |
device_type |
Both | ANDROID, IOS, WINDOWS | Device type. |
device_version |
Both | Platform version. | |
automation_name |
Both | APPIUM, XCUI | Automation used. |
app_name |
IOS | Application Name | |
app_type |
Both | HYBRID, NATIVE, WEB | Application Type. |
app_package |
Android | Application Package Name. | |
app_location |
Both | Local path to Application apk or ipa file. | |
external_app |
Both | true, false | false, if app resides in src/test/resources folder, otherwise, true. |
app_activity |
Android | Loading activity name. | |
app_wait_activity |
Android | Activity name to wait for to load. | |
clear_system_files |
Both | true, false | true, will clear all the files after driver quit, else, false. |
default_wait |
Both | Int | Implicit wait. |
wait_for_element_until |
Both | Int | Explicit wait. |
bundle_id |
IOS | Application Bundle Id. | |
udid |
IOS | Device UDID. | |
team_id |
IOS | Apple Developer 10 char unique id. | |
signing_id |
IOS | iPhone Developer | Hardcoded value. |
agent_path |
IOS | WebDriverAgent project location. | |
bootstrap_path |
IOS | WebDriverAgent xcode project file location. | |
updated_wda_bundle_id |
IOS | Bundle Id for which code is signed. | |
use_new_wda |
IOS | true, false | true, will use new wda app everytime, else false. |
use_prebuilt_wda |
IOS | true, false | true, will not build and deploy wda app on device, else, false. |
wda_connection_timeout |
IOS | Int | WDA connection timeout value. |
session_timeout |
Both | Int | New command timeout value. |
no_reset |
Both | false | true, if app reset is not required, else, can be omitted. |
full_reset |
Both | false | true, if full reset of app is required, else, can be omitted. |
Framework handles all the events and throws a meaningful exception which is easy to identify the cause of failure. Following is the list of exception and their events of occurring:
Exception | Events |
---|---|
AppiumConfigFileNotFoundException |
When Config file is not found. |
AppiumConfigNotLoadedException |
When some error occurs while loading the config file. |
AppiumConfigParameterNotFoundException |
When the config file is missing mandatory params. |
AppiumServerAlreadyRunningException |
When Appium server is already running. |
AppiumServerNotRunningException |
When Appium server is not running. |
AppiumServerNotStartingException |
When there is Error while starting the server. |
AppiumServerNotStoppingException |
When there is Error while stopping the server. |
AppiumServerStoppedException |
When trying to interact with device while Appium server is stopped. |
DeviceAppNotClosingException |
When there is Error while closing Device app. |
DeviceAppNotFoundException |
When device app is not found on local machine. |
DeviceDesiredCapabilitiesNotSetException |
When device mandatory desired capabilities is not set. |
DeviceDriverDefaultWaitException |
When there is Error while setting implicit waits. |
DeviceDriverInitializationFailedException |
When there is Error while initializing device driver. |
DeviceDriverNotStartingException |
When there is Error while starting device driver. |
DeviceDriverNotStoppingException |
When there is Error while quitting device driver. |
DeviceElementDisabledException |
When you are trying to interact with disabled element. |
DeviceElementFindTimedOutException |
When element is not ready within specified explicit delay given in config file. |
DeviceElementNotDisplayedException |
When you are trying to interact with element which is not yet displayed. |
DeviceElementNotFoundException |
When device element cannot be found. |
DeviceTypeNotSupportedException |
When the mentioned device type is not supported by the framework. |
By default, framework create 3 types of log files under /logs
folder as specified below:
Log File Name | Description |
---|---|
test-log-all.log |
Logs all the events in this file. |
test-log-error.log |
Logs only the errors encountered in this file. |
test-log-main.log |
Logs only the info events in this file. |
The name of the file cannot be changed. If it is requested by many, then it can be done. Following is the sample content of the logs which is generated by the framework.
[21:25:58.889] [INFO ] - Preparing to perform actions on iOS device element UserName... (IOSActivity:)
[21:25:58.903] [INFO ] - Loading elements on iOS activity... (DeviceActivity:)
[21:26:03.472] [INFO ] - Clearing element [UserName]... (DeviceElementActions:)
[21:26:05.509] [INFO ] - Entering text [User1] in element [UserName]... (DeviceElementActions:)
[21:26:07.148] [INFO ] - Preparing to perform actions on iOS device element Password... (IOSActivity:)
[21:26:12.372] [INFO ] - Clearing element [Password]... (DeviceElementActions:)
[21:26:14.491] [INFO ] - Entering text [Pass@123] in element [Password]... (DeviceElementActions:)
[21:26:15.985] [INFO ] - Preparing to perform actions on iOS device element Go... (IOSActivity:)
[21:26:23.432] [INFO ] - Tapping on element [Go] using [1] finger(s) with [100] ms delay... (DeviceElementActions:)
It is possible to do assertion on any device element without writing any assertion ourself. This is handled in the framework where you can do assertion on any Element inline. Following is an example on how to do it.
. . .
final DashboardActivity main = new DashboardActivity (this.device);
main.onElement ("TypedAmt")
.verifyThat ()
.textShouldBeEqualTo ("$0.1");
. . .
Neat and clean, is'nt it??
Let's demonstrate basic example of how to use this framework.
Here we can configure all the required servers and devices.
servers:
android: // Used to refer config in tests.
ip: 127.0.0.1
port: 4723
external: true
iphone:
ip: 127.0.0.1
port: 4724
external: true
ipad:
ip: 127.0.0.1
port: 4725
external: true
devices:
android:
device_type: ANDROID
device_name: Mi4
device_version: 6.0.1
app_type: HYBRID
automation_name: APPIUM
app_location: app/android/your_app.apk
app_package: com.company.package
app_activity: com.company.activity.MainActivity
app_wait_activity: com.company.activity.SplashActivity
session_timeout: 6000
clear_system_files: true
iphone:
device_type: IOS
device_name: iPhone 6
device_version: 10.3.1
udid: XXXX
bundle_id: com.company.app
app_type: HYBRID
app_location: app/iphone/your_app.ipa
automation_name: XCUI
app_name: Campus Key
team_id: XXXXXXXXXX
signing_id: iPhone Developer
bootstrap_path: /usr/local/lib/node_modules/appium/node_modules/appium-xcuitest-driver/WebDriverAgent
agent_path: /usr/local/lib/node_modules/appium/node_modules/appium-xcuitest-driver/WebDriverAgent/WebDriverAgent.xcodeproj
session_timeout: 6000
ipad:
device_type: IOS
device_name: iPad Mini 4
device_version: 10.3.1
udid: XXXX
bundle_id: com.company.app
app_type: HYBRID
app_location: app/ipad/your_app.ipa
automation_name: XCUI
app_name: [Your App Name]
team_id: XXXXXXXXXX
signing_id: iPhone Developer
bootstrap_path: /usr/local/lib/node_modules/appium/node_modules/appium-xcuitest-driver/WebDriverAgent
agent_path: /usr/local/lib/node_modules/appium/node_modules/appium-xcuitest-driver/WebDriverAgent/WebDriverAgent.xcodeproj
session_timeout: 6000
wda_connection_timeout: 6000
full_reset: true
clear_system_files: true
For each activity, we need to create an Activity class by extending AndroidActivity class if working on Android, or IOSActivity if working on IOS device.
import org.openqa.selenium.By;
import com.github.wasiqb.coteafs.appium.device.DeviceElement;
import com.github.wasiqb.coteafs.appium.ios.IOSActivity;
import com.github.wasiqb.coteafs.appium.ios.IOSDevice;
public class LoginActivity extends IOSActivity {
public LoginActivity (final IOSDevice device) {
super (device);
}
// You need to implement this protected method.
@Override
protected DeviceElement prepare () {
// This the root element as seen in Inspector.
final DeviceElement login = DeviceElement.create ("Login")
.using (By.className ("UIAApplication"));
// This is the child element of login created above.
DeviceElement.create ("UserName")
.parent (login)
.using (By.className ("UIATextField"));
// This is also the child element of login created above.
DeviceElement.create ("Password")
.parent (login)
.using (By.className ("UIASecureTextField"));
// This is also the child element of login created above.
DeviceElement.create ("Login")
.parent (login)
.using (By.name ("Sign In"));
return login;
}
}
After creating the activity above, you now will move straight to writing tests. Below is a sample test.
import org.testng.Assert;
import org.testng.annotations.Test;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeSuite;
import com.github.wasiqb.coteafs.appium.ios.IOSDevice;
import com.github.wasiqb.coteafs.appium.service.AppiumServer;
import io.appium.java_client.SwipeElementDirection;
public class IOSHappyPathDemo {
protected IOSDevice device;
protected AppiumServer server;
@BeforeSuite (alwaysRun = true)
public void setupTestSuite () {
this.server = new AppiumServer ("ipad");
this.server.start ();
this.device = new IOSDevice (this.server, "ipad");
this.device.start ();
}
@AfterSuite (alwaysRun = true)
public void tearDownTestSuite () {
if (this.server != null && this.device != null) {
this.device.stop ();
this.server.stop ();
}
}
@Test
public void test1 () {
final LoginActivity login = new LoginActivity (this.device);
login.onElement ("UserName")
.enterText ("UserID_1");
login.onElement ("Password")
.enterText ("Password1");
login.onElement ("Login")
.tap (100);
}
}
- Sometimes it is observed that due some conflicting dependencies for SnakeYaml and Google's Guava, you need to make sure you add exclusion in other library dependenceis (which is internally using an old version of the above mentioned library) which you may use in your pom along with this framework. This is how you do this:
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.11</version>
<exclusions>
<exclusion>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
If this won't solve the issues, than you need to remove old versions from your .m2 repository from your local machine for both SnakeYaml and Google's Guava.
- For now, you must use external server as server started from Framework is causing problem for IOS. For Android it works.
Windows platform is not yet supported which will be done later.
- Windows Platform
- Mobile web apps
Area | Status |
---|---|
Dependencies | |
Issues | |
Pull Requests | |
Release | |
Stars | |
Forks | |
License | |
Maven Central |