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
1FROM node:latest
2ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
3WORKDIR /puppeteer
4RUN apt-get update \
5 && apt-get install -y \
6 fonts-liberation \
7 gconf-service \
8 libasound2 \
9 libatk1.0-0 \
10 libcairo2 \
11 libcups2 \
12 libfontconfig1 \
13 libgbm-dev \
14 libgdk-pixbuf-2.0-0 \
15 libgtk-3-0 \
16 libicu-dev \
17 libjpeg-dev \
18 libnspr4 \
19 libnss3 \
20 libpango-1.0-0 \
21 libpangocairo-1.0-0 \
22 libpng-dev \
23 libx11-6 \
24 libx11-xcb1 \
25 libxcb1 \
26 libxcomposite1 \
27 libxcursor1 \
28 libxdamage1 \
29 libxext6 \
30 libxfixes3 \
31 libxi6 \
32 libxrandr2 \
33 libxrender1 \
34 libxss1 \
35 libxtst6 \
36 xdg-utils \
37 chromium
38RUN npm install puppeteer
39RUN npm ci
40RUN 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
1const puppeteer = require('puppeteer');
2var somethingToLookFor = 'JQMIGRATE'
3var someFunkyUrl = 'https://alistapart.com/'
4
5function doTheThing(var1) {
6 console.log("callback func", var1);
7}
8
9let thisWouldBeANiceFunction = async function (doThisThing) {
10 let vars = {
11 results: "Not installed",
12 found: null
13 }
14 let browser;
15 let myPromise;
16 try {
17 myPromise = new Promise(async (resolve, reject) => {
18 browser = await puppeteer.launch({
19 headless: true,
20 args: ['--use-gl=el', '--no-sandbox'],
21 executablePath: '/usr/bin/chromium-browser'
22 });
23 let page = await browser.newPage();
24 page.on('console', async (msg) => {
25 if (msg.text().includes(somethingToLookFor)) {
26 vars.found = msg.text();
27 vars.results = "Found"
28 resolve(vars);
29 }
30 })
31 await page.goto(someFunkyUrl);
32 })
33 await myPromise.then(async (resp) => {
34 doThisThing(resp)
35 })
36 } catch (err) {
37 console.log(err);
38 } finally {
39 // await browser.close();
40 }
41}
42
43thisWouldBeANiceFunction(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:
1docker build -t puppeteer:latest .
That builds me an image tagged puppeteer:latest
that I can call when I need to run puppet, like so:
1docker 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 tagnode 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
1 2 let sendBack = async ()=>{return await myPromise} 3 let answer = await sendBack(); 4 return answer;
ps: In other words, using then() after the awaits was wrong.