Ir al contenido

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.

GET /api/v1/runs/{runUlid}/cases

Requiere viewer+ en el proyecto de la ejecución. Devuelve 404 si la ejecución está fuera de la organización del llamador.

Cada elemento del array cases incluye los campos habituales más:

CampoTipoDescripción
caseLifecycleStatusobject | nullEl 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[].optionSystemKeystring | nullLa 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.
executedByApiTokenobject | nullEl 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.
caseRevisionnumber | nullLa 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.
caseCurrentRevisionnumber | nullLa revisión actual del caso fuente (currentRevision del caso de prueba). null cuando el caso fue eliminado.
caseUpdatedbooleantrue 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.
canApplyLatestbooleantrue 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.

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.kind es api_token y su author.app tiene 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 (executedByApiToken tiene 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.

Cuando no es null, el objeto tiene la misma forma que un CustomFieldValueEntry:

CampoTipoDescripción
optionUlidstringULID de la opción de ciclo de vida seleccionada
optionNamestringNombre visible de la opción, resuelto según el idioma de la solicitud (ver más abajo)
optionColorstring | nullColor hexadecimal, si existe
optionIconstring | nullIdentificador de icono, si existe
optionSystemKeystring | nullClave 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.

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.

Ventana de terminal
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-keys

Devuelve 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.

CampoTipoDescripción
caseDisplayIdstringEl id de visualización del caso, por ejemplo ACME-14
automationKeystring | nullLa 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 responde 422 validation_failed) y cursor, el nextCursor de la página anterior. nextCursor es null en 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 responde 403 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 reciben 403 forbidden; un token de API personal o un token de aplicación llegan a todos los proyectos de su organización, incluidos los privados.
Ventana de terminal
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
}
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:

CampoDescripción
parametersLos parámetros que reportó el intento actual, [{ name, value }] en el orden enviado
linksLos enlaces que reportó el intento actual, [{ url, name }] en el orden enviado (name es null si no se envió)
resultStepsLos 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.

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.

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.

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

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.

POST /api/v1/runs/{runUlid}/cases/{runCaseUlid}/apply-latest
POST /api/v1/runs/{runUlid}/cases/apply-latest

Lleva 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ónerror.message / error.details.reason
La ejecución está cerradarun is closed
El caso fuente fue eliminadocase_deleted
El caso fuente no cambió desde que este caso de la ejecución lo congeló, sea cual sea su estadocase_not_outdated
El caso de la ejecución está completado o su intento tiene resultados de pasosrun_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}/cases

Reconcilia 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.

{
"caseUlids": ["01J...A", "01J...B", "01J...D"],
"caseAssignees": [{ "caseUlid": "01J...D", "assigneeUlid": "01J...USER" }]
}
CampoTipoDescripción
caseUlidsstring[]La selección completa deseada (ULIDs de caso fuente). Un array vacío es válido — elimina todos los casos de la ejecución.
caseAssigneesarray (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 caseUlids que 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 caso DELETE /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.

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.

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.

Ventana de terminal
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"

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 — caseUlids en POST /api/v1/projects/{projectId}/runs, en POST /runs/{runUlid}/cases y los casos que añade un PATCH /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 incluye notApprovedSkippedCount, cuántos quedaron fuera. Si ninguno está aprobado, la solicitud se rechaza con 422 validation_failed (details.code empty_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.

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.

EndpointCampoLímite
POST /api/v1/runs/{runUlid}/cases (agregar casos)caseUlids500
PATCH /api/v1/runs/{runUlid}/cases (reconciliar)caseUlids5000
POST /api/v1/projects/{projectId}/runs (crear ejecución)caseUlids5000

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.