# Jest test metadata and links

> The probara.* helpers of @probara/jest-reporter — title, suite, tags, fields, parameters, comments, ignoring an attempt, links and issues — and where they work.

`probara` tells the reporter more about a test, from the test body or a `beforeEach` or `afterEach`
hook. Require it (or import it) from the reporter's package:

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

test('pays with a card', () => {
  probara.title('Pays with a saved card').suite(['Payments', 'Cards']).tags('smoke');
});
```

In a test file compiled from ES modules (Babel, `ts-jest`),
`import { probara } from '@probara/jest-reporter'` gives the same helpers. The package ships its
types, so TypeScript tests get them too.

| Helper                                | What it does                                                                                                                | Repeated calls     |
| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------ |
| `id(id \| ids)`                       | Links existing cases ([linking](https://docs.probara.net/en/developers/ci-integrations/jest/linking/))                                              | Add up             |
| `title(text)`                         | Title of the case the report creates                                                                                        | The last one wins  |
| `suite(title \| titles)`              | Suite path of the case the report creates                                                                                   | The last one wins  |
| `comment(text)`                       | A comment, first in the notes of the result, before the error                                                               | The last one wins  |
| `ignore()`                            | Leaves this attempt out: it is not reported                                                                                 | —                  |
| `tags(...names)`                      | Tags of the case the report creates                                                                                         | Add up, each once  |
| `fields(record)`                      | Fields of the case the report creates: system fields, custom fields, `description`                                          | Merged by name     |
| `parameters(record)`                  | Parameters of the result, shown with it                                                                                     | Merged by name     |
| `link(url, name?)`, `issue(id)`       | Links of the result ([links and issues](#links-and-issues))                                                                 | Add up             |
| `attach({ name, path \| body, ... })` | Attaches a file ([attachments](https://docs.probara.net/en/developers/ci-integrations/jest/steps-and-attachments/#probaraattach))                   | Each one attaches  |
| `step(title, body?, options?)`        | A step of the result, and of the new case ([steps](https://docs.probara.net/en/developers/ci-integrations/jest/steps-and-attachments/#probarastep)) | Each one is a step |

The helpers that return nothing else return `probara`, so calls chain. Each applies to the running
attempt only: a retry starts empty.

## Created cases only

`title`, `suite`, `tags`, `fields` and the case steps describe the case a report **creates**, the
first time a test is reported. An existing case never changes: Probara keeps what people wrote
there. The automation key never changes either
([keys](https://docs.probara.net/en/developers/ci-integrations/jest/linking/#automation-keys)), so a new title still matches
the same case.

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

test('pays', () => {
  probara
    .title('Pays with a saved card')
    .suite(['Payments', 'Cards'])
    .tags('smoke', 'payments')
    .fields({
      description: 'A returning customer pays with the card saved in their profile.',
      severity: 'critical',
      priority: 'high',
      'Risk area': 'Checkout',
    });
});
```

```json
[
  {
    "automationKey": "tests/payments.test.js > pays",
    "title": "Pays with a saved card",
    "suitePath": ["Payments", "Cards"],
    "case": {
      "description": "A returning customer pays with the card saved in their profile.",
      "tags": ["smoke", "payments"],
      "fields": { "severity": "critical", "priority": "high", "Risk area": "Checkout" }
    }
  }
]
```

### Fields

`fields()` takes names and values (numbers and booleans become strings):

| Name                                                                      | What it sets                                         |
| ------------------------------------------------------------------------- | ---------------------------------------------------- |
| `description`                                                             | The case description                                 |
| `severity`, `priority`, `type`, `layer`, `behavior`, `status`, `is_flaky` | The system fields, by the name of an option (`high`) |
| `preconditions`, `postconditions`                                         | The system text fields                               |
| Any other name, such as `'Risk area'`                                     | The custom field of that name                        |

Probara resolves names and options when it creates the case. A field or an option it does not know
is skipped and logged as a warning; the result is recorded all the same. A field with an empty
value is skipped silently.

### Title and suite

The title of a created case is the test title (case ids removed) unless `title()` says otherwise.
Its suite is the test file (`tests/payments.test.js`) or, with `keyIncludesFile: false`, the
outermost `describe`, unless `suite()` gives one: `suite('Payments')`, or
`suite(['Payments', 'Cards'])` for nested suites, created as needed under `suiteUlid`.

## Comment

`comment()` writes a line first in the notes of the result, before the error and its stack:

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

test('searches the catalog', () => {
  probara.comment('Ran against the staging catalog');
});
```

```json
[
  {
    "automationKey": "tests/search.test.js > searches the catalog",
    "notes": "Ran against the staging catalog"
  }
]
```

When the attempt fails, its error follows the comment in the same notes, without the terminal's
colors: the comment never hides the error.

## Parameters

`parameters()` records the values an attempt ran with, shown with its result, such as the user or
the data set of a data-driven test. They are never part of the key: the test is one case whatever
its parameters. When the case declares a parameter with the same name, the result is recorded on the run case with those values. A parameter the case does not declare stays on the result for display only. A case that a report creates takes its values as an `Examples` parameter when at least two of its results report the same names with different values.

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

test.each(['admin', 'viewer'])('opens the dashboard as %s', (role) => {
  probara.parameters({ role, seats: 5 });
});
```

```json
[
  {
    "automationKey": "tests/users.test.js > opens the dashboard as admin",
    "parameters": { "role": "admin", "seats": "5" }
  },
  {
    "automationKey": "tests/users.test.js > opens the dashboard as viewer",
    "parameters": { "role": "viewer", "seats": "5" }
  }
]
```

A `test.each` title that holds the value (as above) is one case per value, which is how Jest names
its rows. Keep a placeholder in the title: rows with the same name in one file share one key, and
what their helpers said is left out
([names](https://docs.probara.net/en/developers/ci-integrations/jest/linking/#names-as-jest-junit-writes-them)).

## Ignore an attempt

`ignore()` leaves the running attempt out: it still runs, and Jest reports it, but Probara does not
get it. The count in the log says how many were left out.

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

test('tries the new checkout', () => {
  probara.ignore();
});

test('pays', () => {});
```

```text
$ npx jest
[probara] Sending 1 result of 1 test (1 passed, 0 failed, 0 skipped, 0 blocked); 1 ignored with probara.ignore()
[probara] Recorded 1 result (1 new case, 0 unmatched) in R-1 (closed): https://app.probara.net/projects/SHOP/runs/R-1
```

## Links and issues

A result can carry links, shown with it in Probara: the build that ran it, the page it tested, the
issues of your tracker it is about.

### `probara.link()`

`probara.link(url, name?)` takes an absolute `http` or `https` URL and an optional name, shown
instead of the URL:

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

test('searches the catalog', () => {
  probara
    .link('https://shop.example.com/search?q=boots', 'Search page')
    .link('https://ci.example.com/builds/1042');
});
```

```json
[
  {
    "automationKey": "tests/search.test.js > searches the catalog",
    "links": [
      { "url": "https://shop.example.com/search?q=boots", "name": "Search page" },
      { "url": "https://ci.example.com/builds/1042" }
    ]
  }
]
```

### `probara.issue()` and `issueUrlTemplate`

`probara.issue(id)` names an issue of your tracker by its id. With `issueUrlTemplate`
(`PROBARA_ISSUE_URL_TEMPLATE`), a URL with `%s` where the id goes, it becomes a link of the result,
named by the id:

```js
reporters: [
  'default',
  ['@probara/jest-reporter', { issueUrlTemplate: 'https://jira.example.com/browse/%s' }],
],
```

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

test('rounds the total', () => {
  probara.issue('PAY-7').issue('PAY 12');
});
```

```json
[
  {
    "automationKey": "tests/checkout.test.js > rounds the total",
    "links": [
      { "url": "https://jira.example.com/browse/PAY-7", "name": "PAY-7" },
      { "url": "https://jira.example.com/browse/PAY%2012", "name": "PAY 12" }
    ]
  }
]
```

- The id goes into the template URL-encoded (`PAY 12` becomes `PAY%2012`), in place of the first
  `%s`.
- The template is an `http` or `https` URL with `%s`; any other value turns reporting off with an
  error that names `issueUrlTemplate`.
- Without a template, the issues are left out, and one warning says so: the result is recorded
  without them.

```text
$ npx jest
[probara] Dropped the issues of probara.issue(): no issueUrlTemplate turns their ids into links (first seen in "rounds the total"; repeats are logged at debug)
[probara] Sending 1 result of 1 test (1 passed, 0 failed, 0 skipped, 0 blocked)
[probara] Recorded 1 result (1 new case, 0 unmatched) in R-1 (closed): https://app.probara.net/projects/SHOP/runs/R-1
```

### Link rules

| Rule                                             | What happens                                     |
| ------------------------------------------------ | ------------------------------------------------ |
| Links and issues of one attempt                  | Sent in call order, links and issues together    |
| A URL that is not an absolute `http`/`https` URL | Left out, with a warning                         |
| A URL over 2048 characters                       | Left out, with a warning                         |
| A name over 255 characters                       | Cut to 255 characters, with a warning            |
| More than 20 links in a result                   | The first 20 are sent, with a warning            |
| A retry                                          | Starts without links: each attempt sends its own |

Links belong to the result, not to the case: an existing case never changes, and each result keeps
the links its attempt added. To link a case itself to a Jira issue, see
[Traceability with Jira](https://docs.probara.net/en/guides/integrations/jira-traceability/).

## Where to call the helpers

| Where                                                        | What happens                                             |
| ------------------------------------------------------------ | -------------------------------------------------------- |
| The test body, `beforeEach`, `afterEach`                     | Applies to the running attempt                           |
| A helper function or module the test calls                   | The same: the helpers find the running test on their own |
| The module scope, a `describe` body, `beforeAll`, `afterAll` | Ignored, with one warning: no single test runs there     |
| A `test.concurrent` test                                     | Applies to that test                                     |

A helper never throws into the test. A wrong argument, or a call while no test runs, is one
`[probara]` warning in the reporter's log, naming the test file, and the call is ignored:

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

beforeAll(() => {
  probara.tags('orders');
});

test('lists the orders', () => {
  probara.tags('orders');
});
```

```text
$ npx jest
[probara] probara.tags() only works while a test runs (in a test, or a beforeEach or afterEach hook) (first seen in tests/orders.test.js; repeats are logged at debug)
[probara] Sending 1 result of 1 test (1 passed, 0 failed, 0 skipped, 0 blocked)
[probara] Recorded 1 result (1 new case, 0 unmatched) in R-1 (closed): https://app.probara.net/projects/SHOP/runs/R-1
```

## Every worker, every environment

The helpers work wherever Jest runs a test: in its worker processes and in band (`--runInBand`), in
the `node` and `jsdom` test environments, in watch mode, and in test files written with `import`.
Nothing needs to be set up for them: the reporter tells the test processes where to write before
they start.

```js
// jest.config.js (excerpt)
testEnvironment: 'jsdom',
```

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

test('shows the banner', () => {
  document.body.innerHTML = '<p role="banner">Free shipping</p>';
  probara.parameters({ locale: document.documentElement.lang || 'en' });
});
```

```json
[{ "automationKey": "tests/banner.test.js > shows the banner", "parameters": { "locale": "en" } }]
```

### How metadata travels

Jest runs tests in worker processes and the reporter in its main one. When the run starts, the
reporter creates a folder in the system's temporary directory and names it to the test processes;
each `probara.*` call appends a line there, keyed by the test file, the test's full name and its
attempt, which the helpers read from Jest's own state (jest-circus). `probara.attach()` copies its
file there at once. The reporter reads the lines when each file ends, and removes the folder when
`jest` ends. Without the reporter, every helper does nothing (and `probara.step()` still runs its
body), so tests keep passing where the reporter is not registered.

Known limits:

- **jest-circus only.** With `testRunner: 'jest-jasmine2'`, no call is recorded, and one warning
  says so.
- **`jest.mock('fs')`** replaces the file system of the test file, which the helpers write with:
  their calls in that file are lost, silently. Mock only the modules your code imports, or use
  `jest.spyOn` on the functions it calls.
- **Two tests with the same full name** in one file: what their calls said is left out, with a
  warning.
- **Jest 30.0 to 30.4 and `test.concurrent` retries**: those versions do not say which attempt of a
  concurrent test runs, so the calls of a retry of a `test.concurrent` test go to its first attempt.
  Jest 29 never retries concurrent tests, and 30.5 says.

Related: [Steps and attachments](https://docs.probara.net/en/developers/ci-integrations/jest/steps-and-attachments/) ·
[Linking](https://docs.probara.net/en/developers/ci-integrations/jest/linking/) ·
[Migrating from Qase](https://docs.probara.net/en/developers/ci-integrations/jest/migrating/#from-qase) · Source:
[metadata.md](https://github.com/cynch-me/probara-sdk/blob/main/packages/jest-reporter/docs/metadata.md),
[links.md](https://github.com/cynch-me/probara-sdk/blob/main/packages/jest-reporter/docs/links.md)
