There are quite a few requests for more advanced logic to retrieve elements matching selectors. For example:
Proposal:
page.waitForSelectors(selector, countOrPredicate, { state?: 'attached' | 'visible' | 'hidden', filter? }): Promise<ElementHandle[]>
This method will query all elements matching selector, filter them by them by visibility state, then filter by the filter predicate, and then resolve with remaining elements if there are not less than countOrPredicate of them when it's a number or the predicate returns true.
Examples:
page.waitForSelectors('button', 2) - at least two buttons.page.waitForSelectors('button', buttons => buttons.length >= myButtonsCount) - at least myButtonsCount buttons; page.waitForSelectors('button', 1, { state: 'visible' }) - at least one visible button;page.waitForSelectors('button', 3, { state: 'visible', filter: b => !b.disabled }) - at least three visible and enabled buttons.There are requests to match not the first element, but the first visible one. This is more flaky and the preferred way is to use a stable selector the uniquely identifies the visible target element. That said, maybe it makes sense to introduce a wrapper that takes a regular selector:
selectors.visible('css=div')
Questions:
waitForSelector() state option that could be visible | hidden | attached | detached?This could simplify selecting a particular item from the list.
Examples:
page.click(selectors.index('.my-list-item', 3)) - click at the third list item;page.waitForSelector(selectors.index('button', 2)) - wait until there are at least two buttons.waitForSelectorAll() - wait and return allwaitForSelectorAll({ min: 5 }) - wait for at least 5 and return allwaitForSelectorAll({ min: 3, filter: el => !el.disabled })If we are adding filter, it would make sense for waitForSelector as well.
Hi, I would like to know when these capabilities will be implemented or a different solution was decided to the issues presented here
Just adding my use case:
We have some tests that need to click a button, but the text displayed on this button is randomly set to either A or B. So we'd love a selector that would search for two things, but return the first match.
Something along the lines of
page.click(['text=A', 'text=B'])
or
page.click(await waitForSelectors(["text=A", "text=B"]))
Right now our workaround for this is
await page
.click("text=A")
.catch(() => page.click("text=B"));
This works, but unfortunately it's pretty slow because the selector inside catch will only run when the first selector has timed out.
@torkjels Did you try Promise.any()? See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any
Or Promise.race() https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
(although built-in support would have been nice as well)
@thernstig that's a good suggestion, however as both Promises are started, they will both fulfill if they can when using that method. So let's say you have a button with text that is sometimes "A" and sometimes "B", and pressing that button reveals a new button somewhere else on the page that has a the text "B". Using Promise.any() or Promise.race() would click both those buttons, even if you only wanted to press the first one.
Hi @torkjels, while we align on a solution here, sharing a snippet with waitForSelector + Promise.race. Would this work for your needs?
// returns ElementHandle to either A or B (whichever is found first)
const element = await Promise.race(page.waitForSelector('text=A'), page.waitForSelector('text=B'));
await element.click();
Most helpful comment
Hi, I would like to know when these capabilities will be implemented or a different solution was decided to the issues presented here