# Pasos y adjuntos en Vitest

> Registra pasos anidados con probara.step(), adjunta archivos con probara.attach(), envía los archivos de las anotaciones de Vitest y las capturas de los fallos, captura la salida de consola de cada intento y conoce los límites que se aplican.

Los pasos de una prueba se muestran bajo su resultado en Probara, anidados tal como se ejecutaron,
cada uno con su estado, su duración y su error. Vitest no tiene pasos propios: escríbelos con
`probara.step()`, que ejecuta su cuerpo y lo registra. Un resultado lleva también los archivos que
su intento adjuntó con `probara.attach()` y con el propio `context.annotate()` de Vitest, las
capturas de pantalla que Vitest toma de un fallo en modo navegador y, con `captureOutput`, la salida
de consola del intento.

## `probara.step()`

```ts
// tests/checkout.test.ts
import { probara } from '@probara/vitest-reporter';
import { expect, test } from 'vitest';

test('checks out', async () => {
  const cart = await probara.step('Open the cart', async () => ({ items: 2 }));
  await probara.step('Pay', async () => {
    probara.step('Enter the card', () => {
      expect(cart.items).toBe(2);
    });
    await probara.step('Confirm', async () => {
      expect(await Promise.resolve('declined')).toBe('paid');
    });
  });
});
```

```json
[
  {
    "status": "failed",
    "steps": [
      { "action": "Open the cart", "status": "passed" },
      {
        "action": "Pay",
        "status": "failed",
        "steps": [
          { "action": "Enter the card", "status": "passed" },
          { "action": "Confirm", "status": "failed" }
        ]
      }
    ]
  }
]
```

`probara.step(title, body, options)` ejecuta `body` y devuelve lo que este devuelve, así que un paso
puede calcular un valor que use la prueba (`cart`, arriba):

| El cuerpo                          | El paso                                                              | Qué hace `probara.step()`                          |
| ---------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------- |
| Devuelve un valor                  | `passed`                                                             | Devuelve su valor                                  |
| Lanza un error                     | `failed`, con el mensaje y la traza del error                        | Lanza el mismo error: la prueba falla como siempre |
| Devuelve una promesa (`async`)     | Termina cuando la promesa se resuelve: `passed`, o `failed`          | Devuelve una promesa que se resuelve igual         |
| Devuelve otro thenable             | Termina cuando vuelve el cuerpo (ver [abajo](#promesas-y-thenables)) | Lo devuelve intacto                                |
| Ninguno (`probara.step('Seeded')`) | `passed`, sin duración                                               | Devuelve `undefined`                               |

- **El anidamiento** sigue a las llamadas: un paso que empieza dentro del cuerpo de otro es su
  hijo, también a través de `await`. Dos pasos del mismo padre ejecutados con `Promise.all` son
  ambos sus hijos. Un error que sale del cuerpo de un paso hace fallar el paso, y también cada paso
  padre del que sale.
- **Un paso que nunca terminó** (una promesa que la prueba no esperó, todavía pendiente al acabar la
  prueba) queda `failed`, con el error `The step had not finished when the test ended`. Espera con
  `await` cada `probara.step()` que tenga un cuerpo `async`.
- **Hooks**: un paso en `beforeEach` o `afterEach` es un paso de la prueba en curso. En `beforeAll`,
  `afterAll` o el cuerpo de un `describe` no corre una sola prueba: el cuerpo se ejecuta, y el paso
  no se registra
  ([dónde llamar a los helpers](https://docs.probara.net/es/developers/ci-integrations/vitest/metadata/#dónde-llamar-a-los-helpers)).
- **Reintentos**: cada intento registra sus propios pasos; un reintento empieza sin ninguno
  ([reintentos y repeticiones](https://docs.probara.net/es/developers/ci-integrations/vitest/results-and-retries/#reintentos-y-repeticiones)).
- **Modo navegador**: un navegador no tiene un contexto asíncrono que seguir, así que un paso solo
  queda anidado bajo otro cuando empieza en la parte síncrona del cuerpo del otro. Un paso que
  empieza después de un `await` en un cuerpo no es hijo de ese paso.

### Pasos de un caso nuevo

Los pasos de un resultado registran lo que se ejecutó. Los pasos de un **caso** son su
especificación: una acción, el resultado esperado y los datos de prueba. Los pasos más externos de
un intento, en el orden en que empezaron, son los pasos del caso que crea el informe; `expected` y
`data` van con ellos:

```ts
// tests/refund.test.ts
import { probara } from '@probara/vitest-reporter';
import { test } from 'vitest';

test('refunds an order', () => {
  probara.step('Open the order', () => {}, { expected: 'The order is shown', data: 'order=1042' });
  probara.step(
    'Refund it',
    () => {
      probara.step('Confirm the dialog', () => {});
    },
    { expected: 'The order is refunded' },
  );
});
```

```json
[
  {
    "steps": [
      { "action": "Open the order", "expected": "The order is shown", "data": "order=1042" },
      {
        "action": "Refund it",
        "expected": "The order is refunded",
        "steps": [{ "action": "Confirm the dialog" }]
      }
    ],
    "case": {
      "steps": [
        { "action": "Open the order", "expected": "The order is shown", "data": "order=1042" },
        { "action": "Refund it", "expected": "The order is refunded" }
      ]
    }
  }
]
```

- Los pasos anidados (`Confirm the dialog`) son solo pasos del resultado: cómo funciona una prueba,
  no lo que especifica.
- Como todos los campos del caso, los pasos del caso solo se aplican cuando el informe crea el caso
  ([solo casos creados](https://docs.probara.net/es/developers/ci-integrations/vitest/metadata/#solo-casos-creados)). Un
  caso existente conserva sus propios pasos.
- `expected` y `data` son cadenas: un paso que recibe otra cosa se registra solo con su título, y
  una advertencia lo indica.

### Promesas y thenables

Un cuerpo que devuelve una promesa termina su paso cuando la promesa se resuelve. `probara.step()`
devuelve una promesa nueva que se resuelve igual, así que `await probara.step(...)` equivale a
esperar el cuerpo. Un rechazo que la prueba nunca gestiona sigue siendo un rechazo no gestionado,
que Vitest reporta, igual que sin el paso.

Un cuerpo también puede devolver un **thenable** que no es una promesa: una petición de
`supertest`, un constructor de consultas. Llamar a su `then()` lo iniciaría (o lo iniciaría dos
veces), así que `probara.step()` lo devuelve intacto, y el paso termina en cuanto vuelve el cuerpo,
antes de que se envíe la petición. Para medir la petición dentro del paso, espérala en un cuerpo
`async`:

```ts
// tests/api.test.ts
import { probara } from '@probara/vitest-reporter';
import { expect, test } from 'vitest';

// A stand-in for a supertest request: it runs when awaited.
const request = () => ({
  then: (resolve: (response: { status: number }) => void) => resolve({ status: 200 }),
});

test('answers the health check', async () => {
  const response = await probara.step('Call /health', async () => await request());
  expect(response.status).toBe(200);
});
```

```json
[{ "status": "passed", "steps": [{ "action": "Call /health", "status": "passed" }] }]
```

## `probara.attach()`

Pásale un archivo en disco o un cuerpo, y espéralo con `await`. Funciona desde cualquier lugar en
el que corre una prueba: el cuerpo de la prueba, un hook `beforeEach` o `afterEach`,
`onTestFinished`, un paso, una función auxiliar o un page object que llama la prueba.

```ts
// tests/receipt.test.ts
import { writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { probara } from '@probara/vitest-reporter';
import { test } from 'vitest';

test('prints the receipt', async () => {
  const path = join(tmpdir(), 'receipt.txt');
  writeFileSync(path, 'Total: 25.00');
  await probara.attach({ name: 'receipt', path });
  await probara.attach({
    name: 'order',
    body: JSON.stringify({ id: 1042, total: 25 }),
    contentType: 'application/json',
  });
  await probara.attach({ name: 'log', body: 'paid in 1.2 s' });
});
```

El resultado recibe tres archivos:

```text
receipt.txt text/plain
order.json application/json
log.txt text/plain
```

| Campo         | Con `path`                                                                       | Con `body`                                                                                                                 |
| ------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `name`        | Obligatorio: el nombre en Probara; se añade la extensión del archivo si no tiene | Obligatorio: el nombre en Probara; se añade la extensión del tipo de contenido si no tiene                                 |
| `path`        | El archivo, absoluto o relativo al directorio en el que se ejecuta `vitest`      | —                                                                                                                          |
| `body`        | —                                                                                | Una cadena, un `Buffer` o un `Uint8Array`, enviado tal cual                                                                |
| `contentType` | Opcional: se deduce del nombre del archivo                                       | Opcional: se deduce de la extensión de `name`; si no, `text/plain` para una cadena y `application/octet-stream` para bytes |

- **Se copia al llamarlo.** El archivo (o el cuerpo) se copia en ese momento, así que una prueba
  puede borrarlo o sobrescribirlo justo después: Probara recibe lo que había en la llamada.
- **Espéralo con `await`.** En Node la copia se hace antes de que vuelva la llamada; en modo
  navegador termina cuando se resuelve la promesa, y una copia que termina cuando su prueba ya acabó
  no se adjunta, con una advertencia que dice `Await probara.attach() before the test ends`
  ([`probara.attach()` en un navegador](https://docs.probara.net/es/developers/ci-integrations/vitest/browser-mode/#probaraattach-en-un-navegador)).
- **Una cadena se envía tal cual**, como texto UTF-8, nunca se decodifica: pasa un `Buffer` o un
  `Uint8Array` para contenido binario.
- **Un archivo que no se puede adjuntar** (no existe, es una carpeta, pesa más de 32 MiB) es una
  advertencia, como `probara.attach() did not attach "receipt": it does not exist`; la prueba
  continúa, y su resultado se envía.
- **Cada intento tiene sus propios archivos**: una prueba reintentada sube los archivos de cada
  intento, cada uno a su propio resultado.

### Archivos de un paso

Un archivo adjuntado mientras se ejecuta un `probara.step()`, desde el cuerpo del paso o desde
cualquier cosa que este llame, va a ese paso, y Probara lo muestra bajo el paso:

```ts
// tests/upload.test.ts
import { probara } from '@probara/vitest-reporter';
import { test } from 'vitest';

test('uploads an avatar', async () => {
  await probara.step('Choose the file', async () => {
    await probara.attach({ name: 'avatar.png', body: new Uint8Array([137, 80, 78, 71]) });
  });
});
```

```text
avatar.png image/png
```

## Anotaciones de Vitest

Los archivos del `context.annotate(message, attachment)` de Vitest se envían con el intento que los
creó, con el mensaje de la anotación como nombre. El mensaje en sí no se envía:
[`probara.comment()`](https://docs.probara.net/es/developers/ci-integrations/vitest/metadata/#comentario) escribe una nota.

```ts
// tests/export.test.ts
import { test } from 'vitest';

test('exports the report', async ({ annotate }) => {
  await annotate('summary', { body: 'Exported 3 rows', bodyEncoding: 'utf-8' });
});
```

```text
summary.txt text/plain
```

| Vitest                 | Un `body` de texto                                                       | Contenido binario                                                                                         |
| ---------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- |
| 4.1.3 y posteriores, 5 | Pasa `bodyEncoding: 'utf-8'`: sin él, Vitest marca el cuerpo como base64 | Un cuerpo `Uint8Array` con su `contentType`, o un `path`                                                  |
| 3.2, de 4.0 a 4.1.2    | Tal cual: todavía no hay `bodyEncoding`                                  | Un `path`: Vitest 3 pierde algunos bytes de un cuerpo binario, y ese cuerpo se omite, con una advertencia |

Vitest copia un `path` en su `attachmentsDir`, y el archivo en Probara recibe el mensaje de la
anotación y la extensión de la ruta: `annotate('screen', { path: 'shot.png' })` es `screen.png`.
Vitest rechaza `annotate()` cuando el cuerpo de la prueba ya terminó: en `afterEach` y
`onTestFinished`, usa `probara.attach()`.

## Capturas de pantalla de los fallos

En [modo navegador](https://docs.probara.net/es/developers/ci-integrations/vitest/browser-mode/), Vitest toma una captura
de pantalla cuando una prueba falla, y el reporter la adjunta a la ejecución fallida, como
`image/png`: la primera captura al primer intento fallido, y así sucesivamente, o todas las capturas
al último intento fallido cuando sus cantidades no coinciden. `attachScreenshots: false`
(`PROBARA_ATTACH_SCREENSHOTS=false`) las deja fuera. Una prueba que se ejecuta en Node no tiene
ninguna.

## Salida de consola

`captureOutput: true` (`PROBARA_CAPTURE_OUTPUT=true`) adjunta lo que cada intento escribe en la
consola: `console.log`, `console.info` y `console.debug` como `stdout.log`, y `console.warn` y
`console.error` como `stderr.log`, `text/plain`, cada uno solo cuando tiene texto. Está desactivado
por defecto y no necesita nada más: ni archivo de setup ni cambios en las pruebas. Vitest sigue
imprimiendo la salida como siempre.

```ts
reporters: ['default', ['@probara/vitest-reporter', { captureOutput: true }]],
```

```ts
// tests/logs.test.ts
import { beforeEach, test } from 'vitest';

beforeEach(() => {
  console.info('Seeding the cart');
});

test('prints what it does', () => {
  console.log('Opening the cart');
  console.error('Slow response: 2.1 s');
});
```

```text
stdout.log text/plain
stderr.log text/plain
```

Aquí `stdout.log` contiene `Seeding the cart` y `Opening the cart`, y `stderr.log` contiene
`Slow response: 2.1 s`: los hooks `beforeEach` y `afterEach` de una prueba escriben en su intento.

### Qué recibe cada intento

| Salida                                                                    | Adónde va                                                                                                                                                                  |
| ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| El cuerpo de la prueba, sus hooks `beforeEach` y `afterEach`              | El intento que la escribió                                                                                                                                                 |
| Cada intento de una prueba reintentada o repetida                         | Su propio `stdout.log` y `stderr.log`                                                                                                                                      |
| Una prueba `test.concurrent`, o una prueba en un `describe.concurrent`    | Se deja fuera, con una advertencia                                                                                                                                         |
| Una prueba reintentada cuyos intentos no se registraron                   | Se deja fuera, con una advertencia (ver [cuándo se ejecutan los reintentos](https://docs.probara.net/es/developers/ci-integrations/vitest/results-and-retries/#cuándo-se-ejecutan-los-reintentos)) |
| El ámbito del módulo, el cuerpo de un `describe`, `beforeAll`, `afterAll` | No se adjunta: Vitest no la asigna a ninguna prueba                                                                                                                        |

- **Las pruebas concurrentes se dejan fuera.** Vitest asigna cada escritura a la prueba en curso en
  su worker, que no siempre es la que la escribió cuando varias pruebas se ejecutan a la vez: antes
  que adjuntar una escritura a la prueba equivocada, el reporter deja fuera la salida de las pruebas
  concurrentes, y dice cuántas.
- **Los reintentos se separan por tiempo.** Los archivos de un intento contienen lo que Vitest
  recibió desde su inicio hasta el inicio del intento siguiente. Vitest entrega al reporter las
  escrituras de una prueba por lotes, así que una escritura hecha justo cuando empieza un reintento
  puede acabar en el archivo del intento anterior.

```ts
reporters: ['default', ['@probara/vitest-reporter', { captureOutput: true }]],
```

```ts
// tests/search.test.ts
import { test } from 'vitest';

test.concurrent('finds boots', async () => {
  console.log('searching for boots');
});

test('finds hats', () => {
  console.log('searching for hats');
});
```

Solo la prueba que se ejecuta sola recibe su salida, `stdout.log`, y el registro dice por qué la
otra no tiene ninguna:

```text
$ npx vitest run
[probara] The console output of 1 test is left out: Vitest attributes the writes of concurrent tests to the wrong test, and the attempts of a retried or repeated test without recorded attempts cannot be told apart (first seen in the run; repeats are logged at debug)
[probara] Sending 2 results of 2 tests (2 passed, 0 failed, 0 skipped, 0 blocked)
[probara] Recorded 2 results (2 new cases, 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)
```

### `silent` y `onConsoleLog`

`silent` y `onConsoleLog` de Vitest deciden lo que muestra la terminal, no lo que reciben los
reporters: la salida que ocultan se adjunta igualmente. Para que la salida de una prueba no llegue a
Probara, deja `captureOutput` desactivado.

```ts
silent: true,
reporters: ['default', ['@probara/vitest-reporter', { captureOutput: true }]],
```

```ts
// tests/quiet.test.ts
import { test } from 'vitest';

test('works quietly', () => {
  console.log('nothing shows in the terminal');
});
```

```text
stdout.log text/plain
```

### Límites de la salida

- **32 MiB por prueba y flujo.** A partir de ahí, el flujo conserva lo que cabe y termina con la
  línea `[probara] The output of this test was cut here: an attachment holds at most 32 MiB`. La
  salida de una ejecución completa no tiene límite.
- **Se guarda en memoria.** El reporter conserva la salida de las pruebas de la ejecución hasta que
  esta termina, cuando envía los resultados: una ejecución cuyas pruebas escriben mucho necesita la
  memoria para ello.
- **Las ejecuciones con sharding la conservan.** El blob de un shard contiene la salida de sus
  pruebas, y `vitest --merge-reports` adjunta los mismos archivos que una ejecución sin sharding
  ([sharding](https://docs.probara.net/es/developers/ci-integrations/vitest/sharding/)).

## Límites y qué se omite

| Límite                                                        | Qué pasa                                                              |
| ------------------------------------------------------------- | --------------------------------------------------------------------- |
| 32 MiB por archivo o cuerpo                                   | Uno mayor se omite, con una advertencia                               |
| 20 archivos por resultado                                     | Los archivos a partir del vigésimo se omiten, con una advertencia     |
| Imágenes de más de 10 MiB o de más de 8192 px de ancho o alto | Se omiten con una advertencia (Probara convierte las imágenes a WebP) |
| Ejecutables y scripts                                         | Se omiten: Probara rechaza sus tipos de contenido                     |
| Un archivo que falta, que no se puede leer o vacío            | Se omite con una advertencia                                          |
| Un resultado que no registró nada                             | Sus archivos no se suben                                              |

Las advertencias nombran el archivo, y el registro termina con cuántos archivos se adjuntaron, se
omitieron y fallaron. `uploadAttachments: false` (`PROBARA_UPLOAD_ATTACHMENTS=false`) no sube
ninguno. Los archivos se suben después de que su informe queda registrado, mientras la ejecución
sigue abierta, y después se cierra la ejecución.

## Dónde se guardan las copias

El reporter guarda sus copias en `<attachmentsDir>/probara/<id>/`, dentro del `attachmentsDir` de
Vitest (`.vitest-attachments` en Vitest 3 y 4, `.vitest/attachments` en Vitest 5, por defecto),
hasta que se envían:

| La ejecución                                                                             | Sus copias                                                         |
| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| Todos los reporters son de los que trae Vitest (`default`, `verbose`, `junit`...) o este | Se eliminan cuando se envía la ejecución                           |
| Un shard (`--shard`), una ejecución con `blob`, o cualquier otro reporter                | Se conservan: un blob puede nombrarlas, y una fusión las lee       |
| Resultados que no se pudieron enviar, con `resultsFile`                                  | Se copian junto al archivo de resultados, en `<name>-attachments/` |

- **Registra el reporter por el nombre de su paquete**, `'@probara/vitest-reporter'`. Indicado por
  una ruta o como objeto, cuenta como otro reporter, y sus copias se conservan.
- **Lleva el `attachmentsDir` con los blobs.** El blob de un shard nombra los archivos que adjuntó
  por su ubicación en la máquina del shard: copia el `attachmentsDir` del shard junto con su blob al
  job que ejecuta `vitest --merge-reports`, en la misma ruta. Un archivo que la fusión no encuentra
  se omite con la advertencia `Skipped an attachment: its file is missing or unreadable`, y su
  resultado se envía igualmente
  ([lleva los blobs y los adjuntos](https://docs.probara.net/es/developers/ci-integrations/vitest/sharding/#lleva-los-blobs-y-los-adjuntos)).
- **Las copias propias de Vitest** de las rutas de las anotaciones se quedan en `attachmentsDir`:
  Vitest nunca las elimina, y el reporter tampoco. Elimina la carpeta cuando crezca.

## Cuando faltan pasos o archivos

- **Falta un paso.** Solo `probara.step()` crea pasos: las llamadas a `expect` y las funciones
  auxiliares no. Un paso llamado fuera de una prueba (`beforeAll`, el cuerpo de un `describe`)
  ejecuta su cuerpo y no registra nada, con una advertencia.
- **Los pasos quedan aplanados, o fallan con `The step had not finished`.** Un paso iniciado sin
  `await` termina después de la prueba: espera con `await` cada `probara.step()` cuyo cuerpo sea
  `async`.
- **El caso no tiene pasos.** Solo un caso que crea el informe recibe pasos. Un caso existente
  conserva los suyos.
- **Falta un archivo.** El registro nombra cada archivo que dejó fuera, y por qué
  ([falta un adjunto](https://docs.probara.net/es/developers/ci-integrations/vitest/troubleshooting/#falta-un-adjunto)). Un
  archivo adjuntado dentro de un paso va a ese paso, no al resultado.

Relacionado: [Metadatos y enlaces](https://docs.probara.net/es/developers/ci-integrations/vitest/metadata/) ·
[Configuración](https://docs.probara.net/es/developers/ci-integrations/vitest/configuration/#captureoutput) ·
[Solución de problemas](https://docs.probara.net/es/developers/ci-integrations/vitest/troubleshooting/) · Fuente:
[steps.md](https://github.com/cynch-me/probara-sdk/blob/main/packages/vitest-reporter/docs/steps.md),
[attachments.md](https://github.com/cynch-me/probara-sdk/blob/main/packages/vitest-reporter/docs/attachments.md),
[output.md](https://github.com/cynch-me/probara-sdk/blob/main/packages/vitest-reporter/docs/output.md)
