Building a Docker container for Puppeteer testing

I hate recipes online that wax philosophically for years, take me to the code

I've started following some coder channels on Twitch and having a great time doing it. I really enjoy the Twitch channels that have interactive hosts, and found LadyOfCode to be interesting and entertaining. Enough that I decided to join her Discord and see what it was like there.

While there a fellow member posted an issue they were having getting Puppeteer running - Puppeteer being a NodeJS browser automation/ testing framework. They posted their JS and I could read it - so even though I didn't know Puppeteer I tried offering some advise. The advise wasn't working so I decided to build a Docker container for Puppeteer and see if I could help further. The Google searches on getting Puppeteer were miss and miss (ie. they didn't work) so I screwed up my courage and my minimal Docker skills to build my own container.

Looks like I had been paying attention to the server team at work as I got it working a lot faster than I expected. Here's what I made:

The Code

Dockerfile

FROM node:latest
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
WORKDIR /puppeteer
RUN apt-get update \ 
    && apt-get install -y \
    fonts-liberation \
    gconf-service \
    libasound2 \
    libatk1.0-0 \
    libcairo2 \
    libcups2 \
    libfontconfig1 \
    libgbm-dev \
    libgdk-pixbuf-2.0-0 \
    libgtk-3-0 \
    libicu-dev \
    libjpeg-dev \
    libnspr4 \
    libnss3 \
    libpango-1.0-0 \
    libpangocairo-1.0-0 \
    libpng-dev \
    libx11-6 \
    libx11-xcb1 \
    libxcb1 \
    libxcomposite1 \
    libxcursor1 \
    libxdamage1 \
    libxext6 \
    libxfixes3 \
    libxi6 \
    libxrandr2 \
    libxrender1 \
    libxss1 \
    libxtst6 \
    xdg-utils \
    chromium
RUN npm install puppeteer
RUN npm ci    
RUN ln -s /usr/bin/chromium /usr/bin/chromium-browser

That's the collected "this works" from combining a bunch of found Dockerfile bits. Note that one of the machines I used this on was a new M1 Mac so I had to not install the Puppeteer Chromium (using the PUPPETEER_SKIP_CHROMIUM_DOWNLOAD env variable), and instead installed it via apt-get.

tests/thisscript.js

const puppeteer = require('puppeteer');
var somethingToLookFor = 'JQMIGRATE'
var someFunkyUrl = 'https://alistapart.com/'

function doTheThing(var1) {
    console.log("callback func", var1);
}

let thisWouldBeANiceFunction = async function (doThisThing) {
    let vars = {
        results: "Not installed",
        found: null
    }
    let browser;
    let myPromise;
    try {
        myPromise = new Promise(async (resolve, reject) => {
            browser = await puppeteer.launch({
                headless: true,
                args: ['--use-gl=el', '--no-sandbox'],
                executablePath: '/usr/bin/chromium-browser'
            });            
            let page = await browser.newPage();
            page.on('console', async (msg) => {
                if (msg.text().includes(somethingToLookFor)) {
                    vars.found = msg.text();
                    vars.results = "Found"
                    resolve(vars);
                }
            })
            await page.goto(someFunkyUrl);
        })
        await myPromise.then(async (resp) => {
            doThisThing(resp)
        })
    } catch (err) {
        console.log(err);
    } finally {
        // await browser.close();
    }
}

thisWouldBeANiceFunction(doTheThing)

This JS just tests a site to see if JQMigrate is installed as it puts a message in the console. Lots of async and other Puppeteer parallel calls. It works!

Calling out specific changes I made to make it work - I added in the "--use-gl=el","--no-sandbox" bit to run headless in docker. I also added the executablePath: '/usr/bin/chromium-browser' to tell Puppet where I'd put Chromium to run.

So to run it there's:

docker build -t puppeteer:latest .

That builds me an image tagged puppeteer:latest that I can call when I need to run puppet, like so:

docker run -i --rm -v ./tests:/puppeteer/tests -t puppeteer:latest node tests/quicktest.js
  • -i is for an interactive shell
  • --rm is to remove the built image once execution is over, so it doesn't fill up my HD
  • -v ./tests:/puppeteer/tests mounts my local tests directory to /puppeteer/tests in the image, so puppeteer can access it
  • -t puppeteer:latest tells it which image to use by its tag
  • node tests/quicktest.js is the command to run in the container

Interested in recommendations to improve, as always.

Update 2021-12-26

Said friend managed to find a way to do this without needing the callback function! You can see his writeup on the puppeteer issue raised


 let sendBack = async ()=>{return await myPromise}
 let answer = await sendBack();
 return answer; 

ps: In other words, using then() after the awaits was wrong.