Siempre me ha gustado ver cómo se ejecutan (y pasan) mis pruebas unitarias. Son rápidas y pasar las pruebas me da la seguridad de que mis piezas individuales se comportan como se supone que deben hacerlo. Por el contrario, a menudo me costaba priorizar las pruebas de extremo a extremo para el navegador porque escribirlas y ejecutarlas era extremadamente lento.
Afortunadamente, las herramientas para realizar pruebas de extremo a extremo en el navegador han mejorado y son mucho más rápidas con el paso de los años. Y con una configuración de navegador sin interfaz gráfica, puedo ejecutar mis pruebas de navegador como parte de mi integración continua.
Recientemente, me encontré con esta publicación del blog de Heroku que habla sobre la automatización de las pruebas en el navegador con Chrome sin interfaz gráfica dentro de Heroku CI. Heroku tiene un paquete de compilación que instala Chrome sin interfaz gráfica, que puedes invocar para tus pruebas en el flujo de trabajo de CI.
La configuración de ejemplo de la publicación del blog fue una aplicación React probada con Puppeteer y Jest . Es un gran comienzo... pero ¿qué pasa si uso Playwright en lugar de Puppeteer? ¿Es posible?
Decidí investigar y resultó que sí, ¡también puedes hacer esto con Playwright! Así que capté los pasos que necesitarías para ejecutar las pruebas de Playwright en el navegador Chrome sin interfaz gráfica que se usa en Heroku CI. En esta publicación, te guiaré por los pasos necesarios para configurarlo.
Las pruebas de extremo a extremo capturan cómo interactúan los usuarios con tu aplicación en un navegador, lo que permite validar flujos de trabajo completos. Playwright hace que este proceso sea bastante sencillo con las pruebas en Chrome, Firefox y Safari. Por supuesto, ejecutar una serie completa de pruebas de navegador en CI es bastante pesado, por lo que el modo sin interfaz gráfica resulta de gran ayuda.
El paquete de compilación Chrome for Testing de Heroku instala Chrome en una aplicación de Heroku, de modo que puedes ejecutar tus pruebas de Playwright en Heroku CI con una configuración realmente liviana.
Como estaba probando esto, bifurqué el repositorio de GitHub al que se hizo referencia originalmente en la publicación del blog de Heroku. La aplicación era una aplicación React simple con un enlace, una entrada de texto y un botón de envío. Hubo tres pruebas:
Bastante simple. Ahora, solo necesitaba cambiar el código para usar Playwright en lugar de Puppeteer y Jest. Ah, y también quería usar pnpm en lugar de npm. Aquí hay un enlace a mi repositorio de GitHub bifurcado .
Repasemos los pasos que seguí para modificar el código. Empecé con mi repositorio bifurcado, idéntico al repositorio heroku-examples
.
Quería usar pnpm en lugar de npm (es una preferencia personal). Esto fue lo que hice primero:
~/project$ corepack enable pnpm ~/project$ corepack use pnpm@latest Installing [email protected] in the project… … Progress: resolved 1444, reused 1441, downloaded 2, added 1444, done … Done in 14.4s ~/project$ rm package-lock.json ~/project$ pnpm install # just to show everything's good Lockfile is up to date, resolution step is skipped Already up to date Done in 1.3s
A continuación eliminé Puppeteer y Jest, y agregué Playwright.
~/project$ pnpm remove \ babel-jest jest jest-puppeteer @testing-library/jest-dom ~/project$ $ pnpm create playwright Getting started with writing end-to-end tests with Playwright: Initializing project in '.' ✔ Do you want to use TypeScript or JavaScript? · JavaScript ✔ Where to put your end-to-end tests? · tests ✔ Add a GitHub Actions workflow? (y/N) · false ✔ Install Playwright browsers (can be done manually via 'pnpm exec playwright install')? (Y/n) · false ✔ Install Playwright operating system dependencies (requires sudo / root - can be done manually via 'sudo pnpm exec playwright install-deps')? (y/N) · false Installing Playwright Test (pnpm add --save-dev @playwright/test)… … Installing Types (pnpm add --save-dev @types/node)… … Done in 2.7s Writing playwright.config.js. Writing tests/example.spec.js. Writing tests-examples/demo-todo-app.spec.js. Writing package.json.
También eliminé la sección de configuración de Jest de package.json
.
Puedes ejecutar tus pruebas de Playwright en Chrome, Firefox y Safari. Como me concentré en Chrome, eliminé los demás navegadores de la sección projects
del archivo playwright.config.js
generado:
/* Configure projects for major browsers */ projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, // { // name: 'firefox', // use: { ...devices['Desktop Firefox'] }, // }, // // { // name: 'webkit', // use: { ...devices['Desktop Safari'] }, // }, ], …
El código original tenía un archivo de prueba de Puppeteer en src/tests/puppeteer.test.js
. Moví ese archivo a tests/playwright.spec.js
. Luego, actualicé la prueba para usar las convenciones de Playwright, que se asignaron de manera bastante clara. El nuevo archivo de prueba se veía así:
const ROOT_URL = 'http://localhost:8080'; const { test, expect } = require('@playwright/test'); const inputSelector = 'input[name="name"]'; const submitButtonSelector = 'button[type="submit"]'; const greetingSelector = 'h5#greeting'; const name = 'John Doe'; test.beforeEach(async ({ page }) => { await page.goto(ROOT_URL); }); test.describe('Playwright link', () => { test('should navigate to Playwright documentation page', async ({ page }) => { await page.click('a[href="https://playwright.dev/"]'); await expect(page.title()).resolves.toMatch('| Playwright'); }); }); test.describe('Text input', () => { test('should display the entered text in the text input', async ({ page }) => { await page.fill(inputSelector, name); // Verify the input value const inputValue = await page.inputValue(inputSelector); expect(inputValue).toBe(name); }); }); test.describe('Form submission', () => { test('should display the "Hello, X" message after form submission', async ({ page }) => { const expectedGreeting = `Hello, ${name}.`; await page.fill(inputSelector, name); await page.click(submitButtonSelector); await page.waitForSelector(greetingSelector); const greetingText = await page.textContent(greetingSelector); expect(greetingText).toBe(expectedGreeting); }); });
start-server-and-test
, utilizar en su lugar el servidor web de Playwright Para probar mi aplicación React, primero tuve que ponerla en marcha (en http://localhost:8080
) en un proceso independiente y luego pude ejecutar mis pruebas. Esto sería así independientemente de si usara Puppeteer o Playwright. Con Puppeteer, el ejemplo de Heroku utilizó el paquete start-server-and-test
.
Sin embargo, puedes configurar Playwright para que inicie la aplicación antes de ejecutar las pruebas. ¡Esto es bastante conveniente!
Eliminé start-server-and-test
de mi proyecto.
~/project$ pnpm remove start-server-and-test
En playwright.config.js
, descomenté la sección webServer en la parte inferior, modificándola para que se vea así:
/* Run your local dev server before starting the tests */ webServer: { command: 'pnpm start', url: 'http://127.0.0.1:8080', reuseExistingServer: !process.env.CI, },
Luego, eliminé el script test:ci
del archivo package.json
original. En su lugar, mi script de prueba se veía así:
"scripts": { … "test": "playwright test --project=chromium --reporter list" },
Playwright instala los binarios más recientes del navegador para usarlos en sus pruebas. Por lo tanto, en mi máquina local, necesitaba que Playwright instalara su versión de Chromium.
~/project$ pnpm playwright install chromium Downloading Chromium 130.0.6723.31 (playwright build v1140) from https://playwright.azureedge.net/builds/chromium/1140/chromium-linux.zip 164.5 MiB [====================] 100%
Nota: El paquete de compilación de Chrome para pruebas en Heroku instala el navegador que usaremos para las pruebas. Configuraremos nuestra CI para que Playwright use ese navegador en lugar de invertir tiempo y recursos en instalar el suyo propio.
Con eso, ya estaba todo listo. Era hora de probar mis pruebas localmente.
~/project$ pnpm test > playwright test --project=chromium --reporter list Running 3 tests using 3 workers ✓ 1 [chromium] > playwright.spec.js:21:3 > Text input > should display the entered text in the text input (911ms) ✘ 2 [chromium] > playwright.spec.js:14:3 > Playwright link > should navigate to Playwright documentation page (5.2s) ✓ 3 [chromium] > playwright.spec.js:31:3 > Form submission > should display the "Hello, X" message after form submission (959ms) ... - waiting for locator('a[href="https://playwright.dev/"]') 13 | test.describe('Playwright link', () => { 14 | test('should navigate to Playwright documentation page', async ({ page }) => { > 15 | await page.click('a[href="https://playwright.dev/"]'); | ^ 16 | await expect(page.title()).resolves.toMatch('| Playwright'); 17 | }); 18 | });
¡Ah! Así es. Modifiqué mi prueba para esperar que el enlace en la aplicación me lleve a la documentación de Playwright en lugar de a la de Puppeteer. Necesitaba actualizar src/App.js
en la línea 19:
<Link href="https://playwright.dev/" rel="noopener"> Playwright Documentation </Link>
Ahora, era el momento de ejecutar las pruebas nuevamente…
~/project$ pnpm test > playwright test --project=chromium --reporter list Running 3 tests using 3 workers ✓ 1 [chromium] > playwright.spec.js:21:3 > Text input > should display the entered text in the text input (1.1s) ✓ 2 [chromium] > playwright.spec.js:14:3 > Playwright link > should navigate to Playwright documentation page (1.1s) ✓ 3 [chromium] > playwright.spec.js:31:3 > Form submission > should display the "Hello, X" message after form submission (1.1s) 3 passed (5.7s)
¡Las pruebas han pasado! Ahora es el momento de pasar a Heroku CI.
Seguí las instrucciones de la publicación del blog de Heroku para configurar mi aplicación en una canalización CI de Heroku.
En Heroku, creé una nueva canalización y la conecté a mi repositorio de GitHub bifurcado.
A continuación, agregué mi aplicación al entorno de pruebas.
Luego, fui a la pestaña Pruebas y hice clic en Habilitar Heroku CI .
Finalmente, modifiqué el archivo app.json
para eliminar el script de prueba que estaba configurado para llamar npm test:ci
. Ya había eliminado el script test:ci
de mi archivo package.json
. El script test
en package.json
era ahora el que se debía usar y Heroku CI lo buscaría de forma predeterminada.
Mi archivo app.json
, que se aseguró de usar el paquete de compilación de Chrome para pruebas, se veía así:
{ "environments": { "test": { "buildpacks": [ { "url": "heroku-community/chrome-for-testing" }, { "url": "heroku/nodejs" } ] } } }
Envié mi código a GitHub y esto activó una ejecución de prueba en Heroku CI.
La prueba falló, pero no me preocupé. Sabía que habría que realizar algunas configuraciones en Playwright.
Investigando en el registro de pruebas encontré esto:
Error: browserType.launch: Executable doesn't exist at /app/.cache/ms-playwright/chromium-1140/chrome-linux/chrome
Playwright buscaba la instancia del navegador Chrome. Podría instalarla con el comando playwright install chromium
como parte de mi configuración de prueba de CI. Pero eso anularía el propósito de tener el paquete de compilación de Chrome para pruebas. Chrome ya estaba instalado; solo necesitaba señalarlo correctamente.
Al volver a mirar mi registro de configuración de pruebas para Heroku, encontré estas líneas:
Installed Chrome dependencies for heroku-24 Adding executables to PATH /app/.chrome-for-testing/chrome-linux64/chrome /app/.chrome-for-testing/chromedriver-linux64/chromedriver Installed Chrome for Testing STABLE version 130.0.6723.91
Entonces, el navegador que quería usar estaba en /app/.chrome-for-testing/chrome-linux64/chrome
. Solo necesitaba que Playwright lo buscara allí.
Nota: Si no te interesan los detalles esenciales, puedes omitir esta sección y simplemente copiar el archivo app.json
completo más abajo. Esto debería darte lo que necesitas para comenzar a usar Playwright en Heroku CI.
En la documentación de Playwright, descubrí que se puede configurar una variable de entorno que le indica a Playwright si se utilizó una ubicación personalizada para todas las instalaciones de su navegador . Esa variable de entorno es PLAYWRIGHT_BROWSERS_PATH
. Decidí comenzar por ahí.
En app.json
, establezco una variable env
como esta:
{ "environments": { "test": { "env": { "PLAYWRIGHT_BROWSERS_PATH": "/app/.chrome-for-testing" }, ...
Subí mi código a GitHub para ver qué pasaría con mis pruebas en CI.
Como era de esperar, volvió a fallar. Sin embargo, el error de registro mostró lo siguiente:
Error: browserType.launch: Executable doesn't exist at /app/.chrome-for-testing/chromium-1140/chrome-linux/chrome
Eso me dejó bastante cerca. Decidí que haría esto:
mkdir -p "$PLAYWRIGHT_BROWSERS_PATH/chromium-1140/chrome-linux"
ln -s \ $PLAYWRIGHT_BROWSERS_PATH/chrome-linux64/chrome \ $PLAYWRIGHT_BROWSERS_PATH/chromium-1140/chrome-linux/chrome
Sin embargo, me preocupaba si esto sería a prueba de futuro. Con el tiempo, Playwright usaría una nueva versión de Chromium y ya no se vería como una carpeta chromium-1140
. ¿Cómo podía averiguar dónde buscaría Playwright?
Fue entonces cuando descubrí que se puede realizar una prueba de instalación del navegador.
~/project$ pnpm playwright install chromium --dry-run browser: chromium version 130.0.6723.31 Install location: /home/alvin/.cache/ms-playwright/chromium-1140 Download url: https://playwright.azureedge.net/builds/chromium/1140/chromium-linux.zip Download fallback 1: https://playwright-akamai.azureedge.net/builds/chromium/1140/chromium-linux.zip Download fallback 2: https://playwright-verizon.azureedge.net/builds/chromium/1140/chromium-linux.zip
La línea “Ubicación de instalación” fue crucial. Y, si configuramos PLAYWRIGHT_BROWSERS_PATH
, esto es lo que veríamos:
~/project$ PLAYWRIGHT_BROWSERS_PATH=/app/.chrome-for-testing \ pnpm playwright install chromium --dry-run browser: chromium version 130.0.6723.31 Install location: /app/.chrome-for-testing/chromium-1140 ...
Eso es lo que quiero. Con un poco de magia awk
, hice esto:
~/project$ CHROMIUM_PATH=$( \ PLAYWRIGHT_BROWSERS_PATH=/app/.chrome-for-testing \ pnpm playwright install --dry-run chromium \ | awk '/Install location/ {print $3}' ) ~/project$ echo $CHROMIUM_PATH /app/.chrome-for-testing/chromium-1140
Una vez resuelto todo esto, solo necesitaba agregar un script test-setup
a app.json
. Como PLAYWRIGHT_BROWSERS_PATH
ya está configurado en env
, mi script sería un poco más simple. Este fue mi archivo app.json
final:
{ "environments": { "test": { "env": { "PLAYWRIGHT_BROWSERS_PATH": "/app/.chrome-for-testing" }, "buildpacks": [ { "url": "heroku-community/chrome-for-testing" }, { "url": "heroku/nodejs" } ], "scripts": { "test-setup": "CHROMIUM_PATH=$(pnpm playwright install --dry-run chromium | awk '/Install location/ {print $3}'); mkdir -p \"$CHROMIUM_PATH/chrome-linux\"; ln -s $PLAYWRIGHT_BROWSERS_PATH/chrome-lin ux64/chrome $CHROMIUM_PATH/chrome-linux/chrome" } } } }
Explicaré brevemente lo que hace test-setup
:
Teniendo en cuenta PLAYWRIGHT_BROWSERS_PATH
, se utiliza playwright install -- dry-run
con awk
para determinar la carpeta raíz donde Playwright buscará el navegador Chrome. Establece este valor para la variable CHROMIUM_PATH
.
Crea una nueva carpeta (y cualquier carpeta principal necesaria) en CHROMIUM_PATH/chrome-linux
, que es la carpeta real donde Playwright buscará el binario chrome
.
Crea un enlace simbólico en esa carpeta para que Chrome apunte a la instalación del paquete de compilación Heroku de Chrome ( /app/.chrome-for-testing/chrome-linux64/chrome
).
Con mi archivo app.json
actualizado, Playwright debería poder usar la instalación de Chrome desde el paquete de compilación. Era hora de ejecutar las pruebas una vez más.
¡Éxito!
El script test-setup
se ejecutó como se esperaba.
Playwright pudo acceder al binario chrome
y ejecutar las pruebas, que pasaron.
Las pruebas de extremo a extremo para mis aplicaciones web se están volviendo menos complicadas, por lo que le estoy dando cada vez más prioridad. En los últimos días, eso ha significado también usar más Playwright. Es flexible y rápido. Y ahora que he hecho el trabajo (¡para mí y para ti !) para ponerlo en funcionamiento con el paquete de compilación Chrome for Testing en Heroku CI, puedo comenzar a desarrollar mis suites de pruebas de automatización del navegador una vez más.
El código para este tutorial está disponible en mi repositorio de GitHub .
¡Feliz codificación!