CI/CD con Newman y GitHub Actions: del comando local al pipeline automático
Newman + GitHub Actions: el mismo comando local ahora corre en cada push. Reporte HTML en GitHub Pages. Primer push exitoso, 150 assertions, 48s.
Nota para el lector
El post anterior parametrizó el CRUD con CSV y Newman. Ahora ese mismo comando corre solo en cada push. Si querés ver el reporte en vivo → cesarbeassuarez.github.io/postman-api-testing.
Este post es parte de la serie de Postman.
Contexto
Tenía Newman corriendo local. Abría PowerShell, navegaba a la carpeta, ejecutaba el comando, esperaba, revisaba el reporte HTML. Funcionaba.
Pero era manual. Cada vez que cambiaba algo en la colección, tenía que acordarme de correr Newman. Y si no lo hacía, el reporte quedaba desactualizado.
Ya hice esto con Selenium. En el post de CI/CD del qa-automation-lab armé un pipeline con GitHub Actions que corre los 96 tests en cada push, genera el reporte Allure y lo publica en GitHub Pages. Ese proceso me llevó +15 commits arreglando problemas reales de CI.
Con Newman la promesa era más simple: no hay Maven, no hay Java, no hay Chrome headless. Es Node.js, un npm install y un comando.
Quería ver si era tan simple como parecía.
El comando que ya funcionaba local
Este era mi comando en PowerShell, corriendo desde la carpeta newman/:
newman run "Serenity Demo - Auth + Customer API Flow.postman_collection.json" -e "serenity-demo.postman_environment.json" -d "create_customer_data.csv" --folder Auth --folder "CRUD Happy Path" -r htmlextra
Qué hace:
- Corre la colección exportada desde Postman
- Usa el environment de Serenity Demo
- Alimenta el CRUD con datos del CSV (6 escenarios)
- Ejecuta solo las carpetas Auth y CRUD Happy Path
- Genera un reporte HTML con
htmlextra
El reporte caía en newman/newman/ con un nombre largo autogenerado. Para GitHub Pages necesitaba que caiga en una ruta predecible.
El workflow
Creé .github/workflows/newman-tests.yml:
name: Newman API Tests
on:
push:
branches: [ main ]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Newman and HTML reporter
run: |
npm install -g newman
npm install -g newman-reporter-htmlextra
- name: Run Newman tests
working-directory: newman
run: |
newman run "Serenity Demo - Auth + Customer API Flow.postman_collection.json" \
-e "serenity-demo.postman_environment.json" \
-d "create_customer_data.csv" \
--folder Auth \
--folder "CRUD Happy Path" \
-r htmlextra \
--reporter-htmlextra-export ./report/index.html
- name: Upload report artifact
if: always()
uses: actions/upload-pages-artifact@v3
with:
path: newman/report
deploy:
needs: test
if: always() && needs.test.result != 'cancelled'
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
Comparación con el pipeline de Selenium
El pipeline de Selenium necesitaba:
- Java 17
- Maven
- Chrome headless instalado con
setup-chrome - Configurar locale español y resolución 1920×1080 vía CDP
- Generar el reporte Allure como paso separado
- Timeout de 30s porque CI es más lento que local
El de Newman necesita:
- Node.js
npm install -g newman newman-reporter-htmlextra- El mismo comando que corría local
Eso es todo. No hay browser, no hay driver, no hay build tool. Newman es un runner de terminal que hace requests HTTP. La complejidad está en la colección, no en el pipeline.
Lo que sí cambié respecto al comando local
Una cosa: el export del reporte.
Local, htmlextra generaba archivos con nombres como:
Serenity Demo - Auth + Customer API Flow-2026-03-22-04-32-07-899-0.html
Para GitHub Pages necesitaba un nombre fijo. Agregué:
--reporter-htmlextra-export ./report/index.html
Así el reporte siempre se llama index.html y GitHub Pages lo sirve directo en la raíz.
El if: always() y por qué importa
Dos líneas clave:
- name: Upload report artifact
if: always()
deploy:
needs: test
if: always() && needs.test.result != 'cancelled'
Si Newman encuentra assertions fallidas, retorna exit code 1. GitHub Actions interpreta eso como fallo y no ejecuta los steps siguientes.
Pero mi colección tiene 2 fallos intencionales (del post de data-driven testing). Sin if: always(), el reporte nunca se publica.
Con if: always():
- El step de upload corre aunque Newman falle
- El job de deploy corre aunque el job de test falle
- Pero no corre si alguien cancela el workflow manualmente (
!= 'cancelled')
El reporte se publica siempre. El pipeline se muestra rojo si hay fallos. Las dos cosas son correctas.
Primera corrida
Pusheé. Fui a Actions. El workflow arrancó.
48 segundos después:
test: rojo (exit code 1)deploy: verde- Reporte publicado en GitHub Pages
Entré a cesarbeassuarez.github.io/postman-api-testing:
- 6 iteraciones
- 150 assertions
- 2 failed tests (los intencionales)
- 72 requests
- 10.7s de ejecución
- Response time promedio: 129ms

Funcionó en el primer push.
Por qué dejé el pipeline rojo
Podría agregar || true al comando de Newman para que siempre retorne éxito. El pipeline se vería verde.
No lo hice.
Si un día Serenity Demo cae, o un endpoint cambia, o la colección se rompe por un cambio mío, quiero enterarme. Un pipeline verde con || true me oculta eso.
Los 2 fallos intencionales ya están explicados en el reporte HTML y en el README del repo. Quien entre al dashboard entiende qué falló y por qué.
Un pipeline rojo con explicación es más honesto que un pipeline verde que esconde información.

Los warnings de Node.js 20
Aparecieron dos warnings:
Node.js 20 actions are deprecated. The following actions are running on Node.js 20
and may not work as expected: actions/checkout@v4, actions/setup-node@v4...
Actions will be forced to run with Node.js 24 by default starting June 2nd, 2025.

No son errores. Las actions (checkout@v4, setup-node@v4, etc.) internamente usan Node.js 20 como runtime. GitHub está migrando a Node.js 24. Cuando actualicen las actions, los warnings desaparecen solos.
No afecta la ejecución. Newman corrió bien.
Dependencia externa: Serenity Demo
Hay una diferencia fundamental con el pipeline de Selenium.
En qa-automation-lab, los tests corren contra the-internet.herokuapp.com y SauceDemo. Son apps estables, siempre disponibles.
Acá los tests corren contra Serenity Demo, una app en Azure que no controlo. Si está caída o lenta, el pipeline falla.
No por un bug en mi colección. Por un problema de infraestructura externa.
Esto es un problema real de CI contra apps que no controlás. No tiene solución elegante: o aceptás que a veces falla por causas externas, o montás un mock server (que elimina el valor de testear contra una app real).
Por ahora lo acepto. Si en el futuro se vuelve inestable, evaluaré alternativas.
Estado actual
El repo postman-api-testing ahora tiene:
- Pipeline de GitHub Actions que corre Newman en cada push
- Reporte HTML publicado automáticamente en GitHub Pages
- 6 iteraciones, 150 assertions, 72 requests
- Reporte en vivo: cesarbeassuarez.github.io/postman-api-testing
La serie de Postman tiene pipeline. El item del roadmap en el README ya se puede tachar.
Comparando los dos pipelines
Armé CI/CD para dos repos distintos:
qa-automation-lab: Selenium + Java + TestNG + Allure + Chrome headless → +15 commitspostman-api-testing: Newman + htmlextra → funcionó en el primer push
La diferencia no es que uno sea mejor. Es que son problemas distintos.
Selenium necesita un browser real corriendo en un servidor. Eso implica drivers, resolución, locale, timeouts ajustados para CI. Cada variable es un punto de fallo.
Newman hace requests HTTP. No hay browser, no hay UI, no hay driver. La complejidad está en la colección y en la app externa, no en el pipeline.
Si tu automation es UI → prepárate para iterar el pipeline. Si tu automation es API → el pipeline es lo más simple de todo.