Search code examples
typescriptplaywright

ReferenceError: document is not defined error Typescript in Playwright


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();
  });
}

Solution

  • 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.