# Cypress test metadata and links

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

`probara` tells the reporter more about a test, from the test body or a `beforeEach` or `afterEach`
hook. The [support file](https://docs.probara.net/en/developers/ci-integrations/cypress/#4-load-the-support-file) publishes
it as a global of the spec, so a spec calls it with no import:

```js
// cypress/e2e/payments.cy.js
it('pays with a card', () => {
  probara.title('Pays with a saved card').suite(['Payments', 'Cards']).tags('smoke');
  cy.get('.pay').click();
});
```

The helpers need the plugin and the support file
([registration](https://docs.probara.net/en/developers/ci-integrations/cypress/configuration/#registration)). The package
ships its types, so TypeScript specs get the `probara` global too.

| Helper                                | What it does                                                                                                                   | Repeated calls     |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------ |
| `id(id \| ids)`                       | Links existing cases ([linking](https://docs.probara.net/en/developers/ci-integrations/cypress/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/cypress/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/cypress/steps-and-attachments/#probarastep)) | Each one is a step |

The metadata helpers 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/cypress/linking/#automation-keys)), so a new title still
matches the same case.

```js
// cypress/e2e/payments.cy.js
it('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',
    });
  cy.get('.pay').click();
});
```

```json
[
  {
    "automationKey": "cypress/e2e/payments.cy.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 path is the spec file and the `describe` blocks the test is in
(`cypress/e2e/payments.cy.js › Checkout`) or, with `keyIncludesFile: false`, the `describe` blocks
alone, 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
// cypress/e2e/search.cy.js
it('searches the catalog', () => {
  probara.comment('Ran against the staging catalog');
  cy.get('#search').type('boots{enter}');
});
```

```json
[
  {
    "automationKey": "cypress/e2e/search.cy.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.

```js
// cypress/e2e/users.cy.js
['admin', 'viewer'].forEach((role) => {
  it(`opens the dashboard as ${role}`, () => {
    probara.parameters({ role, seats: 5 });
    cy.visit('/dashboard');
  });
});
```

```json
[
  {
    "automationKey": "cypress/e2e/users.cy.js > opens the dashboard as admin",
    "parameters": { "browser": "electron", "role": "admin", "seats": "5" }
  },
  {
    "automationKey": "cypress/e2e/users.cy.js > opens the dashboard as viewer",
    "parameters": { "browser": "electron", "role": "viewer", "seats": "5" }
  }
]
```

Two parameters are added on their own: `browser`, the browser Cypress runs
([`browserAsParameter`](https://docs.probara.net/en/developers/ci-integrations/cypress/configuration/#browserasparameter)),
and `attempt`, from the second attempt of a retried test on. Keep the value in the title of a
generated test, as above: tests with the same full title in one spec share one key, and what their
helpers said is left out.

## Ignore an attempt

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

```js
// cypress/e2e/experimental.cy.js
it('tries the new checkout', () => {
  probara.ignore();
  cy.visit('/checkout-beta');
});
```

```text
$ npx cypress run
[probara] Sending 3 results of 4 tests (3 passed, 0 failed, 0 skipped, 0 blocked); 1 ignored with probara.ignore()
```

## 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
// cypress/e2e/checkout.cy.js
it('pays by card', () => {
  probara.link('https://ci.example.com/build/12', 'Build 12');
  cy.get('.pay').click();
});
```

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

`probara.issue(id)` names an issue of your tracker by its id, not by its URL. 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
// cypress.config.js
reporterOptions: {
  projectId: 'SHOP',
  issueUrlTemplate: 'https://jira.example.com/browse/%s',
},
```

```js
// cypress/e2e/checkout.cy.js
it('pays by card', () => {
  probara.issue('PAY-7');
  cy.get('.pay').click();
});
```

```json
{
  "automationKey": "cypress/e2e/checkout.cy.js > pays by card",
  "links": [{ "url": "https://jira.example.com/browse/PAY-7", "name": "PAY-7" }]
}
```

- The id goes into the template URL-encoded, in place of `%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 line says so: the result is recorded without
  them.

```text
[probara] Dropped the issues of probara.issue(): no issueUrlTemplate turns their ids into links
```

### Link rules

| Rule                                             | What happens                                                                        |
| ------------------------------------------------ | ----------------------------------------------------------------------------------- |
| A URL that is not an absolute `http`/`https` URL | Left out: `probara.link() takes an absolute http(s) URL of at most 2048 characters` |
| A URL over 2048 characters                       | Left out, with the same warning                                                     |
| A name that is not a string                      | Left out: `probara.link() takes the name as a string`                               |
| 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 test the hook runs for                                                                    |
| The `after` hook of a `describe`         | Applies to the last test of that `describe`, which Cypress names as the running one there                |
| The `before` hook of a `describe`        | Left out, with one warning per spec: no attempt has started, and the call is never given to another test |
| A spec's top-level code                  | Left out: there is no `cy` chain to send it with                                                         |

A helper never fails the test. A wrong argument, or a call that belongs to no test, is one
`[probara]` warning in the run's log, and the call is ignored:

```text
$ npx cypress run
[probara] Left out 1 probara.* call in cypress/e2e/helpers.cy.js: what it said belongs to no attempt that ran (probara.title() of a hook that runs no test) (first seen in cypress/e2e/helpers.cy.js; repeats are logged at debug)
```

### How a call reaches the report

The browser cannot reach the reporter on its own: each call goes to the plugin with
`cy.task('probara', …)`, queued and awaited like any other Cypress command, so it reaches the report
before the test ends. Without the plugin there is nothing to carry it: every helper does nothing,
and one line on the browser console says why. A warning about a call inside a spec is logged when
that spec ends, not at the end of the run.

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