最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

node.js - How to boolean check if an element is visible after some time, without catching an exception - Stack Overflow

programmeradmin0浏览0评论

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
Add a comment  | 

3 Answers 3

Reset to default 4

Returning 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();

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论