Easiest way to wait for existence of a set of elements, where only one is required to appear
I've been trying to figure this out on my own, but I wanted to see if there was a built-in way for Selene to do this. If there is, I could not find anything to definitively tell me what to do.
Basically I have a few elements:
foo = s("#foo")
bar = s("#bar")
baz = s("#baz")
I want to wait until any of them are visible without using my own loop. I also don't want to have to wait for one element to not be present before looking for the other.
What i want to do:
browser.wait_until(foo.should(be.visible) or bar.should(be.visible) or baz.should(be.visible)
or
foo.should(be.visible).or_(bar.should(be.visible).or_(baz.should(be.visible)))
The only thing I can think of uses loops and sleeps:
for _ in range(16):
if foo.with_(timeout=0).wait_until(be.visible) or bar.with_(timeout=0).wait_until(be.visible) or baz.with_(timeout=0).wait_until(be.visible):
break
time.sleep(.25)
else:
raise Exception("None found")
hm... Good question... Seems like I have used some kind of Ok-workaround for such cases in the past... But can't recall which... I will try to recall... I will also think what can be a predefined feature in Selene for that.
Meanwhile, you can use something like this as a workaround:
ss("#foo, #bar, #baz").should(have.no.size(0))
This is the implementation I'm going with:
def wait_until_one_of_visible(elements: List[Element], timeout=4):
"""Waits until one of the elements are visible
Args:
elements (List[Element]): The elements to check
timeout: Amount of time to wait
"""
for _ in range(int(timeout)*4):
found_elements = [element for element in elements if element.matching(be.visible)]
if found_elements:
return found_elements[0]
time.sleep(.25)
else:
raise TimeoutException(f"None of the elements met the condition within {timeout} seconds")
While your suggestion works, I store my locators as Elements and I'm not storing the locator strings separately...therefore to do what you suggested I would need to pull the locators out of the elements and build out the locator string.
Yes, my proposed workaround is pretty awkward:) I also don't practice storing selectors separately...
Yet, there should be a better way to implement what you want (wihtout additional wait_until_one_of_visible) logic. I mentioned such way here: https://github.com/yashaka/selene/issues/578, as browser.all(located(foo, bar, baz)) where foo, bar, baz - are Element instances.
for that, you need to implement a located method, that will return a Locator class instance built with a location logic that will be simply calling .locate() on all passed elements and combining results into list of WebElements to return. I will provide an actual implementation later, if you don't find out how to do it on your own...