Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use await expect(locator).toBeAttached() to check whether an element is connected to the page’s DOM. If you mean “can a user see it?”, use toBeVisible(); if you mean “how many elements match?”, use toHaveCount(n). These assertions retry while Playwright waits for the expected state, making them a better fit than an immediate state reading when the page may still be changing.

Choose the check that matches what “exists” means

An element can be attached to the DOM but hidden, or a locator can match multiple elements when you expect one. Decide which condition your test needs before choosing an assertion:

What you want to know Playwright check What it establishes
Is a node connected to the page? await expect(locator).toBeAttached() The locator points to an element attached to a Document or ShadowRoot.
Can a user see the element? await expect(locator).toBeVisible() The element is attached and meets Playwright’s visibility definition.
How many matching nodes are there? await expect(locator).toHaveCount(n) The locator matches exactly n nodes.
What is the state at this instant? await locator.isVisible() or await locator.count() An immediate reading, with no retry while waiting for a later state.

The distinction matters in dynamic interfaces. A hidden menu may already exist in the DOM; a loading result may not yet exist; and a duplicated button may make a locator ambiguous. An assertion should test the condition your user-facing behavior depends on, not just whichever method sounds closest to “exists.” See the official LocatorAssertions API and Auto-waiting documentation for the assertion definitions and visibility rules.

Set up a locator and assert attachment

For an interactive control, start with a locator based on how a user identifies it. For example, getByRole() with an accessible name makes the intended button explicit:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { test, expect } from '@playwright/test';

test('save button is attached to the page', async ({ page }) => {
  await page.goto('https://example.com');

  const saveButton = page.getByRole('button', { name: 'Save' });
  await expect(saveButton).toBeAttached();
});

Replace the example URL and accessible name with those for your app. The assertion passes when the locator points to a node connected to a Document or ShadowRoot. It does not prove that the control is visible or usable. A hidden element can still be attached.

Playwright locators resolve against the current DOM when used, which is useful when a page re-renders. Prefer user-facing attributes such as role and accessible name, or other meaningful attributes such as a label, placeholder, text, alt text or title. Use a test ID when it is an explicit and stable contract in your application. The Playwright locators guide describes the available locator strategies.

Make the test wait for the application’s state

toBeAttached() is a web-first assertion: it retries until the condition is met or the configured assertion timeout is reached. That makes it suitable when application code inserts an element asynchronously. Usually, assert the state you need rather than adding an arbitrary sleep before checking it.

const status = page.getByRole('status');
await expect(status).toBeAttached();

This check does not wait for a meaningful status message or establish that the status is visible. If the requirement is “the user sees a success message,” assert visibility or the expected text instead.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Check whether the element is visible

If “exists” means that a person can see the element, use toBeVisible():

const saveButton = page.getByRole('button', { name: 'Save' });
await expect(saveButton).toBeVisible();

Playwright considers an element visible when it has a non-empty bounding box and its computed visibility is not hidden. An element with display: none or an empty element is not visible under this definition. Visibility is still a narrower claim than “the user can successfully interact with it”: this assertion is not a substitute for checking the behavior that follows a click or another action. The visibility definition is documented in Auto-waiting.

Use this assertion for a menu after opening it, an alert after a request completes, or a button that should appear after a form becomes valid. Because the assertion retries, it can accommodate a UI that takes time to update, within the configured assertion timeout.

Check the exact number of matches

Use toHaveCount(n) when the number of matching DOM nodes is part of the requirement:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const saveButtons = page.getByRole('button', { name: 'Save' });
await expect(saveButtons).toHaveCount(1);

This asserts exactly one matching node, not “one or more.” If the interface intentionally has a Save button in each row, for example, use the expected number for the state under test, or choose a locator scoped to the relevant row. An exact-count assertion can expose duplicated markup that a visibility assertion on a broad locator would not express clearly.

Like the other web-first assertions, toHaveCount() retries for the expected count. If you need to know the number at this exact instant to make a conditional decision, locator.count() returns the current number instead; it does not wait for the count to change. Playwright discusses locator count and visibility methods in the Locator API.

Understand immediate readings versus retrying assertions

isVisible() and count() are useful when an immediate snapshot is genuinely what the code needs. They are easy to misuse as test assertions on an interface that may still be rendering:

const saveButton = page.getByRole('button', { name: 'Save' });

// Immediate snapshot: this does not wait for a later UI update.
const visibleNow = await saveButton.isVisible();

// Retrying assertion: use when the UI may still be updating.
await expect(saveButton).toBeVisible();

The boolean from isVisible() describes the state when the method runs. If it is false because a request has not finished yet, that result does not establish that the button will remain hidden. Use the retrying assertion when the test should wait for the eventual state. Playwright’s Best Practices also distinguish an immediate visibility read from a waiting assertion.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Handle ambiguous locators deliberately

If a locator matches more than one element, an operation that requires a unique target can fail with a strictness error. That is often a useful signal: the test has not identified which element it intends to check. Narrow the locator by role, accessible name, label, or a relevant parent region before asserting.

const dialog = page.getByRole('dialog', { name: 'Confirm changes' });
const saveButton = dialog.getByRole('button', { name: 'Save' });
await expect(saveButton).toBeAttached();

Do not use .first(), .last(), or .nth() simply to silence ambiguity. Use one only when position is part of the intended behavior, and make the test’s expectation clear. Locators are evaluated against the current DOM when used, so keeping a locator is generally preferable to retaining a handle to an element across a re-render. See the locators guide for locator behavior and recommendations.

Or skip the browser setup

Playwright is the right tool for testing DOM attachment, visibility, and matching counts. ScreenshotNeo is for capturing a website image or PDF; a screenshot does not replace a Playwright locator assertion. If your separate task is to capture a page, one GET request can return an image. See the ScreenshotNeo API documentation for request options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp

ScreenshotNeo accepts cookie or consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Every feature is on every plan. Learn more at ScreenshotNeo, or sign up free for 1,000 screenshots a month with no card.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshoot failed checks

Attachment times out

If toBeAttached() times out, first check that the locator identifies the element you expect: confirm its role, accessible name, label, or relevant text in the rendered page. Then check whether the element is inserted only after an interaction, a response, or another application state change. Make the test perform the necessary action or wait on the relevant state before asserting; do not assume that a delay will fix an incorrect locator.

The element is attached but not visible

This is not contradictory. Attachment tests DOM connection; visibility additionally requires Playwright’s visibility conditions. If the requirement is only that the page has rendered a node, keep the attachment assertion. If a user needs to see it, investigate whether it is hidden, has an empty bounding box, or is not yet displayed, and assert visibility for the intended state.

Visibility returns false unexpectedly

If you used isVisible(), remember that it is an immediate reading. For an element that should appear after a UI update, switch to await expect(locator).toBeVisible(). If that assertion also fails, check the locator and the application state rather than treating a screenshot or a fixed delay as proof of visibility.

Count is greater than expected or a strictness error appears

A broad locator may match repeated controls, hidden copies, or another element with the same name. Scope it to the correct dialog, row, or region and rerun the count assertion. If multiple matches are genuinely expected, assert that expected count rather than arbitrarily choosing one.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The test passes locally but fails during rendering

Use a retrying assertion for the state that can change, and ensure the test’s locator maps to a stable, meaningful UI contract. A one-time count() or isVisible() read can capture a transient state. Adding sleeps may increase runtime while still failing when the page takes longer than the chosen delay.

Keep checks fast and meaningful

Use the narrowest assertion that verifies the actual requirement. Avoid checking the same element’s attachment, visibility, and count in every test unless each condition matters; overlapping assertions can add waiting time without increasing useful coverage. Prefer a single web-first assertion for the expected state, then test a user action or resulting state when behavior—not just markup—is the purpose of the test. When a failure occurs, the assertion and locator should make it clear whether the problem was absence, hidden state, or unexpected duplicates.

Frequently Asked Questions

How do I assert that an element is absent?

Use await expect(locator).toHaveCount(0) when the locator should match no nodes, such as after a loading indicator is removed. Because it is a web-first assertion, it waits for the expected count rather than checking only one instant.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.