# Recording a transaction

A "transaction" is a standard Playwright test — no PandoraFMS import required. Three native constructs map to plugin behavior:

| You write | Becomes |
|-----------|---------|
| `test.step('name', ...)` | a monitored **phase** (status + time) |
| `test.info().annotations.push({ type: 'pandora.metric', description: 'name=value' })` | a custom **metric** module |
| a failing assertion | the test fails; a **screenshot** is captured automatically |

## 1. Record the flow with Playwright's recorder (`codegen`)

Playwright ships its own recorder, `codegen`: it opens a real browser, and every click, fill, and navigation you perform is turned into Playwright code in real time, plus a Pick Locator / Explore mode to test selectors against the live page. Official documentation: **[playwright.dev/docs/codegen-intro](https://playwright.dev/docs/codegen-intro)**. General authoring reference: **[playwright.dev/docs/writing-tests](https://playwright.dev/docs/writing-tests)**.

On any machine with Node and Playwright installed (this does not need to be the plugin's Docker image):

```bash
npm init playwright@latest    # first time only, if the project isn't set up yet
npx playwright codegen https://your-app.example.com
```

Two windows open: the browser you interact with, and the **Playwright Inspector**, which shows the generated code live and lets you pick/copy a locator for any element on the page. Useful flags:

- `--browser=firefox` / `--browser=webkit` — record against a specific engine (matches the plugin's `_browser_` setting).
- `--viewport-size=1920,1080` — record at the same resolution the plugin will run (matches `_browserWidth_`/`_browserHeight_`).
- `--save-storage=state.json` — capture cookies/localStorage after an interactive login, to seed later authenticated recordings with `--load-storage=state.json` (see [Authentication](https://playwright.dev/docs/auth) in the official docs if the flow needs a persisted session).

`codegen` output is **flat, ungrouped code** — clicks and assertions one after another, with no `test.step(...)` and no metric annotations. It is a starting point, not the final transaction: copy it into your `.ts` file and go to step 2.

## 2. Turn it into phases

Wrap each meaningful part of the recorded flow in `test.step('name', async () => { ... })`. Every **top-level** `test.step` call — one written directly inside the `test(...)` callback — becomes one phase, with its own `Phase <name> status` and `Phase <name> time` module (see [Agent and modules generated by the plugin](#agent-and-modules-generated-by-the-plugin)). Official reference: **[test.step() API](https://playwright.dev/docs/api/class-test#test-step)**.

```typescript
await test.step('open home', async () => {
  await page.goto('https://your-app.example.com');
  await expect(page).toHaveTitle(/Shop/);
});
```

Things to know:

- **Only top-level steps become phases.** A `test.step(...)` nested *inside* another `test.step(...)` is not reported as a separate phase — the plugin only reads the test's own top-level `steps` array from Playwright's JSON reporter. Keep steps flat (one level) for anything you want to see as an independent phase in the console.
- **Assertions belong inside the step**, not after it — `expect(...)` must run while the step is still open so a failure is attributed to that phase (and captured in its status/description), not to a later one or to the test as a whole.
- **A phase with no assertion is only a timing box.** `test.step('open home', async () => { await page.goto(...); })` with no `expect` will basically always report `status = 1`, since Playwright only marks a step failed when something inside it throws. Add at least one assertion per phase you actually want monitored, not just timed.
- **Ordering and duration**: the phase order in the console matches the order the steps run in; `Phase <name> time` is that step's own wall-clock duration, not cumulative.

## 3. Add custom metrics

Push a `pandora.metric` annotation with `test.info()` — from anywhere in the test body, including inside a `test.step`:

```typescript
const count = await page.locator('.cart-count').innerText();
test.info().annotations.push({ type: 'pandora.metric', description: `cart_items=${count}` });
```

Official reference for annotations: **[test.info().annotations](https://playwright.dev/docs/api/class-testinfo#test-info-annotations)**.

Parsing rules (exact, from the runner):

- `type` must be the literal string `pandora.metric`; anything else is ignored.
- `description` must be `name=value`, split on the **first** `=` only — so a value containing `=` (e.g. a URL query string) is not truncated.
- `name` and `value` are trimmed of surrounding whitespace. If `description` has no `=`, or `name` is empty after trimming, that annotation is silently skipped — no module, no error.
- The module type is inferred from `value`: parses as a number → `generic_data`; anything else → `generic_data_string`.
- The module is named exactly `name` and tagged `extra_data = pw:metric:<name>`.
- Push **one annotation per metric name per test run.** The annotation list is not deduplicated — pushing the same name twice in one run queues two modules with the same name/`extra_data`, which is redundant at best and ambiguous for Pandora to reconcile at worst.

## Full example

```typescript
import { test, expect } from '@playwright/test';

test('checkout flow', async ({ page }) => {
  await test.step('open home', async () => {
    await page.goto('https://your-app.example.com');
    await expect(page).toHaveTitle(/Shop/);
  });

  await test.step('login', async () => {
    await page.fill('#user', 'demo');
    await page.fill('#password', 'demo');
    await page.click('#submit');
    await expect(page.locator('.dashboard')).toBeVisible();
  });

  await test.step('add to cart', async () => {
    await page.click('text=Add to cart');
    const count = await page.locator('.cart-count').innerText();
    test.info().annotations.push({ type: 'pandora.metric', description: `cart_items=${count}` });
  });
});
```

## Notes

- **Naming**: module names come from `test.step` titles — keep them descriptive. Renaming a test starts a new agent.
- **Continue after a failure**: a normal `expect` aborts the test on failure, so later phases do not run at all (they simply don't appear in that run). Use `expect.soft(locator)` if every phase must be measured even when one fails.
- **Multiple transactions**: several `test(...)` blocks in one `.ts` file produce several agents.
- Instead of hand-writing or manually recording the transaction, it can also be generated end-to-end by an AI coding agent that drives a real browser (via a Playwright/browser-automation tool) to validate every locator against the live target before handing over the file — catching ambiguous or strict-mode-violating selectors that plain `codegen` cannot.