UNPKG

puppeteer-browser-ready

Version:

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

248 lines (210 loc) 10.3 kB
# puppeteer-browser-ready <img src=https://centerkey.com/graphics/center-key-logo.svg align=right width=180 alt=logo> _Simple utility to go to a URL and wait for the HTTP response_ [![License:MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/center-key/puppeteer-browser-ready/blob/main/LICENSE.txt) [![npm](https://img.shields.io/npm/v/puppeteer-browser-ready.svg)](https://www.npmjs.com/package/puppeteer-browser-ready) [![Build](https://github.com/center-key/puppeteer-browser-ready/actions/workflows/run-spec-on-push.yaml/badge.svg)](https://github.com/center-key/puppeteer-browser-ready/actions/workflows/run-spec-on-push.yaml) **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.&nbsp; It's primarily intended for use within [Mocha](https://mochajs.org) test cases.&nbsp; In addition to the raw HTML, you get a [node-html-parsed](https://github.com/taoqf/node-html-parser) root so you can immediately run queries on the DOM. ## A) Setup **Install packages:** ```shell $ npm install --save-dev puppeteer puppeteer-browser-ready ``` **Import packages:** ```javascript 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. ```javascript 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](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: ```typescript 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](spec/start-web-server.spec.js) is ready: ```typescript export type Http = { server: Server, terminator: httpTerminator.HttpTerminator, folder: string, url: string, port: number, verbose: boolean, }; ``` ## D) Examples ### Example 1: Node.js program **Code:** ```javascript 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:** ```javascript // 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)](spec/start-web-server.spec.js) 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:** ```javascript // 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:<br> `$ 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.&nbsp; 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: ```json "scripts": { "pretest": "run-scripts clean build", "test": "mocha spec/*.spec.js --timeout 7000" }, ``` <br> --- **CLI Build Tools for package.json** - 🎋 [add-dist-header](https://github.com/center-key/add-dist-header):&nbsp; _Prepend a one-line banner comment (with license notice) to distribution files_ - 📄 [copy-file-util](https://github.com/center-key/copy-file-util):&nbsp; _Copy or rename a file with optional package version number_ - 📂 [copy-folder-util](https://github.com/center-key/copy-folder-util):&nbsp; _Recursively copy files from one folder to another folder_ - 🪺 [recursive-exec](https://github.com/center-key/recursive-exec):&nbsp; _Run a command on each file in a folder and its subfolders_ - 🔍 [replacer-util](https://github.com/center-key/replacer-util):&nbsp; _Find and replace strings or template outputs in text files_ - 🔢 [rev-web-assets](https://github.com/center-key/rev-web-assets):&nbsp; _Revision web asset filenames with cache busting content hash fingerprints_ - 🚆 [run-scripts-util](https://github.com/center-key/run-scripts-util):&nbsp; _Organize npm package.json scripts into groups of easy to manage commands_ - 🚦 [w3c-html-validator](https://github.com/center-key/w3c-html-validator):&nbsp; _Check the markup validity of HTML files using the W3C validator_ [MIT License](LICENSE.txt)