# Pasos y adjuntos de Playwright

> Cómo @probara/playwright-reporter envía a Probara los árboles de test.step, los hooks, los pasos declarados de un caso, las capturas, los vídeos, las trazas y tus propios adjuntos.

Los pasos y los archivos de cada intento van con su resultado en Probara. Escribe los pasos con el
`test.step` de Playwright, activa las capturas, los vídeos y las trazas en `use` como siempre, y el
reporter los envía: no hay nada que activar en el reporter.

## Pasos

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.

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

test('checks out', async ({ page }) => {
  await test.step('Open the cart', async () => {
    await page.goto('https://shop.example.com/cart');
  });
  await test.step('Pay', async () => {
    await test.step('Enter the card', async () => {
      await page.getByLabel('Card number').fill('4242 4242 4242 4242');
    });
    await test.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" }
        ]
      }
    ]
  }
]
```

| Qué se ejecutó                                                                    | En el resultado                                         |
| --------------------------------------------------------------------------------- | ------------------------------------------------------- |
| Un `test.step`                                                                    | Un paso, con sus hijos                                  |
| Un hook (`beforeEach`, `afterEach`, `Before Hooks`...) que ejecutó un `test.step` | Un paso con el nombre del hook, que contiene esos pasos |
| Un hook o un fixture sin `test.step`                                              | Nada: sus hijos `test.step`, si los hay, suben un nivel |
| Llamadas a `expect`, a la API de Playwright (`page.click`...), fixtures           | Nada: son detalles internos de Playwright, no tus pasos |

El estado de un paso es `failed` cuando lanzó un error (sus padres fallan con él), `skipped` para
`test.step.skip()` y `passed` en los demás casos. Su error (mensaje y stack) va con él, y su
duración es la de Playwright.

### Hooks

Un hook que ejecuta pasos aparece como un paso propio, así la preparación y la limpieza quedan
separadas de la prueba:

```ts
// tests/orders.spec.ts
import { test } from '@playwright/test';

test.beforeEach(async () => {
  await test.step('Sign in', async () => {});
});

test('lists the orders', async () => {
  await test.step('Open the orders', async () => {});
});
```

```json
[
  {
    "steps": [
      {
        "action": "Before Hooks",
        "steps": [{ "action": "beforeEach hook", "steps": [{ "action": "Sign in" }] }]
      },
      { "action": "Open the orders" }
    ]
  }
]
```

### Pasos de un caso nuevo: `probara.step()`

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. Decláralos con
`probara.step(action, expected, data)` como título de un `test.step`; el paso se ejecuta como
siempre, y el caso que crea el informe lo incorpora:

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

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

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

- `probara.step('Pay')` devuelve `Pay [probara:1]`: los informes de Playwright muestran ese título,
  y el reporter lee la referencia corta y envía `Pay`.
- Los pasos del caso son los pasos `probara.step()` que se ejecutaron y no están dentro de otro, en
  el orden en que empezaron. Un `test.step` normal es solo un paso del resultado: los pasos anidados
  como `Confirm the dialog` describen cómo funciona la prueba, no lo que especifica.
- Como cualquier campo del caso, solo se aplican cuando el informe crea el caso
  ([solo casos creados](https://docs.probara.net/es/developers/ci-integrations/playwright/metadata/#solo-casos-creados)).

## Adjuntos

Cada archivo de un intento va a su resultado: lo que Playwright graba por su cuenta y lo que adjunta
una prueba.

### Lo que graba Playwright

Actívalo en `use`, como siempre; el reporter sube lo que produce:

```ts
use: {
  screenshot: 'only-on-failure',
  video: 'retain-on-failure',
  trace: 'retain-on-failure',
},
```

| Playwright                                       | El archivo en Probara                                       |
| ------------------------------------------------ | ----------------------------------------------------------- |
| `screenshot`                                     | `test-failed-1.png` (y los demás que conserva Playwright)   |
| `video`                                          | El vídeo `.webm` del intento                                |
| `trace`                                          | `trace.zip`: ábrelo con `npx playwright show-trace`         |
| Un intento fallido (Playwright 1.51 o posterior) | `error-context.md`: la instantánea de la página en el fallo |
| Fallos de `toHaveScreenshot()`                   | Las imágenes esperada, real y de diferencias                |

Cada intento tiene sus propios archivos, así que una prueba reintentada sube los archivos de cada
intento, cada uno a su resultado.

### `testInfo.attach()`

La API de Playwright funciona tal cual, con un archivo o con un cuerpo:

```ts
// tests/api.spec.ts
import { test } from '@playwright/test';

test('creates an order', async ({ page }, testInfo) => {
  await testInfo.attach('order', {
    body: JSON.stringify({ id: 1042, total: 25 }),
    contentType: 'application/json',
  });
  await testInfo.attach('checkout', { body: await page.screenshot(), contentType: 'image/png' });
});
```

Los archivos toman el nombre del adjunto, con la extensión de su tipo de contenido:

```text
order.json application/json
checkout.png image/png
```

### `probara.attach()`

`probara.attach()` hace lo mismo desde cualquier sitio donde se ejecute una prueba (un helper, un
fixture, un page object), sin tener que pasar `testInfo`. Pásale un archivo en disco o un cuerpo, y
espéralo con `await`:

```ts
// tests/receipt.spec.ts
import { writeFile } from 'node:fs/promises';
import { test } from '@playwright/test';
import { probara } from '@probara/playwright-reporter';

test('prints the receipt', async () => {
  const path = test.info().outputPath('receipt.txt');
  await writeFile(path, 'Total: 25.00');
  await probara.attach({ name: 'receipt.txt', path });
  await probara.attach({ name: 'log', body: 'paid in 1.2 s', contentType: 'text/plain' });
});
```

```text
receipt.txt text/plain
log.txt text/plain
```

| Campo         | Con `path`                                    | Con `body`                               |
| ------------- | --------------------------------------------- | ---------------------------------------- |
| `name`        | Obligatorio: el nombre en Probara             | Obligatorio: el nombre en Probara        |
| `path`        | El archivo que adjuntar (Playwright lo copia) | —                                        |
| `body`        | —                                             | Un string, un `Buffer` o un `Uint8Array` |
| `contentType` | Opcional: se deduce del nombre del archivo    | Obligatorio, por ejemplo `text/plain`    |

Un cuerpo de tipo string se envía tal cual; pasa un `Buffer` para contenido binario. Un archivo que
no se puede adjuntar (porque no existe, por ejemplo) es una advertencia `[probara]` en el stderr de
la prueba, y la prueba continúa.

### Archivos de un paso

Un archivo adjuntado mientras se ejecuta un `test.step` (con `testInfo.attach()` o
`probara.attach()`) va a ese paso, y Probara lo muestra bajo el paso, donde ocurrió:

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

test('uploads an avatar', async ({ page }) => {
  await test.step('Choose the file', async () => {
    await probara.attach({
      name: 'avatar',
      body: await page.screenshot(),
      contentType: 'image/png',
    });
  });
});
```

Un archivo adjuntado dentro de un hook o de un fixture que no es un `test.step` va al `test.step`
más cercano que lo rodea, o al resultado.

### Nombres de archivo

Un archivo conserva su propio nombre (`test-failed-1.png`, `trace.zip`). Playwright guarda lo que
copia `testInfo.attach()` con un nombre con hash (`receipt-<sha1>.txt`), y un informe blob combinado
guarda así todos los archivos; un archivo así toma el nombre de su adjunto, con la extensión del
archivo cuando el nombre no la tiene: `receipt.txt`, `avatar.png`.

### Salida de las pruebas

Con `captureOutput: true` (`PROBARA_CAPTURE_OUTPUT=true`), el stdout y el stderr de cada intento se
adjuntan como `stdout.log` y `stderr.log`, cuando hay alguna salida:

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

```ts
// tests/logs.spec.ts
import { test } from '@playwright/test';

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

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

### Límites y lo que se omite

| Límite                                                        | Qué pasa                                                              |
| ------------------------------------------------------------- | --------------------------------------------------------------------- |
| 32 MiB por archivo                                            | Un archivo más grande se omite con una advertencia                    |
| 20 archivos por resultado                                     | Los archivos a partir del 20 se omiten, con una sola 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, ilegible 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. Cada resultado con archivos cuesta dos peticiones más contra el
[límite de tasa](https://docs.probara.net/es/developers/ci-integrations/playwright/configuration/#límite-de-tasa) de la
organización.

Los archivos se suben después de que su informe se registra, mientras la ejecución sigue abierta, y
luego la ejecución se cierra. Conserva la carpeta de salida de Playwright (`test-results/`) hasta
que el reporter termine: sube los archivos desde ahí.

## Versiones de Playwright

| Playwright       | Qué cambia                                                                                                                       |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| 1.51 o posterior | Un intento fallido sube además `error-context.md`                                                                                |
| 1.50 o posterior | Los archivos van al paso en el que se adjuntaron; `test.step.skip()` es un paso `skipped`                                        |
| 1.42 a 1.49      | Playwright no indica a qué paso pertenece un archivo: todos los archivos se quedan en el resultado; `test.step.skip()` no existe |

Nada falla con una versión anterior de Playwright dentro del rango admitido: esos detalles
simplemente no aparecen.

## Problemas habituales

- **Falta un paso.** Solo `test.step` crea pasos. Una función auxiliar que no está envuelta en
  `test.step` no muestra nada; tampoco las llamadas a `expect`.
- **Los pasos salen aplanados.** Un paso que empieza sin `await` termina antes que sus hijos: espera
  cada `test.step` con `await`.
- **El caso no tiene pasos.** Solo los títulos `probara.step()` declaran pasos de caso, y solo para
  un caso que crea el informe. Un caso existente conserva los suyos.
- **Falta un adjunto.** Consulta la
  [solución de problemas](https://docs.probara.net/es/developers/ci-integrations/playwright/troubleshooting/#falta-un-adjunto).

Siguiente: [resultados y reintentos](https://docs.probara.net/es/developers/ci-integrations/playwright/results-and-retries/).
