이 영역을 누르면 첫 페이지로 이동
스니펫 텔러 | Snippet Teller 블로그의 첫 페이지로 이동

스니펫 텔러 | Snippet Teller

페이지 맨 위로 올라가기

스니펫 텔러 | Snippet Teller

아이템이나 서비스 리뷰, 해외생활 등 좋은 정보와 이야기들을 짧고 읽기 쉽게 요약해, 빠르게 전달해 드립니다.

Swift로 🍏 UI XCTest 기반 잘 다지는 법 #2 - Action

  • 2020.04.04 22:15
  • 👨🏻‍💻 QA이야기/📱 모바일자동화
반응형

💥 Extension 잘 사용하기

Swift의 extension을 잘 사용하면, 테스트 코드가 매우 깔끔해지고, 관리하기도 쉬워지며 새로운 테스트를 작성하기도 수월해진다.

 

기본적인 코드는

XCUIApplication().tabBars.buttons["Home"].tap()

이러한 방식이다. 매우 간단하지만, XCUIApplication().app()을 매번 불러야하며, Page Object 컨셉을 적용하면, 더욱 코드가 더러워진다.

 

XCTest 라이브러리의 Action Extension을 만들어서 관리해주면, 매우 간편하게 코드를 만들수 있다:

extension XCTest {
  struct UIApplication {
  	static var app: XCUIApplication?
  }

  var app: XCUIApplication {
    get {
      if UIApplication.app == nil { UIApplication.app = XCUIApplication() }
      return UIApplication.app!
    }
  }

  func tap(_ element: XCUIElement) {
  	element.tap()
  }
}

 

이후 테스트 코드는 다음처럼 훨씬 더 읽기 쉬워지며, 깔끔해진다:

tap(app.tabBars.buttons["Home"])

 

다음 코드들을 내가 작성한 많이 쓰이고 유용한 Action function들이다:

// Element가 화면에 안보이면 스크롤하여 찾아내는 function이다.

func locateToElement(element: XCUIElement) {
  if elementIsOnWindow(element) == false {
  	scrollUntilElementAppears(element)
  }
}

private func elementIsOnWindow(_ element: XCUIElement) -> Bool {
  guard element.exists && element.frame.isEmpty == false && element.isHittable else { return false }
  return app.windows.element(boundBy: 0).frame.contains(element.frame)
}

private func scrollUntilElementAppears(_ element: XCUIElement, threshold: Int = 5) {
  var iteration = 0

  while elementIsWithinWindow(element) == false {
    guard iteration < threshold else { break }
    scrollDown()
    iteration += 1
  }

  while elementIsWithinWindow(element) == false {
    guard iteration > 0 else { break }
    scrollUp()
    iteration -= 1
  }
}

func isKeyboardHidden() -> Bool {
  let keyboard = app.keyboards.element(boundBy: 0)
  return keyboard.children(matching: .any).count == 0
}

func scrollDown(times: Int = 1) {
  var startYPoint = 0.70
  var endYPoint = 0.40
  if isKeyboardHidden() == false {
    startYPoint = 0.50
    endYPoint = 0.20
  }
  let topScreenPoint = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: startYPoint))
  let bottomScreenPoint = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: endYPoint))
  for _ in 0..<times {
    topScreenPoint.press(forDuration: 0, thenDragTo: bottomScreenPoint)
  }
}

func scrollUp(times: Int = 1) {
  var startYPoint = 0.40
  var endYPoint = 0.70
  if isKeyboardHidden() == false {
    startYPoint = 0.20
    endYPoint = 0.50
  }
  let topScreenPoint = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: endYPoint))
  let bottomScreenPoint = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: startYPoint))
  for _ in 0..<times {
    bottomScreenPoint.press(forDuration: 0, thenDragTo: topScreenPoint)
  }
}
// Element가 보이거나 보이지 않을때까지 기다리는 function들이다. 리턴값도 필요하면 가져다 사용할 수 있다.

let waitElementTimeoutInterval: TimeInterval = 5
let waitElementTimeoutLongInterval: TimeInterval = 30

@discardableResult func waitForElementToAppear(element: XCUIElement, forced: Bool = false, message: String? = nil, longWait: Bool = false) -> Bool {
  let expectation = XCTNSPredicateExpectation(predicate: NSPredicate.elementExists,
  object: element)

  let result = XCTWaiter().wait(for: [expectation], timeout: longWait ? waitElementTimeoutLongInterval: waitElementTimeoutInterval)
  if forced {
    if result != .completed {
      XCTFail(message ?? "Test failed due to element not being displayed")
    }
    return true
  } else {
    return result == .completed
  }
}

@discardableResult func waitForElementToDisappear(element: XCUIElement, forced: Bool = false, message: String? = nil, longWait: Bool = false) -> Bool {
  let expectation = XCTNSPredicateExpectation(predicate: NSPredicate.elementNotExists,
  object: element)

  let result = XCTWaiter().wait(for: [expectation], timeout: longWait ? waitElementTimeoutLongInterval: waitElementTimeoutInterval)
  if forced {
    if result != .completed {
      XCTFail(message ?? "Test failed due to element displayed unexpectedly")
    }
    return true
  } else {
    return result == .completed
  }
}

extension NSPredicate {
  static let elementExists: NSPredicate = NSPredicate(format: "exists == true")
  static let elementNotExists: NSPredicate = NSPredicate(format: "exists == false")
}
// Input 필드에 벨류를 입력하는 function이다.

func enterValue(textField: XCUIElement, value: String, takeAction: Bool) {
  tap(textField)
  if textField.value != nil && (textField.value as! String) != value {
    if (textField.value as! String).isEmpty == false && (textField.value as! String) != (textField.placeholderValue)! {
      clearText(textField)
    }
    textField.typeText(value)
    if takeAction { textField.typeText("\n") }
  }
}

private func clearText(_ textField: XCUIElement) {
  let cleatTextButton = textField.buttons["Clear text"]
  if cleatTextButton.exists {
  	tap(cleatTextButton)
  } else {
    var length = (textField.value as! String).count
    while (length > 0) {
      textField.doubleTap()
      textField.typeText(XCUIKeyboardKey.delete.rawValue)
      if (textField.value as! String).count == length { break } else {
        length = (textField.value as! String).count
      }
    }
  }
}
// Label에 정해진 밸류를 포함하고 있는 버튼을 찾아 탭 하는 function이다.

func tapButtonWithLabelContains(_ partialLabel: String) {
  let buttonPredicate = NSPredicate(format: "label CONTAINS '\(partialLabel)'")
  let button = app.buttons.element(matching: buttonPredicate)
  tap(button)
}

 

반응형
저작자표시 비영리 변경금지 (새창열림)

'👨🏻‍💻 QA이야기 > 📱 모바일자동화' 카테고리의 다른 글

Kotlin으로 ☕ Espresso UI 테스트 기반 잘 다지는 법 #2 - Matchers  (0) 2020.04.19
Kotlin으로 ☕ Espresso UI 테스트 기반 잘 다지는 법 #1 - 테스트 케이스  (0) 2020.04.12
Swift로 🍏 UI XCTest 기반 잘 다지는 법 #1 - 테스트 케이스  (0) 2020.04.04
🥒 Cucumber 이해하고 잘 쓰는 방법  (0) 2020.04.03
🗳️ QA의 모바일 자동화를 위한 개발환경  (0) 2020.04.01

댓글

이 글 공유하기

  • 구독하기

    구독하기

  • 카카오톡

    카카오톡

  • 라인

    라인

  • 트위터

    트위터

  • Facebook

    Facebook

  • 카카오스토리

    카카오스토리

  • 밴드

    밴드

  • 네이버 블로그

    네이버 블로그

  • Pocket

    Pocket

  • Evernote

    Evernote

다른 글

  • Kotlin으로 ☕ Espresso UI 테스트 기반 잘 다지는 법 #2 - Matchers

    Kotlin으로 ☕ Espresso UI 테스트 기반 잘 다지는 법 #2 - Matchers

    2020.04.19
  • Kotlin으로 ☕ Espresso UI 테스트 기반 잘 다지는 법 #1 - 테스트 케이스

    Kotlin으로 ☕ Espresso UI 테스트 기반 잘 다지는 법 #1 - 테스트 케이스

    2020.04.12
  • Swift로 🍏 UI XCTest 기반 잘 다지는 법 #1 - 테스트 케이스

    Swift로 🍏 UI XCTest 기반 잘 다지는 법 #1 - 테스트 케이스

    2020.04.04
  • 🥒 Cucumber 이해하고 잘 쓰는 방법

    🥒 Cucumber 이해하고 잘 쓰는 방법

    2020.04.03
다른 글 더 둘러보기

정보

스니펫 텔러 | Snippet Teller 블로그의 첫 페이지로 이동

스니펫 텔러 | Snippet Teller

  • 스니펫 텔러 | Snippet Teller의 첫 페이지로 이동

검색

메뉴

    카테고리

    • 전체보기 (467)
      • 📋 일상이야기 (20)
      • 🇩🇪 베를린이야기 (42)
      • 🇰🇷 한국이야기 (3)
      • 🏅 리뷰이야기 (79)
        • 해외직구 (6)
        • 온라인서비스 (20)
        • 유럽생활 (15)
        • 그 외 (38)
      • 👨🏻‍💻 QA이야기 (19)
        • ⚙️ Quality Engineering (12)
        • 📱 모바일자동화 (7)
      • 🎗 생각하는 글 (49)
      • 🎮 게임정보 (253)
    반응형

    공지사항

    • 공지 - 블로그 공지사항

    방문자

    • 전체 방문자
    • 오늘
    • 어제

    티스토리

    • 티스토리 홈
    • 이 블로그 관리하기
    • 글쓰기

    정보

    무워 | hsoochun의 스니펫 텔러 | Snippet Teller

    스니펫 텔러 | Snippet Teller

    무워 | hsoochun

    블로그 구독하기

    • 구독하기
    • RSS 피드
    Powered by Tistory / Kakao. © 무워 | hsoochun. Designed by Fraccino.

    티스토리툴바