Skip to content

Commit

Permalink
Minor clean up
Browse files Browse the repository at this point in the history
  • Loading branch information
eonist committed Sep 21, 2023
1 parent f370391 commit 64d0bc7
Show file tree
Hide file tree
Showing 18 changed files with 75 additions and 74 deletions.
34 changes: 9 additions & 25 deletions Sources/UITestSugar/query/QueryAsserter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,68 +7,52 @@ public class QueryAsserter {
* Waits for the provided label to appear on the screen within the provided timeout.
* ⚠️️ Beta ⚠️️
* Waits for the provided query to return a non-empty result within the provided timeout.
*
* - Parameters:
* - app: The XCUIApplication instance to search for the label.
* - testCase: The XCTestCase instance to which the assertion should be added.
* - labelString: The string of the label to search for.
* - timeOut: The timeout in seconds to wait for the label to appear. Default is 5 seconds.
*
* - Returns: A boolean indicating whether the query returned a non-empty result within the provided timeout.
*
* - Note: This function adds an assertion to the provided XCTestCase instance to check that the label exists.
* - Fixme: ⚠️️ write example
*/
public static func waitFor(app: XCUIApplication, testCase: XCTestCase, labelString: String, timeOut: Double = 5) {
// Get the XCUIElement instance of the label with the provided string
let label = app.staticTexts[labelString]

// Create a predicate to check if the label exists
let exists = NSPredicate(format: "exists == true")

// Create an expectation for the label to exist
testCase.expectation(for: exists, evaluatedWith: label, handler: nil)

// Wait for the expectation to be fulfilled within the provided timeout
testCase.waitForExpectations(timeout: timeOut, handler: nil)

// Assert that the label exists
XCTAssert(label.exists)
}
/**
* ⚠️️ Beta ⚠️️
* Waits for the provided XCUIElement to appear on the screen within the provided timeout.
*
* - Important: This method supports optional elements, whereas the native method `element.waitForExistence(timeOut:)` wouldn't be able to evaluate it because the element is nil.
*
* - Note: `wait(for:timeout:)` returns an `XCTestWaiterResult`, an enum representing the result of the test. It can be one of four possible values: `completed`, `timedOut`, `incorrectOrder`, or `invertedFulfillment`. Only the first, `completed`, indicates that the element was successfully found within the allotted timeout.
*
* - Remark: A big advantage of this approach is that the test suite reads as a synchronous flow. There is no callback block or completion handler. The helper method simply returns a boolean indicating if the element appeared or not.
*
* - Parameters:
* - element: The XCUIElement to wait for.
* - timeOut: The timeout in seconds to wait for the element to appear. Default is 5 seconds.
*
* - Returns: A boolean indicating whether the element appeared within the provided timeout.
*
* - Example:
* ```
* waitForElementToAppear(app.firstDescendant { $0.elementType == .table }, timeOut: 10)
* ```
*/
public static func waitForElementToAppear(element: XCUIElement, timeOut: Double = 5) -> Bool {
// Create a predicate to check if the element exists
let existsPredicate = NSPredicate(format: "exists == true")

// Create an expectation for the element to exist
let expectation = XCTNSPredicateExpectation(predicate: existsPredicate, object: element)

// Wait for the expectation to be fulfilled within the provided timeout
let result = XCTWaiter().wait(for: [expectation], timeout: timeOut)

// Return a boolean indicating whether the element appeared within the provided timeout
return result == .completed
}
// Create a predicate to check if the element exists
let existsPredicate = NSPredicate(format: "exists == true")
// Create an expectation for the element to exist
let expectation = XCTNSPredicateExpectation(predicate: existsPredicate, object: element)
// Wait for the expectation to be fulfilled within the provided timeout
let result = XCTWaiter().wait(for: [expectation], timeout: timeOut)
// Return a boolean indicating whether the element appeared within the provided timeout
return result == .completed
}
}
#endif
// public static func waitForElementToAppear(element: XCUIElement, testCase: XCTestCase) -> Bool {
Expand Down
5 changes: 0 additions & 5 deletions Sources/UITestSugar/query/QueryHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,10 @@ import XCTest
public class QueryHelper {
/**
* Returns an array of all hittable descendants of a given query.
*
* This function searches for all descendants of a query that are hittable, and returns them as an array.
*
* - Important: ⚠️️ This function uses `element(boundBy:)`, which may not work with waiter calls.
*
* - Parameter query: The query to search for hittable descendants. This can be a single element, or a collection of children or descendants.
*
* - Returns: An array of all hittable descendants found in the query.
*
* ## Example:
* ```
* hittableElements(query: app.descendants(matching: .any)).count // n
Expand Down
12 changes: 6 additions & 6 deletions Sources/UITestSugar/query/QueryParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,13 @@ public class QueryParser {
* cells.firstMatch.tap()
*/
public static func children(query: XCUIElementQuery, strings: [String], type: XCUIElement.ElementType = .any) -> [XCUIElement] {
let result: [[XCUIElement]] = strings.map { string in
let predicate = NSPredicate(format: "label MATCHES %@", string)
let elementQuery: XCUIElementQuery = query.containing(predicate)
let elements: [XCUIElement] = QueryParser.elements(query: elementQuery)
return elements
let result: [[XCUIElement]] = strings.map { string in // Map over each string in the array
let predicate = NSPredicate(format: "label MATCHES %@", string) // Create a predicate to match the string
let elementQuery: XCUIElementQuery = query.containing(predicate) // Create an element query with the predicate
let elements: [XCUIElement] = QueryParser.elements(query: elementQuery) // Get the elements matching the query
return elements // Return the elements
}
return result.flatMap { $0 }
return result.flatMap { $0 } // Flatten the array of arrays and return the result
}
}
#endif
Expand Down
7 changes: 0 additions & 7 deletions Sources/UITestSugar/query/XCUIElement+Ext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,11 @@ import XCTest
extension XCUIElement {
/**
* Waits for the element to appear on the screen.
*
* - Description: This method is needed because the native `.waitForExistence(timeOut:)` doesn't work on optional elements.
* - Remark: We had to change the name to something different than `waitToAppear`, or else chaining would be ambiguous.
* - Fixme: ⚠️️ You could implement the native `waitForExistence` call and then just return the element.
* - Fixme: ⚠️️ Make this not require result.
*
* - Parameter timeOut: The maximum amount of time to wait for the element to appear, in seconds. Defaults to 5 seconds.
*
* - Returns: `true` if the element appears within the specified time, `false` otherwise.
*
* ## Example:
Expand All @@ -29,16 +26,12 @@ extension XCUIElement {
}
/**
* Waits for the element to appear on the screen.
*
* - Description: This method is needed because the native `.waitForExistence(timeOut:)` doesn't work on optional elements.
* - Remark: We had to change the name to something different than `waitToAppear`, or else chaining would be ambiguous.
* - Fixme: ⚠️️ You could implement the native `waitForExistence` call and then just return the element.
* - Fixme: ⚠️️ Make this not require result.
*
* - Parameter timeOut: The maximum amount of time to wait for the element to appear, in seconds. Defaults to 5 seconds.
*
* - Returns: `true` if the element appears within the specified time, `false` otherwise.
*
* ## Example:
* ```
* let app = XCUIApplication()
Expand Down
3 changes: 1 addition & 2 deletions Sources/UITestSugar/query/XCUIElementQuery+Ext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@ import Foundation

#if canImport(XCTest)
import XCTest

/**
* Extension to XCUIElementQuery that provides additional functionality.
*/
extension XCUIElementQuery {

/**
* Returns all descendants of a query that are currently visible and can be interacted with.
* - Returns: An array of XCUIElement objects that are currently hittable.
* - Example: `XCUIApplication().descendants(matching: .any).hittableElements.count` returns the number of hittable elements.
*/
public var hittableElements: [XCUIElement] {
// Get an array of all hittable descendants of the current query
QueryHelper.hittableElements(query: self)
}
}
Expand Down
1 change: 1 addition & 0 deletions Sources/UITestSugar/ui/element/ElementAsserter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public class ElementAsserter {
* - Parameter elements: the array of elements to check if exists
*/
public static func allExists(elements: [XCUIElement]) -> Bool {
// Check if any element in the array does not exist
elements.contains { !$0.exists }
}
/**
Expand Down
8 changes: 7 additions & 1 deletion Sources/UITestSugar/ui/element/debug/ElementDebugger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,14 @@ public class ElementDebugger {
* Extension
*/
extension Array where Element: XCUIElement {
/**
* Generates a debug string for the current element and its descendants.
* This method calls the `debug` method of the `ElementDebugger` class to generate a debug string for the elements.
* - Returns: A debug string for the current element and its descendants.
*/
public func debug() -> String {
ElementDebugger.debug(elements: self)
// Call the `debug` method of the `ElementDebugger` class to generate a debug string for the elements
return ElementDebugger.debug(elements: self)
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ extension XCUIElement {
* - Returns: `true` if the switch is on, `false` otherwise.
*/
public var isOn: Bool {
// Check if the value is a string and equal to "1"
(value as? String) == "1"
}
}
Expand Down
32 changes: 18 additions & 14 deletions Sources/UITestSugar/ui/element/extension/Element+Parsing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -192,26 +192,25 @@ extension XCUIElement {
*/
extension XCUIElement {
/**
* Returns `firstDescendant` based on partial identifier
* - Description: Great for finding identifiers that you only have partial information about (Edge cases were the app was developed with localized ids etc, which is wrong but sometimes you have to work around it)
* - Remark: An alternative is that you can use `a.range(of: b) != nil` and check all ids of all elements
* - Remark: `.matching` finds the target, `.containing` finds a parent that has a child etc
* - Remark: In the future you could add more Predicate args like: https://github.com/PGSSoft/AutoMate/blob/master/AutoMate/XCTest%20extensions/XCUIElementQuery.swift
* - Fixme: ⚠️️ Write a method: `hasDescendant` based on this and `.containing()` call
* Returns the first descendant element of the current element that matches the specified partial identifier and type.
* This method is useful for finding elements with identifiers that you only have partial information about, such as when the app was developed with localized IDs.
* - Important: ⚠️️ This method uses `CONTAINS` in the predicate, which may match elements with similar but not identical identifiers.
* - Parameters:
* - partialId: The partial identifier to search for.
* - type: The type of element to search for. Defaults to `.any`.
* - Returns: The first descendant element of the current element that matches the specified partial identifier and type.
* ## Example:
* ```
* // The button has an id of `theBtn`
* app.firstDescendant(partialId: "theBtn", type: .button).waitForExistence(timeout: 5) // true
* app.firstDescendant(partialId: "heBt", type: .button).waitForExistence(timeout: 5) // true
* app.firstDescendant(partialId: "theBtnX", type: .button).waitForExistence(timeout: 5) (( false
* - Parameters:
* - partialId: - Fixme: ⚠️️
* - type: - Fixme: ⚠️️
* app.firstDescendant(partialId: "theBtnX", type: .button).waitForExistence(timeout: 5) // false
* ```
*/
public func firstDescendant(partialId: String, type: XCUIElement.ElementType = .any) -> XCUIElement {
let query = self.descendants(matching: type)
let predicate = NSPredicate(format: "identifier CONTAINS %@", partialId) // Create a predicate that matches elements with an identifier that contains the specified partial ID
let elementQuery: XCUIElementQuery = query.matching(predicate) // Find all descendants that match the predicate
// Swift.print("matches")
return elementQuery.firstMatch // Return the first descendant that matches the predicate
}
}
Expand Down Expand Up @@ -274,6 +273,7 @@ extension XCUIElement {
* element.firstDescendant { $0.identifier == "someBtn" }
*/
private func firstDescendant(_ condition: ElementParser.MatchCondition) -> XCUIElement? {
// Get the first descendant element of the current element that matches the specified condition and type
self.firstDescendant(type: .any, condition)
}
/**
Expand Down Expand Up @@ -301,15 +301,19 @@ extension XCUIElement {
* element.firstChild(type: .button) { $0.identifier == "someBtn" }
*/
private func firstChild(type: XCUIElement.ElementType = .any, _ condition: ElementParser.MatchCondition) -> XCUIElement? {
// Get the first child element of the current element that matches the specified condition and type
ElementParser.firstChild(element: self, condition: condition, type: type)
}
/**
* Returns the first child element of the current element that matches the specified condition and type.
* This method is a convenient shorthand for calling `firstChild(type: .any, condition)` with a closure that sets the `identifier` property of the child element.
* ## Examples:
* Convenient for doing `element.firstChild { $0.identifier = "someBtn" }`
* - Parameter condition: - Fixme: ⚠️️
* - Returns: - Fixme: ⚠️️
* - Parameter condition: A closure that sets the `identifier` property of the child element.
* - Returns: The first child element of the current element that matches the specified condition and type.
*/
private func firstChild(_ condition: ElementParser.MatchCondition) -> XCUIElement? {
self.firstChild(type: .any, condition) // Find the first child of the element that matches the specified condition
self.firstChild(type: .any, condition)
}
}
// deprecated ⚠️️
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
#if canImport(XCTest)
import Foundation
import XCTest

/**
* Other
* This extension provides additional convenience methods for XCUIElement.
*/
extension XCUIElement {

/**
* A convenient way to add some time after a call.
* - Parameter sleepSecs: The number of seconds to sleep.
Expand Down Expand Up @@ -37,7 +35,9 @@ extension XCUIElement {
* - Returns: The current XCUIElement instance.
*/
@discardableResult public func slide(_ scalar: CGFloat) -> XCUIElement {
// Adjust the slider to the normalized position specified by the scalar
self.adjust(toNormalizedSliderPosition: scalar)
// Return the adjusted slider element
return self
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
#if canImport(XCTest)
import Foundation
import XCTest

/**
* Extension for scrolling XCUIElement objects.
*/
extension XCUIElement {

/**
* Scrolls the element in the specified direction until the search condition is met.
* - Parameters:
Expand All @@ -15,10 +13,11 @@ extension XCUIElement {
* - Returns: The same XCUIElement object.
*/
@discardableResult public func scrollTo(dir: ElementModifier.Direction, searchCondition: ElementParser.MatchCondition) -> XCUIElement {
// Scroll the element in the specified direction until the search condition is met
ElementModifier.scrollTo(element: self, dir: dir, searchCondition: searchCondition)
// Return the modified element
return self
}

/**
* Scrolls the parent element until the specified child element is visible.
* - Parameters:
Expand All @@ -27,17 +26,20 @@ extension XCUIElement {
* - Returns: The same XCUIElement object.
*/
@discardableResult public func scrollToElement(element: XCUIElement, dir: ElementModifier.Direction = .up) -> XCUIElement {
// Scroll the parent element in the specified direction until the child element is visible
ElementModifier.scrollToElement(parent: self, element: element, dir: dir)
// Return the modified element
return self
}

/**
* Scrolls the element until the search condition is met.
* - Parameter searchCondition: The condition to search for.
* - Returns: The same XCUIElement object.
*/
@discardableResult public func scrollTo(searchCondition: ElementParser.MatchCondition) -> XCUIElement {
// Scroll the element in the specified direction until the search condition is met
ElementModifier.scrollToElement(element: self, searchCondition: searchCondition)
// Return the modified element
return self
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ extension XCUIElement {
* - Returns: The receiver, after typing the given `text`.
*/
@discardableResult public func typeString(_ text: String) -> XCUIElement {
// Type the specified text into the element
self.typeText(text)
// Return the modified element
return self
}
/**
Expand All @@ -24,7 +26,9 @@ extension XCUIElement {
* - Warning: The returned value is unused, but the function has side effects.
*/
@discardableResult public func clearSearchFieldAndType(text: String) -> XCUIElement {
// Clear the search field and type the specified text
ElementModifier.clearSearchFieldAndType(searchField: self, text: text)
// Return the modified element
return self
}
/**
Expand Down Expand Up @@ -97,7 +101,6 @@ extension XCUIElement {
// Move the cursor to the end of the text field
let lowerRightCorner = self.coordinate(withNormalizedOffset: CGVector(dx: 0.9, dy: 0.9))
lowerRightCorner.tap()

// Delete the current text by typing the delete key repeatedly
let delete = String(repeating: XCUIKeyboardKey.delete.rawValue, count: stringValue.count)
// Swift.print("delete.count: \(delete.count)")
Expand All @@ -115,10 +118,8 @@ extension XCUIElement {
public func selectAllAndWrite(text: String) {
// Tap the element to make sure it's focused.
self.tap(waitForExistence: 5, waitAfter: 0.2)

// Select all text in the element.
self.typeKey("a", modifierFlags: .command)

// Type the given text.
self.typeText(text)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,14 @@ extension XCUIElement.ElementType {
* app.buttons.firstMatch.elementType.string // "button"
*/
public var string: String {
// 1. Get the raw value of the element type as an integer
// 2. Get the corresponding element type name from the allCases array
// 3. Return the element type name
ElementTypeName.allCases[Int(self.rawValue)].rawValue
}
}
/**
* This typealias allows us to use `XCUIElementType` instead of `XCUIElement.ElementType` for brevity and readability.
*/
public typealias XCUIElementType = XCUIElement.ElementType
#endif
Loading

0 comments on commit 64d0bc7

Please sign in to comment.