In my previous article, we discussed the Kotlin, its advantages, and how it will work with Selenium. We know that Kotlin is a general-purpose, open-source, statically typed programming language that combines object-oriented and functional programming features.
In this article, I would like to share how can we use Kotlin programming language for Appium script development and start the Appium server. It also covered the extent report logic written in Kotlin language.
Kickstart Your Appium with Kotlin
Prerequisites
Following are the prerequisites to start Appium with Kotlin,
- Install Java SDK 8 and above.
- Install Eclipse or IntelliJ IDEA IDEs.
- Install the Kotlin plugin in IDEs. Here I am using Eclipse as IDE and Kotlin plugin for Eclipse downloaded from https://marketplace.eclipse.org/content/kotlin-plugin-eclipse
- The latest version of following maven dependencies:
- testng
- selenium-java
- selenium-server
- java-client
- kotlin-test
- kotlin-stdlib-jdk8
- extentreports
Step-by-step procedure
Step 1: Create a maven project and add the above dependencies in pom.xml of the project.
Step 2: Create a Kotlin class to keep the logic to initiate the driver and start the Appium server. Let say the class name as AutomationBase. I am defining this class as an abstract class. So it is easy to extend by other classes. I have implemented startApplication, startAppiumServer, getPort, getNodePath, getJSPath, getDriverInstance and setDriverInstance methods in the AutomationBase class. Below are the sample code snippets:
fun startApplication() { try { var service: AppiumDriverLocalService = startAppiumServer() var capabilities = DesiredCapabilities() capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "One Plus") capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android") capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, "10") capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.ANDROID_UIAUTOMATOR2) capabilities.setCapability(AndroidMobileCapabilityType.AUTO_GRANT_PERMISSIONS, true) capabilities.setCapability("newCommandTimeout", 180) capabilities.setCapability("udid", "e6916f40") capabilities.setCapability(MobileCapabilityType.APP, "YOUR_APP_PATH") capabilities.setCapability(MobileCapabilityType.NO_RESET, true) if(!service.toString().isEmpty()){ driver = AndroidDriver(service.getUrl(), capabilities) } } catch (e: Exception) { e.printStackTrace() } } //Start the Appium server session private fun startAppiumServer(): AppiumDriverLocalService { var IP_ADDRESS: String = "127.0.0.1" var bootStrapPort: String var chromePort: String var port: Int if (!File(System.getProperty("user.dir") + "\\Logs\\Appium_logs").exists()) { (File(System.getProperty("user.dir") + "\\Logs")).mkdir() (File(System.getProperty("user.dir") + "\\Logs\\Appium_logs")).mkdir() } port = getPort() bootStrapPort = Integer.toString(getPort()) chromePort = Integer.toString(getPort()) var service = AppiumDriverLocalService.buildService( AppiumServiceBuilder().withAppiumJS(File(getJSPath())) .usingDriverExecutable(File(getNodePath())).withIPAddress(IP_ADDRESS).usingPort(port) .withArgument(AndroidServerFlag.BOOTSTRAP_PORT_NUMBER, bootStrapPort) .withArgument(AndroidServerFlag.CHROME_DRIVER_PORT, chromePort) .withLogFile(File(System.getProperty("user.dir") + "\\Logs\\Appium_logs\\appiumLogs.txt"))) service.start() if (service.isRunning()) { System.out.println("Server is running....." + service) } else { System.out.println("Server startup failed.....") System.exit(0) } return service } //Get dynamic available port private fun getPort(): Int { var port: Int = 0 try { var socket = ServerSocket(0) socket.setReuseAddress(true) port = socket.getLocalPort() socket.close() } catch (e: Exception) { e.printStackTrace() } return port } //Get the NodeJS path private fun getNodePath(): String { var nodePath: String var p: Process var reader: BufferedReader var command: String var operatingSystem: String = System.getProperty("os.name") if (operatingSystem.contains("Win")) { command = "where" + " " + "node" } else { command = "which " + "node" } p = Runtime.getRuntime().exec(command) reader = BufferedReader(InputStreamReader(p.getInputStream())) nodePath = reader.readLine() p.waitFor() p.destroy() return nodePath } //Get the main.js file path from Appium installation location fun getJSPath(): String { var jsPaths: String lateinit var actualJSPath: String var command: String var operatingSystem: String = System.getProperty("os.name") if (operatingSystem.contains("Win")) { command = "where" + " " + "appium" var p: Process = Runtime.getRuntime().exec(command) var stdInput: BufferedReader = BufferedReader(InputStreamReader(p.getInputStream())) jsPaths = stdInput.readLine() actualJSPath = jsPaths.replace("appium", "node_modules\\appium\\build\\lib\\main.js") p.waitFor() p.destroy() } else { actualJSPath = "//usr//local//lib//node_modules//appium//build//lib//main.js" } return actualJSPath } fun getDriverInstance(): WebDriver { return this.driver } fun setDriverInstance(driver: WebDriver) { this.driver = driver }
startApplication method helps to instantiate driver sessions based on the specified capabilities. startAppiumServer method helps to start the Appium server and also create and write server logs to a text file. getPort method helps to create the dynamic network port that supporting the startAppiumServer method. getNodePath method helps to get the node path and it supporting the startAppiumServer method. getJSPath method helps to get the Appium main.js path and it supporting the startAppiumServer method.
Step 3: Create a TestRunner class and it extends AutomationBase class. This class holds TestNG annotations @Listeners, @BeforeClass, and @AfterSuite. Below are the complete code snippets:
@Listeners(AutomationReport::class) open class TestRunner : AutomationBase() { @BeforeClass fun setUp() { startApplication() } @AfterSuite fun tearDown() { Thread.sleep(3000) driver.quit() } }
Step 4: Create a sample test class to keep the test cases. Let say SampleTests and it extends TestRunner class. Following are the sample code snippets:
class SampleTests : TestRunner() { @Test fun TC001_testEnterPicode() { SampleTestsHelper.enterPinCode(driver, "682030") } @Test fun TC002_testSearchFail() { Assert.fail() } }
Step 5: Create a Kotlin class which should act as a helper class of test class. Let say SampleTestsHelper and defined this SampleTestsHelper as object. So Kotlin will create an instance automatically when we invoke the methods of SampleTestsHelper, no need to create a separate instance to invoke the methods of the helper class. Below is the complete code snippets:
object SampleTestsHelper { /** * This method used to enter pincode value * * @author sanojs * @since 05-06-2020 */ fun enterPinCode(driver: WebDriver, pincode: String) { try { driver.findElement(By.id("in.dmart:id/et_activity_pincode_pincode")).sendKeys(pincod) } catch (e: Exception) { e.printStackTrace() } } }
Step 6: Create a Kotlin class to keep the logic to generate an Automation test execution report in HTML format. Let say the class name as AutomationReport. Here I am using extentreports library to generate an HTML report. Also, using ITestListener interface to control the executions and results. Below is the complete code snippet of the AutomationReport class,
class AutomationReport : ITestListener { public lateinit var sparkReporter: ExtentSparkReporter public lateinit var extentReport: ExtentReports public lateinit var extentTest: ExtentTest /** * This override method used to create HTML template for the test report * * @author sanojs * @since 05-06-2020 */ override fun onStart(testContext: ITestContext) { try { sparkReporter = ExtentSparkReporter(System.getProperty("user.dir") + "/AutomationReport/") sparkReporter.config().setDocumentTitle("Appium Kotlin Automation") sparkReporter.config().setReportName("Automation Execution Report") sparkReporter.config().setTheme(com.aventstack.extentreports.reporter.configuration.Theme.DARK) extentReport = ExtentReports() extentReport.attachReporter(sparkReporter) extentReport.setSystemInfo("Application Name", "Kotlin Appium Demo") extentReport.setSystemInfo("Platform", System.getProperty("os.name")) extentReport.setSystemInfo("Environment", "QA") } catch (e: Exception) { e.printStackTrace() } } /** * This override method used to collect current test case name add to the report * * @author sanojs * @since 05-06-2020 */ override fun onTestStart(result: ITestResult) { var testName: String = result.getMethod().getMethodName() extentTest = extentReport.createTest(testName) } /** * This override method used to add pass status to the report * * @author sanojs * @since 05-06-2020 */ override fun onTestSuccess(result: ITestResult) { var testName: String = result.getMethod().getMethodName() try { extentTest.log( Status.PASS, MarkupHelper.createLabel(testName + " Test Case PASSED", ExtentColor.GREEN) ) } catch (e: Exception) { e.printStackTrace() } } /** * This override method used to add fail status to the report * * @author sanojs * @since 05-06-2020 */ override fun onTestFailure(result: ITestResult) { var driver: WebDriver var currentClass = result.getInstance() var testName: String = result.getMethod().getMethodName() try { driver = (currentClass as AutomationBase).getDriverInstance() var screenshotPath = Utilities().screenshotCapture(driver, result.getName()) extentTest.log( Status.FAIL, MarkupHelper.createLabel(testName + " Test Case FAILED", ExtentColor.RED) ) extentTest.log( Status.FAIL, MarkupHelper.createLabel("Reason for Failure: " + result.getThrowable().toString(), ExtentColor.RED) ) extentTest.addScreenCaptureFromPath(screenshotPath) } catch (e: Exception) { e.printStackTrace() } } /** * This override method used to add skip status to the report * * @author sanojs * @since 05-06-2020 */ override fun onTestSkipped(result: ITestResult) { var testName: String = result.getMethod().getMethodName() try { extentTest.log( Status.SKIP, MarkupHelper.createLabel(testName + " Test Case SKIPPED", ExtentColor.ORANGE) ) } catch (e: Exception) { e.printStackTrace() } } /** * This override method used to store HTML report to the specified path and flush extent report instance * * @author sanojs * @since 05-06-2020 */ override fun onFinish(testContext: ITestContext) { try { extentReport.flush() val dateFormat = SimpleDateFormat("dd-MMM-yyyy_HH-mm-ss") val date = Date() val filePathdate: String = dateFormat.format(date).toString() var actualReportPath: String = System.getProperty("user.dir") + "/AutomationReport/" + "index.html" File(actualReportPath).renameTo( File( System.getProperty("user.dir") + "/AutomationReport/" + "Automation_Report_" + filePathdate + ".html" ) ) } catch (e: Exception) { e.printStackTrace() } } }
Step 7: Create another class called Utilities to keep common utility functions required for automation. Here I just added one utility to capture the screenshot. Below is the code snippet to capture the screenshot,
fun screenshotCapture(driver: WebDriver, fileName: String): String { var destination: String = "" try { var scrFile = (driver as TakesScreenshot).getScreenshotAs(OutputType.FILE) var dateFormat = SimpleDateFormat("yyyyMMddHHmmss") var cal = Calendar.getInstance() var path = File("Failure_Screenshots").getAbsolutePath() destination = path + "/" + fileName + dateFormat.format(cal.getTime()) + ".png" scrFile.copyTo(File(destination)) } catch (e: Exception) { e.printStackTrace() } return destination }
Step 8: Add a testng.xml file into the project structure and map your test classes into the testng.xml to run the test cases. Once execution complete the HTML report will generate inside the AutomationReport folder in the project structure.
Below is the overall project structure of Appium with Kotlin,
Try to use the above step-by-step procedure in your Appium automation world and enjoy the automation.
make it perfect!
Leave a Reply