# Sharding de Jest y proyectos de Jest

> Una sola ejecución de Probara para todos los shards de un pipeline de Jest con sharding, los projects de Jest, y cómo reportar una batería de pruebas en varios proyectos de Probara.

Fuera de GitHub Actions, cada comando `jest` crea su propia ejecución por defecto, así que cuatro
shards (`--shard=1/4` ... `--shard=4/4`) crean cuatro ejecuciones. En GitHub Actions los shards de un
mismo workflow comparten una ejecución automáticamente
([jobs en paralelo](https://docs.probara.net/es/developers/ci-integrations/jest/ci-providers/#jobs-en-paralelo-una-ejecución-cerrada-por-el-job-final)).
En otros entornos, esta página muestra cómo tener **una** ejecución para todo
un pipeline, cómo se comportan los `projects` propios de Jest, y cómo una batería de pruebas reporta
en varios proyectos de Probara.

Los workers paralelos de un mismo `jest` (`--maxWorkers`) no necesitan nada: todos reportan a través
del único reporter del proceso principal de Jest, en una sola ejecución.

## Una ejecución para todos los shards

| Patrón                                                                                                           | Elígelo cuando                                                              |
| ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| [Crea la ejecución, reporta desde cada shard y ciérrala](#crea-la-ejecución-reporta-desde-cada-shard-y-ciérrala) | La forma habitual: los resultados aparecen mientras corren los shards       |
| [Archivos de resultados importados por un job](#alternativa-archivos-de-resultados-importados-por-un-job)        | Los shards no pueden tener el token, o un solo job debe hacer todo el envío |

Cada job que reporta necesita `PROBARA_API_TOKEN`, un token de aplicación de la tarjeta **Jest** en
**Workspace › Integraciones**, configurado como en la
[sección de tu proveedor de CI](https://docs.probara.net/es/developers/ci-integrations/jest/ci-providers/).

### Crea la ejecución, reporta desde cada shard y ciérrala

1. Un primer job ejecuta `probara run create`, que crea una ejecución automatizada vacía e imprime su
   ULID en stdout.
2. Cada shard ejecuta `npx jest --shard=<i>/<n>` con ese ULID en `PROBARA_RUN_ULID`. Un shard nunca
   cierra una ejecución que no creó, así que todos reportan en la ejecución abierta. Probara
   concilia los shards que reportan a la vez: ningún caso se añade dos veces a la ejecución.
3. Un último job ejecuta `probara run close`, después de todos los shards, **incluso si alguno
   falló**.

En una shell, el flujo completo es:

```bash
PROBARA_RUN_ULID=$(npx @probara/cli run create)
export PROBARA_RUN_ULID
npx jest --shard=1/2
npx jest --shard=2/2
npx @probara/cli run close
```

Asigna el ULID en una línea propia: `export PROBARA_RUN_ULID=$(...)` ocultaría un fallo al crearla.
En CI, los jobs de abajo fijan la versión de la CLI (`npx --yes @probara/cli@0.2`), que no es una
dependencia del reporter; con `@probara/cli` en tus `devDependencies`, `npx @probara/cli` después de
`npm ci` ejecuta la versión del lockfile.
`probara` es la [CLI de Probara](https://docs.probara.net/es/developers/cli/); usa el mismo token y el mismo proyecto. Las
opciones de una ejecución nueva van a `run create` (`--run-name`, `--environment`, `--milestone`,
`--plan`, `--tag`...), porque los shards reutilizan la ejecución e ignoran sus propias opciones de
ejecución.

### GitHub Actions

```yaml
jobs:
  probara-run:
    runs-on: ubuntu-latest
    outputs:
      ulid: ${{ steps.create.outputs.ulid }}
    env:
      PROBARA_API_TOKEN: ${{ secrets.PROBARA_API_TOKEN }}
      PROBARA_PROJECT: SHOP
      # A fork's build gets no secret: reporting off, so run create prints nothing and exits 0.
      PROBARA_ENABLED: ${{ secrets.PROBARA_API_TOKEN != '' }}
    steps:
      - uses: actions/setup-node@v5
        with:
          node-version: 22
      - id: create
        run: |
          ulid=$(npx --yes @probara/cli@0.2 run create)
          echo "ulid=$ulid" >> "$GITHUB_OUTPUT"

  test:
    needs: probara-run
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        shardIndex: [1, 2, 3, 4]
        shardTotal: [4]
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v5
        with:
          node-version: 22
      - run: npm ci
      - run: npx jest --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
        env:
          PROBARA_API_TOKEN: ${{ secrets.PROBARA_API_TOKEN }}
          PROBARA_PROJECT: SHOP
          PROBARA_RUN_ULID: ${{ needs.probara-run.outputs.ulid }}

  close-probara-run:
    needs: [probara-run, test]
    if: always() && needs.probara-run.outputs.ulid != ''
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-node@v5
        with:
          node-version: 22
      - run: npx --yes @probara/cli@0.2 run close
        env:
          PROBARA_API_TOKEN: ${{ secrets.PROBARA_API_TOKEN }}
          PROBARA_PROJECT: SHOP
          PROBARA_RUN_ULID: ${{ needs.probara-run.outputs.ulid }}
```

Sin token, la CLI termina con código 2, así que el primer job define `PROBARA_ENABLED` según esté o
no el secreto: en el build de un fork, `run create` no imprime nada y termina con 0, los shards no
reportan nada, y el job de cierre se omite.

### GitLab CI

Un artefacto dotenv pasa el ULID a cada job posterior:

```yaml
stages:
  - test

create-probara-run:
  stage: .pre
  image: node:22
  variables:
    PROBARA_PROJECT: SHOP
  rules:
    - if: $PROBARA_API_TOKEN
  script:
    - PROBARA_RUN_ULID=$(npx --yes @probara/cli@0.2 run create)
    - echo "PROBARA_RUN_ULID=$PROBARA_RUN_ULID" > probara.env
  artifacts:
    reports:
      dotenv: probara.env

jest:
  stage: test
  image: node:22
  parallel: 4
  variables:
    PROBARA_PROJECT: SHOP
  script:
    - npm ci
    - npx jest --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL

close-probara-run:
  stage: .post
  image: node:22
  variables:
    PROBARA_PROJECT: SHOP
  rules:
    - if: $PROBARA_API_TOKEN
      when: always
  script:
    - npx --yes @probara/cli@0.2 run close
```

`.pre` y `.post` son las etapas inicial y final integradas de GitLab; `when: always` cierra la
ejecución incluso después de shards fallidos.

### Otros proveedores de CI

Jenkins, CircleCI, Azure Pipelines, Bitbucket Pipelines y Buildkite siguen los mismos tres pasos; sus
pipelines completos con sharding están en
[Proveedores de CI](https://docs.probara.net/es/developers/ci-integrations/jest/ci-providers/).

### Alternativa: archivos de resultados importados por un job

Cada shard se ejecuta con el envío desactivado y escribe cada resultado en un
[archivo de resultados](https://docs.probara.net/es/developers/ci-integrations/jest/results-and-retries/#archivo-de-resultados),
que se guarda como artefacto con su carpeta de adjuntos. Un último job, que tiene el token, los
descarga todos y los envía. Los adjuntos se referencian con rutas relativas a cada archivo de
resultados, así que los archivos y sus carpetas se pueden descargar en cualquier lugar.

Un archivo de resultados escrito con el envío desactivado no nombra ninguna ejecución: importado por
separado, cada archivo crea su propia ejecución. Para tener una sola, créala primero, importa en ella
todos los archivos y ciérrala:

```yaml
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        shardIndex: [1, 2, 3, 4]
        shardTotal: [4]
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v5
        with:
          node-version: 22
      - run: npm ci
      - run: npx jest --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
        env:
          PROBARA_ENABLED: 'false'
          PROBARA_PROJECT: SHOP
          PROBARA_RESULTS_FILE: probara-results-${{ matrix.shardIndex }}.json
      - uses: actions/upload-artifact@v4
        if: ${{ !cancelled() }}
        with:
          name: probara-results-${{ matrix.shardIndex }}
          path: probara-results-*
          retention-days: 1

  report:
    needs: test
    # Forks get no token: there is nothing to send with.
    if: ${{ !cancelled() && github.event.pull_request.head.repo.fork != true }}
    runs-on: ubuntu-latest
    env:
      PROBARA_API_TOKEN: ${{ secrets.PROBARA_API_TOKEN }}
      PROBARA_PROJECT: SHOP
    steps:
      - uses: actions/setup-node@v5
        with:
          node-version: 22
      - uses: actions/download-artifact@v4
        with:
          pattern: probara-results-*
          merge-multiple: true
      - run: |
          PROBARA_RUN_ULID=$(npx --yes @probara/cli@0.2 run create)
          export PROBARA_RUN_ULID
          npx --yes @probara/cli@0.2 import results 'probara-results*.json'
          npx --yes @probara/cli@0.2 run close
```

- Da a cada shard su propio nombre de archivo (`probara-results-<shard>.json`), para que las
  descargas nunca se sobrescriban entre sí.
- `probara import results` envía cada archivo a `PROBARA_RUN_ULID`, deja esa ejecución abierta
  (porque la reutilizó) y elimina cada archivo una vez enviado. `run close` la cierra después.
- No llega nada a Probara hasta el último job: un shard que se cae no deja archivo de resultados, y
  sus resultados faltan.
- Volver a ejecutar el job de envío ejecuta `run create` de nuevo: los archivos de los shards,
  descargados otra vez tal como estaban, van a una segunda ejecución con los mismos resultados. Cierra
  o elimina la que no quieras conservar.

### Si el cierre nunca se ejecutó

Una ejecución queda abierta hasta que se cierra. Ciérrala a mano, con el ULID del registro del job
que la creó:

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

### Qué enfoque pierde menos

| Algo sale mal                     | Ejecución compartida (crear, shards, cerrar)                                              | Archivos de resultados, una importación                                       |
| --------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| Un shard se cae antes de terminar | Sus resultados hasta ese momento están en la ejecución                                    | Falta su archivo: no se envía ninguno de sus resultados                       |
| Probara no está accesible         | El archivo de resultados de cada shard guarda lo que no pudo enviar (si hay uno definido) | La importación termina con 1 y conserva en cada archivo lo que no pudo enviar |
| El job de cierre no se ejecuta    | La ejecución queda abierta: [ciérrala a mano](#si-el-cierre-nunca-se-ejecutó)             | Igual                                                                         |

## Proyectos de Jest

Aquí se llaman proyectos a dos cosas distintas. Los
[`projects`](https://jestjs.io/docs/configuration#projects-arraystring--projectconfig) de Jest
dividen un comando `jest` en varias configuraciones (una `node` y una `jsdom`, por ejemplo); la
opción `projects` del reporter permite que una batería de pruebas reporte en
[varios proyectos de Probara](#varios-proyectos-de-probara). Cada uno funciona por su cuenta.

Registra el reporter una vez, en el nivel superior de la configuración, junto a `projects`: Jest usa
un solo conjunto de reporters para todos sus proyectos, y el reporter envía las pruebas de todos los
proyectos a una sola ejecución de Probara.

```js
// jest.config.js
module.exports = {
  reporters: ['default', ['@probara/jest-reporter', { projectId: 'SHOP' }]],
  projects: [
    {
      displayName: 'api',
      testEnvironment: 'node',
      testMatch: ['<rootDir>/tests/api/**/*.test.js'],
    },
    { displayName: 'ui', testEnvironment: 'jsdom', testMatch: ['<rootDir>/tests/ui/**/*.test.js'] },
  ],
};
```

```json
[
  { "automationKey": "tests/ui/cart.test.js > shows the cart" },
  { "automationKey": "tests/api/orders.test.js > lists the orders" }
]
```

- **Las claves no incluyen el proyecto.** Como en el informe de jest-junit, un proyecto de Jest no
  añade nada a la clave de automatización: el archivo (relativo al directorio en el que se ejecuta
  `jest`, no al `rootDir` del proyecto) y después los nombres. Mover pruebas entre proyectos de Jest
  conserva sus casos.
- **Un archivo que ejecutan dos proyectos** (porque sus `testMatch` se solapan, como una carpeta
  compartida) se reporta una vez por proyecto, con la misma clave: dos resultados de un caso, como
  dos reintentos.
- **Helpers en un archivo que dos proyectos ejecutan a la vez.** Jest puede ejecutar ambos al mismo
  tiempo, en dos workers, y una llamada `probara.*` no puede saber a qué ejecución de proyecto
  pertenece. Lo que dijeron los helpers sobre las pruebas de ese archivo se omite entonces, con una
  advertencia que nombra el archivo; los resultados se envían igualmente. Da a cada proyecto sus
  propios archivos, o ejecuta con `--runInBand`, para conservarlos.
- **Archivos de setup por proyecto.** `setupFilesAfterEnv: ['@probara/jest-reporter/setup']` (para
  `captureOutput` y la [selección de pruebas](https://docs.probara.net/es/developers/ci-integrations/jest/run-selection/)) es
  una opción de proyecto: pásala a cada proyecto cuyas pruebas la necesiten.
- **Jest ignora `reporters` dentro de un proyecto**: registra el reporter en el nivel superior.

### `{displayName}` en un título

jest-junit rellena `{displayName}` en el nombre de una prueba con el nombre del proyecto de Jest que la
ejecuta, y el reporter también, así que una prueba que se ejecuta en dos proyectos puede tener una
clave, y un caso, por proyecto:

```js
// jest.config.js
module.exports = {
  reporters: ['default', ['@probara/jest-reporter', { projectId: 'SHOP' }]],
  projects: [
    { displayName: 'node', testEnvironment: 'node' },
    { displayName: 'browser', testEnvironment: 'jsdom' },
  ],
};
```

```js
// tests/format.test.js
test('formats prices in {displayName}', () => {});
```

```json
[
  { "automationKey": "tests/format.test.js > formats prices in browser" },
  { "automationKey": "tests/format.test.js > formats prices in node" }
]
```

Un proyecto sin `displayName` rellena `undefined`, como hace jest-junit. Cuando los dos proyectos
ejecutan un archivo así al mismo tiempo, cada uno envía solo el último intento de esa prueba, con una
advertencia: Jest no indica a qué proyecto pertenece un intento concreto hasta que termina el
archivo.

## Varios proyectos de Probara

Una batería de Jest puede contener las pruebas de varios proyectos de Probara: una batería compartida
cuyos casos viven en `SHOP`, `WEB` y `API`. Enumera los demás proyectos en `projects`, y cada
resultado va al proyecto de su caso, a una ejecución de ese proyecto.

```js
reporters: ['default', ['@probara/jest-reporter', { projectId: 'SHOP', projects: ['WEB'] }]],
```

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

test('SHOP-12 pays', () => {});

test('WEB-3 shows the landing page', () => {});

test('API-7 answers the health check', () => {});

test('opens the help', () => {
  probara.id('API-9');
});
```

```text
$ npx jest
[probara] Did not send the results linked to cases of API: API is neither the project (SHOP) nor one of projects (PROBARA_PROJECTS) (first seen in "opens the help"; repeats are logged at debug)
[probara] Sending 3 results of 3 tests (3 passed, 0 failed, 0 skipped, 0 blocked); 1 linked only to cases of unlisted projects
[probara] Recorded 2 results (1 new case, 0 unmatched) in R-1 of SHOP (closed): https://app.probara.net/projects/SHOP/runs/R-1
[probara] Recorded 1 result (0 new cases, 0 unmatched) in R-2 of WEB (closed): https://app.probara.net/projects/WEB/runs/R-2
```

`PROBARA_PROJECTS=WEB,API` los enumera desde el entorno.

### Qué va a dónde

- **Por el proyecto del caso.** Un resultado vinculado a `WEB-3` va a una ejecución de `WEB`. Los IDs
  de cada proyecto de la lista también vinculan desde los títulos, y salen de la clave, igual que los
  del proyecto configurado.
- **Una prueba vinculada a casos de varios proyectos** se envía una vez por caso, cada una a su
  proyecto, con sus archivos subidos a cada resultado.
- **Una prueba sin caso** va al proyecto configurado (`projectId`), donde se asocia por su clave o se
  crea: las claves de automatización pertenecen a un proyecto, así que solo el proyecto configurado
  crea casos, bajo `suiteUlid`.
- **Un ID de un proyecto que no está en la lista** en un título (`API-7` arriba) no es un ID: queda en
  el título y en la clave, como cualquier texto, y la prueba va al proyecto configurado. Pasado a
  `probara.id()` (`API-9`), es un caso que el reporter no puede enviar a ningún sitio: se omite, con
  una advertencia por proyecto, y el recuento del registro lo indica. Añade el proyecto a la lista
  para reportarlo.

### Una ejecución por proyecto

Cada proyecto de la lista tiene su propia ejecución, creada con su primer resultado (un proyecto sin
resultados no tiene ejecución), con el mismo nombre, descripción, entorno (por nombre), etiquetas y
origen de CI que la del proyecto configurado, y se cierra al final como cualquier ejecución que crea
el reporter.

El hito, el plan y las configuraciones son entidades de planificación de un proyecto: un nombre que
existe en `SHOP` puede no existir en `WEB`, donde Probara rechazaría el informe entero. Por eso
`run.milestone`, `run.plan` y `run.configurations`, como sus formas con ULID (`run.milestoneId`,
`run.configurationUlids`, `run.environmentId`), solo van con la ejecución del proyecto configurado, y
una advertencia los nombra. Para dar los suyos a la ejecución de otro proyecto, créala primero y
reutilízala:

```bash
WEB_RUN=$(npx @probara/cli run create --project WEB --milestone "Web sprint 4")
PROBARA_RUN_ULIDS="WEB=$WEB_RUN" PROBARA_PROJECTS=WEB npx jest
```

`run.ulids` (`PROBARA_RUN_ULIDS=SHOP=<ulid>,WEB=<ulid>`) nombra la ejecución que se reutiliza en cada
proyecto; la entrada del proyecto configurado cuenta como `run.ulid`. Una ejecución reutilizada queda
abierta salvo que `closeRun` sea `true`: ciérrala con
`probara run close --project WEB --run-ulid <ulid>`.

### Jobs con sharding y varios proyectos

Crea una ejecución por proyecto antes de los shards, pásalas todas en `PROBARA_RUN_ULIDS`, y
ciérralas después del último shard:

```bash
SHOP_RUN=$(npx @probara/cli run create)
WEB_RUN=$(npx @probara/cli run create --project WEB)
export PROBARA_PROJECTS=WEB PROBARA_RUN_ULIDS="SHOP=$SHOP_RUN,WEB=$WEB_RUN"
npx jest --shard=1/2
npx jest --shard=2/2
npx @probara/cli run close --run-ulid "$SHOP_RUN"
npx @probara/cli run close --project WEB --run-ulid "$WEB_RUN"
```

Cuando la ejecución del proyecto configurado se reutiliza pero un proyecto de la lista no tiene
ejecución en `run.ulids`, cada shard crea su propia ejecución en ese proyecto, y una advertencia lo
indica.

### Fallos

Un informe fallido solo detiene su proyecto: los demás continúan. El registro nombra el proyecto de
cada error (`WEB: ...`), y un archivo de resultados guarda lo que cada proyecto no pudo enviar, con
la ejecución a la que vuelve: un solo archivo para toda la ejecución, que
`probara import results 'probara-results*.json'` envía a cada proyecto.

Relacionado: [Proveedores de CI](https://docs.probara.net/es/developers/ci-integrations/jest/ci-providers/) ·
[Resultados y reintentos](https://docs.probara.net/es/developers/ci-integrations/jest/results-and-retries/) ·
[CLI](https://docs.probara.net/es/developers/cli/) · Fuente:
[ci/sharding.md](https://github.com/cynch-me/probara-sdk/blob/main/packages/jest-reporter/docs/ci/sharding.md),
[projects.md](https://github.com/cynch-me/probara-sdk/blob/main/packages/jest-reporter/docs/projects.md)
