You may need to get the outerHTML of the element in your tests. This seems simple, but Playwright doesn’t have an outerHTML() function.
Let’s start with an example.
For this lesson, you must check if the popup window header has a font-sans property in its HTML code.
First of all, let’s check the source code of such an element
<h2 class="contextual-sign-in-modal__context-screen-title
font-sans text-xl text-color-text my-2 mx-4 text-center"
id="public_profile_contextual-sign-in-modal-header">
Sign in to view Eugene’s full profile
</h2>
This seems a pretty simple task since this element already has the id attribute we can select using the XPath attribute selection:
const signInModalHeader =
'//*[@id = "public_profile_contextual-sign-in-modal-header"]';
Feel free to check my How to master reliable XPath locators for your automated tests article, where you can find how to build reliable locators using XPath.
How to master reliable XPath locators for your automated tests
I’ve observed that the XPath locators mastering can challenge many individuals, including junior and mid-level…
You can use the built-in Playwright .toHaveClass to achieve such a task. It ensures the Locator points to an element with given CSS classes. This needs to be a complete match or use a relaxed regular expression.
With this knowledge, you can easily build a test like that:
import { test, expect } from '@playwright/test';
const signInModalHeader = '//*[@id = "public_profile_contextual-sign-in-modal-header"]';
const targetURL = 'https://www.linkedin.com/in/truuts/'
test('Check the outer HTML', async ({ page }) => {
await page.goto(targetURL);
await expect(page.locator(signInModalHeader), {
message: 'Checking if sign-in modal header has font-sans property',
}).toHaveClass(/font-sans/);
});
This test opens a LinkedIn profile page and applies the .toHaveClass Locator assertion to check if the element’s HTML has a “font-sans” in class.
It looks pretty simple.
The problem
But what if you use polling in your tests and want to use something like that?
await expect
.poll(async () => page.locator(signInModalHeader).innerHTML(), {
message: 'Checking if sign-in modal header has font-sans property',
timeout: 5000,
})
.toHaveClass(/font-sans/);
You will get the error message that the Property toHaveClass does not exist on the type. The toHaveClass function needs to be recognized within the Playwright’s expect.poll chain. TypeScript is complaining because it doesn’t have information about the toHaveClass function within the context of expect.poll.
To solve this problem, you can use the toContain instead. This method ensures that the string value contains an expected substring. Comparison is case-sensitive.
Let’s try to rewrite our test
import { test, expect } from '@playwright/test';
const signInModalHeader = '//*[@id = "public_profile_contextual-sign-in-modal-header"]';
const targetURL = 'https://www.linkedin.com/in/truuts/'
test('Check the outer HTML', async ({ page }) => {
await page.goto(targetURL);
await expect
.poll(async () => page.locator(signInModalHeader), {
message: 'Checking if sign-in modal header has font-sans property',
timeout: 5000,
})
.toContain('font-sans');
});
If you run this test, you will receive an error
Error: Checking if sign-in modal header has font-sans property
Expected value: "font-sans"
Received object: {"_frame": {"_guid": "frame@99c2c4a43480ebe8a6f84fb53c31376b",
"_type": "Frame"},
"_selector": "//*[@id = \"public_profile_contextual-sign-in-modal-header\"]"}
The Received object in this error message is an internal representation of a Playwright element located using the //*[@id = “public_profile_contextual-sign-in-modal-header”] XPath selector. We don’t need the element representation; we need our element’s HTML to try adding the .innerHTML, which returns the element.innerHTML.
import { test, expect } from '@playwright/test';
const signInModalHeader = '//*[@id = "public_profile_contextual-sign-in-modal-header"]';
const targetURL = 'https://www.linkedin.com/in/truuts/'
test('Check the outer HTML', async ({ page }) => {
await page.goto(targetURL);
await expect
.poll(async () => page.locator(signInModalHeader).innerHTML(), {
message: 'Checking if sign-in modal header has font-sans property',
timeout: 5000,
})
.toContain('font-sans');
});
Now you’ll get a different error message.
Error: Checking if sign-in modal header has font-sans property
Expected substring: "font-sans"
Received string: "
Sign in to view Eugene’s full profile
"
This happens because you’re trying to get the inner HTML of the element by its id Attribute locator.
<h2 class="contextual-sign-in-modal__context-screen-title
font-sans text-xl text-color-text my-2 mx-4 text-center"
id="public_profile_contextual-sign-in-modal-header">
Sign in to view Eugene’s full profile
</h2>
Since the id Attribute is an outside part of the element, you’re getting its inner part indeed. In this case, the “Sign in to view Eugene’s full profile” text is the inner part of the element. We could try checking the outerHTML() instead, and we have trouble here because Playwright doesn’t have it.
To solve it and the element’s outerHTML in Playwright, you can use the evaluate() method to call the .outerHTML() directly on the element in the DOM.
I’ve just implemented a simple method for obtaining the outer HTML content of a specified element on a web page using Playwright. It handles the element locating and evaluation steps, retrieving HTML content.
await page.locator(element).evaluate((el) => el.outerHTML);
All you have to do now is to call this method inside your polling expectation.
import {expect, test} from '@playwright/test';
const signInModalHeader = '//*[@id = "public_profile_contextual-sign-in-modal-header"]';
const targetURL = 'https://www.linkedin.com/in/truuts/'
test('Check the outer HTML', async ({ page }) => {
await page.goto(targetURL);
const element = page.locator(signInModalHeader);
await expect.poll(async () => {
return await element.evaluate((el) => el.outerHTML);
}, {
message: 'Checking if sign-in modal header has font-sans property',
timeout: 5000,
}).toContain('font-sans');
});
Now our test has passed, and we can just a little refactor it to make it easy to understand:
import {expect, Page, test} from '@playwright/test';
const signInModalHeader = '//*[@id = "public_profile_contextual-sign-in-modal-header"]';
const targetURL = 'https://www.linkedin.com/in/truuts/'
const TIMEOUT = 5000;
async function checkOuterHtmlText(page: Page, elementLocator, expectedText) {
await expect.poll(async () => {
return await page.locator(elementLocator).evaluate((el) => el.outerHTML);
}, {
message: `Checking if ${elementLocator} outer HTML has a ${expectedText} property during the ${TIMEOUT}ms.`,
timeout: TIMEOUT,
}).toContain(expectedText);
}
test('Check the outer HTML', async ({ page }) => {
await page.goto(targetURL);
await checkOuterHtmlText(page, signInModalHeader, 'font-sans');
});
This test verifies that the outer HTML content of the element specified by signInModalHeader contains the text ‘font-sans’ within a 5-second timeout period. If the text is found within the element’s outer HTML, the test passes; otherwise, it fails with the error message:
Error: Checking if //*[@id = "public_profile_contextual-sign-in-modal-header"]
outer HTML has a font-sans property during the 5000ms.