Scenario Composition (use:)

Scenario Composition allows Gherkio tests to remain modular, reusable, and DRY (Don't Repeat Yourself). By using the use: property, a scenario can import and run another scenario file in-line as a single step.


⚡ Basic Usage

A common testing pattern is performing an authentication sequence before requesting resource endpoints. Instead of copy-pasting the authentication request in dozens of test files, define it once in a shared test:

Shared Auth Step: .gherkio/tests/shared/login.yaml

scenario: Authenticate User
steps:
  - request:
      method: POST
      url: /auth/login
      body:
        # Option A: Dynamic runtime account resolution (selected via --account <name> CLI flag)
        username: $accounts.username
        password: $accounts.password

        # Option B: Explicit account name (hardcoded to specific credential block)
        # username: $accounts.admin.username
        # password: $accounts.admin.password
    expect:
      status: 200
    save:
      authToken: body.token
```text

> 💡 **Dynamic Account Resolution with `--account`**:
> If you write `$accounts.username` or `$accounts.password` without an account name, Gherkio automatically maps those variables to the active account specified by the `--account` CLI parameter:
> * `gherkio run --account admin` → `$accounts.username` resolves to `$accounts.admin.username`
> * `gherkio run --account alice` → `$accounts.username` resolves to `$accounts.alice.username`
>
> This allows your shared `login.yaml` file to be 100% reusable across different user roles and test accounts without modifying the scenario YAML!

### Main Scenario: `.gherkio/tests/users/list.yaml`
```yaml
scenario: Retrieve User List
steps:
  - use: shared/login.yaml          # Executes the login scenario in-line

  - request:
      method: GET
      url: /users
      headers:
        Authorization: "Bearer ${authToken}"   # Uses variable saved in the used scenario
    expect:
      status: 200
```text


---

## 🔄 Composition in Setup, Steps, and Teardown

In Gherkio, the `setup`, `steps`, and `teardown` blocks are structurally identical lists of steps. Because of this elegant design, **you can use `use:` composition in any phase of your scenario's lifecycle**:

*   **`setup`**: Use composition to log in, seed initial databases, or construct pre-condition states.
*   **`steps`**: Use composition to execute core nested business operations or user flows.
*   **`teardown`**: Use composition to run post-condition cleanups, delete created test objects, or invalidate session tokens.

### Comprehensive Lifecycle Composition Example:
```yaml
scenario: Complete Order Cycle
setup:
  - use: shared/login.yaml               # Logs in and bubbles up $adminToken
  - use: shared/seed-inventory.yaml      # Seeds item database before run

steps:
  - request:
      method: POST
      url: /orders
      headers:
        Authorization: "Bearer ${adminToken}"
      body:
        itemId: 105
        quantity: 1
    expect:
      status: 201
    save:
      createdOrderId: body.orderId

teardown:
  - use: shared/clear-inventory.yaml     # Resets DB inventory back to baseline
```text

---

## 🧭 File Resolution Logic

When Gherkio encounters a `use: <path>` step, it attempts to resolve the filepath in the following sequence:

1. **Relative Path**: Relative to the directory containing the current test file.
2. **Workspace Root**: Relative to the root `.gherkio/tests/` directory.
3. **Absolute Path**: Absolute file paths on the local filesystem.

---

## ⚠️ Recursion & Composition Rules

To prevent infinite loops and ensure robust execution, Gherkio enforces strict structural and execution rules on all composed steps.

### 1. Request/Use Mutual Exclusion
A single step **cannot define both `use:` and `request:`**. They are strictly mutually exclusive:

*   **❌ Illegal Step**:
    ```yaml
    # Fails static analysis!
    - use: shared/login.yaml
      request:
        method: GET
        url: /profile
    ```
*   **➔ Correct Structure**:
    ```yaml
    - use: shared/login.yaml
    - request:
        method: GET
        url: /profile
    ```

### 2. Forbidden Retry Blocks on Composed Steps
You **cannot apply a `retry:` block to a `use:` step**. Retries are only allowed on individual HTTP requests, not entire composed scenarios. If you define a retry block on a composed step, Gherkio halts immediately with a validation error.

*   **❌ Illegal Step**:
    ```yaml
    # Fails validation!
    - use: shared/login.yaml
      retry:
        attempts: 3
        backoff: 1s
    ```
*   **➔ Correct Structure**: Apply the retry block *inside* the shared `login.yaml` step definition directly.

### 3. Nesting & Recursion Limits
To protect runner performance and prevent system freezes:
*   **Default Composition Depth**: Nesting is limited to a maximum depth of **5 nested layers** by default (customizable in your `config.yaml` using the `maxCompositionDepth` property).
*   **Strict Cutoff Limit**: Regardless of configurations, Gherkio enforces a hard ceiling at **10 nested layers**. Exceeding this triggers a static error (`circular reference or max depth exceeded`).
*   **Circular Import Checks**: Gherkio builds an import dependency graph prior to execution. If a circular loop is found (e.g. `A.yaml` ➔ `B.yaml` ➔ `A.yaml`), the executor aborts before making any HTTP requests.

### 4. Context Inheritance & Variables Bubbling
*   Any dynamic variables or state captured (via the `save` keyword) inside a composed scenario are **automatically merged and bubbled up** into the parent scenario's execution context.
*   Once a composed step finishes executing, all subsequent steps in the parent file have full access to its saved variables.

---

## ↔ Variable Overrides (`with:`)

Sometimes you need to pass dynamic input parameters *into* a composed scenario (parameterized test templates) rather than just consuming variables that bubble *out*. The `with:` block lets you inject temporary variable overrides into the target composed scenario's execution context.

```yaml
- use: shared/lookup/status_claim.yaml
  with:
    CLAIM_ID: $CREATED_CLAIM_ID
    EXPECTED_STATUS: "APPROVED"
```text

### ⚙️ How `with:` Works

1. **Interpolation**: Each expression in `with:` is evaluated against the current parent context before injection.
2. **Context Scope**: The resolved values are injected as local variables into the target scenario for the duration of its execution.
3. **Automatic Restoration**: Once the composed scenario finishes executing, previous parent variable values are restored so the injected override doesn't pollute subsequent steps in the main scenario.

---

### 📂 Complete Two-File Workflow Example

Below is a complete real-world setup showing both the shared target scenario file and the main parent scenario file using `with:`.

#### 1. Shared Composed Scenario: `.gherkio/tests/shared/lookup/status_claim.yaml`

This scenario expects two input variables (`$CLAIM_ID` and `$EXPECTED_STATUS`), which will be injected via `with:` by any parent scenario:

```yaml
scenario: Shared Claim Status Lookup
steps:
  - name: Fetch claim status details
    request:
      method: GET
      url: /v1/claims/$CLAIM_ID            # Uses injected $CLAIM_ID
      headers:
        Authorization: "Bearer ${authToken}"
    expect:
      status: 200
      body.data.status: "$EXPECTED_STATUS" # Asserts against injected $EXPECTED_STATUS
    save:
      claimAssignee: body.data.assignedTo  # Bubbles up saved variable back to parent
```text

#### 2. Main Parent Scenario: `.gherkio/tests/claims/verify-approval.yaml`

The parent scenario imports `shared/lookup/status_claim.yaml` and passes runtime parameters using the `with:` block:

```yaml
scenario: Verify Order Claim Approval Flow

setup:
  - use: shared/login.yaml                # Logs in and provides $authToken

steps:
  - name: Create new insurance claim
    request:
      method: POST
      url: /v1/claims
      headers:
        Authorization: "Bearer ${authToken}"
      body:
        policyId: "POL-1002"
        amount: 500
    expect:
      status: 201
    save:
      NEW_CLAIM_ID: body.data.id          # Saves "CLM-9948"

  - name: Validate claim status using shared lookup template
    use: shared/lookup/status_claim.yaml
    with:
      CLAIM_ID: $NEW_CLAIM_ID             # Passes $NEW_CLAIM_ID into the shared step as $CLAIM_ID
      EXPECTED_STATUS: "PENDING"          # Passes expected status threshold

  - name: Verify assignee bubbled up from shared step
    request:
      method: GET
      url: /v1/users/$claimAssignee       # Uses $claimAssignee saved by the shared lookup step
    expect:
      status: 200
```text

---

### 💡 Key Rules for `with:`

- **`use:` Only**: `with:` is **only valid on `use:` steps**. It cannot be combined with standard `request:` steps.
- **Full Interpolation Support**: Values inside `with:` support all Gherkio expressions (`$var`, `${var:default}`, `$accounts.username`, built-in generators, etc.).
- **Scoped Precedence**: Injected variables take highest precedence inside the target composed scenario without modifying the caller's global variable state.


---

## 🔍 Composed Traceability & Debugging

When executing nested scenarios, tracing execution flows and context variables can become challenging. Gherkio provides native composed traceability inside its HTML run reports:

1. **Visual Grouping**: Composed scenarios are rendered as distinct, visually encapsulated boxes containing their inner steps.
2. **Nesting Indentation**: Steps inside composed scenarios are dynamically indented according to their nesting `depth`.
3. **Variable Snapshots**: The entry point of every composed scenario captures a complete snapshot of all active context variables (both inherited and overridden via `with:`), allowing you to inspect execution states during debug.