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

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 —