How can I assert that an element is NOT on the page in playwright?
Asked Answered
R

7

44

I'm testing a website that includes a logo, and I want to make sure the logo does not appear on some pages.

How can I assert that an element does NOT exist? I checked the Playwright assertions documentation, but it only has examples of checking for things that do exist.

async assertNoLog(): Promise<boolean> {
  await this.page.locator('div#page-id'); // now the page has loaded
  // How do I check if logo is on page without throwing an error if it is missing
}

I'm not sure what to write here to assert that my element is not found anywhere on the page.

Radiophone answered 19/2, 2022 at 7:47 Comment(4)
I think its important to differentiate here that logo may attach to the page DOM as element but just should not be visible for the requirement to begin with , NOT until its gone.Bergius
See this - https://mcmap.net/q/375360/-in-playwright-how-to-wait-for-all-instances-of-loading-spinner-to-disappearBergius
@VishalAggarwal Don't spam. Your linked answer is a completely different question, and there is a better explanation in the accepted answer to this question.Radiophone
It's definitely related , as its all about verifying the non existence of an element.Bergius
C
26

To check it instantly without auto-waiting, you can use count() and assert that it returns 0.

expect(await page.locator('.notexists').count()).toEqual(0);

If you want Playwright to wait until the count becomes 0 (as recommended by web-first assertions) then you should await the expect and use toHaveCount()

await expect(page.locator('.notexists')).toHaveCount(0);

https://playwright.dev/docs/api/class-locator#locator-count https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-have-count

Cinquecento answered 2/8, 2022 at 22:25 Comment(10)
For me, count() times out! What is it possibly waiting for?Lyric
I was missing an await. (Still weird.)Lyric
My Visual Studio Code puts 3 little dots to highlight unnecessary awaits so that makes it easy to delete them. Then I can just add "await" to the beginning of pretty much everything.Cinquecento
Great idea! I've managed to set up the no-floating-promises ESLint rule now.Lyric
Erik's answer is now preferred; follows Playwright guidelines on using web first assertions.Tolerable
@DanielDarabos without an await then it was returning a promise instead of a value and expecting the promise to equal zero. I would think that it should just fail the assertion but if the timing gets messed up sometimes that expresses itself as a timeout insteadCinquecento
The await should be before expect to follow playwright guideline of web first assertion playwright.dev/docs/best-practices#use-web-first-assertions. So it should be await expect(page.locator('.notexists')).toEqual(0); or await expect(page.locator('.notexists')).toHaveCount(0);something like this. This is not exactly right but somewhere along the lines that await should be before expect.Baur
I disagree, @utkarsh-k. There's nothing in the playwright best practices doc that says you should have an unnecessary await before an expect. Web first assertions are an unrelated topic.Cinquecento
@Cinquecento Please read the link in utkarsh-k's comment. The docs show: await expect(page.getByText('welcome')).toBeVisible(); // 👍 and expect(await page.getByText('welcome').isVisible()).toBe(true); // 👎 which is pretty clear. The await outside the expect is not unnecessary, it's a web-first assertion that auto-waits for the condition to become true. This is the standard assertion in Playwright and is relevant here. I suggest modifying this answer to clarify that it's not the standard assertion.Boswell
I have updated my answer as per comments by Dennis, ggorlen and @BaurCinquecento
N
31

I wanted to know that an element wasn't on screen, but I also wanted to wait until it was gone, and this is the way to do that:

await expect(locator).toHaveCount(0);

Found here

Norvan answered 26/10, 2022 at 13:56 Comment(1)
Unlike await expect(locator).not.toBeAttached(), this works regardless of whether you're going from 3 elements down to 0, or from 1 element down to 0. Use await expect(locator).not.toBeAttached() if you want that implicit single element assertion guarantee. Both idioms are useful in different contexts.Boswell
C
26

To check it instantly without auto-waiting, you can use count() and assert that it returns 0.

expect(await page.locator('.notexists').count()).toEqual(0);

If you want Playwright to wait until the count becomes 0 (as recommended by web-first assertions) then you should await the expect and use toHaveCount()

await expect(page.locator('.notexists')).toHaveCount(0);

https://playwright.dev/docs/api/class-locator#locator-count https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-have-count

Cinquecento answered 2/8, 2022 at 22:25 Comment(10)
For me, count() times out! What is it possibly waiting for?Lyric
I was missing an await. (Still weird.)Lyric
My Visual Studio Code puts 3 little dots to highlight unnecessary awaits so that makes it easy to delete them. Then I can just add "await" to the beginning of pretty much everything.Cinquecento
Great idea! I've managed to set up the no-floating-promises ESLint rule now.Lyric
Erik's answer is now preferred; follows Playwright guidelines on using web first assertions.Tolerable
@DanielDarabos without an await then it was returning a promise instead of a value and expecting the promise to equal zero. I would think that it should just fail the assertion but if the timing gets messed up sometimes that expresses itself as a timeout insteadCinquecento
The await should be before expect to follow playwright guideline of web first assertion playwright.dev/docs/best-practices#use-web-first-assertions. So it should be await expect(page.locator('.notexists')).toEqual(0); or await expect(page.locator('.notexists')).toHaveCount(0);something like this. This is not exactly right but somewhere along the lines that await should be before expect.Baur
I disagree, @utkarsh-k. There's nothing in the playwright best practices doc that says you should have an unnecessary await before an expect. Web first assertions are an unrelated topic.Cinquecento
@Cinquecento Please read the link in utkarsh-k's comment. The docs show: await expect(page.getByText('welcome')).toBeVisible(); // 👍 and expect(await page.getByText('welcome').isVisible()).toBe(true); // 👎 which is pretty clear. The await outside the expect is not unnecessary, it's a web-first assertion that auto-waits for the condition to become true. This is the standard assertion in Playwright and is relevant here. I suggest modifying this answer to clarify that it's not the standard assertion.Boswell
I have updated my answer as per comments by Dennis, ggorlen and @BaurCinquecento
J
13

https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-be-attached

toBeAttached

Added in: v1.33

Ensures that Locator points to an attached DOM node.

With this, now we can do:

expect(locator).not.toBeAttached() 
Jonis answered 11/7, 2023 at 15:10 Comment(4)
This should be the accepted answer - clean and with no magic constants like zero in "toHaveCount(0)"Zymo
@Dušan I agree this probably is better, but generally speaking, 0 isn't exactly a magic constant under the traditional definition. It's not subject to change and always means 0. For example, writing for (let i = 0; i < arr.length; i++) {} does not use "magic constants" any more than this does. One potentially useful feature in the toHaveCount(0) approach is that it doesn't fail if the count goes from 3 to 0, whereas this only handles 1 to 0.Boswell
@JohnSpencer don't forget to await expect(locator).not.toBeAttached().Boswell
Personally prefer closer to the user assertions like tobevisible. But good idea too.Upgrowth
U
6

You could try using not before toBeVisible assertion on the routes you don't want it to be.

async assertNoLog(): Promise<boolean> {
  await this.page.locator('div#page-id'); // now the page has loaded

  // Check if the logo is on the page without throwing an error if it is missing
await expect(page.locator('div#page-id')).not.toBeVisible();
}

It's recommended to use more direct assertions when available:

await expect(page.locator('div#page-id')).toBeHidden();

Check: https://playwright.dev/docs/best-practices

Upgrowth answered 28/6, 2023 at 13:7 Comment(1)
locator.toBeHidden() is more readable.Comparative
P
4

You can play with the conditions you expect your element to have. For example, at Playwright's homepage you expect an element by the class .navbar__brand to be visible, but you also expect an element by the class .notexists NOT to be visible (in this case this element would not exist). Then you can do:

test('element does exist @pass', async ({ page }) => {
  await page.goto('https://playwright.dev/');
  const locator = await page.locator('.navbar__brand').isVisible();
  expect(locator).toBeTruthy();
});

test('element does NOT exist @fail', async ({ page }) => {
  await page.goto('https://playwright.dev/');
  const locator = await page.locator('.notexists').isVisible();
  expect(locator).toBeTruthy();
});

Doing this, of course, would return the same results:

test('element does exist @pass', async ({ page }) => {
  await page.goto('https://playwright.dev/');
  expect(await page.locator('.navbar__brand').isVisible()).toBe(true);
});

test('element does NOT exist @fail', async ({ page }) => {
  await page.goto('https://playwright.dev/');
  expect(await page.locator('.notexists').isVisible()).toBe(true);
});

As I say, the element's conditions are up to you. For example, if you want to assert an element with visibility:hidden is also not present in the DOM, because it simply shouldn't be, you can wrap the visibility and a .isHidden() conditions within a if/else, etc. And of course, feel free to play with booleans (toBe(true)/toBe(false), toBeTruthy()/toBeFalsy()).

These are not the most elegant solutions out there, but I hope they can help.

Paratroops answered 19/2, 2022 at 10:11 Comment(2)
I was tempted to downvote this because it's not a very elegant solution as compared to using count() but it does provide some very useful information.Cinquecento
Warning: this doesn't use web-first assertions.Boswell
H
2

You could also use Locator#waitFor method with 'detached' state. The benefit is that is also waits for some time for element state to change. It is helpful if detaching an element from DOM takes some time.

https://playwright.dev/docs/next/api/class-locator#locator-wait-for

Example:

await page.locator('someSelector').waitFor({ state: "detached" })

Other states allow for verification whether an element is visible / hidden

Haase answered 28/9, 2023 at 8:27 Comment(0)
S
0

Another way using Chai assertions is using the "!" symbol before the element:

should.exist(!your selector here)
Snotty answered 27/2, 2023 at 23:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.