Résoudre les problèmes liés à Puppeteer

Chrome sans interface graphique ne se lance pas sous Windows

Certaines règles Chrome peuvent exiger l'exécution de Chrome ou de Chromium avec certaines extensions.

Puppeteer transmet l'option --disable-extensions par défaut et ne se lance donc pas lorsque de telles règles sont actives.

Pour contourner ce problème, essayez de l'exécuter sans l'indicateur:

const browser = await puppeteer.launch({
  ignoreDefaultArgs: ['--disable-extensions'],
});

Contexte: problème 3681.

Chrome sans interface graphique ne se lance pas sous UNIX

Assurez-vous que toutes les dépendances nécessaires sont installées. Vous pouvez exécuter ldd chrome | grep not sur une machine Linux pour vérifier les dépendances manquantes.

Dépendances Debian (Ubuntu)

ca-certificates
fonts-liberation
libappindicator3-1
libasound2
libatk-bridge2.0-0
libatk1.0-0
libc6
libcairo2
libcups2
libdbus-1-3
libexpat1
libfontconfig1
libgbm1
libgcc1
libglib2.0-0
libgtk-3-0
libnspr4
libnss3
libpango-1.0-0
libpangocairo-1.0-0
libstdc++6
libx11-6
libx11-xcb1
libxcb1
libxcomposite1
libxcursor1
libxdamage1
libxext6
libxfixes3
libxi6
libxrandr2
libxrender1
libxss1
libxtst6
lsb-release
wget
xdg-utils

Dépendances CentOS

alsa-lib.x86_64
atk.x86_64
cups-libs.x86_64
gtk3.x86_64
ipa-gothic-fonts
libXcomposite.x86_64
libXcursor.x86_64
libXdamage.x86_64
libXext.x86_64
libXi.x86_64
libXrandr.x86_64
libXScrnSaver.x86_64
libXtst.x86_64
pango.x86_64
xorg-x11-fonts-100dpi
xorg-x11-fonts-75dpi
xorg-x11-fonts-cyrillic
xorg-x11-fonts-misc
xorg-x11-fonts-Type1
xorg-x11-utils

Après avoir installé les dépendances, vous devez mettre à jour la bibliothèque nss à l'aide de cette commande

yum update nss -y

Consultez les discussions:

  • #290 : Dépannage Debian
  • #391 :
    de dépannage CentOS
  • N° 379 : dépannage de Alpine

Chrome sans interface graphique désactive la composition GPU

Chrome et Chromium nécessitent --use-gl=egl pour activer l'accélération GPU en mode sans interface graphique.

const browser = await puppeteer.launch({
  headless: true,
  args: ['--use-gl=egl'],
});

Chrome a été téléchargé, mais ne parvient pas à se lancer sur Node.js

Si un message d'erreur semblable à celui-ci s'affiche lorsque vous essayez de lancer Chromium:

(node:15505) UnhandledPromiseRejectionWarning: Error: Failed to launch the browser process!
spawn /Users/.../node_modules/puppeteer/.local-chromium/mac-756035/chrome-mac/Chromium.app/Contents/MacOS/Chromium ENOENT

Cela signifie que le navigateur a été téléchargé, mais que son extraction a échoué. La cause la plus fréquente est un bug dans Node.js v14.0.0 qui a entraîné l'interruption de extract-zip, que le module Puppeteer utilise pour extraire les téléchargements du navigateur au bon endroit. Ce bug a été corrigé dans Node.js v14.1.0. Assurez-vous donc d'exécuter cette version ou une version ultérieure.

Configurer un bac à sable Chrome Linux

Afin de protéger l'environnement hôte contre les contenus Web non approuvés, Chrome utilise plusieurs couches de bac à sable. Pour que cela fonctionne correctement, vous devez d'abord configurer l'hôte. En l'absence de bac à sable approprié, Chrome plante et renvoie l'erreur No usable sandbox!.

Si vous avez tout à fait confiance dans le contenu que vous ouvrez dans Chrome, vous pouvez lancer Chrome avec l'argument --no-sandbox:

const browser = await puppeteer.launch({
  args: ['--no-sandbox', '--disable-setuid-sandbox'],
});

Il existe deux façons de configurer un bac à sable dans Chromium.

Le clonage d'espace de noms Sser n'est compatible qu'avec les noyaux modernes. Les espaces de noms utilisateur non privilégiés peuvent généralement être activés, mais ils peuvent ouvrir une surface d'attaque de noyau plus importante pour les processus non racines (hors bac à sable) afin d'élever les privilèges du noyau.

sudo sysctl -w kernel.unprivileged_userns_clone=1

[alternative] Configurer le bac à sable setuid

Le setuid sandbox est un exécutable autonome. Il se trouve à côté du fichier Chromium téléchargé par Puppeteer. Il est possible de réutiliser le même exécutable de bac à sable pour différentes versions de Chromium. Par conséquent, les opérations suivantes ne peuvent être effectuées qu'une seule fois par environnement hôte:

# cd to the downloaded instance
cd <project-dir-path>/node_modules/puppeteer/.local-chromium/linux-<revision>/chrome-linux/
sudo chown root:root chrome_sandbox
sudo chmod 4755 chrome_sandbox
# copy sandbox executable to a shared location
sudo cp -p chrome_sandbox /usr/local/sbin/chrome-devel-sandbox
# export CHROME_DEVEL_SANDBOX env variable
export CHROME_DEVEL_SANDBOX=/usr/local/sbin/chrome-devel-sandbox

Vous pouvez exporter la variable d'environnement CHROME_DEVEL_SANDBOX par défaut. Dans ce cas, ajoutez le code suivant à ~/.bashrc ou .zshenv:

export CHROME_DEVEL_SANDBOX=/usr/local/sbin/chrome-devel-sandbox

Exécuter Puppeteer sur Travis CI

Nous avons exécuté nos tests pour Puppeteer sur Travis CI jusqu'à la version 6.0.0, après quoi nous avons migré vers GitHub Actions. Vous pouvez consulter la section .travis.yml (v5.5.0) pour référence.

Voici certaines des bonnes pratiques à suivre :

  • Le service xvfb doit être lancé pour exécuter Chromium en mode sans interface graphique
  • S'exécute par défaut sur Xenial Linux sur Travis
  • Exécute npm install par défaut
  • node_modules est mis en cache par défaut

.travis.yml pourrait se présenter comme suit:

language: node_js
node_js: node
services: xvfb

script:
  - npm run test

Exécuter Puppeteer sur CircleCI

  1. Commencez par une image NodeJS dans votre configuration. yaml docker: - image: circleci/node:14 # Use your desired version environment: NODE_ENV: development # Only needed if puppeteer is in `devDependencies`
  2. Les dépendances comme libXtst6 doivent probablement être installées avec apt-get. Vous devez donc utiliser l'orbe threetreeslight/puppeteer (instructions) ou coller des parties de sa source dans votre propre configuration.
  3. Enfin, si vous utilisez Puppeteer via Jest, une erreur peut se produire lors de la génération de processus enfants : shell [00:00.0] jest args: --e2e --spec --max-workers=36 Error: spawn ENOMEM at ChildProcess.spawn (internal/child_process.js:394:11) Cela est probablement dû à la détection automatique par Jest du nombre de processus sur l'ensemble de la machine (36) plutôt que du nombre autorisé pour votre conteneur (2). Pour résoudre ce problème, définissez jest --maxWorkers=2 dans votre commande de test.

Exécuter Puppeteer dans Docker

La mise en place de Chrome sans interface graphique dans Docker n'est pas une mince affaire. Il manque les dépendances de bibliothèque partagée nécessaires au package Chromium intégré que Puppeteer installe.

Pour résoudre ce problème, vous devez installer les dépendances manquantes et le dernier package Chromium dans votre fichier Dockerfile:

FROM node:14-slim

# Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others)
# Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer
# installs, work.
RUN apt-get update \
    && apt-get install -y wget gnupg \
    && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
    && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
    && apt-get update \
    && apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \
      --no-install-recommends \
    && rm -rf /var/lib/apt/lists/*

# If running Docker >= 1.13.0 use docker run's --init arg to reap zombie processes, otherwise
# uncomment the following lines to have `dumb-init` as PID 1
# ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_x86_64 /usr/local/bin/dumb-init
# RUN chmod +x /usr/local/bin/dumb-init
# ENTRYPOINT ["dumb-init", "--"]

# Uncomment to skip the chromium download when installing puppeteer. If you do,
# you'll need to launch puppeteer with:
#     browser.launch({executablePath: 'google-chrome-stable'})
# ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true

# Install puppeteer so it's available in the container.
RUN npm init -y &&  \
    npm i puppeteer \
    # Add user so we don't need --no-sandbox.
    # same layer as npm install to keep re-chowned files from using up several hundred MBs more space
    && groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser \
    && mkdir -p /home/pptruser/Downloads \
    && chown -R pptruser:pptruser /home/pptruser \
    && chown -R pptruser:pptruser /node_modules \
    && chown -R pptruser:pptruser /package.json \
    && chown -R pptruser:pptruser /package-lock.json

# Run everything after as non-privileged user.
USER pptruser

CMD ["google-chrome-stable"]

Créez le conteneur :

docker build -t puppeteer-chrome-linux .

Exécutez le conteneur en transmettant node -e "<yourscript.js content as a string>" comme commande:

 docker run -i --init --rm --cap-add=SYS_ADMIN \
   --name puppeteer-chrome puppeteer-chrome-linux \
   node -e "`cat yourscript.js`"

Vous trouverez un exemple complet à l'adresse https://github.com/ebidel/try-puppeteer qui montre comment exécuter ce Dockerfile à partir d'un serveur Web exécuté sur l'environnement flexible App Engine (Node).

Courez sur Alpine

Le plus récent package Chromium compatible avec Alpine est la version 100, ce qui correspond à Puppeteer v13.5.0.

Exemple de Dockerfile:

FROM alpine

# Installs latest Chromium (100) package.
RUN apk add --no-cache \
      chromium \
      nss \
      freetype \
      harfbuzz \
      ca-certificates \
      ttf-freefont \
      nodejs \
      yarn

...

# Tell Puppeteer to skip installing Chrome. We'll be using the installed package.
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
    PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser

# Puppeteer v13.5.0 works with Chromium 100.
RUN yarn add puppeteer@13.5.0

# Add user so we don't need --no-sandbox.
RUN addgroup -S pptruser && adduser -S -G pptruser pptruser \
    && mkdir -p /home/pptruser/Downloads /app \
    && chown -R pptruser:pptruser /home/pptruser \
    && chown -R pptruser:pptruser /app

# Run everything after as non-privileged user.
USER pptruser

...

Bonnes pratiques pour Docker

Par défaut, Docker exécute un conteneur avec un espace de mémoire partagé /dev/shm de 64 Mo. Cet élément est généralement trop petit pour Chrome et provoque le plantage de Chrome lors de l'affichage de pages volumineuses. Pour résoudre ce problème, exécutez le conteneur avec docker run --shm-size=1gb afin d'augmenter la taille de /dev/shm. Depuis Chrome 65, ce n'est plus nécessaire. À la place, lancez le navigateur avec l'indicateur --disable-dev-shm-usage:

const browser = await puppeteer.launch({
  args: ['--disable-dev-shm-usage'],
});

Les fichiers de mémoire partagée sont écrits dans /tmp au lieu de /dev/shm. Consultez crbug.com/736452.

Constatez-vous d'autres erreurs bizarres lors du lancement de Chrome ? Essayez d'exécuter votre conteneur avec docker run --cap-add=SYS_ADMIN lorsque vous développez en local. Étant donné que le Dockerfile ajoute un utilisateur pptr en tant qu'utilisateur non privilégié, il se peut qu'il ne dispose pas de tous les droits nécessaires.

dumb-init vaut la peine d'être examiné si vous constatez de nombreux processus Chrome basés sur des zombies. Il existe un traitement spécial pour les processus avec PID=1, ce qui rend difficile l'arrêt correct de Chrome dans certains cas (par exemple, avec Docker).

Exécuter Puppeteer dans le cloud

Sur Google App Engine

L'environnement d'exécution Node.js de l'environnement standard App Engine est fourni avec tous les packages système nécessaires à l'exécution de Chrome sans interface graphique.

Pour utiliser puppeteer, répertoriez le module en tant que dépendance dans votre fichier package.json et déployez-le sur Google App Engine. Pour en savoir plus sur l'utilisation de puppeteer sur App Engine, suivez le tutoriel officiel.

Sur Google Cloud Functions

L'environnement d'exécution Node.js 10 de Google Cloud Functions est fourni avec tous les packages système nécessaires à l'exécution de Chrome sans interface graphique.

Pour utiliser puppeteer, répertoriez le module en tant que dépendance dans votre fichier package.json et déployez votre fonction sur Google Cloud Functions à l'aide de l'environnement d'exécution nodejs10.

Exécuter Puppeteer sur Google Cloud Run

L'environnement d'exécution Node.js par défaut de Google Cloud Run n'est pas fourni avec les packages système nécessaires à l'exécution de Chrome sans interface graphique. Configurez votre propre Dockerfile et incluez les dépendances manquantes.

Sur Heroku

L'exécution de Puppeteer sur Heroku nécessite des dépendances supplémentaires qui ne sont pas incluses dans la boîte Linux que Heroku génère pour vous. Pour ajouter les dépendances lors du déploiement, ajoutez le buildpack Puppeteer Heroku à la liste des packs de création de votre application sous Paramètres > Buildpacks.

L'URL du buildpack est https://github.com/jontewks/puppeteer-heroku-buildpack.

Veillez à utiliser le mode '--no-sandbox' lorsque vous lancez Puppeteer. Pour ce faire, transmettez-le en tant qu'argument à votre appel .launch() : puppeteer.launch({ args: ['--no-sandbox'] });.

Lorsque vous cliquez sur "Add buildpack", collez cette URL dans l'entrée, puis cliquez sur Save (Enregistrer). Lors du prochain déploiement, votre application installe également les dépendances dont Puppeteer a besoin pour s'exécuter.

Si vous devez afficher des caractères chinois, japonais ou coréens, vous devrez peut-être utiliser un pack de création contenant des fichiers de police supplémentaires, par exemple https://github.com/CoffeeAndCode/puppeteer-heroku-buildpack

Il existe également un autre guide de @timleland qui inclut un exemple de projet.

Sur AWS Lambda

AWS Lambda limite la taille des packages de déploiement à environ 50 Mo. Cela présente des défis pour exécuter Chrome sans interface graphique (et donc Puppeteer) sur Lambda. La communauté a mis en place quelques ressources qui permettent de résoudre ces problèmes:

Instance AWS EC2 exécutant Amazon-Linux

Si vous disposez d'une instance EC2 exécutant amazon-linux dans votre pipeline CI/CD et que vous souhaitez exécuter des tests Puppeteer sous amazon-linux, procédez comme suit.

  1. Pour installer Chromium, vous devez d'abord activer amazon-linux-extras, qui fait partie d'EPEL (Extra Packages for Enterprise Linux):

    sudo amazon-linux-extras install epel -y
    
  2. Ensuite, installez Chromium:

    sudo yum install -y chromium
    

Puppeteer peut désormais lancer Chromium pour exécuter vos tests. Si vous n'activez pas EPEL et continuez à installer Chromium dans le cadre de npm install, Puppeteer ne pourra pas lancer Chromium en raison de l'indisponibilité de libatk-1.0.so.0 et de nombreux autres packages.

Problèmes de transpilation du code

Si vous utilisez un transpilateur JavaScript tel que babel ou TypeScript, l'appel de evaluate() avec une fonction asynchrone risque de ne pas fonctionner. En effet, alors que puppeteer utilise Function.prototype.toString() pour sérialiser des fonctions, alors que les transpilateurs peuvent modifier le code de sortie d'une manière incompatible avec puppeteer.

Pour contourner ce problème, vous pouvez demander au transpilateur de ne pas interférer avec le code. Par exemple, configurez TypeScript pour utiliser la dernière version ecma ("target": "es2018"). Une autre solution consiste à utiliser des modèles de chaîne au lieu de fonctions:

await page.evaluate(`(async() => {
   console.log('1');
})()`);