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⚙️ 기본 설정 Espresso (에스프레소) 는 구글에서 제공하는 안드로이드 UI 테스트 라이브러리이다. 3.x 버전부터는 AndroidX Library에 통합되어 제공되고 있다. 기본적인 설정은 안드로이드 개발 코드 안에서 직접 환경설정할 수 있다. build.gradle android { defaultConfig { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } } // https://developer.android.com/jetpack/androidx/releases/test def androidxTest = '1.2.0' def espresso = '3.2.0' dependencies { …. androidTestI… -
Swift로 🍏 UI XCTest 기반 잘 다지는 법 #1 - 테스트 케이스
Swift로 🍏 UI XCTest 기반 잘 다지는 법 #1 - 테스트 케이스
2020.04.04 -
🥒 Cucumber 이해하고 잘 쓰는 방법
🥒 Cucumber 이해하고 잘 쓰는 방법
2020.04.03
댓글을 사용할 수 없습니다.