Ir al contenido

Resultados, estados y reintentos en Jest

Cada comando jest reporta en una ejecución de Probara: una ejecución automatizada nueva por defecto, que se cierra al final, o una existente. Cada intento de cada prueba es un resultado en ella, con el veredicto del propio Jest. Esta página cubre la ejecución, los estados, los reintentos, qué pasa cuando los resultados no se pueden enviar, y quién recibe los fallos.

Estas opciones describen la ejecución que crea el reporter:

reporters: [
'default',
[
'@probara/jest-reporter',
{
run: {
name: 'Nightly regression',
description: 'Every night on main, against staging.',
environment: 'staging',
milestone: 'Sprint 12',
plan: 'PLAN-2',
configurations: [
{ group: 'Node', name: '22' },
{ group: 'OS', name: 'Linux' },
],
tags: ['nightly', 'unit'],
},
},
],
],
OpciónVariableQué define
run.namePROBARA_RUN_NAMEEl nombre. Por defecto: el nombre del build de CI (CI #42), si no Automated run <date> <time> UTC
run.descriptionPROBARA_RUN_DESCRIPTIONLa descripción, hasta 2000 caracteres (si es más larga se recorta, con una advertencia)
run.environmentPROBARA_ENVIRONMENTEl entorno, por nombre: se crea en el proyecto si ninguno tiene ese nombre
run.milestonePROBARA_MILESTONEEl hito, por ID visible (M-3) o nombre exacto
run.planPROBARA_PLANEl plan de pruebas, por ID visible (PLAN-2) o nombre exacto: la ejecución empieza con los casos del plan
run.configurationsPROBARA_CONFIGURATIONSValores de configuración por grupo y nombre (Node=22,OS=Linux), cada grupo una vez
run.tagsPROBARA_RUN_TAGSEtiquetas (nightly,unit), hasta 50

La misma ejecución desde el entorno de un paso de CI, con el proyecto en la configuración:

Ventana de terminal
PROBARA_RUN_NAME="Nightly regression" PROBARA_ENVIRONMENT=staging PROBARA_RUN_TAGS=nightly,unit npx jest

El entorno, el hito y las configuraciones también aceptan ULIDs: run.environmentId, run.milestoneId y run.configurationUlids (PROBARA_ENVIRONMENT_ID, PROBARA_MILESTONE_ID, PROBARA_CONFIGURATION_ULIDS). Define una sola forma de cada uno, nunca ambas: las dos a la vez son un problema de configuración que desactiva el envío. Los nombres son la forma que debes usar con un token de aplicación, que no puede consultar ULIDs.

  • Un nombre de entorno desconocido crea el entorno.
  • Un hito, un plan o una configuración desconocidos hacen que Probara rechace el primer informe (422, nombrando el campo): no se registra nada, y el registro dice por qué. Comprueba los nombres en Probara.

Una ejecución registra de dónde viene: la rama, el commit y la URL del build de CI, leídos de las variables de GitHub Actions, GitLab CI, CircleCI, Azure Pipelines, Jenkins, Bitbucket Pipelines y Buildkite (lo que rellena cada CI). Sustituye un campo con source o su variable, o no envíes ninguno:

reporters: ['default', ['@probara/jest-reporter', { source: { branch: 'release/2.4' } }]],
OpciónVariableValor por defecto
source.branchPROBARA_BRANCHdetectado del CI
source.commitPROBARA_COMMITdetectado del CI
source.buildUrlPROBARA_BUILD_URLdetectado del CI
source: false—no envía origen

El nombre del build de CI también da nombre a la ejecución cuando run.name no está definido.

run.ulid (PROBARA_RUN_ULID) reporta en una ejecución que ya existe, como la que creó probara run create para los shards de un pipeline (sharding) o una que alguien creó en Probara (selección de pruebas). El reporter añade sus resultados a esa ejecución y la deja abierta:

$ PROBARA_RUN_ULID=01J9Z3K4M5N6P7Q8R9S0T1V2W3 npx jest
[probara] Sending 3 results of 3 tests (3 passed, 0 failed, 0 skipped, 0 blocked)
[probara] Recorded 3 results (2 new cases, 0 unmatched) in R-1 (open): https://app.probara.net/projects/SHOP/runs/R-1

Las opciones de una ejecución nueva se ignoran entonces, con una advertencia: la ejecución conserva su propio nombre, entorno, hito y configuraciones.

[probara] Ignored name: a reused run (run.ulid) keeps its own

Para obtener el ULID de una ejecución en Probara, abre la ejecución y elige Copiar ULID de la ejecución en Acciones de la ejecución (el menú de la cabecera de la ejecución).

La ejecuciónAl final de jest
Creada por el reporterSe cierra, cuando se enviaron todos los resultados y archivos
Reutilizada (run.ulid)Queda abierta: la cierra quien la creó
Creada en una sesión --watchQueda abierta, para la siguiente repetición
Cualquiera, cuando un informe falló tras los reintentosQueda abierta: el registro la nombra, y el error dice qué hacer

closeRun (PROBARA_CLOSE_RUN) decide para cada ejecución de un comando que no sea una sesión watch: true cierra también una ejecución reutilizada, false deja abierta una creada. closeRuns decide por proyecto ({ SHOP: true, WEB: false }) cuando closeRun no está definido. Cuando falla un informe, lo que no se envió puede ir a un archivo de resultados, que probara import results envía después a la misma ejecución, y la cierra como lo habría hecho el reporter.

Cierra una ejecución a mano con la CLI de Probara:

Ventana de terminal
npx @probara/cli run close --run-ulid 01J9Z3K4M5N6P7Q8R9S0T1V2W3

Con varios proyectos de Probara, cada proyecto tiene su propia ejecución. El nombre, la descripción, el entorno (por nombre) y las etiquetas van con cada ejecución nueva; el hito, el plan y las configuraciones solo van con la ejecución del proyecto configurado.

Cada intento se envía con el veredicto del propio Jest:

El intentoEstado
Aprobadopassed
Fallido: una aserción, un error, un tiempo agotado, un beforeAll o beforeEach que lanzó un errorfailed
test.skip, xit, un describe omitido, una prueba excluida por .only o -tskipped
test.todoskipped, con la nota Todo
test.failing, y lanzó un error como se esperabapassed
test.failing, pero pasófailed

El reporter nunca envía blocked por su cuenta: mapea un estado a él (más abajo) cuando lo necesites.

tests/statuses.test.js
test('passes', () => {});
test('fails', () => {
expect(1 + 1).toBe(3);
});
test.skip('is skipped', () => {});
test.todo('is not written yet');
test.failing('is a known bug', () => {
expect(0.1 + 0.2).toBe(0.3);
});
[
{ "automationKey": "tests/statuses.test.js > passes", "status": "passed" },
{ "automationKey": "tests/statuses.test.js > fails", "status": "failed" },
{
"automationKey": "tests/statuses.test.js > is not written yet",
"status": "skipped",
"notes": "Todo"
},
{ "automationKey": "tests/statuses.test.js > is a known bug", "status": "passed" },
{ "automationKey": "tests/statuses.test.js > is skipped", "status": "skipped" }
]
  • Orden. Las pruebas se envían en el orden en que las reporta Jest: las que se ejecutaron y las todo a medida que terminan, y después las pruebas omitidas del archivo, cuando este termina.
  • Los errores van a las notas del resultado: cada error del intento, con su mensaje y su traza, sin los colores de la terminal, después de un comentario si la prueba escribió uno.
  • Las pruebas todo nunca se pierden: son resultados omitidos con la nota Todo, así que el caso muestra lo que queda por escribir. probara import junit las registra como aprobadas, porque jest-junit las escribe como testcases aprobados.
  • Los fallos esperados son los de Jest: una prueba test.failing que lanza un error hace lo que debe, así que pasa, y la ejecución sigue en verde en Probara igual que en Jest. Cuando se corrige el bug, la prueba pasa, Jest la hace fallar, y Probara también, lo que te recuerda quitar .failing.
  • Las pruebas omitidas no tienen duración: Jest no las ejecuta. En Jest 29 reciben la hora de inicio de su archivo; Jest 30 da a cada una la suya.

Un hook afterAll que lanza un error hace fallar el archivo de prueba después de que sus pruebas pasaron. Jest muestra el error y hace fallar el comando; sus pruebas siguen en verde. Para que Probara tampoco muestre el archivo en verde, el reporter envía un resultado fallido más para el archivo, el que escribe jest-junit, con el error en sus notas:

tests/cleanup.test.js
afterAll(() => {
throw new Error('Could not drop the test database');
});
test('creates an order', () => {});
[
{ "automationKey": "tests/cleanup.test.js > creates an order", "status": "passed" },
{
"automationKey": "tests/cleanup.test.js > Test execution failure: could be caused by test hooks like 'afterAll'.",
"status": "failed"
}
]

Su clave es la que probara import junit da al mismo fallo en el informe de jest-junit, así que ambas herramientas lo registran en el mismo caso.

Un archivo de prueba que falla antes de que existan sus pruebas (un error de sintaxis, una importación que lanza un error) no tiene pruebas que reportar. El reporter lo nombra en una advertencia, y reporta los demás archivos como siempre:

$ npx jest
[probara] Could not report tests/broken.test.js: Jest could not run it (Cannot find module './missing-module' from 'tests/broken.test.js')
[probara] Sending 1 result of 1 test (1 passed, 0 failed, 0 skipped, 0 blocked)
[probara] Recorded 1 result (1 new case, 0 unmatched) in R-1 (closed): https://app.probara.net/projects/SHOP/runs/R-1

Corrige el archivo: la salida del propio Jest muestra el error completo.

statusMapping envía un estado como otro; statusFilter no envía ningún resultado con los estados indicados, después del mapeo. Ambos aceptan passed, failed, skipped y blocked:

reporters: [
'default',
[
'@probara/jest-reporter',
{ statusMapping: { failed: 'blocked' }, statusFilter: ['skipped'] },
],
],

Con una prueba que pasa, una que falla y un test.todo:

$ npx jest
[probara] Sending 2 results of 2 tests (1 passed, 0 failed, 0 skipped, 1 blocked); 1 left out by statusFilter
[probara] Recorded 2 results (2 new cases, 0 unmatched) in R-1 (closed): https://app.probara.net/projects/SHOP/runs/R-1
[probara] Filtered out 1 result by their status (statusFilter): not sent

Como variables: PROBARA_STATUS_MAPPING=failed=blocked,skipped=passed y PROBARA_STATUS_FILTER=skipped,blocked. Un valor que no es un estado, o un estado mapeado dos veces, es un problema de configuración que desactiva el envío. statusFilter: ['skipped'] también omite las pruebas todo: son resultados omitidos. El mapeo solo cambia lo que registra Probara: el código de salida de Jest sigue siendo el de las pruebas.

Cada intento de una prueba es un resultado propio. Una prueba que jest.retryTimes(2) reintenta dos veces envía tres resultados, en el orden en que se ejecutaron, cada uno con su estado, su duración, su error, sus pasos y sus archivos.

tests/dashboard.test.js
jest.retryTimes(1);
let attempts = 0;
test('loads the dashboard', () => {
attempts += 1;
// Fails the first time, passes on the retry.
expect(attempts).toBe(2);
});
$ npx jest
[probara] Sending 2 results of 1 test (1 passed, 1 failed, 0 skipped, 0 blocked)
[probara] Recorded 2 results (1 new case, 0 unmatched) in R-1 (closed): https://app.probara.net/projects/SHOP/runs/R-1
  • Lo que muestra Probara. El caso de la ejecución conserva el último resultado (passed aquí), y cada intento queda en su historial: una prueba inestable es un caso cuyo historial mezcla fallos y aprobados en la misma ejecución. El registro también lo dice: más resultados que pruebas.
  • Sin marca de inestable. El reporter no marca los intentos como inestables: los propios intentos son la evidencia. jest-junit y probara import junit conservan solo el último intento de una prueba reintentada.
  • Cada intento empieza vacío. Las llamadas probara.* se aplican al intento que las hizo, así que un reintento que termina antes no hereda los pasos, los archivos ni los metadatos del primer intento.
  • El resumen de Jest cuenta la prueba una vez, por su último intento; Probara cuenta cada intento.

Por defecto, Jest reintenta las pruebas fallidas de un archivo después de las demás pruebas del archivo; jest.retryTimes(n, { retryImmediately: true }) de Jest 30 reintenta cada una al momento. En ambos casos, cada intento se envía cuando termina, así que el orden de los intentos de una prueba es siempre el orden en que se ejecutaron.

JestQué cambia para Probara
retryImmediately, waitBeforeRetry (Jest 30)Cuándo se ejecuta cada intento; cada intento sigue siendo un resultado
logErrorsBeforeRetryLo que muestra Jest; el error de cada intento fallido está igualmente en su propio resultado
test.concurrent en Jest 29Jest nunca lo reintenta: un resultado
test.concurrent en Jest 30.0 a 30.4Cada intento es un resultado, pero las llamadas probara.* de un reintento van al primer intento: Jest no dice cuál se ejecuta
test.concurrent en Jest 30.5 y posterioresCada intento es un resultado, con sus propias llamadas probara.*

Para enviar solo el último intento, no reintentes en Jest: ninguna opción del reporter descarta intentos. Para que los fallos de las pruebas reintentadas no cuenten, mapéalos, sabiendo que se aplica a todos los fallos.

Cuando un informe no se puede enviar (Probara inalcanzable, la red caída, un informe rechazado), sus resultados se pierden salvo que los guardes. Define un archivo de resultados, y el reporter escribe en él lo que no pudo enviar al final de la ejecución; probara import results lo envía después, a las mismas ejecuciones.

reporters: ['default', ['@probara/jest-reporter', { resultsFile: 'probara-results.json' }]],

O PROBARA_RESULTS_FILE=probara-results.json, relativo al directorio en el que se ejecuta jest.

$ PROBARA_RESULTS_FILE=probara-results.json npx jest
[probara] Sending 3 results of 3 tests (3 passed, 0 failed, 0 skipped, 0 blocked)
[probara] 3 results were not sent: Probara answered 403 forbidden: <message from Probara>. No run was created or updated
[probara] Wrote the 3 results that were not sent to /work/shop/probara-results.json: send them with probara import results /work/shop/probara-results.json

Después, desde cualquier job que tenga el token, envía lo que haya quedado:

Ventana de terminal
npx @probara/cli import results 'probara-results*.json'

Pon el glob entre comillas: probara lo expande, y coincide con el archivo y sus hermanos. Cuando no coincide nada, no quedó nada sin enviar: la importación lo dice y termina con 0.

  • Solo cuando hace falta. El archivo solo se escribe cuando algo no se envió: si todos los resultados llegaron, no hay archivo.
  • A las mismas ejecuciones. El archivo nombra las ejecuciones a las que pertenecen sus resultados: una ejecución que el informe fallido dejó abierta recibe el resto de sus resultados, y una ejecución que nunca se creó la crea la importación con el mismo nombre y los mismos ajustes. Las ejecuciones se cierran como las habría cerrado el reporter.
  • La importación lo consume. probara import results envía cada archivo por separado, y después lo elimina (con su carpeta de adjuntos) cuando se enviaron todos sus resultados, o lo reescribe con solo lo que sigue sin enviarse. Importar de nuevo nunca envía un resultado dos veces, así que la importación se puede ejecutar sin riesgo en cada pipeline. --dry-run nunca toca los archivos.
  • Nunca toca un archivo anterior. Cuando ya hay un archivo en esa ruta (el de otro shard en un disco compartido, o el de una ejecución anterior), el reporter lo deja como está y escribe en el primer hermano libre: probara-results-2.json, después -3, y así sucesivamente, cada uno con su propia carpeta de adjuntos. El registro nombra el archivo escrito, y el glob los importa todos, del más antiguo al más reciente.
  • Nunca a medio escribir. Cada archivo se escribe completo primero en un archivo temporal de la misma carpeta (con un nombre que empieza por punto, que el glob nunca encuentra), y después aparece de golpe con su nombre: una importación que se ejecuta a la vez ve el archivo completo o no lo ve.

Los archivos que lleva un resultado (lo que copió probara.attach(), la salida de consola de captureOutput) se guardan junto al archivo de resultados, en su carpeta de adjuntos (probara-results-attachments/ para probara-results.json), y el archivo los nombra con rutas relativas a sí mismo. Mueve o descarga el archivo y su carpeta juntos, a cualquier lugar, e impórtalo desde allí: un artefacto de CI que descarga otro job conserva sus adjuntos.

Una subida que falla durante la importación no se guarda para otro intento: su resultado se registró, así que ya no está en el archivo, y la carpeta se va con el archivo cuando se enviaron todos los resultados. La importación lo registra y termina con 1.

Con el envío desactivado (PROBARA_ENABLED=false, o sin token), el reporter escribe cada intento en el archivo, incluidos los vínculos con los casos. Ejecuta las pruebas donde no hay token (un fork, una máquina sin red), e importa el archivo desde un job que lo tenga:

$ PROBARA_ENABLED=false PROBARA_RESULTS_FILE=probara-results.json npx jest
[probara] Wrote 3 results to /work/shop/probara-results.json: send them with probara import results /work/shop/probara-results.json

El mismo archivo muestra qué se enviaría sin enviarlo (comprobar qué se enviaría).

Guarda los archivos y sus carpetas de adjuntos como artefactos del job de pruebas, e impórtalos en un job (o paso) posterior que tenga el token:

- name: Run Jest tests
run: npx jest
env:
PROBARA_API_TOKEN: ${{ secrets.PROBARA_API_TOKEN }}
PROBARA_RESULTS_FILE: probara-results.json
- name: Send what Probara did not get
if: always()
run: npx --yes @probara/cli@0.2 import results 'probara-results*.json'
env:
PROBARA_API_TOKEN: ${{ secrets.PROBARA_API_TOKEN }}
PROBARA_ENABLED: ${{ secrets.PROBARA_API_TOKEN != '' }}

Si Probara solo estuvo caído un momento, la importación envía el resto. Si sigue sin poder, la importación termina con 1 y conserva en cada archivo lo que no pudo enviar; los archivos que envió desaparecen.

Un archivo de resultados es una entrada de confianza: sus adjuntos se suben desde las rutas que nombra, así que importa solo los archivos que escribieron tus propios jobs. La importación omite, con una advertencia, un adjunto cuya ruta relativa sale de la carpeta del archivo; las rutas absolutas se conservan.

La versión 1 de un documento JSON: el proyecto, las ejecuciones y los ajustes del reporter (nunca el token), y después cada resultado tal como lo construyó el reporter, antes de statusMapping (que se aplica cuando se envía el archivo). El formato completo está en el archivo de resultados de core, y la importación (varios archivos y globs, el resumen JSON, los códigos de salida) en probara import results.

assignFailedTo nombra a las personas que revisan los fallos. Cada informe pide a Probara que asigne cada caso de la ejecución que deja fallido, y que aún no tiene responsable, a una de ellas, por turnos, para que una ejecución nocturna que falla llegue a la lista de alguien sin que nadie tenga que clasificarla antes.

reporters: [
'default',
[
'@probara/jest-reporter',
{ assignFailedTo: ['ana@example.com', 'bo@example.com', 'cy@example.com'] },
],
],

O desde el entorno: PROBARA_ASSIGN_FAILED_TO=ana@example.com,bo@example.com. Con ana@example.com y bo@example.com como miembros del proyecto y cy@example.com no, dos casos fallidos van a Ana y a Bo, y Probara indica que un correo no coincidió con nadie:

$ npx jest
[probara] Sending 3 results of 3 tests (1 passed, 2 failed, 0 skipped, 0 blocked)
[probara] Probara warned: assignFailedTo: 1 of 3 emails did not match a member who can be assigned in this project
[probara] Recorded 3 results (3 new cases, 0 unmatched) in R-1 (closed): https://app.probara.net/projects/SHOP/runs/R-1
ReglaPor qué
Solo los casos de la ejecución que un informe deja en failedLos resultados aprobados, omitidos y bloqueados no necesitan a nadie
Solo los casos de la ejecución sin responsableUn responsable existente nunca se sustituye: alguien ya se encarga
Solo miembros activos que pueden ejecutar pruebas en el proyectoUn caso no se puede asignar a quien no puede trabajar en él
Los miembros de la lista se turnan, en el orden en que los indicasLos fallos se reparten, no se acumulan en el primer nombre
Un correo que no coincide con ningún miembro así se omiteLos resultados se registran igualmente; la advertencia de Probara los cuenta
  • Después de statusMapping. El estado que cuenta es el que registra Probara: con statusMapping: { failed: 'blocked' } no hay nada fallido, así que no se asigna nada.
  • La advertencia nunca nombra los correos, solo cuántos se omitieron: el registro de un job de CI no es el lugar para listar quién es miembro y quién no. Compara la lista con los miembros del proyecto en Probara.
  • Los turnos empiezan de nuevo con cada informe: cada informe (un bloque de hasta 500 resultados, un shard, una repetición en modo watch) da su primer caso fallido al primer miembro de la lista.
  • Cada informe de la ejecución lo pide, con la misma lista: una ejecución con varios proyectos de Probara lo pide en la ejecución de cada proyecto, y los shards de una ejecución compartida asignan cada uno los casos que dejan fallidos.
  • Un archivo de resultados conserva la lista. probara import results la envía con los resultados, salvo que --assign-failed-to o PROBARA_ASSIGN_FAILED_TO nombren a otras personas.

Relacionado: Configuración · Sharding y proyectos · Solución de problemas · Fuente: runs.md, statuses.md, retries.md, results-file.md, assign-failed.md