Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Cross-platform UI tests or Say no to Appium

Cross-platform UI tests or Say no to Appium

Roman Tysiachnik

Avatar for Alexander Saenko

Alexander Saenko

August 31, 2019
Tweet

More Decks by Alexander Saenko

Other Decks in Programming

Transcript

  1. AGENDA
 — 2 A little bit about UI tests at

    all Talk about Appium setup Why we’ve decided to drop Appium Cross-platform UI tests using native frameworks and code generation Demo
  2. 5 ▪ long-term product. ▪ projects with critical metrics (ads).

    ▪ projects with banking transactions. CASES WHEN UI TESTS ARE REQUIRED —
  3. 8 Appium is a tool for automating mobile applications and

    writing cross-platform UI tests based on Selenium WebDriver. The difference between Selenium and Appium is that first is a tool for automating browsers and web applications, whereas second is a tool for automating Native / Hybrid mobile applications. WHAT IS APPIUM? —
  4. 9 ▪ Choose language and build tool that you will

    use (Kotlin + Gradle e.g.). ▪ Setup JAVA environment (JDK, JAVA_HOME etc.). ▪ Install node and npm to install Appium. ▪ Download Appium desktop app. ▪ Create a base class for cross-platform tests. HOW TO SETUP APPIUM? —
  5. 11 BASE CLASS — @BeforeClass @JvmStatic fun beforeClass() { appiumService

    = AppiumDriverLocalService.buildService(AppiumServiceBuilder().withIPAddress(“127.0.0.1”)) appiumService.start() driver = getDriverFor(IOS) wait = WebDriverWait(driver, 15) longWait = WebDriverWait(driver, 60 * 5, 5000) } @AfterClass @JvmStatic fun afterClass() { driver.quit() appiumService.stop() }
  6. 12 GET APPIUM DRIVER — fun getDriverFor(platform: Platform): AppiumDriver<MobileElement> =

    when (platform) { Platform.Android -> AndroidDriver(URL(remoteAddressUrl), DesiredCapabilities().apply { setCapability("platformName", MobilePlatform.ANDROID) setCapability("platformVersion", "7.0") setCapability("deviceName", "deviceName") setCapability("app", “Application.app") setCapability("avd", "Pixel") setCapability("fullReset", "true") setCapability("automationName", "UiAutomator2") }) Platform.IOS -> IOSDriver(URL(remoteAddressUrl), DesiredCapabilities().apply { setCapability("platformName", MobilePlatform.IOS) setCapability("platformVersion", "12.1") setCapability("deviceName", "iPhone XR") setCapability("app", “Application.apk") setCapability("newCommandTimeout", 150) setCapability("fullReset", true) }) }
  7. 13 TEST STEPS — @Step private fun clickButton(id: String) {

    val button = MobileBy.AccessibilityId(id) longWait.until(ExpectedConditions.presenceOfElementLocated(button)).click() } @Step private fun type(text: String, id: String) { val textField = MobileBy.AccessibilityId(id) wait.findElement(id).sendKeys(text) } @Step private fun waitFor(id: String) { longWait.until(ExpectedConditions.presenceOfElementLocated(By.id(id))) } @Step private fun goBack(id: String) { driver.navigate().back() }
  8. 15 SHORT BRIEF ABOUT TEST RECORDING — ▪ a lot

    of code ▪ too many code - hard to read. ▪ sometimes code is not working. ▪ imposible to implement reusability of code.
  9. 18 DIFFERENCES IN APPIUM API FOR iOS AND ANDROID —

    ▪ iOS @Step private fun selectCellInTable(title: String) { if (!driver.findElementByAccessibilityId(title).isDisplayed) { val parent = driver.findElement(By.className("XCUIElementTypeTable")) val scrollObject = HashMap<String, String>() scrollObject["element"] = parent.id scrollObject["predicateString"] = "label == '$title'" driver.executeScript("mobile:scroll", scrollObject) } driver.findElementByAccessibilityId(title).click() }
  10. 19 DIFFERENCES IN APPIUM API FOR iOS AND ANDROID —

    ▪ Android @Step private fun selectCellInTable(title: String) { driver.findElementByAndroidUIAutomator( "new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(" + "new UiSelector().textContains(\"$title\").instance(0));") .click() }
  11. 23 NATIVE + CODE GENERATION — ▪ XCUITest for iOS.

    ▪ UIAutomator for Android. ▪ YAML file with tests. ▪ Script to generate Swift and Java code using erb.
  12. 24 Suits: - Authorization: Initial: - waitForText: "SIGMA CONFERENCE HALL"

    - verifyButtonDisabled: "loginButton" - verifyActivityViewHidden: Should be logged in successfully: - selectTextField: "login" - type: "RomanTysiachnik" - selectSecureTextField: "password" - type: "qwerty123" - clickButton: "loginButton" - verifyActivityViewAnimating: - waitForImage: “successImage" YAML —
  13. 25 class SSUITestCase: XCTestCase { fileprivate var app: XCUIApplication! override

    func setUp() { super.setUp() continueAfterFailure = false app = XCUIApplication() app.launchArguments = ["SS_USE_MOCKS"] app.launch() } } iOS BASE CLASS —
  14. 26 extension SSUITestCase { func clickButton(_ name: String) { let

    element = app.buttons[name] if element.waitForExistence(timeout: 30) { element.tap() } } func clickOnAlertButton() { let alert = app.alerts.firstMatch if alert.waitForExistence(timeout: 10) { alert.buttons.firstMatch.tap() } } func type(_ string: String) { app.typeText(string) } func selectTextField(_ name: String, isSecure: Bool = false) { let element: XCUIElement = { guard isSecure else { return app.textFields[name] } return app.secureTextFields[name] }() if element.waitForExistence(timeout: 30) { element.tap() } } iOS BASE CLASS —
  15. 27 def createSuite(context) key = context.keys.first value = { suites:

    context[key].map { |suite| { suiteName: suite.keys.first, tests: suite.values.first.map { |name, steps| { testName: name, steps: steps.map { |step| { method: step.keys.first, parameter: step.values.first } } } } } } } value end RUBY SCRIPT —
  16. 29 <% testSuites[:suites].each do |suite| -%> class <%=suite[:suiteName].alphabetize.camelize%>UITests: SSUITestCase {

    <% suite[:tests].each do |testCase| -%> <% if testCase[:testName] == "Initial" -%> override func setUp() { super.setUp() <% testCase[:steps].each do |step| -%> <%=step[:method]%>(<% if step[:parameter] != nil -%>"<%= step[:parameter] -%>"<% end -%>) <% end -%> } <% else -%> func test<%=testCase[:testName].alphabetize.camelize%>() { <% testCase[:steps].each do |step| -%> <%=step[:method]%>(<% if step[:parameter] != nil -%>"<%= step[:parameter] -%>"<% end -%>) <% end -%> } <% end -%> <% end -%> } <% end -%> ERB SCRIPT FOR IOS —
  17. 31 GENERATED TEST FOR ANDROID — @Test public void testShouldBeLoggedInSuccessfully()

    throws UiObjectNotFoundException, InterruptedException { selectTextField("login"); type("RomanTysiachnik"); selectSecureTextField("password"); type("qwerty123"); clickButton("loginButton"); verifyActivityViewAnimating(); waitForImage("successImage"); } ▪ Java
  18. 34 ▪ Write tests on your platform language. ▪ Tests

    run faster than Appium up to x3. ▪ Easy setup on CI. ▪ No dependencies. ▪ A lot of space for creativity. ▪ Easy to reuse between projects. PROS —