Soluciona problemas de Puppeteer

Headless Chrome no se inicia en Windows

Algunas políticas de Chrome podrían exigir la ejecución de Chrome o Chromium con ciertas extensiones.

Puppeteer pasa la marca --disable-extensions de forma predeterminada y, por lo tanto, no se inicia cuando esas políticas están activas.

Para solucionar este problema, intenta ejecutar sin la marca:

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

Contexto: error 3681.

Chrome sin interfaz gráfica no se inicia en UNIX

Asegúrate de que todas las dependencias necesarias estén instaladas. Puedes ejecutar ldd chrome | grep not en una máquina Linux para comprobar qué dependencias faltan.

Dependencias de 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

Dependencias de 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

Después de instalar las dependencias, debes actualizar la biblioteca nss con el siguiente comando.

yum update nss -y

Consulta los debates:

  • #290 - Solución de problemas de Debian
  • #391 - Solución de problemas de CentOS
  • #379 - Solución de problemas de Alpine

Chrome sin interfaz gráfica inhabilita la composición GPU

Chrome y Chromium requieren --use-gl=egl para habilitar la aceleración de GPU en modo sin interfaz gráfica.

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

Chrome se descarga, pero no se puede iniciar en Node.js.

Si al intentar iniciar Chromium recibes un error similar al siguiente, sigue estos pasos:

(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

Esto significa que se descargó el navegador, pero no se pudo extraer correctamente. La causa más común es un error en Node.js v14.0.0 que rompió extract-zip, el módulo que Puppeteer usa para extraer las descargas del navegador en el lugar correcto. El error se corrigió en la versión 14.1.0 de Node.js, así que asegúrate de ejecutar esa versión o una posterior.

Configurar una zona de pruebas de Chrome Linux

Para proteger el entorno de host contra contenido web que no sea de confianza, Chrome usa varias capas de zona de pruebas. Para que esto funcione correctamente, primero se debe configurar el host. Si no hay una buena zona de pruebas para que use Chrome, fallará y se mostrará el error No usable sandbox!.

Si confías absolutamente en el contenido que abres en Chrome, puedes iniciar el navegador con el argumento --no-sandbox:

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

Existen 2 maneras de configurar una zona de pruebas en Chromium.

La clonación de espacios de nombres de Sser solo es compatible con los kernels modernos. Por lo general, es posible habilitar los espacios de nombres de usuario sin privilegios, pero puede abrir más superficie de ataque del kernel para los procesos no raíz (sin zona de pruebas) a fin de elevarlos a los privilegios del kernel.

sudo sysctl -w kernel.unprivileged_userns_clone=1

[alternativa] Cómo configurar la zona de pruebas de setuid

La zona de pruebas de setuid viene como un archivo ejecutable independiente y se encuentra junto a Chromium que descarga Puppeteer. Se puede volver a usar el mismo ejecutable de la zona de pruebas para diferentes versiones de Chromium, por lo que se podría hacer lo siguiente una sola vez por entorno de host:

# 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

Es posible que quieras exportar la variable de entorno CHROME_DEVEL_SANDBOX de forma predeterminada. En ese caso, agrega lo siguiente a ~/.bashrc o .zshenv:

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

Ejecuta Puppeteer en Travis CI

Ejecutamos nuestras pruebas de Puppeteer en Travis CI hasta la versión 6.0.0 y, luego, migramos a GitHub Actions. Puedes consultar .travis.yml (v5.5.0) como referencia.

Aquí encontrarás algunas prácticas recomendadas:

  • Se debe iniciar el servicio xvfb para ejecutar Chromium en modo sin interfaz gráfica.
  • Se ejecuta en Xenial Linux en Travis de forma predeterminada.
  • Ejecuta npm install de forma predeterminada.
  • node_modules se almacena en caché de forma predeterminada

.travis.yml podría verse de la siguiente manera:

language: node_js
node_js: node
services: xvfb

script:
  - npm run test

Ejecuta Puppeteer en CircleCI

  1. Comienza con una imagen de NodeJS en la configuración. yaml docker: - image: circleci/node:14 # Use your desired version environment: NODE_ENV: development # Only needed if puppeteer is in `devDependencies`
  2. Es probable que las dependencias como libXtst6 deban instalarse con apt-get, por lo que debes usar el orbe threeslight/puppeteer (instructions) o pega partes de su fuente en tu propia configuración.
  3. Por último, si usas Puppeteer mediante Jest, es posible que encuentres un error que genere procesos secundarios: shell [00:00.0] jest args: --e2e --spec --max-workers=36 Error: spawn ENOMEM at ChildProcess.spawn (internal/child_process.js:394:11). Es probable que esto se deba a que Jest detecta automáticamente la cantidad de procesos en toda la máquina (36), en lugar del número permitido para tu contenedor (2). Para solucionar esto, configura jest --maxWorkers=2 en tu comando de prueba.

Ejecutar Puppeteer en Docker

Ejecutar Chrome sin interfaz gráfica y ejecutarlo en Docker puede ser complicado. El paquete de Chromium que instala Puppeteer no tiene las dependencias de biblioteca compartida necesarias.

Para solucionarlo, deberás instalar las dependencias faltantes y el paquete de Chromium más reciente en tu 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"]

Compila el contenedor:

docker build -t puppeteer-chrome-linux .

Para ejecutar el contenedor, pasa node -e "<yourscript.js content as a string>" como el comando:

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

Hay un ejemplo completo en https://github.com/ebidel/try-puppeteer que muestra cómo ejecutar este Dockerfile desde un servidor web que se ejecuta en el entorno flexible de App Engine (nodo).

Ejecuta en Alpine

El paquete de Chromium más reciente compatible con Alpine es 100, que corresponde a Puppeteer v13.5.0.

Ejemplo 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

...

Prácticas recomendadas con Docker

De forma predeterminada, Docker ejecuta un contenedor con un espacio de memoria compartida de /dev/shm de 64 MB. Esto es normalmente demasiado pequeño para Chrome y hará que Chrome falle al procesar páginas grandes. Para solucionarlo, ejecuta el contenedor con docker run --shm-size=1gb a fin de aumentar el tamaño de /dev/shm. A partir de Chrome 65, esto ya no es necesario. En su lugar, inicia el navegador con la marca --disable-dev-shm-usage:

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

De esta manera, los archivos de memoria compartidos se escriben en /tmp, en lugar de en /dev/shm. Consulta crbug.com/736452.

¿Ves otros errores extraños al iniciar Chrome? Intenta ejecutar el contenedor con docker run --cap-add=SYS_ADMIN cuando desarrolles de forma local. Dado que Dockerfile agrega un usuario pptr como usuario sin privilegios, es posible que no cuente con todos los privilegios necesarios.

Vale la pena revisar dumb-init si tienes muchos procesos zombis de Chrome que permanecen en el sistema. Hay un tratamiento especial para los procesos con PID=1, lo que dificulta cerrar Chrome de manera correcta en algunos casos (como con Docker).

Ejecuta Puppeteer en la nube

En Google App Engine

El entorno de ejecución de Node.js del entorno estándar de App Engine incluye todos los paquetes de sistema necesarios para ejecutar Headless Chrome.

Para usar puppeteer, enumera el módulo como una dependencia en tu package.json y, luego, impleméntalo en Google App Engine. Obtén más información sobre el uso de puppeteer en App Engine con el instructivo oficial.

En Google Cloud Functions

El entorno de ejecución de Node.js 10 de Google Cloud Functions incluye todos los paquetes de sistema necesarios para ejecutar Headless Chrome.

Para usar puppeteer, enumera el módulo como una dependencia en tu package.json y, luego, implementa tu función en Google Cloud Functions mediante el entorno de ejecución de nodejs10.

Ejecuta Puppeteer en Google Cloud Run

El entorno de ejecución predeterminado de Node.js de Google Cloud Run no incluye los paquetes de sistema necesarios para ejecutar Headless Chrome. Configura tu propio Dockerfile e incluye las dependencias faltantes.

En Heroku

La ejecución de Puppeteer en Heroku requiere algunas dependencias adicionales que no se incluyen en la caja de Linux que Heroku inicia por ti. Para agregar las dependencias en la implementación, agrega el paquete de compilación de Puppeteer Heroku a la lista de paquetes de compilación para tu app en Configuración > Paquetes de compilación.

La URL del paquete de compilación es https://github.com/jontewks/puppeteer-heroku-buildpack.

Asegúrate de usar el modo '--no-sandbox' cuando inicies Puppeteer. Para ello, puedes pasarlo como argumento a tu llamada a .launch(): puppeteer.launch({ args: ['--no-sandbox'] });.

Cuando hagas clic en Agregar paquete de compilación, pega esa URL en la entrada y haz clic en Guardar. En la siguiente implementación, tu app también instalará las dependencias que Puppeteer necesita para ejecutar.

Si necesitas renderizar caracteres chinos, japoneses o coreanos, es posible que debas usar un paquete de compilación con archivos de fuentes adicionales, como https://github.com/CoffeeAndCode/puppeteer-heroku-buildpack.

También hay otra guía de @timleland que incluye un proyecto de muestra.

En AWS Lambda

AWS Lambda limita el tamaño de los paquetes de implementación a alrededor de 50 MB. Esto presenta desafíos para ejecutar Chrome (y, por lo tanto, Puppeteer) sin interfaz gráfica en Lambda. La comunidad reunió algunos recursos para resolver los problemas:

Instancia de AWS EC2 que ejecuta Amazon-Linux

Si tienes una instancia de EC2 que ejecuta amazon-linux en tu canalización de CI/CD y quieres ejecutar pruebas de Puppeteer en amazon-linux, sigue estos pasos.

  1. Para instalar Chromium, primero debes habilitar amazon-linux-extras, que forma parte de EPEL (paquetes adicionales para Enterprise Linux):

    sudo amazon-linux-extras install epel -y
    
  2. A continuación, instala Chromium:

    sudo yum install -y chromium
    

Ahora, Puppeteer puede iniciar Chromium para ejecutar tus pruebas. Si no habilitas EPEL y continúas instalando Chromium como parte de npm install, Puppeteer no podrá iniciar Chromium debido a que libatk-1.0.so.0 y muchos más paquetes no están disponibles.

Problemas de transpilación de código

Si estás usando un transpilador de JavaScript, como Babel o TypeScript, es posible que llamar a evaluate() con una función asíncrona no funcione. Esto se debe a que, si bien puppeteer usa Function.prototype.toString() para serializar funciones, los transpiladores podrían cambiar el código de salida de modo que no sea compatible con puppeteer.

Algunas soluciones alternativas para este problema son indicar al transpilador que no arruine el código; por ejemplo, que configures TypeScript para que use la última versión de ecma ("target": "es2018"). Otra solución alternativa podría ser usar plantillas de cadenas en lugar de funciones:

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