Skip to contentPlayForge

What is the difference between toBeHidden() and not.toBeVisible()?

Written by Jatin Thacker, Automation Architect · Updated

In practice there is none: both pass when the locator matches no element or matches one that is not visible, and both retry until that is true. Pick whichever reads more clearly in your test.

This answer is one piece of Chapter 11: Assertions.

  • Web-First Assertions Cookbook
  • Retry & Polling Patterns
  • Visual Regression Testing
  • Making a Failure Explain Itself

The Playwright docs define toBeVisible() as "the Locator points to an attached and visible DOM node". Negating it with not passes in two cases: nothing is attached, or the element is there but not visible. toBeHidden() is defined as the locator "either does not resolve to any DOM node, or resolves to a non-visible one". That is the same two cases.

The example proves both on the dashboard:

  • ghost-control is in the DOM (toHaveCount(1)) but is 0×0 pixels, so it is never visible. Both assertions pass.
  • feed-loading is a spinner that the page removes once the activity feed arrives. After the feed is visible, the spinner has a count of 0, and both assertions pass again.

What matters more is choosing between "hidden" and "gone". If the element must be removed from the DOM, assert toHaveCount(0). If it only has to stop being visible, toBeHidden() says so. Never use isVisible() for this: it reads once and does not retry.

hidden.spec.js(https://practice.learnplayforge.com)
import { test, expect } from '@playwright/test'; test('toBeHidden and not.toBeVisible agree on both kinds of hidden', async ({ page }) => { await page.goto('/index.html?mock=static&seed=42'); // In the DOM, but 0x0 and never visible const ghost = page.getByTestId('ghost-control'); await expect(ghost).toHaveCount(1); await expect(ghost).toBeHidden(); await expect(ghost).not.toBeVisible(); // Removed from the DOM once the activity feed arrives await expect(page.getByTestId('activity-feed')).toBeVisible(); const spinner = page.getByTestId('feed-loading'); await expect(spinner).toHaveCount(0); await expect(spinner).toBeHidden(); await expect(spinner).not.toBeVisible(); });
Output
1 passed (3.3s)
Test yourself
A locator matches no element at all. Which of these assertions pass?

Practised in the full course: the drill Invisible, not uncounted.

See the chapter

AI can write the test now. The job is knowing whether it checks anything. That's what the course teaches, one chapter at a time.

163 lessons · 226 drills · start free

← All questions

Training a team? Talk to us.