Appium Testing Chat, Text, Call between Multiple Emulators, Sep 2019

Sandeep Dinesh
4 min readSep 26, 2019

--

One of the first challenges I took up after joining the Chat app company, where I currently work, was to try and simulate how exactly people chat in the real world; peer to peer or in a group. Till date, I had always automated apps using Appium where the user work flows are up against the app; never been one instance of the app live currently somewhere, against another.

Although, I was skeptical if Appium would be able to do this initially, because for me, the very idea of multi device tests has always been-the same test code (barring frame-working for different platform object identifier and driver instantiation) would run across multiple devices in a device farm with or without a selenium grid.

The exciting news is that Appium can support such scenario too!!!. Here’s how we went about solving this problem.

Do note: the following code is in Kotlin and its a pseduo code with few functions/ utilities, where I haven’t provided the definitions, but the usually name are self explanatory. This example is for Chat. Any real time multi user-device scenario: Call/ Chat/ Group/ Text/ Message can be developed using the ideas mentioned here. Finally, it’s for Android, but it will work for iOS too, provided you do the needful changes at desired capabilities and install XCUITDriver and have the developer license as prescribed by Apple for signing.

Please feel free to contact me/ discuss in the comment section, if you need any technical help in understanding this example/ or to automate similar scenarios.

TLDR:
1. Spawn Multiple Appium Server Instances at different ports

val COMMAND_TO_START_AT_PORT = "appium -p "

shell.runCommand(COMMAND_TO_START_AT_PORT+port, runInBackground = true)

2. Create driver per Mobile Device, per Appium Port

import io.appium.java_client.AppiumDriver
import io.appium.java_client.MobileElement
import io.appium.java_client.android.AndroidDriver
import io.appium.java_client.remote.AutomationName
import io.appium.java_client.remote.MobileCapabilityType
import io.appium.java_client.remote.MobilePlatform
import org.openqa.selenium.remote.DesiredCapabilities
import mu.KotlinLogging
import java.io.File
import java.net.URL...private val LOCALHOST_HUB_URL = "http://localhost:{}/wd/hub"...fun getDriver(deviceId:String, port: String): AppiumDriver<MobileElement> {
val driver: AppiumDriver<MobileElement>
val adbUtitlies = ADBUtilities()
val root = File(System.getProperty("user.dir"))
var app = File(root, "<YOUR_APK_FILE>.apk")
val capabilities = DesiredCapabilities()
capabilities.setCapability(MobileCapabilityType.APP, app.absolutePath)
capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, MobilePlatform.ANDROID)
capabilities.setCapability("name", "<TEST_SUITE_NAME>")
capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.ANDROID_UIAUTOMATOR2)
capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, deviceId)
capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, adbUtitlies.getPlatformVersion(deviceId))
try {
logger.info { "Connecting to local Appium server..." }
driver = AndroidDriver(URL(LOCALHOST_HUB_URL.replace("{}", port)), capabilities)
logger.info { "Connected successfully" }
} catch (e: Exception) {
logger.error{"Could not start driver at port:$port for deviceId:$deviceId"}
throw CustomException("Could not start driver at port:$port for deviceId:$deviceId")
}
return driver
}

3. Initialize all your pages to the corresponding driver

class Pages {...lateinit var homePage: HomePage
lateinit var introPage: IntroPage
lateinit var loginPage: LoginPage...fun initializePages(driver: AppiumDriver<MobileElement>){
try {
homePage = HomePage(driver)
introPage = IntroPage(driver)
loginPage = LoginPage(driver)
} catch (e: Exception) {
logger.error{"Error thrown while Initializing Pages"}
throw CustomException("Error thrown while Initializing Pages")
}
}

4. Define Model Classes

import io.appium.java_client.AppiumDriver
import io.appium.java_client.MobileElement
import mu.KotlinLogging
private val logger = KotlinLogging.logger {}class Instance { lateinit var device: Device
lateinit var appiumInstance: String
lateinit var driver: AppiumDriver<MobileElement>
lateinit var Pages: Pages
}class Device { lateinit var deviceId: String
lateinit var platform: String
}class DeviceManager { val adbUtilities = ADBUtilities() fun getDeviceList(): List<Device> {
val devices: List<String> = adbUtilities.getDevices()
val deviceList: ArrayList<Device> = ArrayList()
for(device in devices){
val deviceObject = Device()
deviceObject.deviceId = device
deviceObject.platform = Constants.DEVICE_PLATFORM_TYPE_ANDROID
deviceList.add(deviceObject)
}
return deviceList
}
}

5. Spawn Instances

class InstanceSpawner {    private val LOCALHOST_HUB_URL = "http://localhost:{}/wd/hub"    lateinit var deviceList: List<Device>
lateinit var Instances: List<Instance>
fun spawnInstances() {
initializeDeviceList()
instances = initializeInstances()
}
fun initializeDeviceList(){
val deviceManager = DeviceManager()
deviceList = deviceManager.getDeviceList()
logger.debug{"Initialized Device List"}
}
fun initializeInstances(): List<Instance>{
val instances: ArrayList<Instance> = ArrayList()
if(deviceList.isEmpty()){
logger.error{"Device List is Empty"}
throw CustomException("Device List is Empty")
} else {
val appiumServer = AppiumServer()
var port = INITIAL_PORT
for(device: Device in deviceList){
logger.debug{"Starting Appium Server at $port"}
appiumServer.start(""+port)
val driver: AppiumDriver<MobileElement> = getDriver(device.deviceId, ""+port)
val pages = Pages()
pages.initializePages(driver)
var instance = Instance()
instance.device = device
instance.appiumInstance = ""+port
instance.driver = driver
instance.pages = pages
instances.add(instance) port++
}
if(port != INITIAL_PORT){
logger.debug{"Appium Servers were started"}
} else {
logger.error{"Appium Servers were NOT started"}
throw CustomException("Appium Servers were NOT started")
}
}
return instances
}

6. Finally, use the InstanceSpawner in your Test Class

class TestMultiDeviceChat() {    val instanceSpawner = InstanceSpawner()
lateinit var senderInstance: Instance
lateinit var recieverInstance: Instance
@BeforeClass(alwaysRun = true)
fun classSetup() {
logger.debug { "Class Setup" }
InstanceSpawner.spawnInstances()
senderInstance = InstanceSpawner.instances.get(0)
recieverInstance = InstanceSpawner.instances.get(1)
senderInstance.driver.resetApp()
recieverInstance.driver.resetApp()
senderInstance.pages.login(Users.USER1)
senderInstance.pages.confirmHomePage()
recieverInstance.pages.login(Users.USER2)
recieverInstance.pages.confirmHomePage()
}
@BeforeMethod(alwaysRun = true)
fun setup() {
logger.debug { "Method Setup" }
senderInstance.pages.returnToHomeScreenAndDeleteChatsIfAny()
recieverInstance.pages.returnToHomeScreenAndDeleteChatsIfAny()
}
@AfterClass(alwaysRun = true)
fun classTearDown() {
logger.debug { "Class Teardown" }
senderInstance.pages.returnToHomeScreenAndDeleteChatsIfAny()
recieverInstance.pages.returnToHomeScreenAndDeleteChatsIfAny()
senderInstance.pages.logout()
recieverInstance.pages.logout()
}
@Test(groups = [Constants.INTEGRATION, Constants.CHAT, Constants.MULTI_DEVICE_CHAT])
fun testSendTextMessage() {
senderInstance.pages.searchUser(Constants.USER2_USERNAME)
senderInstance.pages.selectFirstUserFromFindPeopleResultsAndOpenChatPage()
senderInstance.pages.sendMessageAndConfirm(Constants.OUTGOING_TEXT_MESSAGE_TYPE)
Assert.assertTrue(recieverInstance.pages.homePage.isThereAnyConversation(waitForConversation=true))
recieverInstance.pages.selectFirstUserFromChatListAndOpenChatPage()
recieverInstance.pages.confirmRecievedMessage(type=Constants.INCOMING_TEXT_MESSAGE_TYPE,
messageText=Constants.SAMPLE_TEXT_MESSAGE)
}

Hope this helps! Cheers.

--

--

Sandeep Dinesh
Sandeep Dinesh

Written by Sandeep Dinesh

Test Automation, CI/CD, DevOps & SRE

Responses (1)