domplaywrightjsdomadonis.jsadonisjs-ace

Using DOM in AdonisJS with Playwright for PDF generation


I'm seeking guidance on utilizing the DOM in AdonisJS for a specific use case.

I understand that AdonisJS is primarily designed for server-side development and may not automatically include TypeScript definitions for the document object and other DOM elements, as these concepts are specific to the browser environment.

However, my current project involves using the Playwright library for PDF generation. I need to evaluate HTML classes through the $evaluate method, which requires access to DOM types. Even when attempting to use the JSDOM library, I encounter a definition error.

I'd greatly appreciate any assistance or insights into this matter. Thank you in advance; I'm a bit stuck at this stage.

import { inject } from '@adonisjs/core'
import { HttpContext } from '@adonisjs/core/http'
import { chromium } from 'playwright'
import { ConfigPdfInteface } from '../interface/config_pdf_interface.js'
import { JSDOM } from 'jsdom'
@inject()
export default class PlaywrightService {
  constructor(protected ctx: HttpContext) {}

  async generatePdfPlaywright(
    response: HttpContext['response'],
    path: string,
    documents: any,
    config: ConfigPdfInteface
  ) {
    try {
      const browser = await chromium.launch({
        headless: true,
      })

      const page = await browser.newPage()

      await page.emulateMedia({ media: 'print' })

      const html = await this.ctx.view.render(`${path}`, documents)
      await page.setContent(html, {
        waitUntil: 'networkidle',
      })

      const selector = '#someSelector'
      const bodySize = await page.evaluate((p) => {
        const { document } = new JSDOM(html).window
        console.log('document', document)
        const body = document?.querySelector(p)
        const minHeight = window.getComputedStyle(body!).minHeight
        return minHeight
      }, selector)

      console.log('bodySize', bodySize)

      const pdfBuffer = await page.pdf(config)

      await browser.close()

      response.header('Content-type', 'application/pdf')
      response.header('Content-Disposition', 'inline; filename=example.pdf')

      response.send(pdfBuffer)
    } catch (error) {
      console.error(error)
      response.status(500).send('PDF Error')
    }
  }
}

Error

page.evaluate: ReferenceError: JSDOM is not defined
    at eval (eval at evaluate (:226:30), <anonymous>:2:38)
    at UtilityScript.evaluate (<anonymous>:228:17)
    at UtilityScript.<anonymous> (<anonymous>:1:44)
    at PlaywrightService.generatePdfPlaywright 

Solution

  • I'm not sure what you're trying to accomplish (seems likely a XY problem), but in any case, there's no sense in running JSDOM in a browser. The whole point of JSDOM is to simulate the browser DOM in Node. But with Playwright, you get access to the real DOM in an actual webpage. Use it--that's the purpose of Playwright!

    Even if you wanted to use JSDOM in the browser, you'd have to explicitly add a script tag or pass the variable into evaluate (it wouldn't work since you can only pass serializable values). The variable JSDOM will be undefined once the evaluate callback is executed in the browser process.

    So

    const selector = '#someSelector'
    const bodySize = await page.evaluate((p) => {
      const { document } = new JSDOM(html).window
      console.log('document', document)
      const body = document?.querySelector(p)
      const minHeight = window.getComputedStyle(body!).minHeight
      return minHeight
    }, selector)
    

    becomes:

    const selector = "#someSelector";
    const bodySize = await page.$eval(selector, el =>
      getComputedStyle(el).minHeight
    );
    

    Minimal, runnable example:

    const playwright = require("playwright"); // ^1.42.1
    
    const html = `<!DOCTYPE html><html><body>
    <p style="min-height: 42px"></p></body></html>`;
    
    let browser;
    (async () => {
      browser = await playwright.firefox.launch();
      const page = await browser.newPage();
      await page.setContent(html);
      const selector = "p";
      const bodySize = await page.$eval(selector, el =>
        getComputedStyle(el).minHeight
      );
      console.log(bodySize); // => 42px
    })()
      .catch(err => console.error(err))
      .finally(() => browser?.close());
    

    If selector can't be found, $eval will throw. You can catch it and handle accordingly if you expect it to be missing, or wrap the code in a condition:

    const selector = "#someSelector";
    const el = await page.$(selector);
    
    if (el) {
      const bodySize = await el.evaluate(el =>
        getComputedStyle(el).minHeight
      );
    }
    

    or wait for it:

    const selector = "#someSelector";
    const el = await page.waitForSelector(selector);
    const bodySize = await el.evaluate(el =>
      getComputedStyle(el).minHeight
    );
    

    But bodySize is never used in your code.