Skip to content

Commit

Permalink
Add support for elements requests.
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffdav committed Oct 20, 2023
1 parent 3a11024 commit d4c5aa9
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 3 deletions.
49 changes: 48 additions & 1 deletion Sources/Element.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public struct Element {
}

/// Search for an element in the accessibility tree, starting from this element.
/// - Parameter byAccessiblityId: accessibiilty id of the element to search for.
/// - Parameter byAccessibilityId: accessibiilty id of the element to search for.
/// - Parameter retryTimeout: Optional value to override defaultRetryTimeout.
/// - Returns: The element that was found, if any.
public func findElement(byAccessibilityId id: String, retryTimeout: TimeInterval? = nil) throws -> Element? {
Expand Down Expand Up @@ -122,6 +122,53 @@ public struct Element {
try session.findElement(startingAt: self, using: using, value: value, retryTimeout: retryTimeout)
}

/// Search for elements by id, starting from this element.
/// - Parameter byId: id of the element to search for.
/// - Parameter retryTimeout: Optional value to override defaultRetryTimeout.
/// - Returns: The elements that were found, if any.

public func findElements(byId id: String, retryTimeout: TimeInterval? = nil) throws -> [Element]? {
try findElements(using: "id", value: id, retryTimeout: retryTimeout)
}

/// Search for elements by name, starting from this element.
/// - Parameter byName: name of the element to search for.
/// - Parameter retryTimeout: Optional value to override defaultRetryTimeout.
/// - Returns: The elements that were found, if any.

public func findElements(byName name: String, retryTimeout: TimeInterval? = nil) throws -> [Element]? {
try findElements(using: "name", value: name, retryTimeout: retryTimeout)
}

/// Search for elements in the accessibility tree, starting from this element.
/// - Parameter byAccessibilityId: accessibiilty id of the element to search for.
/// - Parameter retryTimeout: Optional value to override defaultRetryTimeout.
/// - Returns: The elements that were found, if any.
public func findElements(byAccessibilityId id: String, retryTimeout: TimeInterval? = nil) throws -> [Element]? {
try findElements(using: "accessibility id", value: id, retryTimeout: retryTimeout)
}

/// Search for elements by xpath, starting from this element.
/// - Parameter byXPath: xpath of the element to search for.
/// - Parameter retryTimeout: Optional value to override defaultRetryTimeout.
/// - Returns: The elements that were found, if any.
public func findElements(byXPath xpath: String, retryTimeout: TimeInterval? = nil) throws -> [Element]? {
try findElements(using: "xpath", value: xpath, retryTimeout: retryTimeout)
}

/// Search for elements by class name, starting from this element.
/// - Parameter byClassName: class name of the element to search for.
/// - Parameter retryTimeout: Optional value to override defaultRetryTimeout.
/// - Returns: The elements that were found, if any.
public func findElements(byClassName className: String, retryTimeout: TimeInterval? = nil) throws -> [Element]? {
try findElements(using: "class name", value: className, retryTimeout: retryTimeout)
}

// Helper for findElements functions above.
private func findElements(using: String, value: String, retryTimeout: TimeInterval?) throws -> [Element]? {
try session.findElements(startingAt: self, using: using, value: value, retryTimeout: retryTimeout)
}

/// Gets an attribute of this element.
/// - Parameter name: the attribute name.
/// - Returns: the attribute value string.
Expand Down
39 changes: 39 additions & 0 deletions Sources/Requests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ public enum Requests {
}
}

public struct ResponseWithValueArray<Value>: Codable where Value: Codable {
public var value: [Value]

public init(_ value: [Value]) {
self.value = value
}

internal enum CodingKeys: String, CodingKey {
case value
}
}

public struct ElementResponseValue: Codable {
public var element: String

Expand Down Expand Up @@ -229,6 +241,33 @@ public enum Requests {
public typealias Response = ResponseWithValue<ElementResponseValue>
}

// https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidelements
// https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidelementidelements
public struct SessionElements: Request {
public var session: String
public var element: String? = nil
public var using: String
public var value: String

public var pathComponents: [String] {
if let element {
return ["session", session, "element", element, "elements"]
} else {
return ["session", session, "elements"]
}
}

public var method: HTTPMethod { .post }
public var body: Body { .init(using: using, value: value) }

public struct Body: Codable {
var using: String
var value: String
}

public typealias Response = ResponseWithValueArray<ElementResponseValue>
}

// https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidforward
public struct SessionForward: Request {
public var session: String
Expand Down
68 changes: 66 additions & 2 deletions Sources/Session.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public class Session {
public func forward() throws {
try webDriver.send(Requests.SessionForward(session: id))
}

public func refresh() throws {
try webDriver.send(Requests.SessionRefresh(session: id))
}
Expand Down Expand Up @@ -114,7 +114,7 @@ public class Session {
}

/// Finds an element by accessibility id, starting from the root.
/// - Parameter byAccessiblityId: accessibiilty id of the element to search for.
/// - Parameter byAccessibilityId: accessibiilty id of the element to search for.
/// - Parameter retryTimeout: Optional value to override defaultRetryTimeout.
/// - Returns: The element that was found, if any.
public func findElement(byAccessibilityId id: String, retryTimeout: TimeInterval? = nil) throws -> Element? {
Expand Down Expand Up @@ -158,6 +158,70 @@ public class Session {
return elementId.map { Element(in: self, id: $0) }
}


/// Finds elements by id, starting from the root.
/// - Parameter byId: id of the element to search for.
/// - Parameter retryTimeout: Optional value to override defaultRetryTimeout.
/// - Returns: The elements that were found, if any.
public func findElements(byId id: String, retryTimeout: TimeInterval? = nil) throws -> [Element]? {
try findElements(startingAt: nil, using: "id", value: id, retryTimeout: retryTimeout)
}

/// Finds elements by name, starting from the root.
/// - Parameter byName: name of the element to search for.
/// - Parameter retryTimeout: Optional value to override defaultRetryTimeout.
/// - Returns: The elements that were found, if any.
public func findElements(byName name: String, retryTimeout: TimeInterval? = nil) throws -> [Element]? {
try findElements(startingAt: nil, using: "name", value: name, retryTimeout: retryTimeout)
}

/// Finds elements by accessibility id, starting from the root.
/// - Parameter byAccessibilityId: accessibiilty id of the element to search for.
/// - Parameter retryTimeout: Optional value to override defaultRetryTimeout.
/// - Returns: The elements that were found, if any.
public func findElements(byAccessibilityId id: String, retryTimeout: TimeInterval? = nil) throws -> [Element]? {
try findElements(startingAt: nil, using: "accessibility id", value: id, retryTimeout: retryTimeout)
}

/// Finds elements by xpath, starting from the root.
/// - Parameter byXPath: xpath of the element to search for.
/// - Parameter retryTimeout: Optional value to override defaultRetryTimeout.
/// - Returns: The elements that were found, if any.
public func findElements(byXPath xpath: String, retryTimeout: TimeInterval? = nil) throws -> [Element]? {
try findElements(startingAt: nil, using: "xpath", value: xpath, retryTimeout: retryTimeout)
}

/// Finds elements by class name, starting from the root.
/// - Parameter byClassName: class name of the element to search for.
/// - Parameter retryTimeout: Optional value to override defaultRetryTimeout.
/// - Returns: The elements that were found, if any.
public func findElements(byClassName className: String, retryTimeout: TimeInterval? = nil) throws -> [Element]? {
try findElements(startingAt: nil, using: "class name", value: className, retryTimeout: retryTimeout)
}

// Helper for findElements functions above.
internal func findElements(startingAt element: Element?, using: String, value: String, retryTimeout: TimeInterval?) throws -> [Element]? {
let request = Requests.SessionElements(session: id, element: element?.id, using: using, value: value)

let elementIds = try poll(timeout: retryTimeout ?? defaultRetryTimeout) {
let elementIds: [String]?
do {
// Allow errors to bubble up unless they are specifically saying that the element was not found.
elementIds = try webDriver.send(request).value.map { $0.element }
} catch let error as ErrorResponse where error.status == .noSuchElement {
elementIds = nil
}

guard let elementIds, elementIds.count > 0 else {
return PollResult.failure(elementIds)
}

return PollResult.success(elementIds)
}.value

return elementIds.map { $0.map { Element(in: self, id: $0) } }
}

/// Moves the pointer to a location relative to the current pointer position or an element.
/// - Parameter element: if not nil the top left of the element provides the origin.
/// - Parameter xOffset: x offset from the left of the element.
Expand Down
9 changes: 9 additions & 0 deletions Tests/WinAppDriverTests/MSInfo32App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,13 @@ class MSInfo32App {
var searchSelectedCategoryOnlyCheckbox: Element {
get throws { try _searchSelectedCategoryOnlyCheckbox.get() }
}

private lazy var _listView = Result {
let elements = try XCTUnwrap(session.findElements(byAccessibilityId: "202"), "List view not found")
try XCTSkipIf(elements.count != 1, "Expected exactly one list view; request timeout?")
return elements[0]
}
var listView: Element {
get throws { try _listView.get() }
}
}
5 changes: 5 additions & 0 deletions Tests/WinAppDriverTests/RequestsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ class RequestsTests: XCTestCase {

override class func tearDown() { _app = nil }

func testCanGetChildElements() throws {
let children = try XCTUnwrap(app.listView.findElements(byXPath:"//ListItem"))
XCTAssert(children.count > 0)
}

func testStatusReportsWinAppDriverOnWindows() throws {
let status = try XCTUnwrap(app.session.webDriver.status)
XCTAssertNotNil(status.build?.version)
Expand Down

0 comments on commit d4c5aa9

Please sign in to comment.