Skip to content

Commit

Permalink
code improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
shamanec committed Jul 24, 2023
1 parent f01ac26 commit 63601d9
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 73 deletions.
16 changes: 8 additions & 8 deletions xcuitest-sample-proj.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
objects = {

/* Begin PBXBuildFile section */
FF4D5CB02A68033C00B52624 /* ElementsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4D5CAF2A68033C00B52624 /* ElementsHelper.swift */; };
FF4D5CB02A68033C00B52624 /* Elements.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4D5CAF2A68033C00B52624 /* Elements.swift */; };
FF79C1EC2A66F1B700BBC60A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FF79C1EB2A66F1B700BBC60A /* Assets.xcassets */; };
FF79C1EF2A66F1B700BBC60A /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FF79C1EE2A66F1B700BBC60A /* Preview Assets.xcassets */; };
FF79C2032A66F1B700BBC60A /* SampleAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF79C2022A66F1B700BBC60A /* SampleAppUITests.swift */; };
FF79C27F2A66F3A000BBC60A /* XCUIElement+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF79C27E2A66F3A000BBC60A /* XCUIElement+Extensions.swift */; };
FF79C2812A66F45200BBC60A /* XCUIElementQuery+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF79C2802A66F45200BBC60A /* XCUIElementQuery+Extensions.swift */; };
FF79C2832A66F54E00BBC60A /* BaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF79C2822A66F54E00BBC60A /* BaseTest.swift */; };
FF79C2862A66F72200BBC60A /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF79C2852A66F72200BBC60A /* TestConstants.swift */; };
FF79C2892A66F9F500BBC60A /* InteractionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF79C2882A66F9F500BBC60A /* InteractionHelper.swift */; };
FF79C2892A66F9F500BBC60A /* Interactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF79C2882A66F9F500BBC60A /* Interactions.swift */; };
FF86799B2A69699D00C4A1A7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF86799A2A69699D00C4A1A7 /* AppDelegate.swift */; };
FF86799E2A696BDC00C4A1A7 /* CarouselItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF86799D2A696BDC00C4A1A7 /* CarouselItemView.swift */; };
FFF0348F2A6CFA6500A33E97 /* FirstPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF0348E2A6CFA6500A33E97 /* FirstPage.swift */; };
Expand Down Expand Up @@ -47,7 +47,7 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
FF4D5CAF2A68033C00B52624 /* ElementsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementsHelper.swift; sourceTree = "<group>"; };
FF4D5CAF2A68033C00B52624 /* Elements.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Elements.swift; sourceTree = "<group>"; };
FF79C1E42A66F1B600BBC60A /* xcuitest-sample-proj.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "xcuitest-sample-proj.app"; sourceTree = BUILT_PRODUCTS_DIR; };
FF79C1EB2A66F1B700BBC60A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
FF79C1EE2A66F1B700BBC60A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
Expand All @@ -58,7 +58,7 @@
FF79C2802A66F45200BBC60A /* XCUIElementQuery+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElementQuery+Extensions.swift"; sourceTree = "<group>"; };
FF79C2822A66F54E00BBC60A /* BaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTest.swift; sourceTree = "<group>"; };
FF79C2852A66F72200BBC60A /* TestConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConstants.swift; sourceTree = "<group>"; };
FF79C2882A66F9F500BBC60A /* InteractionHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractionHelper.swift; sourceTree = "<group>"; };
FF79C2882A66F9F500BBC60A /* Interactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interactions.swift; sourceTree = "<group>"; };
FF86799A2A69699D00C4A1A7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
FF86799D2A696BDC00C4A1A7 /* CarouselItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselItemView.swift; sourceTree = "<group>"; };
FFF0348E2A6CFA6500A33E97 /* FirstPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstPage.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -181,8 +181,8 @@
FF79C2872A66F9E500BBC60A /* Helpers */ = {
isa = PBXGroup;
children = (
FF79C2882A66F9F500BBC60A /* InteractionHelper.swift */,
FF4D5CAF2A68033C00B52624 /* ElementsHelper.swift */,
FF79C2882A66F9F500BBC60A /* Interactions.swift */,
FF4D5CAF2A68033C00B52624 /* Elements.swift */,
);
path = Helpers;
sourceTree = "<group>";
Expand Down Expand Up @@ -359,8 +359,8 @@
FFF034932A6CFEA200A33E97 /* SecondPage.swift in Sources */,
FF79C2032A66F1B700BBC60A /* SampleAppUITests.swift in Sources */,
FFF034952A6D027B00A33E97 /* TabBar.swift in Sources */,
FF79C2892A66F9F500BBC60A /* InteractionHelper.swift in Sources */,
FF4D5CB02A68033C00B52624 /* ElementsHelper.swift in Sources */,
FF79C2892A66F9F500BBC60A /* Interactions.swift in Sources */,
FF4D5CB02A68033C00B52624 /* Elements.swift in Sources */,
FFF0349B2A6D227100A33E97 /* XCUIApplication+Extensions.swift in Sources */,
FFF0348F2A6CFA6500A33E97 /* FirstPage.swift in Sources */,
FF79C2862A66F72200BBC60A /* TestConstants.swift in Sources */,
Expand Down
Binary file not shown.
44 changes: 40 additions & 4 deletions xcuitest-sample-projUITests/Foundations/BasePage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ class BasePage {
self.app = app
}

func waitForPageLoading(element: XCUIElement) {
XCTAssertTrue(element.waitForExistence(timeout: TestConstants.Timeout.medium))
}

// MARK: - Reflection idle hack code
// Function to turn on/off the hack that disables waitForQuiescenceIncludingAnimationsIdle - the method that forces the test to wait until the app is idle - by replacing it with the [replace()](x-source-tag://replace) method
/// - Parameter state: Bool parameter to activate/deactivate the app idle wait logic
private func setReflectionIdleHack(_ state: Bool) {
Expand Down Expand Up @@ -49,4 +46,43 @@ class BasePage {
block()
self.setReflectionIdleHack(false)
}

// MARK: Utility methods
func keyboardVisible() -> Bool {
let startTime = NSDate().timeIntervalSince1970
while (NSDate().timeIntervalSince1970 - startTime) < TestConstants.Timeout.medium {
if app.keyboards.count >= 1 {
return true
}
usleep(100_000)
}

return false
}

func hideKeyboard() {
// Delay to avoid instability due to keyboard open animation
sleep(1)
if keyboardVisible() {
app.toolbars.buttons["Done"].tap()
}
}

func tapDelete() {
app.keys["Delete"].tap()
}

func tapDelete(times: Int) {
for _ in 0...times {
tapDelete()
}
}

func tapOK() {
app.buttons["OK"].tap()
}

func tabCancel() {
app.buttons["Cancel"].tap()
}
}
14 changes: 11 additions & 3 deletions xcuitest-sample-projUITests/Foundations/BaseTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,22 +86,22 @@ class BaseTest: XCTestCase {

let settingsIcon = springboard.icons["Settings"]

if ElementsHelper.waitForElement(settingsIcon, 2) {
if Elements.waitForElement(settingsIcon, 2) {
// Sleep for half a second after finding the settings button because sometimes it swipes before finishing transition to initial springboard screen
usleep(500_000) // 0.5 seconds
springboard.swipeLeft()
}

let appName = "SampleApp"
let icon = springboard.icons[appName]
let iconExists = ElementsHelper.waitForElement(icon, TestConstants.Timeout.short)
let iconExists = Elements.waitForElement(icon, TestConstants.Timeout.short)

guard iconExists else { return }

icon.press(forDuration: TestConstants.Timeout.short)

let editHomeScreenAlert = springboard.alerts["Edit Home Screens"]
if ElementsHelper.waitForElement(editHomeScreenAlert, 1) {
if Elements.waitForElement(editHomeScreenAlert, 1) {
let editHomeScreenAlertButton = editHomeScreenAlert.buttons["OK"]
editHomeScreenAlertButton.tap()
}
Expand Down Expand Up @@ -129,4 +129,12 @@ class BaseTest: XCTestCase {
return nil
}
}

func waitForElementHittableAttributeToBe(_ element: XCUIElement, _ timeoutValue: Double, _ visibility: Bool) {
let predicate = "hittable == \(String(visibility))"
let isDisplayedPredicate = NSPredicate(format: predicate)
let expectation = [XCTNSPredicateExpectation(predicate: isDisplayedPredicate, object: element)]
let result = XCTWaiter().wait(for: expectation, timeout: timeoutValue)
XCTAssertEqual(result, .completed, "Element \(element) is not displayed and/or hittable")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,39 @@

import XCTest

class ElementsHelper: XCTest {
/// Wait until an XCUIElementQuery has at least X number of elements
class Elements {
/// Validate the relative position of the first element to the second element
///
/// - Parameters:
/// - elements: XCUIElementQuery that will be polled
/// - elementsCount: Minimum number of elements to expect in the XCUIElementQuery
/// - timeoutValue: How long to wait until the condition is met
/// - Returns: Boolean value if the condition was met and the query has at least the X number of elements
static func waitUntilTableFilled(_ elements: XCUIElementQuery,
_ elementsCount: Int = 1,
_ timeoutValue: Double = TestConstants.Timeout.medium) {
/// - firstElement: The element for which we validate the position
/// - secondElement: The element against which we validate the first element position
/// - relativePosition: The expected position of the first element relative to the second element
static func validateElementToElementPosition(_ firstElement: XCUIElement,
_ secondElement: XCUIElement,
_ relativePosition: TestConstants.ElementPosition) {
var result = false
let startTime = Date().timeIntervalSince1970

while (Date().timeIntervalSince1970 - startTime) < timeoutValue {
if elements.count >= elementsCount {
result = true
break
}
usleep(300_000) // 300ms
switch relativePosition {
case .leftOf:
result = firstElement.frame.maxX <= secondElement.frame.minX

case .rightOf:
result = firstElement.frame.minX >= secondElement.frame.maxX

case .above:
result = firstElement.frame.maxY <= secondElement.frame.minY

case .below:
result = firstElement.frame.minY >= secondElement.frame.maxY
}
XCTAssertTrue(result, "XCUIElementQuery was not filled with \(elementsCount) elements in \(timeoutValue) seconds")
XCTAssertTrue(result, "\(firstElement) is not in `\(relativePosition)` relative position to \(secondElement)")
}

/// Performs drag and drop actions on non-hittable elements
///
/// - Parameters:
/// - firstElement: The element that will be dragged
/// - secondElement: The element which will be used as an end coordinate to drag the first element to
/// - pressDuration: How long to press the element to activate the drag and drop functionality before moving it
static func dragAndDrop(_ firstElement: XCUIElement, _ secondElement: XCUIElement, _ pressDuration: TimeInterval) {
let startCoordinate = firstElement.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
let endCoordinate = secondElement.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
startCoordinate.press(forDuration: pressDuration, thenDragTo: endCoordinate)
static func waitForElement(_ element: XCUIElement, _ timeoutValue: Double) -> Bool {
return element.waitForExistence(timeout: timeoutValue)
}

// MARK: - Wait functions

/// Wait until XCUIElement disappears
///
/// - Parameters:
Expand All @@ -62,33 +59,65 @@ class ElementsHelper: XCTest {
XCTAssertFalse(elementVisible, "\(element) is still visible(exists) after \(timeoutValue) seconds")
}

/// Validate the relative position of the first element to the second element
/// Wait until an XCUIElementQuery has at least X number of elements
///
/// - Parameters:
/// - firstElement: The element for which we validate the position
/// - secondElement: The element against which we validate the first element position
/// - relativePosition: The expected position of the first element relative to the second element
static func validateElementToElementPosition(_ firstElement: XCUIElement,
_ secondElement: XCUIElement,
_ relativePosition: TestConstants.ElementPosition) {
/// - elements: XCUIElementQuery that will be polled
/// - elementsCount: Minimum number of elements to expect in the XCUIElementQuery
/// - timeoutValue: How long to wait until the condition is met
/// - Returns: Boolean value if the condition was met and the query has at least the X number of elements
static func waitUntilTableFilled(_ elements: XCUIElementQuery,
_ elementsCount: Int = 1,
_ timeoutValue: Double = TestConstants.Timeout.medium) {
var result = false
switch relativePosition {
case .leftOf:
result = firstElement.frame.maxX <= secondElement.frame.minX

case .rightOf:
result = firstElement.frame.minX >= secondElement.frame.maxX

case .above:
result = firstElement.frame.maxY <= secondElement.frame.minY

case .below:
result = firstElement.frame.minY >= secondElement.frame.maxY
let startTime = Date().timeIntervalSince1970

while (Date().timeIntervalSince1970 - startTime) < timeoutValue {
if elements.count >= elementsCount {
result = true
break
}
usleep(300_000) // 300ms
}
XCTAssertTrue(result, "\(firstElement) is not in `\(relativePosition)` relative position to \(secondElement)")
XCTAssertTrue(result, "XCUIElementQuery was not filled with \(elementsCount) elements in \(timeoutValue) seconds")
}

static func waitForElement(_ element: XCUIElement, _ timeoutValue: Double) -> Bool {
return element.waitForExistence(timeout: timeoutValue)
func waitForElementHittableAttributeToBe(_ element: XCUIElement, _ timeoutValue: Double, _ visibility: Bool) {
let predicate = "hittable == \(String(visibility))"
let isDisplayedPredicate = NSPredicate(format: predicate)
let expectation = [XCTNSPredicateExpectation(predicate: isDisplayedPredicate, object: element)]
let result = XCTWaiter().wait(for: expectation, timeout: timeoutValue)
XCTAssertEqual(result, .completed, "Element \(element) is not displayed and/or hittable")
}

func waitForElementEnabledAttributeToBe(_ element: XCUIElement, _ timeoutValue: Double, _ enabledValue: Bool) {
let predicate = "isEnabled == \(String(enabledValue))"
let isEnabledPredicate = NSPredicate(format: predicate)
let expectation = [XCTNSPredicateExpectation(predicate: isEnabledPredicate, object: element)]
let result = XCTWaiter().wait(for: expectation, timeout: timeoutValue)
XCTAssertEqual(result, .completed, "Element \(element) is not enabled")
}

func waitForElementExistenceToBe(_ element: XCUIElement, _ timeoutValue: Double, _ existence: Bool) {
let predicate = "exists == \(String(existence))"
let existsPredicate = NSPredicate(format: predicate)
let expectation = [XCTNSPredicateExpectation(predicate: existsPredicate, object: element)]
let result = XCTWaiter().wait(for: expectation, timeout: timeoutValue)
XCTAssertEqual(result, .completed, "Element \(element) does not exist")
}

func waitForElementLabelAttributeToBe(_ element: XCUIElement, _ timeoutValue: Double, _ labelValue: String) {
let predicate = "label == '\(String(labelValue))'"
let labelEqualsPredicate = NSPredicate(format: predicate)
let expectation = [XCTNSPredicateExpectation(predicate: labelEqualsPredicate, object: element)]
let result = XCTWaiter().wait(for: expectation, timeout: timeoutValue)
XCTAssertEqual(result, .completed, "Element \(element) label value should be '\(labelValue)'")
}

func waitForElementValueAttributeContains(_ element: XCUIElement, _ timeoutValue: Double, _ value: String) {
let valueContainsPredicate = NSPredicate(format: "value CONTAINS[c] %@", value)
let expectation = [XCTNSPredicateExpectation(predicate: valueContainsPredicate, object: element)]
let result = XCTWaiter().wait(for: expectation, timeout: timeoutValue)
XCTAssertEqual(result, .completed, "Element \(element) label value does not contain '\(value)'")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,19 @@

import XCTest

class InteractionHelper {
class Interactions {
/// Performs drag and drop actions on non-hittable elements
///
/// - Parameters:
/// - firstElement: The element that will be dragged
/// - secondElement: The element which will be used as an end coordinate to drag the first element to
/// - pressDuration: How long to press the element to activate the drag and drop functionality before moving it
static func dragAndDrop(_ firstElement: XCUIElement, _ secondElement: XCUIElement, _ pressDuration: TimeInterval) {
let startCoordinate = firstElement.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
let endCoordinate = secondElement.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
startCoordinate.press(forDuration: pressDuration, thenDragTo: endCoordinate)
}

/// Gentler swipe alternative allowing swipe in specified XCUIElement
///
/// **Examples**
Expand Down
Loading

0 comments on commit 63601d9

Please sign in to comment.