API de casos de ejecución
GET /api/v1/runs/{runUlid}/cases devuelve todos los casos que pertenecen a
una ejecución abierta o cerrada. Se añadieron dos campos como
extensiones aditivas y compatibles con versiones anteriores — las
integraciones existentes que ignoran claves desconocidas no se ven afectadas.
Listar casos de una ejecución
Sección titulada «Listar casos de una ejecución»GET /api/v1/runs/{runUlid}/casesRequiere viewer+ en el proyecto de la ejecución. Devuelve 404 si la
ejecución está fuera de la organización del llamador.
Forma de la respuesta (por elemento)
Sección titulada «Forma de la respuesta (por elemento)»Cada elemento del array cases incluye los campos habituales más:
| Campo | Tipo | Descripción |
|---|---|---|
caseLifecycleStatus | object | null | El estado del ciclo de vida actual (en tiempo real, no del snapshot) del caso fuente. null cuando el campo de estado no está configurado o el caso fuente fue eliminado. |
customFieldValues[].optionSystemKey | string | null | La clave de sistema propia de la opción (p. ej. status_active, status_draft). null para opciones personalizadas definidas por el usuario y para tipos de campo no selectores. |
executedByApiToken | object | null | El token de API que ejecutó el intento actual del caso (su último resultado en la ejecución): { ulid, name, app }, donde app es el reporter de CI (junit, playwright, jest, cypress, vitest, cucumber) de un token de aplicación de la organización y null para un token de API personal. null cuando el caso no tiene resultados en la ejecución, cuando el intento actual se registró en la aplicación web y una vez eliminado el token. |
caseRevision | number | null | La revisión del caso fuente que este caso de la ejecución congeló: su título, campos y pasos son esa versión del caso. null solo en un caso de ejecución creado antes de que existieran las revisiones cuyo caso ya estaba eliminado. |
caseCurrentRevision | number | null | La revisión actual del caso fuente (currentRevision del caso de prueba). null cuando el caso fue eliminado. |
caseUpdated | boolean | true cuando la ejecución está abierta, el caso de la ejecución está pendiente (untested, o retested tras un reintento) y el caso cambió después de que este caso de la ejecución lo congelara (caseCurrentRevision > caseRevision). Siempre false para un caso de la ejecución completado y en una ejecución cerrada. |
canApplyLatest | boolean | true cuando caseUpdated es true y el intento actual todavía no tiene resultados de pasos, de modo que aplicar la última versión puede actualizarlo. |
executedByApiToken no es un responsable
Sección titulada «executedByApiToken no es un responsable»Un reporter de CI escribe con un token de aplicación, que no es un miembro, así que un caso que
reporta conserva assigneeUlid: null salvo que se asigne a un miembro (por ejemplo, con
assignFailedTo del reporte). executedByApiToken solo indica qué token ejecutó el intento
actual, y un intento posterior registrado por un miembro lo reemplaza por null. La aplicación
web lo muestra en la columna de responsable de un caso sin miembro asignado; en una ejecución que
creó un token de aplicación (una ejecución de CI), un caso sin ninguno de los dos muestra allí el
token de la ejecución (su author). Ninguno es un responsable: el filtro por responsable, el bloqueo de resultados por
responsable, “asignados a mí” y la asignación por PATCH o masiva siguen viendo el caso sin
asignar.
Abrir un caso solo asigna un responsable vacío
Sección titulada «Abrir un caso solo asigna un responsable vacío»POST /api/v1/runs/{runUlid}/cases/{runCaseUlid}/open asigna automáticamente a quien abre el caso
(o al responsable predeterminado de la ejecución) solo cuando autoAssignOnOpen del proyecto está
activo y el responsable del caso está vacío. No está vacío, y abrir el caso no asigna a nadie,
cuando:
- hay un miembro asignado: abrir nunca cambia a un miembro responsable;
- un token de aplicación de la organización, un reporter de CI, creó la ejecución (su
author.kindesapi_tokeny suauthor.apptiene valor): ningún caso de una ejecución de CI se asigna al abrirlo, ni siquiera después de que un miembro lo marque o lo reintente. Una ejecución que creó un token de API personal no es una ejecución de CI; - en cualquier otra ejecución, un token de API ejecutó el intento actual del caso
(
executedByApiTokentiene valor). Cuando un miembro registra un intento más reciente, abrirlo vuelve a asignar.
Para el intento actual de un caso, los tokens de aplicación y los tokens de API personales cuentan por igual. Un caso que se quita de una ejecución y se vuelve a agregar conserva sus resultados anteriores en ella, así que su intento actual sigue siendo el más reciente de ellos.
Objeto caseLifecycleStatus
Sección titulada «Objeto caseLifecycleStatus»Cuando no es null, el objeto tiene la misma forma que un CustomFieldValueEntry:
| Campo | Tipo | Descripción |
|---|---|---|
optionUlid | string | ULID de la opción de ciclo de vida seleccionada |
optionName | string | Nombre visible de la opción, resuelto según el idioma de la solicitud (ver más abajo) |
optionColor | string | null | Color hexadecimal, si existe |
optionIcon | string | null | Identificador de icono, si existe |
optionSystemKey | string | null | Clave de sistema: status_active, status_draft, status_deprecated, o null para opciones personalizadas |
Idioma de la solicitud. optionName se resuelve según el encabezado
Accept-Language de la solicitud: una organización que tradujo los nombres
de sus opciones de estado al español recibe el nombre en español al enviar
Accept-Language: es. Un idioma ausente o no soportado resuelve al nombre en
inglés, así que los integradores que nunca envían el encabezado obtienen
respuestas idénticas byte a byte a las de antes de este comportamiento.
En tiempo real vs. snapshot
Sección titulada «En tiempo real vs. snapshot»El contenido del caso de prueba (título, pasos, descripción) se
congela en un snapshot cuando el caso se añade a la ejecución — los cambios
en el caso original no afectan ninguna ejecución existente. caseLifecycleStatus
es deliberadamente no un snapshot: se resuelve en tiempo real mediante un
join al caso fuente, por lo que siempre refleja el estado actual del caso en el
repositorio. Si necesitas el estado del snapshot, usa el campo priority
(que sí forma parte del snapshot).
El caso de la ejecución registra qué revisión del caso congeló (caseRevision). Mientras la
ejecución está abierta, una edición del caso pone caseUpdated: true en los casos de la ejecución
que siguen pendientes de ejecutar (untested, o retested tras un reintento); el caso de la
ejecución conserva el contenido anterior hasta que apliques la última versión.
Un caso de la ejecución completado y una ejecución cerrada nunca informan una actualización.
Ejemplo
Sección titulada «Ejemplo»curl -sS \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ "https://app.probara.net/api/v1/runs/01J...RUN/cases"{ "cases": [ { "runCaseUlid": "01J...RC", "caseUlid": "01J...CASE", "caseNumber": 7, "titleSnapshot": "Flujo de pago", "status": "untested", "caseLifecycleStatus": { "optionUlid": "01J...OPT", "optionName": "Borrador", "optionColor": "#F59E0B", "optionIcon": "circle-dashed", "optionSystemKey": "status_draft" }, "customFieldValues": [ { "fieldUlid": "01J...FLD", "fieldName": "Prioridad", "optionUlid": "01J...PRI", "optionName": "Alta", "optionColor": "#EF4444", "optionIcon": null, "optionSystemKey": null } ] } ]}Cuando el caso fuente no tiene estado de ciclo de vida configurado, o el caso fue eliminado:
{ "caseLifecycleStatus": null}Cuando una entrada de customFieldValues pertenece a una opción de sistema (campo de estado del ciclo de vida):
{ "optionSystemKey": "status_active"}Cuando pertenece a una opción personalizada (definida por el usuario):
{ "optionSystemKey": null}Listar las claves de casos de una ejecución
Sección titulada «Listar las claves de casos de una ejecución»GET /api/v1/runs/{runUlid}/case-keysDevuelve cada caso que está ahora en la ejecución con solo lo que un reporter de CI necesita para ejecutar las pruebas vinculadas: su id de visualización y su clave de automatización. Un reporter de framework lo usa para ejecutar solo las pruebas de los casos de una ejecución existente.
| Campo | Tipo | Descripción |
|---|---|---|
caseDisplayId | string | El id de visualización del caso, por ejemplo ACME-14 |
automationKey | string | null | La clave de automatización del caso, null si no tiene una |
- Un caso quitado de la ejecución no aparece, ni tampoco un caso eliminado del proyecto.
- Los elementos se ordenan por ULID del caso y se paginan por cursor:
limit(por defecto 50, máximo 200; por encima responde422 validation_failed) ycursor, elnextCursorde la página anterior.nextCursoresnullen la última página. - Las ejecuciones cerradas y abortadas se pueden leer.
- Es la única lectura que puede hacer un token de aplicación.
Un token de aplicación solo lee ejecuciones de su organización; cualquier otra responde
404 not_found, y en una organización sin plan de pago responde403 forbidden, como toda llamada con token de aplicación. - Una ejecución fuera de la organización de quien llama responde
404 not_found. Una sesión o un token OAuth además necesitan acceso al proyecto de la ejecución y sin él reciben403 forbidden; un token de API personal o un token de aplicación llegan a todos los proyectos de su organización, incluidos los privados.
curl -sS \ -H "Authorization: Bearer $PROBARA_APP_TOKEN" \ "https://app.probara.net/api/v1/runs/01J...RUN/case-keys?limit=200"{ "items": [ { "caseDisplayId": "ACME-14", "automationKey": "cart > adds an item" }, { "caseDisplayId": "ACME-15", "automationKey": null } ], "nextCursor": null}Obtener un caso de una ejecución
Sección titulada «Obtener un caso de una ejecución»GET /api/v1/runs/{runUlid}/cases/{runCaseUlid}Devuelve el caso de ejecución con sus pasos, linkedDefects y lo que corresponde a su intento
actual (el último resultado del caso en la ejecución): resultUlid, resultAttachments y, para
un resultado que registró un reporte de CI, lo que reportó la ejecución:
| Campo | Descripción |
|---|---|
parameters | Los parámetros que reportó el intento actual, [{ name, value }] en el orden enviado |
links | Los enlaces que reportó el intento actual, [{ url, name }] en el orden enviado (name es null si no se envió) |
resultSteps | Los pasos que reportó el intento actual, aplanados en profundidad y en preorden: { position, parentPosition, action, status, durationMs, error, expected, data }, donde position es el índice del paso desde 0 (el stepIndex de un elemento de resultAttachments) y parentPosition el de su padre (null en el nivel superior) |
Los tres son [] cuando el intento actual no reportó ninguno — un resultado marcado en la aplicación
web, o un caso que nunca se marcó — y siguen al intento actual: un intento posterior que no reportó
nada los vacía. Son las mismas listas que devuelve la
instantánea del intento para resultUlid.
resultSteps no es steps: steps son los pasos propios del caso. Cada elemento de steps incluye keyword, la palabra clave Gherkin de un paso de un caso Gherkin, congelada con el paso; es null en un paso clásico.
Marcar un caso
Sección titulada «Marcar un caso»PATCH /api/v1/runs/{runUlid}/cases/{runCaseUlid}Define assigneeUlid / status / elapsedMs. Un cambio de status marca todos los pasos y
agrega una fila de resultado; el resultUlid de la respuesta lleva el intento agregado (null
en un patch que solo cambia el asignado o el tiempo transcurrido, sin cambiar el estado).
Requiere member+ en el proyecto de la ejecución; viewer recibe 403 forbidden.
Comportamiento en una ejecución cerrada
Sección titulada «Comportamiento en una ejecución cerrada»Permitido en una ejecución abierta o cerrada sin abortar cuando la configuración de
ejecución allowResultsInClosedRuns del proyecto (ver
Configuración de ejecución) resuelve a true (el
valor por defecto del código). Una organización o proyecto que lo fije en false restaura el
comportamiento anterior, más estricto: una ejecución cerrada rechaza el marcado con
409 conflict. Una ejecución abortada siempre lo rechaza, sin importar el valor de esta
configuración.
403 run_case_assignee_locked
Sección titulada «403 run_case_assignee_locked»Cuando la configuración assigneeResultLock del proyecto está activa y el caso tiene un
asignado, solo ese asignado — o un solicitante exento por projects.manage o por ser el
propietario actual del proyecto — puede marcarlo. Cualquier otro solicitante es rechazado,
incluido un token de API: no existe una exención para solicitantes automatizados sobre este
bloqueo.
{ "error": { "code": "run_case_assignee_locked", "message": "Only the assigned tester may record a result for this case" }}422 elapsed_ms_required
Sección titulada «422 elapsed_ms_required»Cuando la configuración timeTracking del proyecto es "required" y la solicitud define un
status terminal sin elapsedMs, el marcado se rechaza y no se persiste nada:
{ "error": { "code": "validation_failed", "message": "elapsedMs is required", "details": { "code": "elapsed_ms_required", "path": ["elapsedMs"] } }}Con "off" u "optional", un elapsedMs provisto se sigue aceptando y persistiendo — la
configuración solo gobierna si se muestra la interfaz de seguimiento de tiempo del cliente web y
si se rechaza un marcado sin elapsedMs, nunca si se descarta un valor provisto.
El marcado en bloque y el envío de resultados en bloque están exentos de este rechazo — ni
POST /runs/{runUlid}/cases/bulk-mark ni POST /runs/{runUlid}/cases/bulk-submit-result llevan
un campo elapsedMs por caso que el servidor pudiera exigir, así que timeTracking: "required"
nunca bloquea ninguno de los dos endpoints en bloque.
Consulta Configuración de ejecución para el vocabulario completo de configuraciones y la cadena de resolución.
Aplicar la última versión del caso
Sección titulada «Aplicar la última versión del caso»POST /api/v1/runs/{runUlid}/cases/{runCaseUlid}/apply-latestPOST /api/v1/runs/{runUlid}/cases/apply-latestLleva a la última versión de su caso un caso de la ejecución cuyo caso de prueba se editó después
de que la ejecución lo congelara: se reemplazan su título, suite, descripción, precondiciones,
postcondiciones, prioridad y pasos, y caseRevision pasa a ser la revisión actual del caso. El
responsable, la posición y el estado no cambian. Envía un cuerpo JSON vacío ({}). Requiere
member+ en el proyecto de la ejecución, igual que marcar un caso.
Un caso de la ejecución solo puede tomar la última versión mientras está pendiente
(untested, o retested tras un reintento) y su intento actual no tiene resultados de pasos:
ningún paso con estado, resultado real o adjunto. Para actualizar un caso completado, reinténtalo
primero (POST /api/v1/runs/{runUlid}/cases/{runCaseUlid}/retry). Los intentos anteriores conservan el título, los campos, los
pasos, los resultados de los pasos y los adjuntos con los que se ejecutaron, así que cada resultado
registrado sigue correspondiendo a los pasos que realmente se ejecutaron. canApplyLatest lo
indica de antemano.
El endpoint de un caso responde con el caso de la ejecución y sus pasos, como GET. Rechaza con
409 conflict y no cambia nada en los casos siguientes. Se comprueban en este orden y se informa
el primero que se cumple:
| Situación | error.message / error.details.reason |
|---|---|
| La ejecución está cerrada | run is closed |
| El caso fuente fue eliminado | case_deleted |
| El caso fuente no cambió desde que este caso de la ejecución lo congeló, sea cual sea su estado | case_not_outdated |
| El caso de la ejecución está completado o su intento tiene resultados de pasos | run_case_has_results |
case_not_outdated compara caseRevision con caseCurrentRevision; no depende de caseUpdated,
que es false para todo caso de la ejecución completado. Un caso de la ejecución completado cuyo
caso fuente cambió responde run_case_has_results: reinténtalo primero.
Cuando assigneeResultLock está activado en el proyecto, se aplica la misma regla
403 run_case_assignee_locked que al marcar.
El endpoint de toda la ejecución aplica la última versión a cada caso con caseUpdated: true
cuyo intento actual no tiene resultados de pasos, en una sola transacción, e informa los casos
pendientes desactualizados que dejó sin cambios (los casos completados nunca son candidatos):
{ "updated": ["01J...RC1"], "skipped": [{ "runCaseUlid": "01J...RC2", "reason": "run_case_has_results" }]}reason es run_case_has_results, o assignee_locked cuando el bloqueo por responsable impide
al llamante cambiar ese caso. Una ejecución cerrada responde 409 conflict (run is closed).
Cada caso actualizado registra run.case_version_applied (fromRevision, toRevision); la
llamada de toda la ejecución registra un único run.cases_bulk_version_applied con los casos
actualizados, o nada si no se actualizó ninguno. Consulta la
API de eventos de auditoría.
Reconciliar la selección de casos de una ejecución
Sección titulada «Reconciliar la selección de casos de una ejecución»PATCH /api/v1/runs/{runUlid}/casesReconcilia la selección de casos de una ejecución a la selección completa
deseada que se envíe, en una sola transacción atómica. A diferencia de
POST /runs/{runUlid}/cases (solo agrega — añade los ULIDs de caso dados y
nunca quita nada), este endpoint trata la ausencia del conjunto deseado como
una eliminación. Ambos endpoints siguen disponibles y no cambian entre sí.
Requiere member+ (el mismo permiso de ejecución que correr los tests);
viewer recibe 403. Permitido en una ejecución abierta o cerrada sin
abortar (completada, o cerrada automáticamente al tener resultado todos sus
casos); rechaza solo una ejecución abortada con 409, sin mutar nada.
Cuerpo de la solicitud
Sección titulada «Cuerpo de la solicitud»{ "caseUlids": ["01J...A", "01J...B", "01J...D"], "caseAssignees": [{ "caseUlid": "01J...D", "assigneeUlid": "01J...USER" }]}| Campo | Tipo | Descripción |
|---|---|---|
caseUlids | string[] | La selección completa deseada (ULIDs de caso fuente). Un array vacío es válido — elimina todos los casos de la ejecución. |
caseAssignees | array (opcional) | Asignado por caso para la selección deseada. Un ULID de caso ausente de este array significa que ese caso queda (o pasa a quedar) sin asignar — no existe un valor explícito de “limpiar”. |
Dada la selección actual y la solicitud anterior, el servidor calcula la diferencia y la aplica, de forma atómica:
- Agrega cada caso de
caseUlidsque aún no está en la ejecución — congelando su título y pasos como un snapshot nuevo,status: "untested", con el asignado provisto (si lo hay) aplicado en la misma operación. - Elimina cada caso actual ausente de
caseUlids— en cascada con sus snapshots de pasos y adjuntos de resultado, igual que el endpoint de un solo casoDELETE /runs/{runUlid}/cases/{runCaseUlid}. - Conserva cada caso presente en ambos, sin cambios (
titleSnapshot,status,assigneeUlid,elapsedMs). - Reasigna cualquier caso conservado cuyo asignado provisto difiera del actual.
La respuesta es el resumen reconciliado de la ejecución (RunResponse), la
misma forma que devuelve cualquier otra mutación de ejecución — no
incluye el array de casos de la ejecución; llama a
GET /runs/{runUlid}/cases para obtener el listado actualizado.
Validación del asignado
Sección titulada «Validación del asignado»Cada assigneeUlid provisto debe resolver a un miembro de la organización de
la ejecución. Un ULID de asignado ajeno a la organización o desconocido
devuelve 404 not_found — igual que el comportamiento existente de la
creación (POST /projects/{projectId}/runs) y del patch por caso
(PATCH /runs/{runUlid}/cases/{runCaseUlid}) para el mismo campo, no un
422 validation_failed. En ese caso no se agrega, elimina ni reasigna ningún
caso.
Eventos de auditoría
Sección titulada «Eventos de auditoría»Emite exactamente un evento por cada caso que realmente cambió:
run.case_added por cada caso agregado, run.case_removed por cada caso
eliminado, run.case_assigned por cada caso cuyo asignado cambió (incluyendo
una limpieza, to: null) — ver API de eventos de auditoría
para las formas compartidas de metadata. Un caso sin cambios tanto en
membresía como en asignado no emite nada. El asignado incorporado de un caso
recién agregado se registra en run.case_added y no emite además un
run.case_assigned.
Ejemplo
Sección titulada «Ejemplo»curl -sS -X PATCH \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"caseUlids": ["01J...A", "01J...D"]}' \ "https://app.probara.net/api/v1/runs/01J...RUN/cases"Solo casos aprobados
Sección titulada «Solo casos aprobados»Cuando la configuración de revisión del proyecto tiene
onlyApprovedInRuns activada (plan Pro o Enterprise, con la revisión enabled), una ejecución solo admite
casos de prueba con reviewStatus approved:
-
Los casos que eliges —
caseUlidsenPOST /api/v1/projects/{projectId}/runs, enPOST /runs/{runUlid}/casesy los casos que añade unPATCH /runs/{runUlid}/cases— deben estar todos aprobados. Si no, se rechaza la solicitud entera y nada cambia:{"error": {"code": "validation_failed","message": "only approved test cases can be added to runs in this project","details": {"reason": "cases_not_approved","notApprovedCaseUlids": ["01J...B"]}}}Una reconciliación conserva los casos que ya están en la ejecución aunque hayan dejado de estar aprobados.
-
Los casos que una ejecución toma por su cuenta — la selección de un plan de pruebas cuando se omite
caseUlids, y los casos de la ejecución de origen de un clon — se filtran: solo se añaden los aprobados, y la respuesta incluyenotApprovedSkippedCount, cuántos quedaron fuera. Si ninguno está aprobado, la solicitud se rechaza con422 validation_failed(details.codeempty_selection).
Con la opción desactivada, la revisión desactivada o un plan sin revisión de casos, nada cambia y
notApprovedSkippedCount no aparece. Los reportes de CI (POST /api/v1/projects/{projectId}/reports)
no se ven afectados.
Límites de solicitud
Sección titulada «Límites de solicitud»Cada uno de los siguientes tres endpoints limita el arreglo caseUlids en el
cuerpo de la solicitud. Son techos contra abuso, no techos de producto — el
servidor procesa cualquier tamaño aceptado sin fallar por límite de
parámetros. Enviar más del límite responde 422 validation_failed nombrando
el campo caseUlids, y no muta nada.
| Endpoint | Campo | Límite |
|---|---|---|
POST /api/v1/runs/{runUlid}/cases (agregar casos) | caseUlids | 500 |
PATCH /api/v1/runs/{runUlid}/cases (reconciliar) | caseUlids | 5000 |
POST /api/v1/projects/{projectId}/runs (crear ejecución) | caseUlids | 5000 |
Casos de ejecución parametrizados
Sección titulada «Casos de ejecución parametrizados»Un caso parametrizado tiene un caso de ejecución por cada combinación, y cada caso de ejecución lleva su combinación en parameters. La lista de casos de la ejecución la devuelve en cada elemento ([] para un caso sin parámetros). El detalle del caso de ejecución añade siblings, los demás casos de ejecución del mismo caso en la ejecución, con su estado, su responsable y sus parámetros, y reportedParameters, los parámetros que reportó el intento actual.
POST /api/v1/runs/{runUlid}/cases/{runCaseUlid}/apply-latest se rechaza con 409 conflict y details.reason: combination_removed cuando la última versión del caso ya no produce la combinación del caso de ejecución. La aplicación en lote, POST /api/v1/runs/{runUlid}/cases/apply-latest, muestra el mismo motivo en skipped.
GET /api/v1/runs/{runUlid}/case-keys devuelve una entrada por cada caso de ejecución, con sus parameters, para que un reporter distinga los casos de ejecución de un mismo caso.
Para las reglas detrás de estos campos, consulta Casos de prueba parametrizados y la referencia de la API.
Relacionado
Sección titulada «Relacionado»- Referencia API — paginación, errores, autenticación
- Referencia interactiva v1 — esquemas completos
- API de búsqueda de casos — búsqueda entre suites con
customFieldValues