/puppeteer-browser-ready

🐕‍🦺 Simple utility to go to a URL and wait for the HTTP response

Primary LanguageTypeScriptMIT LicenseMIT

puppeteer-browser-ready

logo

Simple utility to go to a URL and wait for the HTTP response

License:MIT npm Build

puppeteer-browser-ready is a helper utility to reduce the amount of boilerplate code needed to tell Puppeteer to visit a web page and and retrieve the HTML.  It's primarily intended for use within Mocha test cases.  In addition to the raw HTML, you get a node-html-parsed root so you can immediately run queries on the DOM.

A) Setup

Install packages:

$ npm install --save-dev puppeteer puppeteer-browser-ready

Import packages:

import puppeteer from 'puppeteer';
import { browserReady } from 'puppeteer-browser-ready';

B) Usage

Use the browserReady.goto(url, options) function to tell Puppeteer which page to open. The Promise will resolve with a Web object containing a title field and a html field. Pass the Web object to the browserReady.close(web) function to disconnect the page.

const url = 'https://pretty-print-json.js.org/';
let web;  //fields: browser, page, response, status, location, title, html, root
before(async () => web = await puppeteer.launch().then(browserReady.goto(url));
after(async () =>  await browserReady.close(web));

goto() Options

Name (key) Type Default Description
parseHtml boolean true Return the DOM root as an HTMLElement (node-html-parsed).
verbose boolean false Output HTTP connection debug messages.

startWebServer() Options

Name (key) Type Default Description
autoCleanup boolean true Terminate connection on interruption (SIGINT).
folder string '.' Document root for the static web server.
port number 0 Port number for server (0 find open port).
verbose boolean true Output informational messages.

C) TypeScript Declarations

See the TypeScript declarations at the top of the puppeteer-browser-ready.ts file.

The browserReady.goto(url, options) function returns a function that takes a Puppeteer Browser object and returns a Promise that resolves with a Web object:

type Web = {
   browser:  Puppeteer.Browser,
   page:     Puppeteer.Page,
   response: HTTPResponse | null,
   location: Location,
   title:    string,
   html:     string,
   root:     HTMLElement | null,  //see node-html-parsed library
   };

The optional browserReady.startWebServer(options) function starts a static web server and returns a Promise for when the server is ready:

export type Http = {
   server:     Server,
   terminator: httpTerminator.HttpTerminator,
   folder:     string,
   url:        string,
   port:       number,
   verbose:    boolean,
   };

D) Examples

Example 1: Node.js program

Code:

import puppeteer from 'puppeteer';
import { browserReady } from 'puppeteer-browser-ready';

const handleResponse = (web) => {
   console.log('Hello, World!');
   console.log('web fields:', Object.keys(web).join(', '));
   console.log(`The HTML from ${web.location.href} is ${web.html.length} characters`,
      `long and contains ${web.root.querySelectorAll('p').length} <p> tags.`);
   return web;
   };
puppeteer.launch()
   .then(browserReady.goto('https://pretty-print-json.js.org/'))
   .then(handleResponse)
   .then(browserReady.close);

Output:

Hello, World!
web fields: browser, page, response, status, location, title, html, root
The HTML from https://pretty-print-json.js.org/ is 8200 characters
long and contains 7 <p> tags.

Example 2: Mocha specification suite

Code:

// Mocha Specification Suite

// Imports
import puppeteer from 'puppeteer';
import { assertDeepStrictEqual } from 'assert-deep-strict-equal';
import { browserReady } from 'puppeteer-browser-ready';

// Setup
const url = 'https://pretty-print-json.js.org/';
let web;  //fields: browser, page, response, status, location, title, html, root
const loadWebPage = async () =>
   web = await puppeteer.launch().then(browserReady.goto(url));
const closeWebPage = async () =>
   await browserReady.close(web);

/////////////////////////////////////////////////////////////////////////////////////
describe('The web page', () => {
   before(loadWebPage);
   after(closeWebPage);

   it('has the correct URL', () => {
      const actual =   { status: web.status, url: web.location.href };
      const expected = { status: 200,        url: url };
      assertDeepStrictEqual(actual, expected);
      });

   it('title starts with "Pretty-Print JSON"', () => {
      const actual =   { title: web.title.substring(0, 17) };
      const expected = { title: 'Pretty-Print JSON' };
      assertDeepStrictEqual(actual, expected);
      });

   it('body has exactly one header, main, and footer -- node-html-parsed', () => {
      const getTags =  (elems) => [...elems].map(elem => elem.tagName.toLowerCase());
      const actual =   getTags(web.root.querySelectorAll('body >*'));
      const expected = ['header', 'main', 'footer'];
      assertDeepStrictEqual(actual, expected);
      });

   it('body has exactly one header, main, and footer -- page.$$eval()', async () => {
      const getTags =  (elems) => elems.map(elem => elem.nodeName.toLowerCase());
      const actual =   await web.page.$$eval('body >*', getTags);
      const expected = ['header', 'main', 'footer'];
      assertDeepStrictEqual(actual, expected);
      });

   });

/////////////////////////////////////////////////////////////////////////////////////
describe('The document content', () => {
   before(loadWebPage);
   after(closeWebPage);

   it('has a 🚀 traveling to 🪐!', () => {
      const actual =   { '🚀': !!web.html.match(/🚀/g), '🪐': !!web.html.match(/🪐/g) };
      const expected = { '🚀': true,                    '🪐': true };
      assertDeepStrictEqual(actual, expected);
      });

   });

Output:

  The web page
    ✓ has the correct URL
    ✓ title starts with "Pretty-Print JSON"
    ✓ body has exactly one header, main, and footer -- node-html-parsed
    ✓ body has exactly one header, main, and footer -- page.$$eval()

  The document content
    ✓ has a 🚀 traveling to 🪐!

Example 3: Start and shutdown a static web server

The startWebServer(options) and shutdownWebServer(http) functions can be used in global fixtures to start and shutdown a static web server.

For example, the spec/fixtures/setup-teardown.js file below starts a web server on port 7123 with the web root pointed to the project's docs folder.

Code:

// Specification Fixtures
import { browserReady } from 'puppeteer-browser-ready';
let http;  //fields: server, terminator, folder, url, port, verbose

// Setup
const mochaGlobalSetup = async () => {
   http = await browserReady.startWebServer({ folder: 'docs', port: 7123 });
   };

// Teardown
const mochaGlobalTeardown = async () => {
   await browserReady.shutdownWebServer(http);
   };

export { mochaGlobalSetup, mochaGlobalTeardown };

Run specification suites with global fixtures:
$ npx mocha spec/*.spec.js --require spec/fixtures/setup-teardown.js

Output:

  [2021-07-14T11:38:22.892Z] Web Server - listening: true 7123 http://localhost:7123/
  ...Output of Mocha specification suites here...
  [2021-07-14T11:38:26.704Z] Web Server - shutdown: true

E) Test Timeout Errors

By default Mocha allows a test 2,000 ms to complete before timing out with a failure.  Web page load times can vary significantly, so it's sometimes a good idea to use the timeout option to bump up the allowed test execution time.

Example configuration in package.json to allow 5,000 ms:

   "scripts": {
      "pretest": "run-scripts clean build",
      "test": "mocha spec/*.spec.js --timeout 7000"
   },


CLI Build Tools for package.json

  • 🎋 add-dist-headerPrepend a one-line banner comment (with license notice) to distribution files
  • 📄 copy-file-utilCopy or rename a file with optional package version number
  • 📂 copy-folder-utilRecursively copy files from one folder to another folder
  • 🪺 recursive-execRun a command on each file in a folder and its subfolders
  • 🔍 replacer-utilFind and replace strings or template outputs in text files
  • 🔢 rev-web-assetsRevision web asset filenames with cache busting content hash fingerprints
  • 🚆 run-scripts-utilOrganize npm package.json scripts into groups of easy to manage commands
  • 🚦 w3c-html-validatorCheck the markup validity of HTML files using the W3C validator

MIT License