Automatizando pruebas de API con Newman desde GitHub Actions
🔍 Introducción
Automatizando pruebas de API con Newman desde GitHub Actions
🔍 Introducción
En equipos de desarrollo, las pruebas de API se ejecutan de forma manual desde Postman. Es útil en etapas iniciales, pero se convierte en un riesgo cuando el proyecto crece y los despliegues suelen ser más frecuentes.
Si cada cambio en el código depende de ejecutar la colección de forma manual, es posible que en ocasiones se pase por alto y los tests no se realicen.
En este artículo ejecutaremos una colección de Postman utilizando Newman directamente desde GitHub Actions, integrando las pruebas dentro de un pipeline de CI.
⚙️ ¿Qué vamos a construir?
- Creación de archivo YAML para ejecución de repositorio de test
- Colección de Postman
- Variable de entorno en Postman
🖥️ Tecnologías utilizadas
- Newman: Es un ejecutor de colecciones de Postman desde línea de comandos.
- Postman: Es una plataforma unificada para probar, documentar y supervisar API.
- GitHub Actions: Es una plataforma de integración y despliegue continuo (CI/CD) que permite automatizar flujos de trabajo de desarrollo de software.
- Docker: Es una plataforma de código abierto que permite empaquetar aplicaciones y sus dependencias dentro de contenedores.
🏗️ Diagrama de la solución

🧱 Estructura de repositorio de Newman
newman-tests/
├── .github/
│ └── workflows/
│ └── trigger-api.yaml # Configuración del pipeline de CI
├── docs/
│ └── index.html # Redirecciona el reporte a index (GitHub Pages)
├── postman/
│ └── collection.json # Colección de Postman con los request
│ └── environment.json # Variable utilizada por Postman
├── results/
│ └── run_04032026_101702 # Carpeta generada por cada ejecución
│ └── report.html # Configuración del pipeline de CI
├── scripts/
│ └── execute-tests.py # Script de Python que ejecuta Newman
├── .dockerignore # Exclusión de directorios, archivos
├── .gitignore # Exclusión de directorios, archivos para Git
├── docker-compose.yaml # Definición de servicios
├── Dockerfile # Configuración para construir imagen
├── README.md # Información del proyecto
├── requirements.txt # Dependencias del proyecto
🔧 Configuración inicial
- Creación de entorno virtual
python -m venv venv
En Linux: source venv/bin/activate
En Windows: venv\Scripts\activate
Para desactivar el ambiente, utilizar "deactivate"
- Instalación de dependencias
# Instalación de newman
npm install -g newman
# Reporteria
npm install -g newman-reporter-htmlextra
En el archivo trigger-api.yaml, copiar y pegar el siguiente código
name: Postman API Tests
on:
workflow_dispatch:
inputs:
base_url:
description: 'Base URL de la API'
required: false
default: '**URL DE RENDER**'
permissions:
contents: write
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout código
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Instalar Newman y reporter
run: |
npm install -g newman
npm install -g newman-reporter-htmlextra
- name: Verificar instalación
run: newman -v
- name: Ejecutar tests de Postman
id: run_tests
continue-on-error: true
env:
BASE_URL: ${{ github.event.inputs.base_url || 'http://localhost:8000' }}
TZ: America/Guatemala
run: python scripts/execute-tests.py --base-url=$BASE_URL
- name: Obtener timestamp de la ejecución
id: get_timestamp
if: always()
run: |
if [ -f docs/.last_run.txt ]; then
TIMESTAMP=$(cat docs/.last_run.txt)
else
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
fi
echo "timestamp=$TIMESTAMP" >> $GITHUB_OUTPUT
- name: Subir reporte como artefacto
if: always()
uses: actions/upload-artifact@v4
with:
name: run_${{ steps.get_timestamp.outputs.timestamp }}
path: results/run_${{ steps.get_timestamp.outputs.timestamp }}/
retention-days: 30
- name: 💾 Commit report to repository
if: always()
run: |
git config user.name "github-actions"
git config user.email "actions@github.com"
git add docs/
if git diff --staged --quiet; then
echo "No changes to commit"
else
git commit -m "Update Newman report - run ${{ github.run_number }}"
git push
fi
- name: 🧹 Limpiar Docker
if: always()
run: docker system prune -f
Colocar el siguiente script en las request de Postman antes de exportarlas
- Pestaña de Scripts
# Primer Request
// Validaciones básicas
// Primero parsear la respuesta
const json = pm.response.json();
// Status correcto
pm.test("Status code es 201 Created", function () {
pm.response.to.have.status(201);
});
// Tiempo de respuesta
pm.test("Respuesta menor a 500ms", function () {
pm.expect(pm.response.responseTime).to.be.below(500);
});
// Formato JSON
pm.test("Response es JSON", function () {
pm.response.to.be.json;
});
// Validaciones de estructura
// Luego hacer las validaciones con la estructura correcta
pm.test("Tiene exactamente los campos esperados", function () {
const expectedKeys = ["id", "title", "description", "created_at", "updated_at"];
const actualKeys = Object.keys(json);
pm.expect(actualKeys.sort()).to.eql(expectedKeys.sort());
});
// Validación de timestamps
pm.test("Created tiene formato fecha", function () {
pm.expect(new Date(json.created_at).toString()).not.eql("Invalid Date");
});
pm.test("Updated tiene formato fecha", function () {
pm.expect(new Date(json.updated_at).toString()).not.eql("Invalid Date");
});
// Validación de negocio
pm.test("ID es número", function () {
pm.expect(json.id).to.be.a("number");
});
pm.test("Title no está vacío", function () {
pm.expect(json.title.length).to.be.above(0);
});
// Validación de schema
const schema = {
"type": "object",
"required": ["id", "title", "description", "created_at", "updated_at"],
"properties": {
"id": { "type": "number" },
"title": { "type": "string" },
"description": { "type": "string" },
"created_at": { "type": "string" },
"updated_at": { "type": "string" }
}
};
pm.test("Schema válido", function () {
pm.response.to.have.jsonSchema(schema);
});
// Validación de Headers
pm.test("Content-Type correcto", function () {
pm.expect(pm.response.headers.get("Content-Type")).to.include("application/json");
});
# Segundo Request
pm.test("Error por campo obligatorio", function () {
pm.response.to.have.status(422);
});
# Tercer Request
pm.test("No permite title vacío", function () {
pm.response.to.have.status(422);
});
# Cuarto Request
pm.test("Tipo inválido", function () {
pm.response.to.have.status(422);
});
Crear la variable de entorno para utilizar en Postman
- La variable debe llamarse “base_url”, siempre en minúsculas
- Se utiliza de esa forma por buenas prácticas
En el archivo execute-tests.py, copiar y pegar el siguiente código
import time
import subprocess
import os
import sys
import shutil
import platform
# Creación de timestamp
timestamp = time.strftime("%Y%m%d_%H%M%S")
# Definir directorios
results_dir = f"results/run_{timestamp}"
docs_dir = "docs"
os.makedirs(results_dir, exist_ok=True)
os.makedirs(docs_dir, exist_ok=True)
# BASE_URL por defecto (prioridad: CLI > ENV > default)
base_url = os.getenv("BASE_URL", "** URL DE RENDER **")
# Leer argumento desde CLI (CI)
for arg in sys.argv:
if arg.startswith("--base-url="):
base_url = arg.split("=", 1)[1]
print(f"🌐 Ejecutando pruebas contra: {base_url}")
# Definir rutas de archivos de salida
report_file = f"{results_dir}/report.html"
# Ejecutar Newman
command = [
"newman", "run",
"postman/Endpoints Fast API.postman_collection.json",
"-e", "postman/environment.json",
"--env-var", f"base_url={base_url}",
"--reporters", "htmlextra",
"--reporter-htmlextra-export", report_file,
"--timeout-request", "10000", # 10 segundos por request
"--delay-request", "500" # 500ms entre requests
]
# En Windows, usar shell=True para encontrar newman.cmd
is_windows = platform.system() == "Windows"
result = subprocess.run(command, capture_output=True, text=True, shell=is_windows)
print(result.stdout)
if result.stderr:
print(result.stderr)
# Almacenar el timestamp siempre (incluso si Newman falla)
with open(f"{docs_dir}/.last_run.txt", "w") as f:
f.write(timestamp)
# Verificar que el reporte fue generado
if not os.path.exists(report_file):
print(f"❌ Error: No se generó el archivo de reporte en {report_file}")
print("Verifica que Newman se ejecutó correctamente")
# Crear un reporte básico de error
error_html = f"""<!DOCTYPE html>
<html>
<head><title>Error en ejecución</title></head>
<body>
<h1>Error en la ejecución de Newman</h1>
<p>No se pudo generar el reporte. Código de salida: {result.returncode}</p>
<pre>{result.stdout}</pre>
<pre>{result.stderr}</pre>
</body>
</html>"""
with open(report_file, "w") as f:
f.write(error_html)
# Copiar a docs/ para GitHub Pages (siempre, incluso si falló)
shutil.copy(report_file, f"{docs_dir}/index.html")
if result.returncode != 0:
print(f"❌ Newman falló con código de salida: {result.returncode}")
print(f"📁 Resultados guardados en: {results_dir}")
print("📄 Report publicado en GitHub Pages")
sys.exit(result.returncode)
# Mensajes informativos
print("✅ Newman ejecución completada correctamente")
print(f"📁 Resultados guardados en: {results_dir}")
print("📄 Report publicado correctamente en GitHub Pages")
En el archivo .dockerignore, copiar y pegar el siguiente código
# Git
.git
.gitignore
.github
# Python
__pycache__
*.py[cod]
*$py.class
*.so
.Python
*.egg-info
.installed.cfg
*.egg
# Virtual Environment
venv/
ENV/
env/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# Results (se generan en runtime)
results/
# Documentation
README.md
docs/
# Docker
Dockerfile
docker-compose.yml
.dockerignore
# OS
.DS_Store
Thumbs.db
# Logs
*.log
En el archivo .gitignore, copiar y pegar el siguiente código
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual Environment
venv/
ENV/
env/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# Testing
.pytest_cache/
.coverage
htmlcov/
*.cover
# OS
.DS_Store
Thumbs.db
# Logs
*.log
En el archivo docker-compose.yaml, copiar y pegar el siguiente código
version: '3.8'
services:
postman-tests:
build: .
container_name: postman-tests
environment:
- base_url=${base_url:-** URL DE RENDER **}
volumes:
- ./results:/app/results
- ./docs:/app/docs
command: python3 scripts/execute-tests.py
En el archivo Dockerfile, copiar y pegar el siguiente código
# Usar imagen base de Node.js (Newman requiere Node)
FROM node:18-alpine
# Instalar Python para el script de ejecución
RUN apk add --no-cache python3 py3-pip
# Crear directorio de trabajo
WORKDIR /app
# Instalar Newman y el reporter
RUN npm install -g newman newman-reporter-htmlextra
# Copiar archivos del proyecto
COPY postman/ ./postman/
COPY scripts/ ./scripts/
COPY docs/ ./docs/
# Crear directorios necesarios
RUN mkdir -p results docs
# Variable de entorno por defecto
ENV base_url=** URL DE RENDER **
# Comando por defecto
CMD ["python3", "scripts/execute-tests.py"]
✅ Ejecución de la colección desde Postman
- Request creación de tareas exitosa

- Request creación de tareas sin campo title

- Request creación de tareas con title vacío

- Request creación de tareas con campo invalido

✅ Ejecución desde consola con Newman
- Al ejecutarlo desde la CLI local utilizaremos la URL de Render

- Abrimos el reporte HTML y validamos que la ejecución tenga todas las pruebas passed

✅ Ejecución desde GitHub Actions
- Dar clic en Actions
- Dar clic en Postman API Tests
- Dar clic en Run workflow

- Al finalizar la ejecución se publicará el reporte en GitHub Pages

- Ingresar a la ejecución para descargar el artefacto y analizarlo localmente

- Ingresar a GitHub Pages
- https://usuario.github.io/repositorio/

Con este ejemplo práctico pueden seguir explorando sobre pruebas de API.
메타데이터
- post_id
- 33c1b2c5c2d8
- slug
- automatizando-pruebas-de-api-con-newman-desde-github-actions-33c1b2c5c2d8
- url
- https://medium.com/@dmelchornatplat/automatizando-pruebas-de-api-con-newman-desde-github-actions-33c1b2c5c2d8
- canonical_url
- https://medium.com/@dmelchornatplat/automatizando-pruebas-de-api-con-newman-desde-github-actions-33c1b2c5c2d8
- author_url
- https://medium.com/@dmelchornatplat
- status
- ok
- fetched_at
- 2026-07-13 06:23:13