Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Session/Element.requireElement #157

Closed

Conversation

tristanlabelle
Copy link
Contributor

Session/Element.findElement is an outlier in that it is the only method that converts a specific error (element not found) into a nil value. It is also prone to misuse since it makes it perfectly sensible to test for findElement(...) == nil, without realizing that this incurs the worst-case implicit wait, which may not be desirable for such negative tests.

In contrast, requireElement throws upon failure, which makes it directly usable in positive tests (where the element should exist), with the best-case implicit wait and without having to unwrap the result.

Since a pattern is to call findElement in an XCTUnwrap and provide a description of the item that was not found, requireElement takes an optional description and always throws the same error type which captures all relevant information for diagnostics.

Alternate names considered:

  • expectElement (sounds too much like a test framework)
  • getElement (too subtly different from findElement, could still imply a nullable return)
  • findElement, and make the other one tryFindElement (too C#-y)

Possible follow-ups: make the waitTimeout parameter of findElement non-optional to call out the waiting pitfall when used in negative tests.

Copy link
Contributor

@jeffdav jeffdav left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice.

webDriver: WinAppDriver.start(), // Requires WinAppDriver to be installed on the machine
desiredCapabilities: WinAppDriver.Capabilities.startApp(name: "notepad.exe"))
session.findElement(locator: .name("close"))?.click()
try session.requireElement(locator: .name("close")).click()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just change findElement to return a non optional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. A goal of mine is for the API to support the common scenario of negative queries without having to handle thrown errors. i.e. "don't use errors for control flow". It also has the benefits of being less noisy if you're debugging with first-chance exceptions
  2. The name findElement is neutral and I'm trying to call out that if try? findElement() == nil is a bad idea because it will wait the full timeout in the expected scenario. There might be other ways to achieve this, curious for your thoughts

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// - Parameter waitTimeout: The amount of time to wait for element existence. Overrides the implicit wait timeout.
/// - Returns: The element that was found.
@discardableResult // for use as an assertion
public func requireElement(locator: ElementLocator, description: String? = nil, waitTimeout: TimeInterval? = nil) throws -> Element {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be better as an extension method in our app instead? @jeffdav . I feel like it's not quite at the right layer here and I am leaning towards that (so we can have both expectElement and expectNoElement with different timeouts).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to ask, actually, because I was thinking about how to put it in the "supported apis" documentation. Could go either way. We could also have a new target in this project that is "bonus stuff".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like (nonmonetary) bonuses. I think I'll move this to the arc repo. It'll also make more sense with the custom error type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C'est bon.

tristanlabelle added a commit that referenced this pull request Oct 23, 2024
New approach from #157 : Other Selenium bindings (Java, C#) have a
throwing `findElement` method, so do the same for Swift.

Also refactors `poll` to remove the `PollResult` type in favor of having
the closure return a `Result<Value, Error>`. A returned error allows a
retry whereas a thrown error stops the polling.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants