Data-driven testing con Newman: CRUD completo alimentado por CSV

Postman cobra por data files en el Runner. Newman lo hace gratis. CSV con 6 escenarios, CRUD parametrizado, assertions condicionales y reportes HTML.

Newman HTML Dashboard mostrando 6 iteraciones, 150 assertions, 2 failed tests, 72 requests totales, duración 31.1 segundos y response time promedio de 332ms
El dashboard de Newman después de correr las 6 iteraciones del CSV. 150 assertions, 2 fallos (son intencionales), 31 segundos de ejecución. Todo desde la terminal, sin plan pago de Postman.

Nota para el lector

El schema validaba la estructura. Ahora los datos vienen de un archivo externo y el CRUD se adapta a cada caso. Cuando el Create falla, el resto se salta solo.

Este post es parte de la serie de API Testing con Postman sobre Serenity Demo (https://demo.serenity.is/Account/Login/?ReturnUrl=%2F). Los posts anteriores cubren el análisis con DevTools, la construcción de requests, el manejo del CSRF token, assertions con pm.test, testing negativo del login, el debugging del Runner, CRUD completo, y schema validation con Ajv.

Contexto

Hasta ahora, todos los datos del CRUD estaban hardcodeados. El Create siempre mandaba "CESBS", "CesarBeasSuarez Industries", "Cesar Beas Suarez". Retrieve, Update, Delete — todo apuntaba a "CESBS". Si quería probar con otros datos, tenía que cambiar el body a mano, correr, cambiar de nuevo, correr de nuevo. Eso no escala.

Request POST Create Customer en Postman con body JSON usando valores fijos como CustomerID CESBS, CompanyName CesarBeasSuarez Industries y ContactName Cesar Beas Suarez
Antes de parametrizar con variables, el Create usaba datos hardcodeados. Este request fue el punto de partida para entender qué campos necesita la API.

Quería probar con datos distintos en cada corrida: clientes válidos, campos vacíos, IDs duplicados, datos que la API debería rechazar. Y quería que todo corriera automático, sin tocar Postman.

La solución en API testing profesional es data-driven testing: un archivo externo (CSV o JSON) alimenta las requests, y la herramienta corre una iteración por cada fila.

El paywall del Runner

Postman tiene una feature para esto: "Test data file" en el Runner. Le pasás un CSV y corre las iteraciones.

Cuando intenté usarla: paywall. "Upgrade to use data files." Plan Solo, $9/mes.

Pantalla de Postman pidiendo upgrade al plan Solo de 9 dólares mensuales para usar data files en Collection Runner con testing basado en datos
Postman pide plan pago para usar data files en la nube. La alternativa gratuita: correr el CSV desde Newman por línea de comandos.

No iba a pagar $9/mes por algo que puedo hacer gratis.

Newman

Newman es el CLI de Postman. Corre colecciones desde la terminal. Y acepta data files sin restricciones.

¿Qué hace Newman que Postman Runner no (en plan gratuito)?

  • Correr colecciones con archivos de datos (CSV, JSON)
  • Generar reportes HTML
  • Integrarse con CI/CD (GitHub Actions, Jenkins)
  • Correr sin interfaz gráfica

Lo que NO hace: editar requests o scripts. Para eso seguís usando Postman.

Newman es la herramienta que lleva Postman a la terminal. Postman para diseñar, Newman para ejecutar.

Instalación

Newman necesita Node.js. No lo tenía instalado.

Node.js

Fui a https://nodejs.org, descargué la versión LTS (v24.14.0), instalé con las opciones por defecto. En la pantalla de "Tools for Native Modules" no tildé nada — Newman no lo necesita.

Cerré PowerShell, abrí de nuevo:

PS> node -v
v24.14.0

PS> npm -v
11.9.0

Newman

PS> npm install -g newman
added 148 packages in 51s
PS> newman -v
6.2.2

Reporter HTML

Newman por defecto muestra resultados en consola. Para tener un reporte visual (parecido a Allure), instalé el reporter htmlextra:

PS> npm install -g newman-reporter-htmlextra
added 194 packages in 41s

Tres herramientas instaladas en 5 minutos: Node, Newman, reporter.

Parametrizar el body del Create

El body del Create Customer estaba así:

{
    "Entity": {
        "CustomerID": "CESBS",
        "CompanyName": "CesarBeasSuarez Industries",
        "ContactName": "Cesar Beas Suarez",
        "ContactTitle": "SEO",
        "Country": "Argentina",
        "City": "Buenos Aires",
        "Phone": "3517898989"
    }
}

Todo hardcodeado. Lo cambié a variables:

{
    "Entity": {
        "CustomerID": "{{CustomerID}}",
        "CompanyName": "{{CompanyName}}",
        "ContactName": "{{ContactName}}",
        "ContactTitle": "{{ContactTitle}}",
        "Country": "{{Country}}",
        "City": "{{City}}",
        "Phone": "{{Phone}}"
    }
}
Request POST Create Customer en Postman con body JSON usando variables de entorno como CustomerID, CompanyName, ContactName, Country, City y Phone
El body del Create usa variables Postman ({{CustomerID}}, {{CompanyName}}, etc.) que el CSV reemplaza en cada iteración del Runner.

Las {{variables}} se reemplazan automáticamente por los valores del CSV. Newman lo hace en cada iteración.

El CSV

Antes de armar el archivo, necesitaba saber cómo reacciona la API a datos inválidos. Hice tres pruebas manuales.

Sin CompanyName: 400 + "Company Name field is required!"

Request POST Create Customer sin campo CompanyName devolviendo error 400 Bad Request con mensaje Company Name field is required
Sin CompanyName, la API devuelve 400 con código "Required". El campo es obligatorio para crear un cliente.

Sin CustomerID: 400 + "Customer Id field is required!"

Request POST Create Customer sin campo CustomerID devolviendo error 400 Bad Request con mensaje Customer Id field is required
Sin CustomerID pasa lo mismo: 400 con "Required". Ambos campos (ID y CompanyName) son obligatorios.

CustomerID duplicado (ALFKI): 400 + "Can't save record. There is another record with the same Customer Id value!"

Request POST Create Customer con CustomerID ALFKI existente devolviendo error 400 con mensaje de registro duplicado por mismo Customer Id value
ALFKI ya existe en la base. La API rechaza el duplicado con un mensaje claro: "There is another record with the same Customer Id value!"

Los tres devuelven 400 con mensajes claros. La API valida bien. Con eso armé el CSV:

CustomerID,CompanyName,ContactName,ContactTitle,Country,City,Phone,UpdatedContactName,expected_status,test_type
TST01,Test Company One,John Doe,Manager,USA,New York,1234567890,John Doe UPDATED,200,valid
TST02,Test Company Two,Jane Smith,Director,Argentina,Buenos Aires,3517898989,Jane Smith UPDATED,200,valid
TST03,,John Doe,Manager,USA,New York,1234567890,,400,missing_company
,Test Company Four,John Doe,Manager,USA,New York,1234567890,,400,missing_id
ALFKI,Test Company Five,John Doe,Manager,USA,New York,1234567890,,400,duplicate
Archivo create_customer_data.csv con 5 filas de datos de prueba incluyendo casos válidos, missing_company, missing_id y duplicate con expected_status y test_type
El CSV alimenta el Runner con 5 iteraciones: 2 válidas (TST01, TST02), y 3 negativas (sin CompanyName, sin CustomerID, duplicado ALFKI).

5 filas. 5 escenarios distintos:

  • TST01 y TST02: datos válidos, esperamos 200. CRUD completo.
  • TST03: sin CompanyName, esperamos 400. El Create falla, el resto se salta.
  • Fila 4: sin CustomerID, esperamos 400.
  • ALFKI: ID que ya existe en la base, esperamos 400.

Dos columnas clave: expected_status dice qué debería responder la API. test_type identifica el caso para el log.

UpdatedContactName es el valor que usa el Update — solo tiene sentido en las filas válidas.

Exportar colección y environment

Newman necesita los archivos JSON de la colección y del environment.

Colección: click derecho en "Serenity Demo - Auth + Customer API Flow" → Export → Collection v2.1.

Environment: click en los tres puntos de "serenity-demo" → Export.

Los guardé en D:\Proyectos\Testing\postman-api-testing\newman\.

El problema del baseUrl vacío

Corrí Newman por primera vez:

PS> 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"

Todo falló: Invalid URI "http:///Account/Login/". Tres barras. Newman no tenía el baseUrl.

Abrí el environment JSON. El valor de baseUrl estaba vacío:

{
    "key": "baseUrl",
    "value": "",
    "type": "default",
    "enabled": true
}

Postman exporta el "initial value" del environment, no el "current value". Como baseUrl se setea en runtime (o solo tenía current value), exportó vacío.

La solución: edité el JSON directo en Notepad++ y puse el valor:

"value": "https://demo.serenity.is",
Archivo serenity-demo.postman_environment.json con variables baseUrl apuntando a demo.serenity.is y csrfToken vacío para autenticación
El environment tiene dos variables: baseUrl con la URL de la API, y csrfToken que se llena dinámicamente después del login.

Parametrizar todo el CRUD

El Create ya estaba parametrizado. Pero Retrieve, Update y Delete seguían hardcodeados con "CESBS". Cuando Newman corría iteraciones con TST01 o TST02, esas requests fallaban porque buscaban un cliente que no existía.

Parametricé todo:

Retrieve Customer — Body:

{
    "EntityId": "{{CustomerID}}"
}
Request POST Retrieve Customer en Postman con body JSON conteniendo EntityId usando la variable CustomerID del CSV
El Retrieve pide un solo campo: EntityId con el CustomerID de la iteración actual. Simple y directo.

Update Customer — Body:

{
    "Entity": {
        "CustomerID": "{{CustomerID}}",
        "CompanyName": "{{CompanyName}}",
        "ContactName": "{{UpdatedContactName}}",
        "ContactTitle": "{{ContactTitle}}",
        "Country": "{{Country}}",
        "City": "{{City}}",
        "Phone": "{{Phone}}"
    },
    "EntityId": "{{CustomerID}}"
}
Request POST Update Customer en Postman con body JSON incluyendo Entity con datos actualizados y campo ContactName usando UpdatedContactName del CSV
El Update envía todos los campos del cliente pero cambia ContactName por {{UpdatedContactName}}. Incluye EntityId fuera del objeto Entity.

Delete Customer — Body:

{
    "EntityId": "{{CustomerID}}"
}
Request POST Delete Customer en Postman con body JSON conteniendo EntityId usando la variable CustomerID para identificar el cliente a eliminar
El Delete solo necesita EntityId. Mismo patrón que el Retrieve: identifica al cliente y lo borra.

Ahora todo el CRUD lee los datos del CSV.

El problema de las filas negativas

Fila TST03 tiene CompanyName vacío. El Create devuelve 400. Pero Retrieve, Update y Delete corren igual — Newman ejecuta todas las requests de la colección por cada iteración. No hay forma de "saltear" requests desde Newman.

Lo que sí puedo controlar es qué valido. Si el Create falló, no tiene sentido validar que el Retrieve devuelva 200 — el cliente nunca se creó.

La solución: un flag en el environment que conecta el resultado del Create con el resto del CRUD.

Assertions condicionales

Create Customer — Post-response

Script post-response del Create Customer con validación de status code, verificación de EntityId, flag createSuccess y manejo de casos 200 y 400
El script del Create hace dos cosas clave: si es 200, valida el EntityId y setea createSuccess = true. Si es 400, valida que haya error y setea el flag en false.

Tres cosas pasan acá:

pm.iterationData.get("expected_status") lee el valor de la columna del CSV para esa iteración. No hay que configurar nada — Newman inyecta los datos automáticamente.

El nombre del test incluye el tipo de caso: "Status code is 200 (valid)" o "Status code is 400 (missing_company)". En el reporte ves exactamente qué caso es cada iteración.

pm.environment.set("createSuccess", "true") guarda un flag. Las requests siguientes lo leen para decidir si validan o se saltan.

Los console.log al inicio muestran en la terminal de Newman qué datos mandó el CSV y qué devolvió la API. Es el debugging más directo que hay en Postman — no hay breakpoints ni stepping como en un IDE. Es debugging por prints.

Customer List (Verify Create) — Post-response

Script post-response de Customer List verificando TotalCount mayor a 91 y buscando el cliente recién creado en el array Entities con find por CustomerID
Después del Create, este script verifica que el cliente nuevo aparezca en la grilla. Busca por CustomerID en el array Entities y valida que TotalCount subió.

Si el Create fue exitoso, valida que el cliente aparezca en la grilla. Si falló, corre un test que siempre pasa — pm.expect(true).to.be.true — para que Newman lo registre como verde sin validar nada real.

Retrieve Customer — Post-response

Script post-response del Retrieve Customer validando status 200, CustomerID y ContactName original contra datos del CSV con manejo de skip si Create falló
El Retrieve valida que la API devuelva el cliente correcto con los datos originales del CSV: CustomerID y ContactName deben coincidir.

Update Customer — Post-response

Script post-response del Update Customer validando status 200 y que el EntityId devuelto coincida con el CustomerID del CSV con skip si Create falló
El script del Update valida que la API confirme la actualización devolviendo el EntityId correcto.

Retrieve Customer (Verify Update) — Post-response

Script post-response del segundo Retrieve verificando que ContactName cambió al valor UpdatedContactName y que CompanyName no se modificó tras el Update
El segundo Retrieve es la verificación real del Update: ContactName debe ser el valor UPDATED del CSV, y CompanyName no debe haber cambiado.

Delete Customer — Post-response

Script post-response del Delete Customer validando status 200 y que WasAlreadyDeleted sea false confirmando que el cliente existía y fue borrado ahora
El Delete valida dos cosas: status 200 y WasAlreadyDeleted: false. Si fuera true, significaría que el cliente ya estaba borrado antes del test.

Customer List (Verify Delete) — Post-response

Script post-response de Customer List verificando que TotalCount volvió a 91 y que el cliente eliminado ya no aparece en el array Entities
Última verificación: TotalCount debe volver a 91 y el CustomerID del cliente eliminado no debe existir en Entities. El ciclo CRUD cerró limpio.

El patrón es el mismo en todas: leer createSuccess, validar si es "true", skipear si es "false".

Un detalle: Newman ejecuta TODAS las requests siempre. Lo que se salta es la validación, no la ejecución. Si querés saltear la request en sí, necesitás postman.setNextRequest() — más complejo y no era necesario para este caso.

Organización en folders

La colección tenía todas las requests en una lista plana. Antes de correr Newman, reorganicé en folders:

Auth — GET Login Page, POST Login, GET Refresh Token (Post-Login)

CRUD Happy Path — Customer List, Create Customer, Customer List (Verify Create), Retrieve Customer, Update Customer, Retrieve Customer (Verify Update), Delete Customer, Customer List (Verify Delete), Update Customer (Non-existent)

Negative Tests — Login (user correcto, pass mal), Login (user mal, pass correcta), Login (user y pass incorrectos), Login (user y pass vacios)

Panel lateral de Postman mostrando la colección Serenity Demo con carpetas Auth, CRUD Happy Path con 9 requests y Negative Tests con 4 requests de login
La colección completa: Auth (login + token), CRUD Happy Path (9 requests con verificaciones), y Negative Tests (4 variantes de login fallido).

Newman acepta --folder para correr solo ciertos folders:

newman run collection.json --folder Auth --folder "CRUD Happy Path"

Eso corre Auth + CRUD Happy Path sin tocar Negative Tests.

El comando final

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"

Qué hace cada parte:

  • newman run collection.json — corre la colección
  • -e environment.json — usa el environment con baseUrl
  • -d create_customer_data.csv — alimenta con el CSV (una iteración por fila)
  • --folder Auth --folder "CRUD Happy Path" — solo esos folders

Para agregar el reporte HTML:

newman run collection.json -e env.json -d data.csv --folder Auth --folder "CRUD Happy Path" -r htmlextra

El reporte se genera en una carpeta newman/ con un archivo HTML timestamped.

La corrida

5 iteraciones. 150 assertions. Esto es lo que pasó:

Iteración 1: TST01 (valid)

Iteration data: {"CustomerID":"TST01","CompanyName":"Test Company One","test_type":"valid","expected_status":200}
Response: {"EntityId":"TST01"}

Create → 200 ✓. EntityId matches ✓. Verify Create → TotalCount increased ✓, TST01 exists ✓. Retrieve → Entity is TST01 ✓. Update → 200 ✓. Verify Update → ContactName was updated ✓, Other fields unchanged ✓. Delete → WasAlreadyDeleted is false ✓. Verify Delete → TotalCount is 91, TST01 no longer exists ✓.

CRUD completo. Cliente creado, verificado, actualizado, verificado de nuevo, borrado, verificado que no existe. Base limpia.

Iteración 2: TST02 (valid)

Mismo flujo con datos distintos. Argentina, Buenos Aires. Todo verde.

Iteración 3: TST03 (missing_company)

Iteration data: {"CustomerID":"TST03","CompanyName":"","test_type":"missing_company","expected_status":400}
Response: {"Error":{"Code":"Required","Arguments":"CompanyName","Message":"Company Name field is required!"}}

Create → 400 ✓ (missing_company). Response has error message ✓. El resto: SKIP todo ✓.

El Create falló como esperábamos. createSuccess se setea a "false". Verify Create, Retrieve, Update, Verify Update, Delete, Verify Delete — todos muestran "SKIP - Create failed" en verde.

Iteración 4: missing_id

Iteration data: {"CustomerID":"","CompanyName":"Test Company Four","test_type":"missing_id","expected_status":400}
Response: {"Error":{"Code":"Required","Arguments":"CustomerID","Message":"Customer Id field is required!"}}

Mismo patrón. 400 ✓. SKIP el resto ✓.

Iteración 5: ALFKI (duplicate)

Iteration data: {"CustomerID":"ALFKI","CompanyName":"Test Company Five","test_type":"duplicate","expected_status":400}
Response: {"Error":{"Message":"Can't save record. There is another record with the same Customer Id value!"}}

400 ✓. La API detecta el duplicado. SKIP el resto ✓.

Resultado del Collection Runner mostrando Create fallido por duplicado ALFKI, requests CRUD posteriores con SKIP, y test de Update Non-existent pasando con status 400
El Runner ejecutó el flujo CRUD completo. El Create falló por ID duplicado (ALFKI), y el flag createSuccess hizo que el resto del ciclo se saltee con SKIP.

El reporte HTML

Con -r htmlextra, Newman genera un dashboard.

Newman HTML Dashboard mostrando 5 iteraciones, 128 assertions, 0 failed tests, 60 requests totales, duración 23.9 segundos y response time promedio de 316ms
Primera corrida exitosa con 5 iteraciones (sin el caso forced_bug). 128 assertions, cero fallos, 23.9 segundos. Todo verde.

5 iteraciones. 128 assertions. 0 failures. Todo verde.

El reporte muestra: resumen general, detalle por iteración, tiempos de respuesta, requests que fallaron (ninguna), datos enviados y recibidos.

Detalle del Newman Dashboard mostrando response body del Retrieve con datos del cliente TST01, tests pasados y request description del Update Customer
El dashboard de Newman muestra el detalle de cada request: response body, tests pasados, y la descripción que se agregó en Postman para documentar qué hace cada paso.

Romper a propósito

Un reporte verde no prueba que el sistema detecta problemas. Necesitaba demostrar que si los datos esperados no coinciden con la realidad, Newman lo atrapa. El mismo concepto que apliqué con Allure en Selenium — forzar failures para mostrar que el framework funciona.

Agregué una fila al CSV:

TST06,Bug Simulation Co,Test User,Tester,USA,Boston,5551234567,Test User UPDATED,400,forced_bug

Datos válidos, pero expected_status es 400. La API va a devolver 200 (creación exitosa), pero el test espera 400. Discrepancia forzada.

Resultado:

Iteration data: {"CustomerID":"TST06","CompanyName":"Bug Simulation Co","test_type":"forced_bug","expected_status":400}
Response: {"EntityId":"TST06"}
1. Status code is 400 (forced_bug)
   expected response to have status code 400 but got 200

2. Response has error message
   expected { EntityId: 'TST06' } to have property 'Error'
Newman HTML Dashboard mostrando 6 iteraciones, 150 assertions, 2 failed tests, 72 requests totales, duración 31.1 segundos y response time promedio de 332ms
Con la sexta iteración (forced_bug), el dashboard muestra 2 fallos en rojo. Los fallos son intencionales: el CSV manda un caso que debería fallar para probar la detección.
Pestaña Failed Tests del Newman Dashboard mostrando 2 fallos en iteración 6: status code esperado 400 pero recibido 200 y response sin propiedad Error
Los 2 fallos de la iteración 6: el script esperaba status 400 pero la API devolvió 200, y el response no tenía propiedad "Error". El caso forced_bug expuso que la API no rechaza ese dato como se esperaba.

Newman lo atrapó. Dos failures, ambos en la iteración del forced_bug. Las otras 5 iteraciones siguen verdes.

El reporte muestra exactamente qué falló, en qué iteración, qué se esperaba y qué devolvió la API. Si esto fuera un bug real — por ejemplo, la API cambió un status code sin avisar — Newman lo detecta.

Errores que cometí

Environment exportado vacío

No me di cuenta hasta que vi las tres barras: http:///Account/Login. Postman exporta el "initial value", no el "current value". Si seteás variables en runtime, el export las pierde. La solución fue editar el JSON a mano.

Comentario con backticks rompió el script

Intenté agregar un comentario explicativo al inicio del Post-response del Create. Lo escribí con backticks (```). Postman interpretó eso como código y tiró TypeError: "" is not a function. El script nunca llegó a setear createSuccess, y todo el CRUD se salteó en todas las iteraciones. Costó un rato encontrarlo.

Script a nivel de folder que no debería estar

Había un pm.test("Status code is 200") en el script Post-response del folder "CRUD Happy Path". Ese script se aplica a TODAS las requests del folder automáticamente — incluyendo las que esperan 400. Eso generaba un "Status code is 200" duplicado y failures en los tests negativos. Lo borré.

Schema roto por datos del demo server

El schema de Customer List tiene campos required. Pero el demo server es público — otros usuarios crean clientes sin todos los campos. Entre corridas, aparecieron clientes sin Address y sin ContactName. El schema falló en esos. Tuve que sacar Address y ContactName del array de required. No es un error de los tests — es una realidad del demo server.

Debugging en Postman y Newman

Postman no tiene debugger paso a paso como IntelliJ. No hay breakpoints, no hay stepping. El debugging es con console.log() en los scripts.

En Postman: abrís la Console (abajo a la izquierda) y ves todo lo que logueás.

En Newman: aparece directamente en la terminal si corrés sin -r (sin reporter que suprima la salida).

Los console.log en el Create muestran exactamente qué datos mandó el CSV y qué devolvió la API:

Iteration data: {"CustomerID":"TST01","CompanyName":"Test Company One","test_type":"valid","expected_status":200}
Response: {"EntityId":"TST01"}
Salida de consola Newman mostrando iteración 1 con TST01 pasando el flujo CRUD completo: Create, Verify Create, Retrieve, Update, Verify Update, Delete y Verify Delete
Iteración 1 completa con TST01: Create exitoso, verificación en grilla, Retrieve, Update con ContactName cambiado, verificación del cambio, Delete y confirmación de que desapareció. Todo verde.

Eso es suficiente para debuggear. Si algo falla, ves el dato que mandaste y la response que recibiste.

Cómo conecta el CSV con las requests

No hay un script especial. Es una feature nativa de Newman. El flag -d create_customer_data.csv le dice a Newman: "por cada fila del CSV, corré toda la colección."

Las {{variables}} en los body se reemplazan por los valores de cada columna. {{CustomerID}} lee la columna CustomerID de esa fila.

En los scripts, pm.iterationData.get("CustomerID") accede al mismo valor. Así los tests comparan contra los datos de esa iteración específica.

5 filas = 5 iteraciones. Cada iteración corre Auth + CRUD Happy Path completo. Si una fila tiene datos inválidos, el Create falla, createSuccess se pone en "false", y el resto del CRUD se salta.

Estado actual

El comando para correr todo:

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

Lo que valida por iteración válida (200):

  1. Customer List — 91 registros, schema, campos obligatorios
  2. Create Customer — 200 + EntityId correcto
  3. Customer List (Verify Create) — TotalCount subió + cliente existe
  4. Retrieve Customer — datos originales correctos
  5. Update Customer — 200 + EntityId
  6. Retrieve Customer (Verify Update) — ContactName cambió + otros campos intactos
  7. Delete Customer — 200 + WasAlreadyDeleted: false
  8. Customer List (Verify Delete) — TotalCount volvió a 91 + cliente no existe
  9. Update Customer (Non-existent) — 400 + Error

Lo que valida por iteración inválida (400):

  1. Customer List — igual
  2. Create Customer — 400 + Error message 3-8. SKIP — "Create failed, skipping..."
  3. Update Customer (Non-existent) — 400 + Error

Archivos en el repo:

postman-api-testing/
└── newman/
    ├── Serenity Demo - Auth + Customer API Flow.postman_collection.json
    ├── serenity-demo.postman_environment.json
    ├── create_customer_data.csv
    └── newman/
        └── [reportes HTML generados]

Takeaways

Postman Runner cobra por data files. Newman lo hace gratis y además permite CI/CD. Para data-driven testing profesional, Newman es el camino.

Exportar el environment no incluye "current values". Si las variables se setean en runtime, hay que editar el JSON a mano o usar --env-var en el comando.

createSuccess como flag entre requests funciona. Las requests se ejecutan siempre — lo que se controla es la validación. Para saltear la ejecución en sí se necesitaría postman.setNextRequest().

Los console.log son el debugger de Postman. No hay breakpoints. Es debugging por prints.

Scripts a nivel de folder se aplican a todas las requests. Si tenés un test genérico ahí, va a correr donde no debería.

Forzar failures a propósito es tan importante como hacer que todo pase. Si nunca ves rojo, no sabés si el sistema detecta problemas.

Newman corre toda la selección por cada fila del CSV. Auth se repite 6 veces aunque solo necesita correr una. Con 6 filas no importa. Con 100 sería un problema. La solución real para escala es un framework basado en código como REST Assured, donde controlás el flujo completo. Newman es para colecciones moderadas.

Salida de consola Newman mostrando la carpeta Auth en iteración 6 con GET Login Page extrayendo CSRF cookie, POST Login validando cookies y GET Refresh Token post-login
El flujo Auth se ejecuta en cada iteración: GET para obtener el CSRF token, POST para login, y GET post-login para refrescar el token antes del CRUD.

Próximo paso

Newman corre en mi máquina. El siguiente nivel es que corra solo: CI/CD con GitHub Actions. Push al repo, el pipeline ejecuta Newman, el reporte aparece automático.