I want Playwright to try to click my element with id #goBtn
as soon as it becomes visible, however, If goBtn
doesn't become visible after 7500ms then I don't want it to throw an exception. It just needs to click prevButton
, and then try again. I can't just click prevButton
every time, it should only click it if #goBtn
isn't visible on the first try.
I was able to achieve this with the following. Is there any way to return a boolean for element visibility after some max timeout, without catching an exception?
const prevButton = this.page.locator('#prevBtn');
const goBtnLocStr = '#goBtn';
const goButton = this.page.locator(goBtnLocStr);
let clicked = false;
try {
await this.page.waitForSelector(goBtnLocStr, {timeout: 7500});
} catch(e) {
if (e instanceof TimeoutError) {
await this.prevButton.click();
await goButton.click();
clicked = true;
} else {
throw e;
}
}
if (clicked == false) {
await goButton.click();
}
I want Playwright to try to click my element with id #goBtn
as soon as it becomes visible, however, If goBtn
doesn't become visible after 7500ms then I don't want it to throw an exception. It just needs to click prevButton
, and then try again. I can't just click prevButton
every time, it should only click it if #goBtn
isn't visible on the first try.
I was able to achieve this with the following. Is there any way to return a boolean for element visibility after some max timeout, without catching an exception?
const prevButton = this.page.locator('#prevBtn');
const goBtnLocStr = '#goBtn';
const goButton = this.page.locator(goBtnLocStr);
let clicked = false;
try {
await this.page.waitForSelector(goBtnLocStr, {timeout: 7500});
} catch(e) {
if (e instanceof TimeoutError) {
await this.prevButton.click();
await goButton.click();
clicked = true;
} else {
throw e;
}
}
if (clicked == false) {
await goButton.click();
}
Share
Improve this question
edited Mar 28 at 20:42
emery
asked Mar 28 at 17:53
emeryemery
9,76311 gold badges49 silver badges54 bronze badges
2
- It's sorta hard to follow the language explanation. Better to share the page you're automating, since there may be a much better approach to your actual goal you haven't considered yet. – ggorlen Commented Mar 28 at 20:31
- 1 @ggorlen I've edited and nominated this for re-opening because I'm asking the specific question as to whether Playwright can check for element visibility after some timeout, without throwing an exception. Estus Flask's answer implies that it is not possible, but I would think that the basic functionality of checking whether an element is visible after some time should be available without resorting to exception handling, so I'd like to draw on the expertise of the community. – emery Commented Mar 28 at 20:43
3 Answers
Reset to default 4Returning a boolean is possible with the addition of .then().catch()
.
const isVisible = await locator
.waitFor({state: 'visible', timeout: 7_500})
.then(() => true)
.catch(() => false)
waitFor()
gives you a place to put the timeout, and handles any of the states "attached" | "detached" | "visible" | "hidden"
.
This is a full example test,
test('return boolean for visibility state', async ({ page }) => {
let makeVisibleAfter = 6000
const html = `
<html><body>
<button hidden>My button</button>
<script>
setTimeout(() => {
const button = document.querySelector('button')
button.removeAttribute('hidden')
}, ${makeVisibleAfter})
</script>
</body></html>
`
await page.setContent(html);
const locator = page.locator('button')
const isVisible = await locator
.waitFor({state: 'visible', timeout: 7_500})
.then(() => true)
.catch(() => false)
expect(isVisible).toBe(makeVisibleAfter < 7500)
})
When setting visibility at 6 seconds:
When setting visibility at 8 seconds, the test still passes:
The question strikes me as a potential XY problem, but a couple observations may help improve your solution Y, in lieu of a reproducible example...
For one, it's generally not a good idea to branch in browser automation. Sometimes it's necessary, but avoid it when possible. Adding an abstraction that facilitates boolean true/false branching should probably be used judiciously, if ever. There's a reason it's been removed from the library (see the deprecated timeout
parameter to .isVisible()
).
For two, using locator.click()
already waits for visibility, so I'd just optimistically try clicking it:
const goButton = this.page.locator("#goBtn");
const prevButton = this.page.locator("#prevBtn");
try {
await goButton.click();
}
catch {
await prevButton.click();
await goButton.click();
}
If something other than a timeout error occurs, you'll probably still want to take the same action, so I likely wouldn't bother checking/rethrowing (although this is a commendable instinct).
If you want to avoid try
/catch
entirely (it's sort of idiomatic in async
/await
land, so I don't necessarily endorse this), you can use .catch()
or .then()
:
if (!(await goButton.click().then(() => true, () => false))) {
// click failed
}
And if you want to put this into a function, go ahead--a simple vanilla JS function will suffice, but if it doesn't, see Playwright: how to create a static utility method? which discusses the tradeoffs in nearly all of the ways you can write utility abstractions in Playwright.
As the documentation states, the use of waitForSelector
is discouraged in favour of locator waitFor
.
This depends on custom logic that is implemented, so there is no more straightforward way out of the box. If this is a recurring task, custom helper function can be provided:
async function safeWaitFor(locator, options = {timeout: 7500}) {
try {
await locator.waitFor(options);
return true;
} catch(e) {
if (e instanceof TimeoutError)
return false;
throw e;
}
}
...
if (!(await safeWaitFor(goBtnLocStr)))
await this.prevButton.click();
await goButton.click();
Or similarly implemented custom method:
Locator.prototype.safeWaitFor = async function (options = {timeout: 7500}) {
try {
await this.waitFor(options);
return true;
} catch(e) {
if (e instanceof TimeoutError)
return false;
throw e;
}
}
...
if (!(await goBtnLocStr.safeWaitFor()))
await this.prevButton.click();
await goButton.click();