Swift로 🍏 UI XCTest 기반 잘 다지는 법 #2 - Action
반응형
💥 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 |
댓글
이 글 공유하기
다른 글
-
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