Skip to content

Commit

Permalink
Leverage WebDriver built-in implicit wait (#153)
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanlabelle authored Jul 5, 2024
1 parent 79541bb commit 62d5b06
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 115 deletions.
6 changes: 6 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ The library has two logical layers:

Where WebDriver endpoint-specific functionality is provided, such as for launching and using a WinAppDriver instance, the code is kept separate from generic WebDriver functionality as much as possible.

## Timeouts
For UI testing, it is often useful to support retrying some operations until a timeout elapses (to account for animations or asynchrony). `swift-webdriver` offers two such mechanisms:

- **Implicit wait timeout**: A duration for which `findElement` operations will implicitly wait if they cannot immediately find the element being queried. This feature is built into the WebDriver protocol, but optionally implemented by drivers. For drivers that do not support it, the library emulates it by repeating the query until the timeout elapses. By spec, this timeout defaults to zero.
- **Interaction retry timeout**: A duration for which `click`, `flick` and similar operations will retry until they result in a successful interaction (e.g. the button is not disabled). This feature is not part of the WebDriver protocol, but rather implemented by the library. This timeout defaults to zero.

## Contributing

We welcome contributions for:
Expand Down
70 changes: 35 additions & 35 deletions Sources/WebDriver/Element.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public struct Element {
}

/// - Parameters:
/// - retryTimeout: Optional value to override defaultRetryTimeout.
/// - retryTimeout: The amount of time to retry the operation. Overrides the implicit interaction retry timeout.
/// - element: Element id to click
/// - xOffset: The x offset in pixels to flick by.
/// - yOffset: The y offset in pixels to flick by.
Expand All @@ -84,92 +84,92 @@ public struct Element {

/// Finds an element by id, starting from this element.
/// - Parameter byId: id of the element to search for.
/// - Parameter retryTimeout: Optional value to override defaultRetryTimeout.
/// - Parameter waitTimeout: The amount of time to wait for element existence. Overrides the implicit wait timeout.
/// - Returns: The element that was found, if any.
public func findElement(byId id: String, retryTimeout: TimeInterval? = nil) throws -> Element? {
try findElement(using: "id", value: id, retryTimeout: retryTimeout)
public func findElement(byId id: String, waitTimeout: TimeInterval? = nil) throws -> Element? {
try findElement(using: "id", value: id, waitTimeout: waitTimeout)
}

/// Search for an element by name, starting from this element.
/// - Parameter byName: name of the element to search for.
/// - Parameter retryTimeout: Optional value to override defaultRetryTimeout.
/// - Parameter waitTimeout: The amount of time to wait for element existence. Overrides the implicit wait timeout.
/// - Returns: The element that was found, if any.
public func findElement(byName name: String, retryTimeout: TimeInterval? = nil) throws -> Element? {
try findElement(using: "name", value: name, retryTimeout: retryTimeout)
public func findElement(byName name: String, waitTimeout: TimeInterval? = nil) throws -> Element? {
try findElement(using: "name", value: name, waitTimeout: waitTimeout)
}

/// Search for an element 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.
/// - Parameter waitTimeout: The amount of time to wait for element existence. Overrides the implicit wait timeout.
/// - Returns: The element that was found, if any.
public func findElement(byAccessibilityId id: String, retryTimeout: TimeInterval? = nil) throws -> Element? {
try findElement(using: "accessibility id", value: id, retryTimeout: retryTimeout)
public func findElement(byAccessibilityId id: String, waitTimeout: TimeInterval? = nil) throws -> Element? {
try findElement(using: "accessibility id", value: id, waitTimeout: waitTimeout)
}

/// Search for an element by xpath, starting from this element.
/// - Parameter byXPath: xpath of the element to search for.
/// - Parameter retryTimeout: Optional value to override defaultRetryTimeout.
/// - Parameter waitTimeout: The amount of time to wait for element existence. Overrides the implicit wait timeout.
/// - Returns: a new instance of Element wrapping the found element, nil if not found.
public func findElement(byXPath xpath: String, retryTimeout: TimeInterval? = nil) throws -> Element? {
try findElement(using: "xpath", value: xpath, retryTimeout: retryTimeout)
public func findElement(byXPath xpath: String, waitTimeout: TimeInterval? = nil) throws -> Element? {
try findElement(using: "xpath", value: xpath, waitTimeout: waitTimeout)
}

/// Search for an element by class name, starting from this element.
/// - Parameter byClassName: class name of the element to search for.
/// - Parameter retryTimeout: Optional value to override defaultRetryTimeout.
/// - Parameter waitTimeout: The amount of time to wait for element existence. Overrides the implicit wait timeout.
/// - Returns: The element that was found, if any.
public func findElement(byClassName className: String, retryTimeout: TimeInterval? = nil) throws -> Element? {
try findElement(using: "class name", value: className, retryTimeout: retryTimeout)
public func findElement(byClassName className: String, waitTimeout: TimeInterval? = nil) throws -> Element? {
try findElement(using: "class name", value: className, waitTimeout: waitTimeout)
}

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

/// 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.
/// - Parameter waitTimeout: The amount of time to wait for element existence. Overrides the implicit wait timeout.
/// - 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)
public func findElements(byId id: String, waitTimeout: TimeInterval? = nil) throws -> [Element] {
try findElements(using: "id", value: id, waitTimeout: waitTimeout)
}

/// 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.
/// - Parameter waitTimeout: The amount of time to wait for element existence. Overrides the implicit wait timeout.
/// - 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)
public func findElements(byName name: String, waitTimeout: TimeInterval? = nil) throws -> [Element] {
try findElements(using: "name", value: name, waitTimeout: waitTimeout)
}

/// 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.
/// - Parameter waitTimeout: The amount of time to wait for element existence. Overrides the implicit wait timeout.
/// - 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)
public func findElements(byAccessibilityId id: String, waitTimeout: TimeInterval? = nil) throws -> [Element] {
try findElements(using: "accessibility id", value: id, waitTimeout: waitTimeout)
}

/// 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.
/// - Parameter waitTimeout: The amount of time to wait for element existence. Overrides the implicit wait timeout.
/// - 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)
public func findElements(byXPath xpath: String, waitTimeout: TimeInterval? = nil) throws -> [Element] {
try findElements(using: "xpath", value: xpath, waitTimeout: waitTimeout)
}

/// 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.
/// - Parameter waitTimeout: The amount of time to wait for element existence. Overrides the implicit wait timeout.
/// - 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)
public func findElements(byClassName className: String, waitTimeout: TimeInterval? = nil) throws -> [Element] {
try findElements(using: "class name", value: className, waitTimeout: waitTimeout)
}

// 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)
private func findElements(using: String, value: String, waitTimeout: TimeInterval?) throws -> [Element] {
try session.findElements(startingAt: self, using: using, value: value, waitTimeout: waitTimeout)
}

/// Gets an attribute of this element.
Expand Down
4 changes: 2 additions & 2 deletions Sources/WebDriver/HTTPWebDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import FoundationNetworking
public struct HTTPWebDriver: WebDriver {
let rootURL: URL

public static let defaultTimeout: TimeInterval = 5 // seconds
public static let defaultRequestTimeout: TimeInterval = 5 // seconds

public init(endpoint: URL) {
rootURL = endpoint
Expand Down Expand Up @@ -36,7 +36,7 @@ public struct HTTPWebDriver: WebDriver {
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = request.method.rawValue
// TODO(#40): Setting timeoutInterval causes a crash when sending the request on the CI machines.
// urlRequest.timeoutInterval = Self.defaultTimeout
// urlRequest.timeoutInterval = Self.defaultRequestTimeout

// Add the body if the Request type defines one
if Req.Body.self != CodableNone.self {
Expand Down
Loading

0 comments on commit 62d5b06

Please sign in to comment.