The idea is I m trying to get all HTML headers (h1 h2 h3 h4 h5 h6)and test if they are in SEO-friendly order. If I try to run it I get this ReferenceError: document is not defined error Typescript in Playwright error
async assertHeadingsOrder2() {
// eslint-disable-next-line @typescript-eslint/require-await
await test.step('Test html header structure for catalog page', async () => {
// Get all the heading tags (h1, h2, h3, h4, h5, h6) in the document
const headingTags = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
// Define a function to test the hierarchy
function testHeadingHierarchy() {
const hierarchy = [];
headingTags.forEach(heading => {
const tagName = heading.tagName.toLowerCase(); // Get the tag name (e.g., "h1", "h2")
const level = parseInt(tagName.charAt(1), 10); // Extract the numeric level from the tag name
// Check if the hierarchy is valid
if (level > hierarchy.length + 1) {
console.error(`Invalid hierarchy: ${tagName} is not in the correct order.`);
}
// Update the hierarchy array with the current level
hierarchy[level - 1] = tagName;
});
console.log('Heading hierarchy is valid:', hierarchy.join(' > '));
}
// Call the function to test the hierarchy
testHeadingHierarchy();
});
}
Playwright doesn't automatically make document
available to you in Node. Instead, it gives you browser
and page
objects with an extensive API to manipulate the website through.
You could use page.evaluate()
to run code in the browser and access document
, but usually, you'll select elements with the locators API on the page
object.
Your CSS selector should be "h1, h2, h3, h4, h5, h6"
rather than "h1 h2 h3 h4 h5 h6"
. The former uses commas for "or" logic, while the latter selects an <h6>
nested inside of all other headers.
Here's an example:
import {expect, test} from "@playwright/test"; // ^1.39.0
const failMsg = (prev, curr) =>
`h${curr} was more than one level deeper than previous header h${prev}`;
test("detects a skipped heading", async ({page}) => {
test.fail(); // expect this test to fail
const html = `<!DOCTYPE html><html><body>
<h1>1</h1>
<h2>2</h2>
<h4>4</h4>
</body></html>`;
await page.setContent(html); // replace with page.goto("<Your URL>"); when you're working with a real site
const headers = await page
.locator("h1, h2, h3, h4, h5, h6")
.evaluateAll(els => els.map(el => +el.tagName.slice(1)));
for (let i = 1; i < headers.length; i++) {
expect(
headers[i] - headers[i-1],
failMsg(headers[i-1], headers[i])
).toBeLessThan(2);
}
});
test("handles a valid structure", async ({page}) => {
const html = `<!DOCTYPE html><html><body>
<h1>1</h1>
<h2>2</h2>
<h3>3</h3>
<h4>4</h4>
<h4>4</h4>
<h2>2</h2> <!-- it's OK to skip back, just not forward -->
<h3>3</h3>
</body></html>`;
await page.setContent(html);
const headers = await page
.locator("h1, h2, h3, h4, h5, h6")
.evaluateAll(els => els.map(el => +el.tagName.slice(1)));
for (let i = 1; i < headers.length; i++) {
expect(
headers[i] - headers[i-1],
failMsg(headers[i-1], headers[i])
).toBeLessThan(2);
}
});
test("handles a valid structure on a real site", async ({page}) => {
await page.goto("https://playwright.dev/docs/test-assertions");
const headers = await page
.locator("h1, h2, h3, h4, h5, h6")
.evaluateAll(els => els.map(el => +el.tagName.slice(1)));
for (let i = 1; i < headers.length; i++) {
expect(
headers[i] - headers[i-1],
failMsg(headers[i-1], headers[i])
).toBeLessThan(2);
}
});
For your actual tests, you'll probably use page.goto()
rather than page.setContent
, which is used here purely to help demonstrate the assertion logic easily.
You may want to use a custom matcher to help abstract the verbose error template message and the explicit loop and/or write a helper function to extract headers.
Also, this assertion doesn't auto-wait, but I assume headers are probably OK to grab more or less in one-shot. If that assumption isn't true, try poll
or toPass
.