Last active
June 23, 2022 15:11
-
-
Save pogramos/b75873bd6cbb5997041c6dd5eb426d3b to your computer and use it in GitHub Desktop.
UI Test extensions to make our life easier (idea of @rafaellage and my collaboration)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
UIExampleStrings.strings | |
Created by Guilherme on 9/15/18. | |
Copyright © 2018. All rights reserved. | |
*/ | |
"email" = "login@login.com"; | |
"password" = "123456"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// UIExampleStrings.swift | |
// UITests | |
// | |
// Created by Guilherme on 9/15/18. | |
// Copyright © 2018. All rights reserved. | |
// | |
import Foundation | |
class LoginStrings: BaseUITestProtocol { | |
enum StringIdentifiers: String { | |
case email | |
case password | |
} | |
enum Elements: String { | |
case email = "EmailTextField" | |
case password = "PwdTextField" | |
} | |
static func stringValue(for identifier: LoginStrings.StringIdentifiers) -> String { | |
return LocalizerClass.localize(string: identifier.rawValue, for: self) | |
} | |
static func identification(of element: LoginStrings.Elements) -> String { | |
return element.rawValue | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// LoginUITests.swift | |
// | |
// | |
// Created by Guilherme on 9/14/18. | |
// | |
import XCTest | |
class UIExampleUITests: XCTestCase { | |
override func setUp() { | |
super.setUp() | |
continueAfterFailure = false | |
XCUIApplication().launch() | |
} | |
func testLogin() { | |
waitForElement(type: .textField(type: .regular, text: UIExampleStrings.stringValue(for: .email)), | |
identifier: UIExampleStrings.identification(of: .email), action: .type) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// UITestsBaseLocalizingStructure.swift | |
// UITests | |
// | |
// Created by Guilherme on 9/16/18. | |
// Copyright © 2018. All rights reserved. | |
// | |
import Foundation | |
class LocalizerClass { | |
class func localize(string: String, for table: AnyClass) -> String { | |
let bundle = Bundle(for: LocalizerClass.self) | |
return bundle.localizedString(forKey: string, value: string, table: String(describing: table)) | |
} | |
} | |
protocol BaseUITestProtocol: class { | |
/** | |
You should represent this associatedtype as an enumerator with a rawValue, | |
containing all of the identifiers on your strings file. | |
# Usage Example: | |
Create a new a strings file, set a lefthand identifier and then give it a value for your string. | |
Create a child case in your StringIdentifiers Enum | |
and then set the value of this child as your newly created Identifier | |
enum StringIdentifiers: String { | |
case labelText = "This should be a text" | |
} | |
*/ | |
associatedtype StringIdentifiers: RawRepresentable | |
/** | |
You should represent this associatedtype as an enumerator with a rawValue, | |
containing all of the elements identifiers within an workflow. | |
# Usage Example: | |
Set your element accessibility ID (be it on a xib, storyboard or code), | |
create a child case in your Elements Enum (lets give it a String rawValue) | |
and then set the value of this child as your newly created accessibility ID | |
enum Elements: String { | |
case button = "ButtonID" | |
} | |
*/ | |
associatedtype Elements: RawRepresentable | |
/// Returns the righthand value for a given StringIdentifier associated value | |
/// | |
/// - Parameter identifier: StringIdentifier enumerator | |
/// - Returns: Righthand string value | |
static func stringValue(for identifier: StringIdentifiers) -> String | |
/// Returns the accessibility id as a String for a given Element associated value | |
/// | |
/// - Parameter element: Element enumerator | |
/// - Returns: Associated String element identifier | |
static func identification(of element: Elements) -> String | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// XCTestCaseExtension.swift | |
// UITests | |
// | |
// Created by Guilherme Ramos on 14/09/2018. | |
// Copyright © 2018. All rights reserved. | |
// | |
import XCTest | |
extension XCTestCase { | |
private static var kDefaultTimeout: TimeInterval { | |
return 5 | |
} | |
private var kDefaultSleep: UInt32 { | |
return 2 | |
} | |
func waitForElementToAppear(element: XCUIElement, timeout: TimeInterval = kDefaultTimeout, file: String = #file, line: UInt = #line) { | |
let existsPredicate = NSPredicate(format: "exists == true") | |
expectation(for: existsPredicate, evaluatedWith: element, handler: nil) | |
waitForExpectations(timeout: timeout) { (error) -> Void in | |
if (error != nil) { | |
let message = "Failed to find \(element) after \(timeout) seconds." | |
XCTAssertTrue(false, message) | |
self.recordFailure(withDescription: message, inFile: file, atLine: Int(line), expected: true) | |
} | |
} | |
} | |
@discardableResult | |
func waitForElement(type: TestElementType, identifier: String = "", action: ActionType = .none, timeout: TimeInterval = kDefaultTimeout) -> XCUIElement { | |
sleep(kDefaultSleep) | |
var element: XCUIElement | |
switch type { | |
case .label: | |
element = waitForLabel(identifier: identifier, action: action, timeout: timeout) | |
case .button(let buttonType): | |
element = waitForButton(type: buttonType, identifier: identifier, action: action, timeout: timeout) | |
case .textField(let textType, let text): | |
element = waitForTextField(type: textType, identifier: identifier, action: action, text: text, timeout: timeout) | |
case .collection(let collectionType): | |
element = waitForCollection(type: collectionType, timeout: timeout) | |
case .cell(let collectionType, let index): | |
element = waitForCollectionCell(type: collectionType, index: index, action: action, timeout: timeout) | |
} | |
return element | |
} | |
// MARK: - Label | |
func waitForLabel(identifier: String, action: ActionType = .none, timeout: TimeInterval = kDefaultTimeout) -> XCUIElement { | |
let label = XCUIApplication().staticTexts[identifier] | |
waitForElementToAppear(element: label, timeout: timeout) | |
if action == .tap { | |
label.tap() | |
} | |
return label | |
} | |
func assertLabel(in element: XCUIElement, id: String, text: String, timeout: TimeInterval = kDefaultTimeout) { | |
let textField = element.staticTexts[id] | |
waitForElementToAppear(element: textField, timeout: timeout) | |
XCTAssertEqual(textField.label, text) | |
} | |
// MARK: - TextField and SecureTextField | |
func waitForTextField(type: TestElementType.TextFieldType, identifier: String, action: ActionType = .none, text: String, timeout: TimeInterval = kDefaultTimeout) -> XCUIElement { | |
var textField: XCUIElement | |
switch type { | |
case .regular: | |
textField = XCUIApplication().textFields[identifier] | |
case .secure: | |
textField = XCUIApplication().secureTextFields[identifier] | |
} | |
waitForElementToAppear(element: textField, timeout: timeout) | |
if action == .type { | |
textField.clearAndType(text: text) | |
} | |
return textField | |
} | |
// MARK: - Button | |
func waitForButton(type: TestElementType.ButtonType, identifier: String, action: ActionType = .none, timeout: TimeInterval = kDefaultTimeout) -> XCUIElement { | |
var button: XCUIElement | |
switch type { | |
case .standard: | |
button = XCUIApplication().buttons[identifier] | |
case .menu: | |
button = XCUIApplication().menuButtons[identifier] | |
case .radio: | |
button = XCUIApplication().radioButtons[identifier] | |
case .toolbar: | |
button = XCUIApplication().toolbarButtons[identifier] | |
case .popUp: | |
button = XCUIApplication().popUpButtons[identifier] | |
} | |
waitForElementToAppear(element: button, timeout: timeout) | |
if action == .tap { | |
button.tap() | |
} | |
return button | |
} | |
// MARK: - Collection Cells | |
func waitForCollection(type: TestElementType.CollectionType, timeout: TimeInterval = kDefaultTimeout) -> XCUIElement { | |
var collection: XCUIElement | |
switch type { | |
case .tableView: | |
collection = XCUIApplication().tables.firstMatch | |
case .collectionView: | |
collection = XCUIApplication().collectionViews.firstMatch | |
} | |
waitForElementToAppear(element: collection, timeout: timeout) | |
return collection | |
} | |
func waitForCollectionCell(type: TestElementType.CollectionType, index: Int = 0, action: ActionType = .none, timeout: TimeInterval = kDefaultTimeout) -> XCUIElement { | |
let collection = waitForCollection(type: type, timeout: timeout) | |
let cell = collection.cells.element(boundBy: index) | |
waitForElementToAppear(element: cell, timeout: timeout) | |
if action == .tap { | |
cell.tap() | |
} | |
return cell | |
} | |
// MARK: - Tab Bar | |
func waitForTabBar(identifier: String, action: ActionType = .none, index: Int = 0, timeout: TimeInterval = kDefaultTimeout) { | |
sleep(kDefaultSleep) | |
let tabBar = XCUIApplication().tabBars[identifier] | |
waitForElementToAppear(element: tabBar, timeout: timeout) | |
if action == .tap { | |
let button = tabBar.buttons.element(boundBy: index) | |
waitForElementToAppear(element: button, timeout: timeout) | |
button.tap() | |
} | |
} | |
func swipeRight(sleepTime: UInt32 = 0) { | |
sleep(sleepTime) | |
XCUIApplication().swipeRight() | |
} | |
func swipeLeft(sleepTime: UInt32 = 0) { | |
sleep(sleepTime) | |
XCUIApplication().swipeLeft() | |
} | |
func swipeDown(sleepTime: UInt32 = 0) { | |
sleep(sleepTime) | |
XCUIApplication().swipeDown() | |
} | |
func swipeUp(sleepTime: UInt32 = 0) { | |
sleep(sleepTime) | |
XCUIApplication().swipeUp() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// XCUIElementExtension.swift | |
// UITests | |
// | |
// Created by Guilherme on 14/09/2018. | |
// Copyright © 2018. All rights reserved. | |
// | |
import XCTest | |
enum KeyboardType { | |
case number(Bool) | |
case alphaNumeric(Bool) | |
} | |
extension XCUIElement { | |
func clearAndType(text: String) { | |
tap() | |
clearText() | |
typeText(text) | |
XCUIApplication().keyboards.buttons["Hide keyboard"] | |
let firstKey = XCUIApplication().keys.element(boundBy: 0) | |
if firstKey.exists { | |
XCUIApplication().typeText("\n") | |
} | |
} | |
func typeTextInKeyboard(_ text: String, keyboardType: KeyboardType) { | |
sleep(3) | |
switch keyboardType { | |
case .alphaNumeric(let hasToolbar): | |
if hasToolbar { | |
typeInKeyboardWithToolbar(text: text) | |
} else { | |
typeText(text) | |
} | |
case .number(let hasToolbar): | |
if hasToolbar { | |
typeInKeyboardWithToolbar(text: text) | |
} else { | |
typeInNumericKeyboard(text: text) | |
} | |
} | |
} | |
private func typeInKeyboardWithToolbar(text: String) { | |
let app = XCUIApplication() | |
for char in text { | |
let letter = String(char) | |
if char >= "0" && char <= "9" { | |
let button = app.toolbars["Toolbar"].buttons[letter] | |
button.tap() | |
} else { | |
app.keys[letter].tap() | |
} | |
} | |
} | |
private func typeInNumericKeyboard(text: String) { | |
let app = XCUIApplication() | |
for char in text { | |
let letter = String(char) | |
app.keys[letter].tap() | |
} | |
} | |
func clearText() { | |
guard let stringValue = self.value as? String else { | |
return | |
} | |
// workaround for apple bug | |
if let placeholderString = self.placeholderValue, placeholderString == stringValue { | |
return | |
} | |
var deleteString = String() | |
for _ in stringValue { | |
deleteString += XCUIKeyboardKey.delete.rawValue | |
} | |
self.typeText(deleteString) | |
} | |
func stringValue() -> String? { | |
guard let string = self.value as? String else { | |
return nil | |
} | |
return string | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// XCUITestEnums.swift | |
// UITests | |
// | |
// Created by Guilherme on 9/25/18. | |
// Copyright © 2018. All rights reserved. | |
// | |
import Foundation | |
enum TestElementType { | |
case label | |
case button(type: ButtonType) | |
case textField(type: TextFieldType, text: String) | |
case collection(type: CollectionType) | |
case cell(collection: CollectionType, index: Int) | |
enum ButtonType { | |
case standard | |
case menu | |
case radio | |
case toolbar | |
case popUp | |
} | |
enum TextFieldType { | |
case secure | |
case regular | |
} | |
enum CollectionType { | |
case tableView | |
case collectionView | |
} | |
} | |
enum ActionType { | |
case none | |
case tap | |
case clear | |
case type | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment