Skip to content

Instantly share code, notes, and snippets.

@pogramos
Last active June 23, 2022 15:11
Show Gist options
  • Save pogramos/b75873bd6cbb5997041c6dd5eb426d3b to your computer and use it in GitHub Desktop.
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)
/*
UIExampleStrings.strings
Created by Guilherme on 9/15/18.
Copyright © 2018. All rights reserved.
*/
"email" = "login@login.com";
"password" = "123456";
//
// 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
}
}
//
// 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)
}
}
//
// 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
}
//
// 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()
}
}
//
// 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
}
}
//
// 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