# Resultados y reintentos de Playwright

> La ejecución que crea @probara/playwright-reporter, cómo los intentos se convierten en estados, los reintentos y las pruebas inestables, la asignación de fallos y el archivo de resultados para lo que no se pudo enviar.

Cada `playwright test` envía sus resultados a una ejecución de Probara: una ejecución automatizada
nueva por defecto, cerrada al final, o una existente. Esta página cubre esa ejecución, el estado de
cada intento, los reintentos y lo que pasa con los resultados que no se pudieron enviar.

## Opciones de la ejecución

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

```ts
reporter: [
  [
    '@probara/playwright-reporter',
    {
      run: {
        name: 'Nightly regression',
        description: 'Every night on main, against staging.',
        environment: 'staging',
        milestone: 'Sprint 12',
        plan: 'PLAN-2',
        configurations: [
          { group: 'Browser', name: 'Chromium' },
          { 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` | La descripción, de hasta 2000 caracteres (lo que exceda se recorta, con una advertencia)                      |
| `run.environment`    | `PROBARA_ENVIRONMENT`     | El entorno, por nombre: se crea en el proyecto si no hay ninguno con ese nombre                               |
| `run.milestone`      | `PROBARA_MILESTONE`       | El hito, por ID visible (`M-3`) o por nombre exacto                                                           |
| `run.plan`           | `PROBARA_PLAN`            | El plan de prueba, por ID visible (`PLAN-2`) o por nombre exacto: la ejecución empieza con los casos del plan |
| `run.configurations` | `PROBARA_CONFIGURATIONS`  | Valores de configuración por grupo y nombre (`Browser=Chromium,OS=Linux`), cada grupo una vez                 |
| `run.tags`           | `PROBARA_RUN_TAGS`        | Etiquetas (`nightly,e2e`), hasta 50                                                                           |

### Nombres o ULIDs

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`). Usa una sola forma para cada uno, nunca las dos: las dos a la vez
son un problema de configuración que desactiva el envío. Con un token de aplicación, usa los
nombres: el token 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 explica por qué. Revisa 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 del CI. Gana el primer proveedor que coincide:

| CI                  | Se detecta por           | Nombre de la ejecución                            | Rama, commit y URL del build desde                                                               |
| ------------------- | ------------------------ | ------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| GitHub Actions      | `GITHUB_ACTIONS=true`    | `GITHUB_WORKFLOW #GITHUB_RUN_NUMBER`              | `GITHUB_HEAD_REF` o `GITHUB_REF_NAME`, `GITHUB_SHA`, la URL de la ejecución                      |
| GitLab CI           | `GITLAB_CI=true`         | `CI_PROJECT_NAME #CI_PIPELINE_IID`                | `CI_MERGE_REQUEST_SOURCE_BRANCH_NAME` o `CI_COMMIT_REF_NAME`, `CI_COMMIT_SHA`, `CI_PIPELINE_URL` |
| CircleCI            | `CIRCLECI=true`          | `CIRCLE_PROJECT_REPONAME #CIRCLE_BUILD_NUM`       | `CIRCLE_BRANCH`, `CIRCLE_SHA1`, `CIRCLE_BUILD_URL`                                               |
| Azure Pipelines     | `TF_BUILD=True`          | `BUILD_DEFINITIONNAME #BUILD_BUILDNUMBER`         | La rama de origen, `BUILD_SOURCEVERSION`, la URL de los resultados del build                     |
| Jenkins             | `JENKINS_URL` definida   | `JOB_NAME #BUILD_NUMBER`                          | `BRANCH_NAME` o `GIT_BRANCH`, `GIT_COMMIT`, `BUILD_URL`                                          |
| Bitbucket Pipelines | `BITBUCKET_BUILD_NUMBER` | `BITBUCKET_REPO_SLUG #BITBUCKET_BUILD_NUMBER`     | `BITBUCKET_BRANCH`, `BITBUCKET_COMMIT`, la URL del pipeline                                      |
| Buildkite           | `BUILDKITE=true`         | `BUILDKITE_PIPELINE_SLUG #BUILDKITE_BUILD_NUMBER` | `BUILDKITE_BRANCH`, `BUILDKITE_COMMIT`, `BUILDKITE_BUILD_URL`                                    |

Un build de una etiqueta no envía rama. Los valores no válidos se descartan, nunca se envían.
Sustituye un campo con `source` o con su variable, o no envíes ninguno:

```ts
reporter: [['@probara/playwright-reporter', { source: { branch: 'release/2.4' } }]],
```

| Opción            | Variable            | 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`) envía a una ejecución que ya existe, como la que
`probara run create` crea para los shards de un pipeline
([shards](https://docs.probara.net/es/developers/ci-integrations/playwright/sharding/)). Las opciones de una ejecución nueva
se ignoran entonces, con una advertencia: la ejecución conserva su propio nombre, entorno, hito y
configuraciones.

### Cerrar la ejecución

Una ejecución que crea el reporter se cierra al final, cuando se han enviado todos los resultados y
archivos; una reutilizada queda abierta. `closeRun` (`PROBARA_CLOSE_RUN`) decide para cualquier
ejecución. Cuando un informe falla, la ejecución queda abierta: el registro la nombra, y lo que no se
envió puede ir a un [archivo de resultados](#archivo-de-resultados).

## Asignar los resultados fallidos

`assignFailedTo` (`PROBARA_ASSIGN_FAILED_TO=ana@example.com,bo@example.com`) nombra hasta 20
miembros de la organización por email. Cada informe de la ejecución pide a Probara que asigne cada
caso de la ejecución que deja fallido y sin responsable a uno de ellos, por turnos; un caso que ya
tiene responsable lo conserva.

- Los emails se recortan y cuentan una sola vez, sin distinguir mayúsculas. Un valor que no es un
  email, o más de 20 emails, es un problema de configuración que desactiva el envío.
- Un email que no coincide con ningún miembro asignable en el proyecto no es un error: Probara los
  cuenta en una advertencia, que el reporter registra sin nombrarlos.

## Estados

Cada intento se envía con el veredicto de Playwright: lo que hizo el intento comparado con lo que la
prueba esperaba.

| El intento                                                                            | Estado    |
| ------------------------------------------------------------------------------------- | --------- |
| Pasó                                                                                  | `passed`  |
| Falló: una aserción, un error, un hook o un fixture lanzó un error                    | `failed`  |
| Agotó su tiempo                                                                       | `failed`  |
| Se interrumpió (la ejecución se detuvo: `Ctrl+C`, `--max-failures`, un tiempo global) | `failed`  |
| `test.skip()`, `test.fixme()`, un `describe` omitido                                  | `skipped` |
| `test.fail()`, y falló como se esperaba                                               | `passed`  |
| `test.fail()`, pero pasó                                                              | `failed`  |

El reporter nunca envía `blocked` por su cuenta: mapea un estado a él
([abajo](#mapeo-y-filtro-de-estados)) cuando lo necesites.

```ts
// tests/statuses.spec.ts
import { expect, test } from '@playwright/test';

test('passes', async () => {});

test('fails', async () => {
  expect(1 + 1).toBe(3);
});

test('is skipped on CI', async () => {
  test.skip(true, 'Needs the payment sandbox');
});

test.fixme('is not written yet', async () => {});

test('is a known bug', async () => {
  test.fail(true, 'SHOP-99: totals round down');
  expect(0.1 + 0.2).toBe(0.3);
});
```

```json
[
  { "automationKey": "statuses.spec.ts > passes", "status": "passed" },
  { "automationKey": "statuses.spec.ts > fails", "status": "failed" },
  {
    "automationKey": "statuses.spec.ts > is skipped on CI",
    "status": "skipped",
    "notes": "Skipped: Needs the payment sandbox"
  },
  { "automationKey": "statuses.spec.ts > is not written yet", "status": "skipped" },
  { "automationKey": "statuses.spec.ts > is a known bug", "status": "passed" }
]
```

- **Los errores** van en las notas del resultado: todos los errores del intento, con su mensaje y
  su stack, después de un [comentario](https://docs.probara.net/es/developers/ci-integrations/playwright/metadata/#comentario)
  si la prueba escribió uno.
- **Los motivos de omisión** van en las notas como `Skipped: <reason>`, la misma línea que escribe
  `probara import junit`, a partir de `test.skip(condition, reason)` o `test.fixme(condition, reason)`.
- **Los fallos esperados** son los de Playwright: una prueba con `test.fail()` que falla hace lo que
  debe, así que pasa, y la ejecución sigue en verde en Probara igual que en Playwright. Cuando se
  corrige el bug, la prueba pasa, Playwright la hace fallar, y Probara también, para recordarte que
  quites `test.fail()`.
- **Los tiempos agotados** conservan el mensaje de Playwright (`Test timeout of 30000ms exceeded.`)
  en las notas.

### 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. Los dos aceptan `passed`, `failed`, `skipped` y `blocked`:

```ts
reporter: [
  [
    '@probara/playwright-reporter',
    { statusMapping: { failed: 'blocked' }, statusFilter: ['skipped'] },
  ],
],
```

Con una prueba que pasa, una que falla y una omitida:

```text
$ npx playwright test
[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
[probara] Attached 1 file to results (0 skipped, 0 failed)
```

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. El mapeo solo cambia lo que registra
Probara: el código de salida de Playwright sigue siendo el de las pruebas.

## Reintentos y pruebas inestables

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

```ts
// playwright.config.ts (excerpt)
retries: 1,
```

```ts
// tests/flaky.spec.ts
import { expect, test } from '@playwright/test';

test('loads the dashboard', async ({}, testInfo) => {
  // Fails the first time, passes on the retry.
  expect(testInfo.retry).toBe(1);
});
```

```json
[
  { "automationKey": "flaky.spec.ts > loads the dashboard", "status": "failed" },
  { "automationKey": "flaky.spec.ts > loads the dashboard", "status": "passed" }
]
```

```text
$ npx playwright test
[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
[probara] Attached 1 file to results (0 skipped, 0 failed)
```

El archivo es el contexto de error que Playwright escribió para el intento fallido.

- **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. El registro también lo indica: más resultados que pruebas.
- **Sin marca de inestable.** El reporter no marca los intentos como inestables: los propios
  intentos son la evidencia, y el veredicto de Playwright (flaky, cuando un reintento pasó) se queda
  en los informes de Playwright.
- **Cada intento empieza vacío.** Los metadatos `probara.*` se aplican al intento que los definió,
  así que un reintento que se detiene antes no hereda los metadatos del primer intento.
- **Orden.** Los resultados se envían en el orden en que Playwright termina los intentos; como el
  caso de la ejecución conserva el último resultado, el reintento final es el que muestra.

Para enviar solo el último intento, filtra en Playwright: ninguna opción del reporter descarta
intentos. Para que los fallos de las pruebas reintentadas no cuenten en tus cifras,
[mapéalos](#mapeo-y-filtro-de-estados), sabiendo que el mapeo se aplica a todos los fallos.

## Archivo de resultados

Cuando un informe no se puede enviar (Probara inaccesible, la red caída, un informe rechazado), sus
resultados se pierden salvo que los conserves. 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 más
tarde, a las mismas ejecuciones.

```ts
reporter: [['@probara/playwright-reporter', { resultsFile: 'probara-results.json' }]],
```

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

### Guardar lo que no se pudo enviar y enviarlo después

```text
$ PROBARA_RESULTS_FILE=probara-results.json npx playwright test
[probara] Sending 2 results of 2 tests (2 passed, 0 failed, 0 skipped, 0 blocked)
[probara] 2 results were not sent: Probara answered 403 forbidden: <message from Probara>. No run was created or updated
[probara] Wrote the 2 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 quedó con la
[CLI de Probara](https://docs.probara.net/es/developers/cli/):

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

Pon el glob entre comillas: `probara` lo expande, y coincide con el archivo y con los hermanos que
se describen abajo. Si no coincide nada, no quedó nada sin enviar: la importación lo indica y
termina con código 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 borra (junto con su carpeta `<name>-attachments/`) cuando se enviaron todos sus resultados, o
  lo reescribe solo con lo que sigue pendiente. Importar otra vez nunca envía un resultado dos
  veces, así que es seguro ejecutar la importación en todos los pipelines. `--dry-run` nunca toca
  los archivos.
- **Nunca toca un archivo anterior.** Si 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 entero primero en un archivo temporal de la
  misma carpeta (con un nombre que empieza por punto, que el glob nunca encuentra) y aparece con su
  nombre de golpe: una importación simultánea ve el archivo entero o nada, y una ejecución detenida
  a medias no deja ningún archivo parcial.
- **Adjuntos por ruta.** Los archivos se referencian donde los escribió Playwright, por ruta
  absoluta: conserva la carpeta de salida (`test-results/`) en su sitio hasta que se envíe el
  archivo. Los cuerpos en memoria se escriben junto al archivo, en `<name>-attachments/`, y se
  referencian de forma relativa a él, así que el archivo y esa carpeta pueden moverse juntos. Una
  subida que falla durante la importación no se guarda para otro intento: su resultado ya se
  registró, así que ya no está en el archivo. La importación lo registra y termina con código 1.

Una segunda ejecución que no puede enviar mientras el archivo de la primera sigue ahí escribe un
hermano:

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

### Envío desactivado: guardarlo todo

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

```text
$ PROBARA_ENABLED=false PROBARA_RESULTS_FILE=probara-results.json npx playwright test
[probara] Wrote 2 results to /work/shop/probara-results.json: send them with probara import results /work/shop/probara-results.json
```

El mismo archivo muestra lo que se enviaría sin enviarlo: consulta
[comprobar qué se enviaría](https://docs.probara.net/es/developers/ci-integrations/playwright/troubleshooting/#comprobar-qué-se-enviaría).

### En CI

Guarda los archivos, sus carpetas `<name>-attachments/` y la carpeta de salida como artefactos del
job de pruebas, e impórtalos en un job posterior que tenga el token. Un archivo de resultados y su
carpeta se pueden descargar en cualquier sitio; los archivos de la carpeta de salida se buscan en la
ruta absoluta que tenían, así que restáurala en esa ruta, o importa en el mismo job:

```yaml
- name: Run Playwright tests
  run: npx playwright test
  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 inaccesible un momento, la importación envía el resto. Si sigue sin poder,
la importación termina con código 1 y conserva en cada archivo lo que no pudo enviar; los archivos
que sí envió desaparecen.

Un archivo de resultados es una entrada de confianza: la importación sube los archivos que nombra,
desde cualquier ruta que el job pueda leer, así que importa solo los archivos que escribieron tus
propios jobs. Un adjunto cuya ruta relativa sale de la carpeta del archivo se deja fuera con una
advertencia.

### El formato

El archivo es 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 al enviar el archivo. El formato completo está en el
[archivo de resultados de `@probara/core`](https://github.com/cynch-me/probara-sdk/blob/main/packages/core/README.md#results-file)
(en inglés), y la importación (varios archivos y globs, el resumen JSON, los códigos de salida) en
[`probara import results`](https://github.com/cynch-me/probara-sdk/blob/main/packages/cli/docs/commands.md#probara-import-results).

Siguiente: [una ejecución para pipelines con shards](https://docs.probara.net/es/developers/ci-integrations/playwright/sharding/),
o [solución de problemas](https://docs.probara.net/es/developers/ci-integrations/playwright/troubleshooting/#la-ejecución-quedó-abierta).
