How to Check If Element Exists in Playwright?

Playwright is a Node.js library for automating web browsers like Chromium, Firefox and WebKit. It allows you to write tests that run across different browsers and devices. One common task when using Playwright is to check if an element exists on the web page before interacting with it. This allows you to create more robust test scripts that don't just fail when an element is not found.

In this comprehensive guide, you will learn different techniques to check if an element exists in Playwright using JavaScript and Python.

Why Checking Elements is Important

Before diving into the code, it's worth stepping back and understanding why checking for elements is so critical in test automation. Here are some key reasons:

  • Prevent script failures: The most obvious reason is to prevent script failures and exceptions if you try to interact with an element that isn't on the page. This avoids having to handle unnecessary exceptions in your scripts.
  • Catch UI regressions: Checking elements exist allows you to catch UI regressions where some element suddenly goes missing after a code change. Your script will fail fast if a key element disappears.
  • Synchronize actions: Locating elements enables you to synchronize and sequence your test actions, for example clicking an element only after it's displayed.
  • Assert visual correctness: You can assert elements like images, headers and buttons exist to validate overall visual correctness. Catch styling or layout issues.
  • Infer page state: Certain elements appearing on the page can infer what state the application is in. Confirming these elements exist asserts the app state.
  • Support multiple environments: Elements may appear differently across environments like desktop, mobile, and browsers. Checking for them in a robust way allows your tests to run reliably across environments.

As you can see, there are many great reasons to take the extra effort to check for elements correctly. It's a testing best practice that improves reliability, catches regressions, and reveals issues early.

Now let's look at how actually to implement element checks in Playwright scripts.

Prerequisite Setup

Before using any of the Playwright code examples, you'll want to have your script setup with these prerequisites:

1. Install Playwright

npm install playwright

2. Import Playwright and launch browser

const { chromium } = require('playwright'); 

(async () => {
  const browser = await chromium.launch();

3. Create a context and page

const context = await browser.newContext();
const page = await context.newPage();

4. Navigate to a test webpage

await page.goto('');

This gives us a browser page object to locate elements on. The examples will assume this baseline setup. Now let's look at techniques for checking elements.

Technique #1: Get Element Count

One of the simplest and most common techniques is to fetch all matching elements using page.locator(), and then check the .count property to see if any were found:

// Get all submit buttons
const elements = await page.locator('button.submit');

// See if any exist
if (elements.count() > 0) {
  console.log('Submit button found on page');
} else {
  console.log('No submit button found');  

Here we use a CSS selector to locate buttons with a class of submit, and look for a count greater than zero to see if any exist on the page. You can also pass this locator into expect():

// Assert we find at least one
await expect(page.locator('button.submit')).toHaveCount(1);

Some pointers on this technique:

  • Fast execution – Just checks if any elements match, doesn't retrieve them
  • Use CSS selectors – More concise and reliable than XPath overall
  • Returns immediately – Does not wait for element to appear

Keep in mind this returns instantly without waiting for the element. I'll cover waiting later. Let's look at another example, this time checking for an H1 element:

# Python version 

h1 = page.locator("h1")

if h1.count() > 0:
  print("Page has H1 header")
  print("No H1 header found")

As you can see, the .count property gives a quick way to see if any elements exist on the page.

Technique #2: Fetch Single Element

Rather than getting all elements, we can fetch just the first matching one using locator.first():

// Get first submit button
const submitButton = await page.locator('button.submit').first();

// Check if truthy value
if (submitButton) {
  console.log('Submit button found');
} else {
  console.log('No submit button');

Here first() will return the first element matching the selector, or null if none exist. We then check for a truthy value. You may also see it written like:

const [button] = await page.locator('button.submit').elements();

Where it fetches all elements, destructures the first one out, and assigns to a variable. Some pointers on this approach:

  • Faster than counting all elements – Just returns first match
  • Better for taking action – Can chain actions like .click() after
  • Null if no matches – Ensure you check for a truthy value

Fetching the first element can be useful if you want to then take some action on that element, like clicking it. But simply checking existence, .count() is a bit faster since it avoids actually retrieving the elements.

Technique #3: Use .exists and .visible

The locator object in Playwright has some handy built-in properties that can tell you if elements exist or are visible:

const locator = page.locator('button.submit');

// Check if exists at all
if (await locator.exists()) {
  console.log('Element exists');

// Check if visible
if (await locator.visible()) {
  console.log('Element is visible');  

exists will return true if any elements match the selector, even non-visible ones. visible checks that the elements are actually visible on the rendered page. Some key differences:

  • exists – Returns true for any matching elements, including hidden
  • visible – Only returns true if element is visible on the page

For example, an element with display: none would return true for exists(), but false for visible(). I'd recommend using visible in most cases, as you typically only care if the user can actually see the element.

Technique #4: Catch Element Not Found Errors

When Playwright fails to find elements, methods like first() and textContent() will throw errors we can catch:

try {

  // Try to get text of h1
  const title = await page.locator('h1').first().textContent();
  // Will throw error if no H1 exists  
  console.log('Page title: ' + title); 

} catch (error) {

  // Handle element not found 
  console.log('No H1 element found');


Some common errors you may see:

  • Locator.first() failed: vacant locator
  • Locator.textContent() failed: vacant locator

By catching these errors, we can handle cases where elements don't exist without crashing our script. Some best practices for this technique:

  • Catch specific errors, don't use generic catch
  • Ensure you handle the error, don't just log it
  • Consider wrapping smaller sections in try/catch blocks

This approach allows your script to fail gracefully when elements are missing.

Technique #5: Wait for Elements to Appear

A key shortcoming of the techniques so far is that they do not wait for elements to appear. They only check for existence immediately. This can cause flaky failures if you try to check for elements before they have loaded. To address this, we can use the locator.waitFor() method:

// Wait 30 seconds for submit button to appear
const locator = page.locator('button.submit').waitFor({timeout: 30000});

if (await locator.exists()) {
  // Found submit after waiting  
} else {
  // Timed out after 30 seconds

This will keep re-checking until the element appears or times out after 30 seconds. Some things you can pass to .waitFor():

  • timeout – Max time to wait in milliseconds
  • state – Expected state like ‘visible', ‘hidden', ‘enabled'

The key advantage here is reliability. This avoids false negative errors caused by checking too early before elements have loaded. Some performance stats to illustrate why waiting is important:

  • The average page takes 10+ seconds to load all elements (Source: Cloudflare) fully
  • Maps can take over 30 seconds to render (Source: ThinkwithGoogle) fully
  • Pages with a bad network can take 60+ seconds to stabilize

As you can see, just blindly checking for elements immediately can easily result in failures for legitimate pages. Using smart waits prevents this. Here are some more examples of waiting for existence and visibility:

# Wait for ID element to exist
id_locator = page.locator("#signup").waitFor() 


# Wait for class element to be visible 
class_locator = page.locator(".alert").waitFor(state="visible")


In general, I'd recommend always using waits when checking for existence. This produces much more stable and reliable tests.

Technique #6: Assert Elements Exist

When writing tests in Playwright, you can assert elements exist using the expect API:

import { expect } from '@playwright/test';

test('submit button', async ({ page }) => {

  // Assert submit button exists
  await expect(page.locator('button.submit')).toHaveCount(1);


Some other useful assertions:

  • expect(locator).not.toHaveCount(0) – Expect any elements to exist
  • expect(locator).toHaveText() – Expect element with text to exist
  • expect(locator).toBeVisible() – Expect visible element

Using expect provides a clear one-line check that any matching elements exist on the page. This leads to clean test code. Under the hood, it's doing the same checks we've covered but wrapping them in a neat assertion.

CSS Selectors vs XPath

So far all the examples have used CSS selector strings like 'button.submit' to locate elements. You can also use XPath selectors like //*[@id='submit']. However, my recommendation is to prefer CSS selectors whenever possible. Here's why:

  • CSS is faster for browsers to parse and locate
  • CSS is more concise in most cases
  • CSS matches the styles developers use day to day
  • CSS doesn't depend on markup structure like XPath

CSS selectors can be up to 3-4x faster for browsers to execute compared to XPath.

This table summarizes some additional differences:

SpeedVery fastModerate
Use in appsHeavily usedRarely used

As you can see, CSS has significant advantages for element location. The one case where XPath shines is when the markup is very dynamic, and attributes are unreliable. But for most pages, CSS is my recommended approach. When checking for existence, CSS selectors will lead to faster and more maintainable tests over time.

We've crafted an article that exclusively highlights the difference between CSS and XPath.

Common Pitfalls and Troubleshooting

Now that we've explored the variety of techniques available, I want to briefly mention some common pitfalls and troubleshooting tips when checking for elements in Playwright:

Problem: Locator seems to intermittently fail

This is typically caused by not waiting for elements to appear. Always use locator.waitFor() before checking existence to avoid race conditions.

Problem: Elements load too slowly

If elements are taking a long time to load, use a higher timeout of 30+ seconds in waitFor(). Check the Network panel to see if resources are slow.

Problem: CSS selector works in DevTools but not Playwright

Double check you don't have any typos. Playwright doesn't support some newer CSS features than browsers. Try to simplify the selector.

Problem: Elements look wrong or seem stale

Call page.reload() before locating elements or use context.clearCookies() to avoid logged-in state issues.

Problem: Element doesn't exist on mobile or different browsers

Try making the selector more generic and avoiding tight couplings to attributes. Test across the range of environments early.

Problem: Assertion keeps failing unexpectedly

Use await page.pause() to freeze the browser and inspect the page. Generate screenshots to debug visually.

As you can see, there are some common things that can trip you up. Document these tips so you can resolve issues faster.

Key Takeaways

Let's review some of the top things to remember when checking element existence in Playwright:

  • Validate elements before interacting to prevent errors
  • Use .count and .exists to check quickly if any elements found
  • Fetch .first() element if you need to store a handle to it
  • Prefer visibility over existence in most cases
  • Catch errors like Locator.first() failed to handle missing elements
  • Always wait for elements before checking with .waitFor()
  • Assert elements with expect(...).toHaveCount()/toBeVisible()
  • CSS selectors are faster and more maintainable than XPath overall

Following these best practices will level up your test code and prevent many flaky failures caused by elements loading too slowly. Adopting a mindset of carefully validating elements before taking action is critical to creating stable Playwright tests that stand the test of time.


If you found this guide useful, consider sharing it with colleagues so they can level up their Playwright skills as well. Element checks may seem basic but are crucial to building reliable automated tests. Put these skills into practice, and you'll be on your way to taming flaky tests and creating robust browser automation scripts that continue to deliver value over time.

John Rooney

John Rooney

John Watson Rooney, a self-taught Python developer and content creator with a focus on web scraping, APIs, and automation. I love sharing my knowledge and expertise through my YouTube channel, My channel caters to all levels of developers, from beginners looking to get started in web scraping to experienced programmers seeking to advance their skills with modern techniques. I have worked in the e-commerce sector for many years, gaining extensive real-world experience in data handling, API integrations, and project management. I am passionate about teaching others and simplifying complex concepts to make them more accessible to a wider audience. In addition to my YouTube channel, I also maintain a personal website where I share my coding projects and other related content.

We will be happy to hear your thoughts

      Leave a reply

      Compare items
      • Total (0)