# Linking Cypress tests to cases

> How @probara/cypress-reporter links each Cypress test to a Probara test case — case ids in titles, probara.id(), automation keys, it.only and the cases it creates.

Each result goes to a Probara test case. A test that names its case is linked to it; a test that
names none is matched by its automation key, and its case is created the first time it is reported.
Link tests when their cases already exist in Probara (written by hand, or imported).

## Two ways to name a case

| Way                                  | Example                                           | When                                 |
| ------------------------------------ | ------------------------------------------------- | ------------------------------------ |
| A case id in an `it` or `describe`   | `it('SHOP-12 pays with a card', ...)`             | The usual way; works for skipped too |
| `probara.id()` in the test or a hook | `probara.id('SHOP-14')`, `probara.id(['A', 'B'])` | Without touching the title           |

```js
// cypress/e2e/checkout.cy.js
describe('Checkout', () => {
  it('SHOP-12 pays with a card', () => {
    cy.get('.pay-card').click();
  });

  describe('SHOP-13 Vouchers', () => {
    it('pays with a voucher', () => {
      cy.get('.pay-voucher').click();
    });
  });

  it('pays with a gift card', () => {
    probara.id('SHOP-14');
    cy.get('.pay-gift').click();
  });

  it('shows the total', () => {
    // No case named: matched by its key, created the first time.
    cy.get('.total').should('be.visible');
  });
});
```

Probara receives one result per test, each with its case, and the key it would get without the ids:

```json
[
  {
    "caseDisplayId": "SHOP-12",
    "automationKey": "cypress/e2e/checkout.cy.js > Checkout pays with a card"
  },
  {
    "caseDisplayId": "SHOP-13",
    "automationKey": "cypress/e2e/checkout.cy.js > Checkout Vouchers pays with a voucher"
  },
  {
    "caseDisplayId": "SHOP-14",
    "automationKey": "cypress/e2e/checkout.cy.js > Checkout pays with a gift card"
  },
  { "automationKey": "cypress/e2e/checkout.cy.js > Checkout shows the total" }
]
```

- **Title ids** are the ids of the reported project (and of
  [`projects`](https://docs.probara.net/en/developers/ci-integrations/cypress/sharding/#several-probara-projects)) in a
  `describe` or `it` title. An id in a `describe` links every test inside it. They are removed from
  the key, so adding one never changes a key. An id of a project the reporter does not know, such as
  `WEB-3` without `projects: ['WEB']`, stays in the title and in the key, as text.
- **`probara.id()`** takes an id or a list, and links the running test; in a `beforeEach`, it links
  every test of the block. It needs the plugin and the support file. A test skipped before it runs
  (`it.skip`) never runs its body: link it with a title id instead.
  [Run selection](https://docs.probara.net/en/developers/ci-integrations/cypress/run-selection/) does not see `probara.id()`
  either: it decides before the test runs.

The ids are the case's display id: the Project ID, a dash and the number shown in Probara
(`SHOP-12`). An id Probara does not know is not recorded: the log names it as unmatched
([troubleshooting](https://docs.probara.net/en/developers/ci-integrations/cypress/troubleshooting/#results-were-not-recorded-unmatched)).
Several ids, in titles or in `probara.id()`, link the test to several cases, and the test is
recorded once per case.

## Automation keys

A test that names no case is matched by its automation key: the same key finds the same case on
every run, and a new key creates a new case. The key is the one `probara import junit` gives the
test on the report of Cypress's `junit` reporter or of cypress-junit:

```text
<spec path, relative to the project root> > <the full title of the test>
```

| Part           | From                                                                                                   | Example                  |
| -------------- | ------------------------------------------------------------------------------------------------------ | ------------------------ |
| The spec path  | Relative to `rootDir` (Cypress's `projectRoot`), with `/` separators, while `keyIncludesFile` is on    | `cypress/e2e/cart.cy.js` |
| The full title | The `describe` titles and the `it` title joined by single spaces, as one segment, without the case ids | `Cart Checkout pays`     |

```js
// cypress/e2e/cart.cy.js
describe('SHOP-12 Cart', () => {
  describe('Checkout', () => {
    it('pays by card', () => {
      cy.get('.pay').click();
    });
  });
});
```

```text
cypress/e2e/cart.cy.js > Cart Checkout pays by card
```

### What changes a key

| What                                          | Why                                                                         |
| --------------------------------------------- | --------------------------------------------------------------------------- |
| Renaming a spec, a `describe` or an `it`      | The spec path and the titles are the key                                    |
| Moving a spec                                 | Its path is the key                                                         |
| Starting `cypress run` from another directory | The path is relative to Cypress's `projectRoot`                             |
| Changing `keyIncludesFile` or `rootDir`       | They decide whether the spec path is in the key, and what it is relative to |

Nothing else reaches a key: not the browser (a `browser` parameter of the result), not the attempt,
not a parameter, tag or step, not the CI build, the machine, `--spec` or `excludeSpecPattern`. Name
the cases in the titles before a rename, so the tests keep them.

### Traps in Cypress

- **Two tests with the same full title are one case.** Mocha allows `it('adds an item')` twice in
  one `describe`, and `a b` › `c` next to `a` › `b c` has the same full title `a b c`. Both record
  into one case, and what their `probara.*` calls said is left out rather than given to the wrong
  test. Give every test its own title.
- **`it.only` leaves out what it does not run.** Cypress removes the other tests from its tree
  before anything runs, so they are not reported at all, not even as skipped. The same holds for
  the specs `--spec` leaves out.
- **A failing hook is a test of its own**, named `"before each" hook for "…"` by Cypress, and by the
  import. Its key is the one a JUnit report holds
  ([hooks](https://docs.probara.net/en/developers/ci-integrations/cypress/results-and-retries/#a-hook-that-fails)).
- **A spec Cypress cannot parse** is one result named `Spec failed to run`
  ([specs that cannot run](https://docs.probara.net/en/developers/ci-integrations/cypress/results-and-retries/#a-spec-cypress-cannot-run)).

## Missing cases

A test whose key matches no case creates one, named after the test (ids removed), under `suiteUlid`
(the project root by default), in a suite path made of the spec file and the `describe` blocks the
test is in: `cypress/e2e/cart.cy.js › Cart › Checkout`. A test outside any `describe` sits in the
spec alone. With `keyIncludesFile: false`, the spec leaves the suite path too. With
`createMissingCases: false` (`PROBARA_CREATE_MISSING_CASES=false`) such a test records nothing and
is logged as unmatched: only existing cases get results.
[`probara.title()` and `probara.suite()`](https://docs.probara.net/en/developers/ci-integrations/cypress/metadata/#title-and-suite)
choose the name and the suite of a created case.

## The same keys as `probara import junit`

A project that sent Cypress's JUnit XML with `probara import junit` can switch to the reporter and
keep every case: the reporter builds the same keys and reads the same title ids.

| The JUnit report                                                        | Reporter setting                 |
| ----------------------------------------------------------------------- | -------------------------------- |
| Written by Cypress's `junit` reporter or cypress-junit, default options | Nothing: `keyIncludesFile` is on |
| Written without the `file` attribute of its root suite                  | `keyIncludesFile: false`         |

The one visible difference is where a **created** case sits: the reporter puts it under the spec
and its `describe` blocks, the import under the spec alone, since a JUnit report cannot split the
`describe` titles back out of one title. An existing case never moves
([migrating from the JUnit import](https://docs.probara.net/en/developers/ci-integrations/cypress/migrating/#from-the-junit-import)).

Related: [Metadata and links](https://docs.probara.net/en/developers/ci-integrations/cypress/metadata/) ·
[Sharding and projects](https://docs.probara.net/en/developers/ci-integrations/cypress/sharding/) ·
[Upgrading](https://docs.probara.net/en/developers/ci-integrations/cypress/migrating/#upgrading-the-reporter) · Source:
[linking.md](https://github.com/cynch-me/probara-sdk/blob/main/packages/cypress-reporter/docs/linking.md)
