Testing funcional con Puppeteer

1
12935

En este tutorial veremos las funciones más básicas de la herramienta Puppeteer y la integraremos con Jest para crear una suite de testing funcional.

¿Qué es Puppeteer?

Puppeteer es una herramienta, desarrollada principalmente por Google, que nos permite utilizar un navegador Chromium/Chrome headless a través del protocolo DevTools. Se ejecuta sobre NodeJS y se instala a través de npm.

Headless
Un navegador headless se ejecuta dentro de un proceso del terminal. Por lo que no es necesario que haya un navegador «real» abierto.

DevTools
El protocolo que permite a Puppeteer instrumentar, inspeccionar y debuggear Chromium.
Este protocolo expone una API que permite utilizar el navegador desde fuera del mismo.

Gráfico que muestra la interacción entre Puppeteer, el protocolo Devtools, chrome, y una aplicación web

Puppeteer puede usarse para:

  • Automatización de procesos web.
  • Testing de extensiones de Chrome.
  • Testing funcional de aplicaciones web.

Este último será el foco de este tutorial.

Instalación

Puppeteer se instala como una dependencia en un proyecto de npm.

npm install puppeteer 
# o yarn add puppeteer

Hay que tener en cuenta que la librería viene empaquetada con un ejecutable de Chromium. Por lo que ocupa bastante espacio en el disco (~280 MB).

Si no queremos que se descargue el ejecutable, podemos instalar puppeteer-core, que solo incluye la librería de Puppeteer. En este caso deberemos especificar la ubicación del ejecutable que utilizará.

Uso de la API

En esta sección crearemos algunos scripts sencillos con Puppeteer.
Cada ejemplo estará en un archivo JavaScript que podemos ejecutar con Node:

node nombre_fichero.js argumento1 argument2

Creando un navegador

Todos los ejemplos tienen esta estructura:

// Importamos la librería de Puppeteer.
const puppeteer = require("puppeteer");

// Obtenemos el primer argumento del comando.
const [url] = process.argv.slice(2);

(async () => {
  // Lanzamos un nuevo navegador.
  const browser = await puppeteer.launch();
  // Abrimos una nueva página.
  const page = await browser.newPage();

  // Vamos a la URL.
  await page.goto(url);

  // Cerramos la página y el navegador.
  await page.close();
  await browser.close();
})();

Si ejecutamos este script con node script.js https://google.com , veremos que aparentemente no hace nada. Esto es porque actualmente se está ejecutando en modo headless.

Ahora, si añadimos estos parámetros a la función de launch:

const browser = await puppeteer.launch({
  headless: false, // Especificamos que el navegador no es headless
  slowMo: 1000 // Añadimos un delay de 1 segundo entre cada comando.
});

Podemos ver el proceso en tiempo real y con un delay de un segundo entre comandos.

Obtener el título de la página

const puppeteer = require("puppeteer");

const [url] = process.argv.slice(2);

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.goto(url);

  // "page" contiene muchas funciones
  // que nos permiten obtener información de la página.
  // Prueba alguna!
  const title = await page.title();
  console.log("Page title: " + title);

  await browser.close();
})();

Si ejecutamos este script con node script.js https://google.com  deberíamos tener esta respuesta:

Page title: Google

Modificando el Viewport

Hasta ahora, estamos utilizando el Viewport por defecto. Si queremos modificarlo para, digamos, simular un dispositivo móvil, podemos hacerlo con:

await page.setViewport({
  width: 1080,
  height: 1920
});

Esto daría al Viewport una resolución de 1080p.

También existe la posibilidad de sobrescribir el Viewport por defecto con:

const browser = await puppeteer.launch({
  defaultViewport: {
    width: 1080,
    height: 1920
  }
});

Y si queremos probar sobre un dispositivo específico, existe el objeto devices dentro de puppeteer que contiene muchos dispositivos:

puppeteer.devices["LG Optimus L70 landscape"]

Capturando la pantalla

Una vez en una página, podemos usar la función screenshot para capturar la página en una imagen y guardarla en el disco:

await page.screenshot({ "/directorio/nombre_fichero.png", type: "png" });

evaluate()

La función evaluate nos permite ejecutar código en el contexto de la página del navegador de Puppeteer.

Con este código obtenemos el HTML de la página:

await page.screenshot({ "/directorio/nombre_fichero.png", type: "png" });

Selectores

$  y $$  funcionan como document.querySelector y document.querysSelectorAll  respectivamente. Nos devuelven la referencia de los elementos seleccionados.

Este código devuelve la referencia al primer enlace y las referencias a todos los parrafos respectivamente:

const firstLink = await page.$("a");
const allParagraphs = await page.$$("p");

$eval  y $$eval se parecen a los anteriores, pero en lugar de devolver las referencias, las pasan como parámetro al callback provisto:

Este código devuelve el texto del primer enlace y todos los párrafos respectivamente.

const firstLinkText = await page.$eval("a", link => link.textContent);
const allParagraphsText = await page.$$eval("p", links =>
  links.map(link => link.textContent)
);

Click

Para hacer click sobre un elemento, tenemos varias opciones disponibles:

// La función click de page se encarga de seleccionar y clickar
await page.click("selector");

// Podemos seleccionar y clickar por separado
const element = await page.$("selector");
await element.click()

// También podemos utilizar click() en el contexto de la página
await page.$eval("selector", element => element.click());

Por lo general, la primera opción suele ser la más robusta.

Keyboard

En el siguiente ejemplo, se introducen las credenciales en un formulario de login:

// Seleccionamos del input de usuario
const username = await page.$("[name=username]");

// Se hace focus sobre el elemento
await username.focus();

// Simulamos el teclado
await page.keyboard.type("the user name");

// Pasamos el focus al siguiente elemento
await page.keyboard.press("Tab");

// Simulamos el teclado
await page.keyboard.type("the password");

// Pulsamos enter
await page.keyboard.press("Enter");

Si quieres aprender en profundidad sobre la API de Puppeteer, échale un vistazo a la documentación oficial (pptr.dev).

Integración con Jest

Para integrar Puppeteer con Jest, utilizaremos la librería jest-puppeteer que se encarga de conectar el navegador con la ejecución de los tests. Para instalar las dependencias:

npm install jest-puppeteer puppeteer jest
# o yarn add jest-puppeteer puppeteer jest

En la carpeta raíz del proyecto, creamos los archivos de configuración jest.config.js  y jest-puppeteer.config.js :

module.exports = {
  verbose: true,
  preset: "jest-puppeteer"
};
module.exports = {
  launch: {
    dumpio: true,
    // Aquí podemos introducir los argumentos que normalmente irían en la función launch()
    slowMo: 1
  }
};

Y en el package.json  se configura:

// ...
"scripts": {
  "test": "jest"
}
// ...

jest-puppeteer  se encarga de lanzar el navegador y la página por nosotros y expone un browser y page  global.

Nuestro primer test con Puppeteer probará que el título de google.com  es el correcto. En google-title.test.js:

describe("Google Title", () => {
  test("The title is 'Google'", async () => {
    const expected = "Google";
    await page.goto("https://google.com");

    const actual = await page.title();

    expect(actual).toEqual(expected);
  });
});

Si ahora ejecutamos los tests, veremos su resultado:

npm test

Cuando la suite de tests acaba, se nos presenta un resumen.

PASS  src/tests/google-title.test.js
  Google Title
    ✓ the title is 'Google' (12ms)
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.418s, estimated 1s

En este tutorial hemos visto las funciones básicas de Puppeteer y cómo integrarla con Jest para nuestros tests funcionales.

1 COMENTARIO

  1. evaluate()
    La función evaluate nos permite ejecutar código en el contexto de la página del navegador de Puppeteer.

    Con este código obtenemos el HTML de la página:

    await page.screenshot({ «/directorio/nombre_fichero.png», type: «png» });

    Creo que page.screenshot no tiene nada que ver con evaluate()… no?

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad