# Resultados, estados y reintentos en Cypress

> La ejecución en la que reporta cada cypress run, cómo se convierten en estados los intentos, los hooks y las specs rotas, los reintentos y las pruebas inestables, el archivo de resultados para lo que no se pudo enviar, y la asignación de resultados fallidos.

Cada `cypress run` reporta en una ejecución de Probara: una ejecución automatizada nueva por defecto,
cerrada al final, o una existente. Cada intento de cada prueba es un resultado en ella, con el
veredicto del propio Cypress. 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.

## La ejecución

El plugin crea la ejecución, envía a ella cada resultado de cada spec y la cierra cuando Cypress
termina la ejecución. Estas opciones describen la ejecución que crea:

```js
// cypress.config.js
reporterOptions: {
  projectId: 'SHOP',
  run: {
    name: 'Nightly regression',
    description: 'Every night on main, against staging.',
    environment: 'staging',
    milestone: 'Sprint 12',
    plan: 'PLAN-2',
    configurations: [
      { group: 'Browser', name: 'Chrome' },
      { group: 'OS', name: 'Linux' },
    ],
    tags: ['nightly', 'e2e'],
  },
},
```

| Opción               | Variable                  | Qué define                                                                                            |
| -------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------- |
| `run.name`           | `PROBARA_RUN_NAME`        | El nombre. Por defecto: el nombre del build de CI (`CI #42`), si no `Automated run <date> <time> UTC` |
| `run.description`    | `PROBARA_RUN_DESCRIPTION` | Para qué es la ejecución                                                                              |
| `run.environment`    | `PROBARA_ENVIRONMENT`     | El entorno, por nombre: se crea en el proyecto si ninguno tiene ese nombre                            |
| `run.milestone`      | `PROBARA_MILESTONE`       | El hito, por ID visible (`M-3`) o nombre exacto                                                       |
| `run.plan`           | `PROBARA_PLAN`            | El plan de pruebas, por ID visible (`PLAN-2`) o nombre: la ejecución empieza con los casos del plan   |
| `run.configurations` | `PROBARA_CONFIGURATIONS`  | Valores de configuración por grupo y nombre (`Browser=Chrome,OS=Linux`), cada grupo una vez           |
| `run.tags`           | `PROBARA_RUN_TAGS`        | Etiquetas (`nightly,e2e`)                                                                             |

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

```bash
PROBARA_RUN_NAME="Nightly regression" PROBARA_ENVIRONMENT=staging PROBARA_RUN_TAGS=nightly,e2e npx cypress run
```

Un nombre de más de 200 caracteres se recorta, y también una etiqueta de más de 80; las etiquetas
que pasan de 50 se descartan, con una advertencia. Un entorno, un hito o un plan que supera su límite
desactiva el envío, nombrando el campo: tiene que coincidir con un nombre de Probara, y uno recortado
no coincidiría con ninguno.

Sin el plugin, cada spec reporta en una ejecución propia, porque Cypress nunca avisa al reporter de
cuándo terminó toda la ejecución
([registro](https://docs.probara.net/es/developers/ci-integrations/cypress/configuration/#registro)).

### Nombres o ULIDs

El entorno, el hito y las configuraciones aceptan también 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 las dos: las dos a la vez es
un problema de configuración que desactiva el envío. Los nombres son la forma que hay que usar con un
token de aplicación, que no puede buscar ULIDs.

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

### Origen de la ejecución

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 ([qué rellena cada CI](https://docs.probara.net/es/developers/ci-integrations/cypress/ci-providers/)). Sustituye un
campo con `source` o su variable, o no envíes ninguno:

| Opción            | Variable            | Valor por defecto |
| ----------------- | ------------------- | ----------------- |
| `source.branch`   | `PROBARA_BRANCH`    | detectado del CI  |
| `source.commit`   | `PROBARA_COMMIT`    | detectado del CI  |
| `source.buildUrl` | `PROBARA_BUILD_URL` | detectado del CI  |
| `source: false`   | —                   | no envía origen   |

### Una ejecución existente

`run.ulid` (`PROBARA_RUN_ULID`) reporta en una ejecución que ya existe, como una que
`probara run create` creó para las máquinas de un pipeline
([sharding](https://docs.probara.net/es/developers/ci-integrations/cypress/sharding/)) o una que una persona creó en Probara
([selección de pruebas](https://docs.probara.net/es/developers/ci-integrations/cypress/run-selection/)). El reporter añade
sus resultados a esa ejecución y la deja abierta:

```text
$ PROBARA_RUN_ULID=01J9Z3K4M5N6P7Q8R9S0T1V2W3 npx cypress run
[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
[probara] The run R-1 of SHOP stays open: close it in Probara, or with probara run close --project SHOP --run-ulid 01J9Z3K4M5N6P7Q8R9S0T1V2W3
```

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

```text
[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).

### Cierre

| La ejecución                                        | Al final de `cypress run`                                       |
| --------------------------------------------------- | --------------------------------------------------------------- |
| Creada por el reporter                              | Se cierra, sea cual sea el código de salida de las pruebas      |
| Reutilizada (`run.ulid`, `run.ulids`)               | Queda abierta: la cierra quien la creó                          |
| Cualquiera, si un informe falló tras sus reintentos | Queda abierta: el registro la nombra, y el error dice qué hacer |

`closeRun` (`PROBARA_CLOSE_RUN`) decide para cada ejecución: `true` cierra también una reutilizada,
`false` deja abierta una creada. `closeRuns` decide por proyecto (`{ SHOP: true, WEB: false }`)
cuando `closeRun` no está definido. Cierra una ejecución a mano con la
[CLI de Probara](https://docs.probara.net/es/developers/cli/):

```bash
npx @probara/cli run close --run-ulid 01J9Z3K4M5N6P7Q8R9S0T1V2W3
```

## Estados

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

| Lo que reporta Cypress                                                                       | Estado                              |
| -------------------------------------------------------------------------------------------- | ----------------------------------- |
| Una prueba que pasó                                                                          | `passed`                            |
| Una prueba que falló: una aserción, un error, un tiempo agotado                              | `failed`                            |
| Un intento que Cypress va a volver a ejecutar (un reintento)                                 | `failed`                            |
| `it.skip`, o `this.skip()` en la prueba o en un `beforeEach`                                 | `skipped`                           |
| Una prueba que un `beforeEach` fallido impidió ejecutar                                      | `skipped`                           |
| Un `beforeEach` o `before` que falla, reportado como `"before each" hook for "…"`            | `failed`, con ese nombre            |
| Una spec que lanza un error al cargarse (`An uncaught error was detected outside of a test`) | `failed`                            |
| Una spec que Cypress no puede analizar                                                       | `failed`, como `Spec failed to run` |

El reporter nunca envía `blocked` por su cuenta: mapea un estado a él cuando lo necesites
([mapeo de estados](#mapeo-y-filtro-de-estados)). Un resultado lleva el estado, la duración y la hora
de inicio que reportó Cypress, y el error de un intento fallido en sus notas, con su mensaje y su
traza y sin los colores de la terminal.

### Un hook que falla

Un `beforeEach` o un `before` no pertenece a ninguna prueba propia: lo que hizo pertenece a la prueba
para la que se ejecuta. Cuando falla, Cypress reporta el fallo con una prueba a la que da el nombre
del hook y de la prueba en curso, y el reporter conserva ese nombre, que es también el que da la
importación JUnit.

Un hook que falla **una vez** y pasa en el reintento es una prueba con dos resultados: el intento
fallido y el que pasó. Un hook que falla **en todos los intentos**, con `retries.runMode: 1`:

```js
// cypress/e2e/hooks.cy.js
describe('Checkout', () => {
  beforeEach(() => {
    throw new Error('the cart service is down');
  });

  it('never runs', () => {});
  it('never runs either', () => {});
});

describe('Profile', () => {
  it('still runs', () => {});
});
```

```text
cypress/e2e/hooks.cy.js > Checkout never runs                             failed
cypress/e2e/hooks.cy.js > Checkout "before each" hook for "never runs"    failed
cypress/e2e/hooks.cy.js > Checkout never runs either                      skipped
cypress/e2e/hooks.cy.js > Profile still runs                              passed
```

- `Checkout never runs` es el primer intento de la prueba, que falló porque falló su hook y que
  Cypress volvió a ejecutar.
- `"before each" hook for "never runs"` es el fallo que Cypress no pudo superar.
- `Checkout never runs either` no llegó a ejecutarse: el reporter envía como **omitida** cada prueba
  de la suite que no reportó ningún intento, algo que un informe JUnit no puede hacer, porque una
  prueba así no deja rastro en él. No se sabe que esté rota, solo que no se ejecutó.
- `Profile still runs`: un `beforeEach` que falla detiene el resto de **su propio** `describe`, nunca
  el resto de la spec.

### Una spec que Cypress no puede ejecutar

Una spec cuyo código de nivel superior lanza un error mientras Cypress la carga es un resultado
fallido, con el nombre que le da Cypress, y se reintenta como cualquier prueba:

```text
cypress/e2e/throws.cy.js > An uncaught error was detected outside of a test
```

Una spec que Cypress **no puede analizar** (un error de sintaxis) no ejecuta ninguna prueba. El
plugin convierte el fallo que contó Cypress en un resultado fallido, para que una spec rota nunca
aparezca en verde:

```text
cypress/e2e/broken.cy.js > Spec failed to run
```

Sus notas dicen `Cypress could not run this spec: 1 failure(s), no test ran`, y su caso queda solo en
la suite de la spec. Sin el plugin, ese resultado no existe.

### El código de salida es el de Cypress

Cypress termina un `cypress run` con el número de sus propias **pruebas** fallidas, y reportar no
cambia nada de eso: una prueba que falla dos veces es una prueba fallida, y el fallo de un hook es
una prueba propia. Lo que registró Probara (cada intento, un estado mapeado, un resultado filtrado,
una ejecución que no se pudo enviar) nunca llega al código de salida.

### Mapeo y filtro de estados

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

```js
reporterOptions: { projectId: 'SHOP', statusMapping: { failed: 'blocked' } },
```

```text
$ npx cypress run --spec cypress/e2e/cart.cy.js
[probara] Sending 5 results of 5 tests (3 passed, 0 failed, 1 skipped, 1 blocked)
```

Con `statusFilter: ['failed']` en su lugar, el intento fallido no se envía, y la línea `Sending` dice
cuántos se dejaron fuera:

```text
[probara] Sending 4 results of 4 tests (3 passed, 0 failed, 1 skipped, 0 blocked); 1 left out by statusFilter
```

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. Cypress sigue haciendo fallar el
comando: el mapeo y el filtro solo cambian lo que registra Probara.

- **Filtrar `skipped` deja fuera mucho**: cada `it.skip` y cada prueba que un hook fallido impidió
  ejecutar.
- **`blocked` distingue los fallos que no son de la propia prueba**: mapea `failed` a `blocked`
  cuando el fallo de un hook no deba leerse como una prueba que falla.

## Reintentos y pruebas inestables

Cada intento es un resultado propio, en el orden en que se ejecutaron los intentos, cada uno con su
estado, su duración, su error, sus pasos y sus archivos. Cuántos intentos hace Cypress lo decide su
propio ajuste, `retries`:

```js
// cypress.config.js
module.exports = defineConfig({
  e2e: {
    reporter: '@probara/cypress-reporter',
    reporterOptions: { projectId: 'SHOP' },
    setupNodeEvents(on, config) {
      return probaraNodeEvents(on, config);
    },
    retries: { runMode: 1 },
  },
});
```

```text
$ npx cypress run --spec cypress/e2e/retry.cy.js
[probara] Sending 2 results of 1 test (1 passed, 1 failed, 0 skipped, 0 blocked)
```

- **Qué 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
  aciertos en la misma ejecución. La línea `Sending` lo muestra: más resultados que pruebas.
- **El número de intento.** Desde el segundo intento, el resultado lleva un parámetro `attempt`
  (`2`, `3`, ...), junto a `browser`. Ninguno forma parte de la clave de automatización.
- **Sin marca de inestable.** Los propios intentos son la evidencia. `probara import junit` solo
  conserva el **último** intento de una prueba reintentada, así que una batería que pasa de la
  importación JUnit al reporter gana los intentos anteriores en el historial de cada caso inestable.
- **Cada intento empieza vacío.** Lo que dijeron los helpers `probara.*` pertenece al intento que lo
  dijo: los pasos, los archivos y los metadatos de un reintento son suyos, nunca heredados.
- **Capturas de un reintento.** Cada intento fallido recibe la captura que Cypress tomó para él.
- **El resumen propio de Cypress** cuenta cada prueba una vez, por su último intento; Probara cuenta
  cada intento.

Para enviar solo el último intento, no reintentes en Cypress: ninguna opción del reporter descarta
intentos.

## Archivo de resultados

Cuando un informe no se puede enviar (Probara inalcanzable, un token rechazado, un informe que falló
tras sus reintentos), sus resultados se pierden salvo que los guardes. Define un archivo de
resultados, y el reporter escribe en él lo que no pudo enviar; `probara import results` lo envía más
tarde, a las mismas ejecuciones.

```js
reporterOptions: { projectId: 'SHOP', resultsFile: 'results/probara-results.json' },
```

O `PROBARA_RESULTS_FILE=results/probara-results.json`, relativo al directorio desde el que se lanzó
`cypress run`.

```text
$ npx cypress run
[probara] Sending 1 result of 1 test (1 passed, 0 failed, 0 skipped, 0 blocked)
[probara] 1 result was not sent: Probara answered 403 forbidden: <message from Probara>. No run was created or updated
[probara] Wrote the 1 result that was not sent to /work/shop/results/probara-results.json: send them with probara import results /work/shop/results/probara-results.json
```

Después, desde cualquier job que tenga el token, envía lo que quedó pendiente:

```bash
npx @probara/cli import results 'results/probara-results*.json'
```

Pon el glob entre comillas: `probara` lo expande, y coincide con el archivo y sus hermanos.

- **Solo cuando hace falta.** El archivo solo se escribe cuando algo no se envió: cuando todos los
  resultados llegaron, no hay archivo.
- **A las mismas ejecuciones.** El archivo nombra las ejecuciones a las que pertenecen sus
  resultados, los ajustes que los enviaron (proyecto, ejecución, `rootDir`, `createMissingCases`) y
  cada resultado tal como lo construyó el reporter, así que llega a los mismos casos desde cualquier
  máquina. Nunca el token.
- **Nunca encima de un archivo anterior.** Cuando ya hay un archivo en esa ruta (el de otra máquina,
  o el de una ejecución anterior), el reporter escribe en el primer hermano libre:
  `probara-results-2.json`, después `-3`, y así sucesivamente.
- **Estados antes del mapeo.** El archivo guarda cada estado antes de `statusMapping`, que se vuelve
  a aplicar al enviar el archivo.

### Qué viaja con el archivo

| Archivos                                                       | En el archivo de resultados                                                              |
| -------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| Cuerpos y archivos de `probara.attach()`, la salida de consola | Copiados junto a él, en `probara-results-attachments/`, y nombrados relativos al archivo |
| Capturas y vídeos                                              | Nombrados por su ruta absoluta, sin copiar                                               |

Mueve o descarga el archivo y su carpeta de adjuntos juntos, e impórtalo desde ahí: un artefacto de
CI que descarga otro job conserva esos adjuntos. Una máquina que nunca tuvo las capturas y los vídeos
no puede enviarlos.

### Envío desactivado: guardarlo todo

Con el envío desactivado (`PROBARA_ENABLED=false`, o sin token), el reporter escribe **todos** los
resultados en el archivo. Ejecuta las pruebas donde no hay token, y envía el archivo desde un job que
lo tenga:

```bash
PROBARA_ENABLED=false PROBARA_PROJECT=SHOP PROBARA_RESULTS_FILE=results/probara-results.json npx cypress run
npx @probara/cli import results 'results/probara-results*.json'
```

El mismo archivo muestra qué se enviaría sin enviarlo
([comprobar qué se enviaría](https://docs.probara.net/es/developers/ci-integrations/cypress/troubleshooting/#comprobar-qué-se-enviaría)).
La importación (varios archivos y globs, `--dry-run`, códigos de salida) está en
[`probara import results`](https://github.com/cynch-me/probara-sdk/blob/main/packages/cli/docs/commands.md#probara-import-results)
(en inglés).

## Asignar resultados fallidos

`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 fallida llegue a la lista de alguien sin que nadie tenga que
clasificarla antes.

```js
reporterOptions: {
  projectId: 'SHOP',
  assignFailedTo: ['ana@example.com', 'bo@example.com'],
},
```

O desde el entorno: `PROBARA_ASSIGN_FAILED_TO=ana@example.com,bo@example.com`.

| Regla                                                            | Por qué                                                                      |
| ---------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| Solo los casos de la ejecución que un informe deja en `failed`   | Los resultados que pasaron, omitidos o bloqueados no necesitan a nadie       |
| Solo los casos de la ejecución sin responsable                   | Nunca se sustituye a un responsable: alguien ya se ocupa                     |
| Solo miembros activos que pueden ejecutar pruebas en el proyecto | Un caso no se puede asignar a quien no puede trabajar en él                  |
| Los miembros indicados se turnan, en el orden en que los indicas | Los fallos se reparten, no se acumulan en el primer nombre                   |
| Un email que no coincide con ningún miembro así se omite         | Los resultados se registran igualmente; la advertencia de Probara los cuenta |

- **Decide el último intento.** Una prueba inestable que pasa en su reintento deja su caso en
  `passed`, así que nunca se asigna, por muchos intentos suyos que fallaran.
- **Después de `statusMapping`.** Con `statusMapping: { failed: 'blocked' }`, nada queda fallido, así
  que nada se asigna.
- **La advertencia nunca nombra los emails**, solo cuántos se omitieron:
  `[probara] Probara warned: <message from Probara>`.
- **La lista** se recorta y se deduplica. Más de 20 emails, o un valor que no es un email, desactiva
  el envío, nombrando `assignFailedTo`.

Relacionado: [Configuración](https://docs.probara.net/es/developers/ci-integrations/cypress/configuration/) ·
[Sharding y proyectos](https://docs.probara.net/es/developers/ci-integrations/cypress/sharding/) ·
[Solución de problemas](https://docs.probara.net/es/developers/ci-integrations/cypress/troubleshooting/#la-ejecución-queda-abierta) ·
Fuente: [runs.md](https://github.com/cynch-me/probara-sdk/blob/main/packages/cypress-reporter/docs/runs.md),
[statuses.md](https://github.com/cynch-me/probara-sdk/blob/main/packages/cypress-reporter/docs/statuses.md),
[specs.md](https://github.com/cynch-me/probara-sdk/blob/main/packages/cypress-reporter/docs/specs.md),
[retries.md](https://github.com/cynch-me/probara-sdk/blob/main/packages/cypress-reporter/docs/retries.md),
[results-file.md](https://github.com/cynch-me/probara-sdk/blob/main/packages/cypress-reporter/docs/results-file.md),
[assign-failed.md](https://github.com/cynch-me/probara-sdk/blob/main/packages/cypress-reporter/docs/assign-failed.md)
