Search code examples
playwrightplaywright-typescript

How to check if an element is visible?


I want to check if an element is visible. If so, I want to click on it. If not, I want to check if another html element is available and click on it. How can I do this using playwright?

The html element in question has test id "create-btn"


Solution

  • It's a good idea to break your problem into distinct, easily searchable, steps:

    1. Select element by test id (page.getByTestId("create-btn"))
    2. Click it. (page.getByTestId("create-btn").click())

    As the docs state, clicking (and many other actions) auto-wait for actionability by default, which includes being visible in the viewport and not behind another element.

    Here's a minimal example:

    import {test} from "@playwright/test"; // ^1.42.1
    
    const html = `<!DOCTYPE html><html><body>
    <button style="visibility: hidden;" data-testid="create-btn">click me</button>
    <script>
    setTimeout(() => {
      document.querySelector("button").style.visibility = "visible";
    }, 4000);
    </script>
    </body></html>`;
    
    test("create-btn can be clicked", async ({page}) => {
      await page.setContent(html);
      await page.getByTestId("create-btn").click();
    });
    

    If you want to click a different button if the first one never shows up, you can extend the code to do this with a try/catch:

    const html = `<!DOCTYPE html><html><body>
    <button style="visibility: hidden;" data-testid="menu-btn">click me</button>
    <script>
    setTimeout(() => {
      document.querySelector("button").style.visibility = "visible";
    }, 4000);
    </script>
    </body></html>`;
    
    test("create-btn or menu-btn can be clicked", async ({page}) => {
      await page.setContent(html);
      try {
        await page.getByTestId("create-btn").click({timeout: 5_000});
      } catch (err) {
        await page.getByTestId("menu-btn").click();
      }
    });
    

    .or(), the CSS comma operator (,) and Promise.race might also be useful if you want to click the first visible of multiple buttons:

    test("create-btn or menu-btn can be clicked", async ({page}) => {
      await page.setContent(html);
    
      await page.getByTestId("create-btn")
        .or(page.getByTestId("menu-btn")).click();
    
      await page.locator(
        '[data-testid="create-btn"],[data-testid="menu-btn"]'
      ).click();
    
      await Promise.race([
        page.getByTestId("create-btn").click(),
        page.getByTestId("menu-btn").click(),
      ]);
    });
    

    If you don't want to auto-wait, you can use .isVisible() to run an immediate check:

    const createBtn = page.getByTestId("create-btn");
    if (await createBtn.isVisible()) {
      await createBtn.click();
    }
    
    const menuBtn = page.getByTestId("menu-btn");
    if (await menuBtn.isVisible()) {
      await menuBtn.click();
    }
    

    But conditions and nondeterminism aren't generally good practice in tests.