How can I assert that an element is NOT on the page in playwright? - playwright

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.

You can use .count() and assert that it returns 0.
expect(await page.locator('.notexists').count()).toEqual(0);
https://playwright.dev/docs/api/class-locator#locator-count

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

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.

Related

How can I select an element by Id?

I wonder how I can access an element by ID. I want to submit a form.
await page.click("id= 'next'"); --> not possible
await page.getByRole('button', { id: 'next' }).click(); --> does not compile
await page.getByRole('button', { name: 'Sign in' }).click(); --> work but is language dependent
Selecting elements by their ID seems the most robust to me. Am I missing something?
You can do something like this:
await page.locator("#YourId").click()
or just
await page.click("#YourId");
Playwright recommends some predefined locators so you could also check if some off those fit your use case.
What you are missing in the first example are the square brackets, instead of:
await page.click("id= 'next'");
you should do this:
await page.click("[id='next']");
alternatively, you can use the shorthand version like mentioned in the other answer

Running tests in Parallel based on a dynamically generated array from a beforeAll using Playwright

The scenario I'm trying to test is:
Get an array of elements from the page and read the url property of that element
Test 1 - that the array is not empty
For Each of these elements
Test 2->number of elements - Navigate to the url read above and test there are no errors on the page
So far I've got the code working, but only by including the for loop in the second test, which only registers the above as two tests and does not run the second set (ie: the for loop) in parallel.
When trying to make the second set of tests parallel (as I've done in another test that's based on a static array of pages) I end up with the below code, but the second set of tests is not even detected, ie: if I comment out Test 1, no tests found is reported.
Here's the code structure I'm trying to use. As I said above, it is based upon another test that's using a static array of elements that does seem to work as expected. The main difference with the other test is that everything is within the loop and there is no beforeAll. I'm assuming that's where the crux of the issue is.
import { test, expect, chromium } from '#playwright/test';
const pages = [];
test.describe('Pages Load', () => {
test.beforeAll(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(url);
const allPages = await page
.locator('container id')
.evaluateAll(($elements) => {
return $elements.map((element) => {
const pageTitle = element.querySelector('element id');
const pageURL = element.getAttribute(
'attribute id'
);
return {
title: pageTitle,
url: pageURL,
};
});
});
allPages.forEach(($page) => {
pages.push({
title: $page.title,
url: $page.url,
});
});
});
test('Pages exist', () => {
expect(pages.length).toBeGreaterThan(0);
});
test.describe('No errors on pages', () => {
pages.forEach((page) => {
test.describe(`${page.title}`, () => {
test('No errors', async () => {
// Test goes here
});
});
});
});
});
Update:
I've had a bit more of a play with this and while I know why this code isn't working, I still have no idea how to resolve the issue.
Couple of things I've noticed (and these are just theories/assumptions based on what I've observed):
Firstly, the reason why the above isn't working is because the array isn't set up by the time it get's to the loop, as the beforeAll only appears to happen with each test, not the test.describe. While this is just a theory (can't see anything in the documentation proving or disproving this), it does explain why the second test is ignored, because the array.length is 0
Secondly, when attempting to extract the functionality into a separate function outside of the test, as suggested here, however, this too had no luck, as when it gets to setting up the browser with const browser = await chromium.launch(); in an external function, playwright seems to exit with no errors being thrown. My assumption is that the browser setup needs to be within a test context, though that is just a theory.
Any advice on how I can get the desired results would be greatly appreciated. Many thanks in advance.

Playwright, how to leave iframe? Or why my elements are not visible

my question is "How to get an access to other elements which are outside the iframe".\
const frame = this.page.frame({name: "messagecontframe"});
await expect(frame.locator(this.confirmationMessage)).toBeVisible();
const bookingID = await frame.locator("(//td[#class='v1right'])[1]").innerText();
await frame.locator(this.cancelBookingButton).click();
after that actions I need to navigate to another URL and do something on another page, but I simply can't even click something, all elements are not visible for me. I believe that's because "I'm still in iframe". How to solve that problem? Is there something like iframe.leave ?

Can I use whileElement(...).atIndex(..) to distinguish multiple scroll page id's

I was getting the following error: Multiple elements were matched
However it turned out that the scrolling page ID was multiple times matched. This means that I need something like this:
await waitFor(element(by.id("someID")).toBeVisible()whileElement(by.id("anotherID")).atIndex(1).scroll(50, 'down')
I tried this, but get the following error:
TypeError: global.waitFor(...).toBeVisible(...).whileElement(...).atIndex is not a function
So my question is, when there are two scroll elements with the same id, can I select one of them with the function atIndex?
Or is there another solution for this?
Thanks in advance
Same issue experienced here. I was able to solve it using a function that scrolls the list until it meets the condition.
something like that:
await tryTap();
...
const tryTap = async() => {
try { await element(by.id('someID')).tap(); }
catch (e) {
await element(by.type("anotherID")).atIndex(1).scroll(50, "down");
await tryTap();
}
}

"select2" Add constant option

I am currently using Select2 in a project and would like to add a option to my select list that shows up regardless of what the user types or searches. The idea is to have a "Add new" option always present in the list.
I do not think my code is necessary here (but if needed I may provide) as the only thing i'm lacking knowledge in this specific topic is on how to keed the option always showing.
I thought of using the matcher attribute, but i'm not sure how.
I've managed to do it setting a new matcher, the problem was I was not sure on how to create a new matcher and still use the select2 default one.
Something else I was missing was the full version of select2.
function newMatcher(term, text){
//The "ADD NEW" String is the text in the option I want to always show up.
//The code after OR looks for the actual results for the user's search
if ((text.toUpperCase().indexOf("ADD NEW") > -1)
|| (text.toUpperCase().indexOf(term.toUpperCase()) > -1)) {
return true;
}
}
$(document).ready(function() {
$.fn.select2.amd.require(['select2/compat/matcher'], function (oldMatcher) {
$('select').select2({
matcher: oldMatcher(newMatcher)
})
})
});

Resources