# Pasos y adjuntos en Jest

> Registra pasos anidados con probara.step(), adjunta archivos con probara.attach(), captura la salida de consola de cada prueba y conoce los límites que se aplican.

Los pasos de una prueba aparecen bajo su resultado en Probara, anidados tal como se ejecutaron, cada
uno con su estado, su duración y su error. Los archivos que adjunta la prueba aparecen en el
resultado, o en el paso en el que se adjuntaron. Jest no tiene pasos ni archivos propios (ni capturas
de pantalla ni trazas): lo que lleva un resultado es lo que la prueba registró con los helpers
`probara.*`, más su salida de consola si la activas.

## `probara.step()`

`probara.step(title, body, options)` ejecuta `body`, lo registra como un paso y devuelve lo que
devuelve el cuerpo, así que un paso puede calcular un valor que usa la prueba (`cart` más abajo):

```js
// tests/checkout.test.js
const { probara } = require('@probara/jest-reporter');

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

| 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; sus padres fallan      | 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 el cuerpo retorna ([más abajo](#promesas-y-thenables)) | Lo devuelve sin tocarlo                            |
| Ninguno (`probara.step('Seeded')`) | `passed`, sin duración                                                | Devuelve `undefined`                               |

- **El anidamiento** sigue 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.
- **La duración** se mide con el reloj de Node, así que los temporizadores simulados de Jest
  (`jest.useFakeTimers()`) nunca la alteran.
- **Un paso que nunca terminó** (una promesa que la prueba no esperó, aún pendiente cuando termina la
  prueba) es `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 se ejecuta ninguna prueba concreta: el cuerpo se ejecuta,
  y el paso no se registra.

### 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:

```js
// tests/refund.test.js
const { probara } = require('@probara/jest-reporter');

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 cualquier campo 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/jest/metadata/#solo-casos-creados)). Un caso
  existente conserva sus propios pasos.
- `expected` y `data` son texto: 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(...)` se comporta como
esperar el cuerpo. Un rechazo que la prueba nunca gestiona sigue siendo un rechazo no gestionado, que
hace fallar la prueba, igual que sin el paso.

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

```js
// tests/api.test.js
const { probara } = require('@probara/jest-reporter');

// A stand-in for a supertest request: it runs when awaited.
const request = () => ({ then: (resolve) => 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. Funciona desde cualquier lugar en el que se ejecute una
prueba: el cuerpo de la prueba, un hook `beforeEach` o `afterEach`, un helper o un page object al que
llama la prueba.

```js
// tests/receipt.test.js
const { writeFileSync } = require('node:fs');
const { tmpdir } = require('node:os');
const { join } = require('node:path');
const { probara } = require('@probara/jest-reporter');

test('prints the receipt', () => {
  const path = join(tmpdir(), 'receipt.txt');
  writeFileSync(path, 'Total: 25.00');
  probara.attach({ name: 'receipt', path });
  probara.attach({
    name: 'order',
    body: JSON.stringify({ id: 1042, total: 25 }),
    contentType: 'application/json',
  });
  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 `jest`        | —                                                                                                                         |
| `body`        | —                                                                                | Un string, 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 un string y `application/octet-stream` para bytes |

- **Se copia al llamarlo.** El archivo (o el cuerpo) se copia en el momento, así que una prueba puede
  borrarlo o sobrescribirlo justo después: Probara recibe lo que había en el momento de la llamada.
  `probara.attach()` devuelve una promesa ya resuelta; esperarla no hace daño.
- **Un string se envía tal cual**, nunca se decodifica: pasa un `Buffer` para contenido binario.
- **Un archivo que no se puede adjuntar** (porque no existe, por ejemplo) es una advertencia
  `[probara]` en el registro del reporter; la prueba continú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 a la que llame) va a ese paso, y Probara lo muestra bajo el paso:

```js
// tests/upload.test.js
const { probara } = require('@probara/jest-reporter');

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

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

## Salida de consola

Con `captureOutput: true` (`PROBARA_CAPTURE_OUTPUT=true`) y el archivo de setup del reporter, la
salida de consola de cada intento se adjunta como `stdout.log` (`console.log`, `console.info`,
`console.debug`) y `stderr.log` (`console.warn`, `console.error`), cuando la hay, incluidos sus hooks
`beforeEach` y los hooks `afterEach` de sus bloques `describe`:

```js
// jest.config.js
module.exports = {
  reporters: ['default', ['@probara/jest-reporter', { captureOutput: true }]],
  setupFilesAfterEnv: ['@probara/jest-reporter/setup'],
};
```

```js
// tests/logs.test.js
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
```

Jest sigue mostrando la salida como siempre. El archivo de setup envuelve la consola de cada archivo
de prueba, en cada worker; sin él, las pruebas se ejecutan y se reportan sin su salida, con una
advertencia:

```text
[probara] captureOutput needs the setup file: add setupFilesAfterEnv: ['@probara/jest-reporter/setup'] to the Jest config (first seen in tests/checkout.test.js; repeats are logged at debug)
```

| No se captura                                                                                                               | Por qué                                                                              |
| --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `console.dir`, `table`, `group`, `count`, `time`, `timeEnd`, `trace`, `assert`                                              | Solo se envuelven los cinco métodos de arriba                                        |
| `process.stdout.write`, `process.stderr.write`                                                                              | No pasan por la consola                                                              |
| La salida de las pruebas `test.concurrent`                                                                                  | Sus cuerpos se ejecutan fuera de los `beforeEach`/`afterEach` de cada prueba         |
| La salida de un `afterEach` de nivel superior del archivo de prueba                                                         | El `afterEach` del propio archivo de setup, que termina la captura, se ejecuta antes |
| Un método de consola que la prueba simuló (`jest.spyOn(console, 'log').mockImplementation(...)`, `console.log = jest.fn()`) | El mock recibe las llamadas; un spy que llama al original sí se captura              |
| La salida en `beforeAll`, `afterAll`, el cuerpo de un `describe` o el ámbito del módulo                                     | Ahí no se ejecuta ninguna prueba concreta                                            |

Cada flujo se corta a 32 MiB, el archivo más grande que acepta Probara, con una última línea que
indica el corte. Con `projects` de Jest, pasa el archivo de setup a cada proyecto que lo necesite
([proyectos de Jest](https://docs.probara.net/es/developers/ci-integrations/jest/sharding/#proyectos-de-jest)).

## Límites y qué se omite

| Límite                                                        | Qué pasa                                                                 |
| ------------------------------------------------------------- | ------------------------------------------------------------------------ |
| 32 MiB por archivo                                            | Un archivo mayor se omite con una advertencia                            |
| 20 archivos por resultado                                     | Los archivos que siguen a los 20 primeros 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 que está 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. Cada resultado con archivos cuesta dos peticiones más frente al
[límite de peticiones](https://docs.probara.net/es/developers/ci-integrations/jest/configuration/#límite-de-peticiones) de la
organización.

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. Hasta entonces, el reporter guarda sus copias en una
carpeta del directorio temporal del sistema, que se elimina cuando termina `jest`; con un
[archivo de resultados](https://docs.probara.net/es/developers/ci-integrations/jest/results-and-retries/#archivo-de-resultados),
las copias de lo que no se envió se guardan junto a él.

## 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 salen 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 omitió y por qué. Un archivo adjuntado
  dentro de un paso va a ese paso, no al resultado
  ([solución de problemas](https://docs.probara.net/es/developers/ci-integrations/jest/troubleshooting/#falta-un-adjunto)).

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