Gherkio Documentation

The Declarative Integration Testing Platform β€” Build bulletproof API test suites in pure YAML. No boilerplate, no runtime compilation, zero custom glue-code required.

Gherkio is a declarative integration testing platform compiled into a single static Go binary. Teams write API integration tests by describing HTTP request sequences, assertions, and variable extractions in pure YAML. It features a native MCP server for AI assistant integration, outbound network sandboxing for security, and structured reporting for CI/CD pipelines.


scenario: Authenticate & Retrieve Profile
steps:
  - request:
      method: POST
      url: /auth/login
      body:
        username: $accounts.admin.username
        password: $accounts.admin.password
    expect:
      status: 200
      body.token: exists
    save:
      jwt: body.token

  - request:
      method: GET
      url: /auth/me
      headers:
        Authorization: Bearer $jwt
    expect:
      status: 200
      body.role: admin
```text

---

## 🧭 Tiered Learning Paths

Whether you are writing your first test scenario, building reusable enterprise test suites, or integrating AI agent automation into CI/CD, choose your path below:

### 🟒 1. Beginner Path (Zero-to-Hero)
*Ideal for developers & QA engineers getting started with Gherkio.*

| Guide | Description |
| :--- | :--- |
| πŸš€ **[Installation](getting-started/installation.md)** | Download static binaries for Linux, macOS, or Windows. |
| ⚑ **[2-Minute Quickstart](getting-started/quickstart.md)** | Scaffold your workspace and run your first scenario in under two minutes. |
| πŸ“ **[Folder & Project Setup](getting-started/project-setup.md)** | Learn `.gherkio/` directory structure, environments, and configuration. |
| πŸ“ **[Tutorial: Build Your First Test](getting-started/first-test-tutorial.md)** | Step-by-step guide to modeling HTTP requests, status assertions, and saved variables. |
| πŸ” **[What Happens in a Test: Lifecycle](getting-started/execution-lifecycle.md)** | Detailed breakdown of the internal 9-phase execution pipeline, variable resolution, and step execution. |
| 🎨 **[Interactive Playground](getting-started/playground.md)** | Convert legacy cURL commands to Gherkio YAML DSL instantly. |

---

### 🟑 2. Advanced Practitioner Path
*For engineers building modular, resilient, and multi-environment test suites.*

| Guide | Description |
| :--- | :--- |
| 🧩 **[Scenario Composition](dsl/composition.md)** | Modularize tests using `use: path/to/scenario.yaml` for shared auth & setup flows. |
| 🎯 **[Dynamic Assertions & Matchers](dsl/matchers.md)** | Master value matchers (`contains`, `greaterThan`, `oneOf`, `in`, `matchesRegex`), JWT validation, and timing budgets. |
| πŸ“‹ **[JSON Schema Validation](dsl/schemas.md)** | Enforce structural schema rules against response payloads using `.gherkio/schemas/`. |
| πŸ”„ **[Async Retries & Polling](dsl/retry.md)** | Handle asynchronous background jobs with exponential backoff and retry rules. |
| ⚑ **[Redis Cache State Checks](dsl/redis.md)** | Assert key-value states in Redis directly inside scenario test steps. |
| πŸ” **[Credentials & Environments](reference/environments.md)** | Manage multi-account credentials and staging/production target overrides. |
| πŸ› οΈ **[CLI Tooling & Validation](cli/overview.md)** | Run static analysis (`gherkio validate`), generate JSON Schema (`gherkio schema`), and convert cURL (`gherkio convert`). |

---

### πŸ”΄ 3. Expert & Platform Engineer Path
*For architects, DevOps teams, and AI-assisted workflow integration.*

| Guide | Description |
| :--- | :--- |
| πŸ€– **[MCP Server & AI Assistant Workflows](mcp/overview.md)** | Connect Gherkio directly to Cursor, Claude Desktop, or Windsurf for AI-generated testing & self-healing runs. |
| πŸ—οΈ **[Engine Architecture & Security Sandboxing](contributing/architecture.md)** | Deep-dive into static Go engine internals, thread safety, and outbound SSRF protection. |
| πŸ“Š **[Enterprise CI/CD & Reporting](reference/reports.md)** | Generate JUnit XML, HTML, and JSON reports for GitHub Actions, GitLab CI, and Jenkins. |
| πŸ› οΈ **[Extending Gherkio](contributing/adding-matchers.md)** | Add custom matchers and extend the static evaluation engine in Go. |

---

## ⚑ Key Value Pillars

* **Built for Speed**: Written in pure Go, Gherkio compiles to a single static binary with zero external dependencies.
* **Declarative YAML Syntax**: Human-readable scenarios double as executable API documentation.
* **Native Tooling**: Out-of-the-box support for JWT checks, Redis assertions, exponential backoff retries, and dynamic generators (`$randomEmail`, `$uuid`).
* **Agentic AI Ready**: Built-in Model Context Protocol (MCP) server enables AI developer tools to run, validate, and write tests autonomously.

Getting Started with Gherkio

🟒 Beginner Guide β€” Welcome to Gherkio! This guide is structured to take you from a complete beginner to writing and orchestrating advanced declarative integration tests in minutes.


πŸ—ΊοΈ Progressive Onboarding Journey

To get the most out of Gherkio, follow this recommended step-by-step onboarding roadmap:

graph TD
    A[1. Introduction] --> B[2. Installation]
    B --> C[3. 2-Minute Quickstart]
    C --> D[4. Project Setup]
    D --> E[5. Build Your First Test]
    E --> F[6. Test Execution Lifecycle]
    F --> G[7. Interactive Playground]
```text

1. **[Installation](installation.md)**: Compile from source or fetch compiled binaries for Linux, macOS, or Windows.
2. **[2-Minute Quickstart](quickstart.md)**: Scaffold a testing sandbox with `gherkio init` and execute your first local test scenario.
3. **[Project & Folder Setup](project-setup.md)**: Master Gherkio's folder layout, configuration overrides, and environments.
4. **[Tutorial: Build Your First Test](first-test-tutorial.md)**: A hands-on walkthrough guiding you from an empty file to a multi-step variable-injecting test flow.
5. **[What Happens in a Test: Execution Lifecycle](execution-lifecycle.md)**: Deep-dive into the 9-phase internal execution pipeline, variable resolution, and assertion rules.
6. **[Interactive Browser Playground](playground.md)**: Visualize YAML steps and translate legacy cURL statements instantly in your browser.

---

## πŸ’‘ Why Gherkio?

Traditional API integration testing requires writing hundreds of lines of boilerplate code in Javascript, Go, Python, or Java. This code quickly becomes unmaintainable, suffers from runtime syntax errors, and is difficult for non-engineers (like Product Managers or QA specialists) to read.

Gherkio solves this by introducing a **Declarative YAML Domain Specific Language (DSL)**:

| Feature | Gherkio (Declarative YAML) | Traditional Code (Imperative) |
| :--- | :--- | :--- |
| **Readability** | 🟒 Extremely high. Reads like a technical spec. | πŸ”΄ Low. Hidden under imports, async blocks, and classes. |
| **Maintenance** | 🟒 No compiling or dependency management. | πŸ”΄ Constant node/go module security updates. |
| **API Assertions** | 🟒 Zero-boilerplate `body.role: admin` | πŸ”΄ Bulky `expect(res.body.role).to.equal("admin")` |
| **AI Generation** | 🟒 Near-perfect accuracy due to strict schema rules. | πŸ”΄ Prone to syntax hallucinations, imports, and compiler issues. |

---

## ⚑ Core Concepts to Remember

As you read the documentation, keep these three structural pillars in mind:
* **Scenarios**: A scenario is a list of sequential HTTP request steps representing a user journey (e.g. *Login βž” Create Cart βž” Checkout*).
* **Environments**: Define your target base hosts (e.g. `https://api.staging.company.com`) without hardcoding them in test files.
* **Variables**: Save response fields dynamically (`save: authToken: body.token`) and reuse them in subsequent headers or bodies (`Authorization: Bearer $authToken`).

---

## 🎯 Gherkio Philosophy

Gherkio is built on a simple, uncompromising core principle:

> **Integration testing should describe *what* behavior to orchestrate, not *how* to implement it.**

* **Declarative-First**: Scenarios describe high-level API workflows rather than writing custom test scripts or boilerplate code.
* **Readability Matters**: Integration tests are written to be easily read, audited, and maintained by anyone on the team after 1–2 years.
* **Deep Observability**: Every execution outputs structured, high-precision payloads and validation tracebacks so failures are debugged instantly.
* **Constrained DSL**: No arbitrary coding loops or complex branching inside test filesβ€”forcing tests to stay clean, predictable, and robust.

Installation

Gherkio is compiled as a single self-contained binary with zero external dependencies. You can install it using Go or by downloading pre-built binaries.


πŸ› οΈ Requirements

  • Operating System: Linux, macOS, or Windows.
  • Go Version (if installing from source): Go 1.25+
  • HTTP client: curl (optional, for bidirectional conversion tools).

πŸ“¦ Install Options

If you have Go installed on your system, run:

go install github.com/muhfaris/gherkio@latest
```text

This compiles the binary and places it under your `$GOPATH/bin` or `$GOBIN` directory. Make sure this directory is in your system's `PATH`.

### 2. Pre-Built Binaries

For systems without Go, download the latest compiled binary from the [GitHub Releases](https://github.com/muhfaris/gherkio/releases) page:

1. Download the archive matching your operating system and architecture (e.g. `gherkio_Linux_x86_64.tar.gz`).
2. Extract the archive:
   ```bash
   tar -xzf gherkio_Linux_x86_64.tar.gz
  1. Move the binary to a directory in your executable path:
    sudo mv gherkio /usr/local/bin/
    

3. Zero-Install Execution (Go Run)

If you have Go installed but do not want to compile or place the Gherkio binary permanently on your local system path, you can run Gherkio directly from the remote repository using go run:

# Initialize a project sandbox
go run github.com/muhfaris/gherkio@v0.1.0-alpha.9 init

# Execute tests
go run github.com/muhfaris/gherkio@v0.1.0-alpha.9 run .gherkio/tests/example/login.yaml
```text

This is the perfect approach for quick trial runs or executing Gherkio inside ephemeral, single-use CI/CD runner pipelines.


---

## βœ… Verifying Installation

Verify that Gherkio is installed correctly by checking its version:

```bash
gherkio --version
```text

You should see an output similar to:
```text
gherkio version v0.1.0-alpha.9 (commit: abc1234, built: 2026-08-30, go1.25.5)
```text

2-Minute Quickstart

🟒 Beginner Guide β€” Start testing your HTTP APIs with Gherkio in under 2 minutes. We will initialize a sandbox project, review the generated declarative scenario, and execute it.


πŸš€ 1. Initialize a Project

Create a new directory for your test suite and run gherkio init:

mkdir my-api-tests && cd my-api-tests
gherkio init
```text

> πŸ’‘ **Zero-Install Alternative:** If you do not have the binary installed on your local system path, you can run all Gherkio commands using standard remote Go execution:
> ```bash
> go run github.com/muhfaris/gherkio@v0.1.0-alpha.9 init
> ```

This scaffolds the canonical Gherkio folder structure:
```text
.gherkio/
β”œβ”€β”€ config.yaml
β”œβ”€β”€ credentials/
β”‚   └── local.yaml
β”œβ”€β”€ environments/
β”‚   └── local.yaml
β”œβ”€β”€ schemas/
β”‚   └── example/
β”‚       β”œβ”€β”€ login-response.yaml
β”‚       └── user-response.yaml
└── tests/
    └── example/
        β”œβ”€β”€ accounts/
        β”‚   └── login-as-alice.yaml
        β”œβ”€β”€ auth/
        β”‚   β”œβ”€β”€ login.yaml
        β”‚   β”œβ”€β”€ me.yaml
        β”‚   └── refresh.yaml
        └── builtins/
            └── login-with-generators.yaml
```text

---

## πŸ“ 2. Review the Example Test

Gherkio created a pre-configured example test under `.gherkio/tests/example/auth/login.yaml`. Open it to see the declarative YAML DSL:

```yaml
scenario: login example

steps:
  - request:
      method: POST
      url: /auth/login

      body:
        username: $username
        password: $password
        expiresInMins: 30

    expect:
      status: 200
      body.accessToken: exists
      body.refreshToken: exists
      body.email: email
      schema: example/login-response

    save:
      accessToken: body.accessToken
      refreshToken: body.refreshToken
```text

---

## πŸƒ 3. Run the Test Scenario

Run the scaffolded test using `gherkio run`:

```bash
gherkio run example/auth/login.yaml --verbose
```text

> πŸ’‘ **Zero-Install Alternative:** You can run Gherkio dynamically without installing using `go run`:
> ```bash
> go run github.com/muhfaris/gherkio@v0.1.0-alpha.9 run example/auth/login.yaml --verbose
> ```

The `--verbose` flag shows full request and response payloads with automatically masked credentials.

You will see the step-by-step console printer output and a final test summary:
```text
βœ” Step 1: POST https://dummyjson.com/auth/login [200 OK] (182ms)
  βœ” Assertion: status == 200
  βœ” Assertion: body.accessToken exists
  βœ” Assertion: body.refreshToken exists
  βœ” Assertion: body.email matches format email
  βœ” Assertion: response conforms to schema: example/login-response
  β†’ Saved accessToken: eyJhbGciOi...
  β†’ Saved refreshToken: rGhyTk83...

=======================================================
SCENARIO RESULT: PASSED
Total Steps: 1 | Passed: 1 | Failed: 0 | Duration: 182ms
=======================================================
```text

---

## πŸ” What Happened Behind the Scenes?

When you ran `gherkio run`:
1. Gherkio loaded `.gherkio/environments/local.yaml` and resolved the base target URL.
2. Credentials and variables (`$username`, `$password`) were dynamically injected.
3. The HTTP request was compiled and dispatched over a secure connection.
4. Response status, body values, and schema definitions were evaluated.
5. Saved tokens were stored in scenario runtime context for downstream steps.

To learn more about the 9-phase execution pipeline, see **[What Happens in a Test: Execution Lifecycle](execution-lifecycle.md)**.

Project Setup

Learn how Gherkio structures its folders, configures environments, and maps services for declarative integration testing.


πŸ“ Directory & Workspace Layout

All Gherkio configuration, variables, tests, schemas, and reports reside inside a single, hidden .gherkio/ folder at the root of your project workspace. Test media files (images, avatars, PDFs for multipart uploads) reside in an assets/ directory at the project root:

my-project/
β”œβ”€β”€ .gherkio/                           # Gherkio engine configuration & test files
β”‚   β”œβ”€β”€ config.yaml                     # Global test execution config
β”‚   β”œβ”€β”€ credentials/                    # Environment-specific usernames & keys
β”‚   β”‚   └── local.yaml
β”‚   β”œβ”€β”€ environments/                   # Target API hosts and service mappings
β”‚   β”‚   └── local.yaml
β”‚   β”œβ”€β”€ schemas/                        # Reusable JSON/YAML validation schemas
β”‚   β”‚   └── example/
β”‚   β”‚       β”œβ”€β”€ login-response.yaml
β”‚   β”‚       └── user-response.yaml
β”‚   β”œβ”€β”€ tests/                          # Your declarative YAML test scenario suites
β”‚   β”‚   └── example/
β”‚   β”‚       β”œβ”€β”€ accounts/
β”‚   β”‚       β”‚   └── login-as-alice.yaml
β”‚   β”‚       β”œβ”€β”€ auth/
β”‚   β”‚       β”‚   β”œβ”€β”€ login.yaml
β”‚   β”‚       β”‚   β”œβ”€β”€ me.yaml
β”‚   β”‚       β”‚   └── refresh.yaml
β”‚   β”‚       └── builtins/
β”‚   β”‚           └── login-with-generators.yaml
β”‚   └── reports/                        # Automatically generated test run logs & HTML reports
β”‚       β”œβ”€β”€ archive/
β”‚       β”œβ”€β”€ failures/                   # Dynamic JSON snapshots for failed assertions
β”‚       └── latest/
└── assets/                             # Test media files (images, PDFs) for multipart file uploads
    β”œβ”€β”€ john-avatar.png
    β”œβ”€β”€ sample-image.jpg
    └── resume.pdf
```text


---

## βš™οΈ Global Configuration (`config.yaml`)

The global configuration file handles execution defaults, reporting, credential masking, and request sandboxing. Gherkio uses these fields to coordinate runtime behavior:

```yaml
gherkio_version: 0.1.0

# ----------------------------------------------------------------------
# 1. Project Metadata
# ----------------------------------------------------------------------
project:
  name: "my-api-testing-suite"
  version: "1.0.0"

# ----------------------------------------------------------------------
# 2. Environments Directory Configuration
# ----------------------------------------------------------------------
environments:
  default: local                    # Default environment if --env is omitted
  path: .gherkio/environments       # Path to environment configurations

# ----------------------------------------------------------------------
# 3. Tests & Scenarios Configuration
# ----------------------------------------------------------------------
tests:
  path: .gherkio/tests              # Directory where test scenarios reside

# ----------------------------------------------------------------------
# 4. JSON Schemas Configuration
# ----------------------------------------------------------------------
schemas:
  path: .gherkio/schemas            # Directory containing validation schemas

# ----------------------------------------------------------------------
# 5. Multipart File Assets Directory
# ----------------------------------------------------------------------
assets:
  path: assets                      # Default directory for multipart file uploads

# ----------------------------------------------------------------------
# 6. Reports & Core Failure Snapshots
# ----------------------------------------------------------------------
reports:
  path: .gherkio/reports            # Output path for test summaries
  format: html                      # Report format ("html", "json", or "html,json")
  archive: true                     # Keep a history of past reports
  retention: 10                     # Keep up to 10 historical reports
  maskSensitive: true               # Mask credentials in HTML report pages
  failures:
    enabled: true                   # Write detailed JSON debug snapshots on failures
    path: .gherkio/reports/failures # Directory for failure snapshots
    maskSensitive: true             # Mask credentials in failure dumps
    retainCount: 50                 # Retain up to 50 debug snapshots

# ----------------------------------------------------------------------
# 7. Security and Sandboxing (Policy Engine)
# ----------------------------------------------------------------------
security:
  mask:
    enabled: true                   # Enable masking of sensitive fields in outputs
    fields:                         # Case-insensitive sensitive keys to redact
      - password
      - token
      - secret
      - Authorization
  sandboxing:
    enabled: true                   # Enforce outbound network boundaries
    allowedDomains:                 # Allowed target domains (supports wildcards)
      - "*.company.com"
      - "api.stripe.com"
    blockedDomains:                 # Explicitly blocked targets
      - "analytics.untrusted.com"
    blockPrivateSubnets: true       # Prevent loopback (127.0.0.1) and private IP calls

# ----------------------------------------------------------------------
# 8. Authentication Defaults (Optional)
# ----------------------------------------------------------------------
jwt_token_path: "body.token"        # JSON path for auto-extracting JWT tokens
```text

The default `assets.path: assets` points to the `assets/` directory beside `.gherkio/`. You can place the directory elsewhere by changing the path relative to the project root:

```yaml
assets:
  path: .gherkio/test-assets
```text

This produces a layout such as:

```text
my-project/
└── .gherkio/
    β”œβ”€β”€ config.yaml
    β”œβ”€β”€ test-assets/
    β”‚   └── attachments/
    β”‚       └── sample.pdf
    └── tests/
```text

A multipart file value of `attachments/sample.pdf` will then resolve inside `.gherkio/test-assets/`. See [Multipart Form Data](../dsl/requests.md#multipart-form-data-multipart) for complete examples and lookup rules.

---

## πŸ”’ Security & Sandboxing Policies

Gherkio includes a native, industrial-grade **outbound traffic sandbox** and **credential masking engine** to secure enterprise testing pipelines and prevent security risks (like SSRF or private data leaks).

### 1. Credentials & Token Masking (`security.mask`)

To prevent sensitive keys, tokens, or credentials from leaking into test summaries, build logs, and console outputs, Gherkio scans response JSON keys and HTTP headers to mask matching fields.

*   **`enabled`**: (`boolean`) Turns output redacting on or off.
*   **`fields`**: (`[]string`) List of case-insensitive JSON paths or header keys to mask. When found, values are replaced with `[MASKED]`.
    *   **Built-in Defaults (automatically masked)**: `token`, `accessToken`, `access_token`, `refreshToken`, `refresh_token`, `password`, `secret`, `clientSecret`, `client_secret`, `apiKey`, `api_key`, and `authorization` (case-insensitive). You can add custom fields to this list; the built-in defaults remain active.

### 2. Domain & Outbound Sandboxing (`security.sandboxing`)

The sandboxing engine controls which external network servers Gherkio is allowed to communicate with. If a test scenario attempts to hit a blocked or unlisted host, Gherkio halts the step immediately before performing the HTTP call.

*   **`enabled`**: (`boolean`) Enforces network boundaries.
*   **`allowedDomains`**: (`[]string`) White-list of target endpoints. Supports simple wildcards (e.g. `*.company.com` matches `api.company.com` and `web.company.com`). If left empty, all domains not explicitly blocked are allowed.
*   **`blockedDomains`**: (`[]string`) Explicit black-list of endpoints. Take precedence over allowed domains.
*   **`blockPrivateSubnets`**: (`boolean`) When set to `true`, Gherkio resolves the target domain's IP addresses via DNS lookup prior to execution. If the resolved IP belongs to a local or private subnet (e.g. `127.0.0.1`, `::1`, `10.0.0.0/8`, `192.168.0.0/16`), the request is immediately rejected. This prevents SSRF (Server-Side Request Forgery) attacks and unauthorized internal port scanning.


---

## 🌐 Configuring Environments

Managing tests across local, staging, and production environments can easily lead to configuration bloat. Gherkio solves this using a **declarative environment mapping strategy**. 

All target environment host files reside as separate YAML configurations inside `.gherkio/environments/` (e.g. `local.yaml`, `staging.yaml`, `production.yaml`). You switch environments instantly via the CLI flag:
```bash
gherkio run example/auth/login.yaml --env staging
```text

---

### 1. The Environment Configuration Schema

Each environment file has a simple, clean, but highly extensible schema:

| YAML Key | Type | Required | Description | Example |
| :--- | :--- | :--- | :--- | :--- |
| `baseUrl` | `string` | **Yes** | The default host URL. Used as a prefix for all relative URL request steps. | `baseUrl: https://api.staging.company.com` |
| `services` | `map[string]Service` | No | Overrides for specific microservices or downstream third-party APIs. | (See below) |
| `services.<name>.baseUrl` | `string` | **Yes** | The service-specific host URL prefix. | `baseUrl: http://auth-service:3000` |

---

### 2. Multi-Service Microservice Routing

In modern cloud-native architectures, single scenarios often span multiple distinct backend microservices (e.g. creating a user on the Auth API, making a charge on the Payment API, and verifying stock on the Inventory API). 

Gherkio allows you to declare all service endpoints in a single environment configuration, and route requests dynamically on a step-by-step basis:

#### Environment Config: `.gherkio/environments/staging.yaml`
```yaml
# Global default fallback
baseUrl: https://api.staging.company.com

# Microservice host overrides
services:
  auth:
    baseUrl: https://auth.staging.company.internal
  payments:
    baseUrl: https://payments.staging.company.internal
  notifications:
    baseUrl: http://localhost:8080   # Port-forwarded or local mock service
```text

#### Test Scenario Routing: `.gherkio/tests/orders/checkout.yaml`
```yaml
scenario: Complete Order Checkout Flow
steps:
  # 1. Routes to Auth Service (https://auth.staging.company.internal/session)
  - request:
      method: POST
      service: auth
      url: /session
      body:
        token: $accounts.customer.apiKey
    save:
      authToken: body.accessToken

  # 2. Routes to Payments Service (https://payments.staging.company.internal/charges)
  - request:
      method: POST
      service: payments
      url: /charges
      headers:
        Authorization: "Bearer ${authToken}"
      body:
        amount: 4500
        currency: usd
    expect:
      status: 201

  # 3. Routes to Default baseUrl (https://api.staging.company.com/orders)
  # (No service tag is declared, so it defaults to the root baseUrl)
  - request:
      method: GET
      url: /orders
      headers:
        Authorization: "Bearer ${authToken}"
    expect:
      status: 200
```text

---

### 3. URL Precedence & Resolution Formula

When Gherkio executes a test step, it parses the target endpoint according to the following strict order of precedence (highest to lowest):

```text
[1. Absolute Step URL]    -->  - request: { url: "https://google.com" } (Overrides everything)
            ↓
[2. Named Service Route]  -->  - request: { service: "auth", url: "/login" } (Resolves via services map)
            ↓
[3. Default Base URL]     -->  - request: { url: "/login" } (Resolves via root baseUrl)
```text

> πŸ’‘ **Best Practice:** Keep scenario steps generic by using relative paths (e.g., `/users`). Never hardcode staging or production domain names inside your YAML test scenarios. This ensures that the same scenario runs flawlessly across all pipelines simply by swapping the `--env` flag.

---

## πŸ› οΈ Editor Autocomplete & JSON Schema

To make your development workflow fast and error-free, Gherkio includes a built-in JSON Schema generator (`gherkio schema`). This provides inline autocompletions, real-time syntax checking, and hover documentation inside editors like VSCode, Neovim, and others.

### 1. Generate the Autocomplete Schema

Generate the schema file in your project's workspace root:

```bash
gherkio schema > .gherkio-schema.json
```text

> πŸ’‘ **Zero-Install Alternative:** If you do not have the binary installed on your local system path, you can run the generator dynamically using remote `go run`:
> ```bash
> go run github.com/muhfaris/gherkio@v0.1.0-alpha.9 schema > .gherkio-schema.json
> ```

---

### 2. VS Code Integration

1. Install the official [YAML Extension by Red Hat](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml) inside VS Code.
2. Open or create your workspace `settings.json` file under `.vscode/settings.json` and associate the schema with your Gherkio test folders:

```json
{
  "yaml.schemas": {
    "./.gherkio-schema.json": [".gherkio/tests/**/*.yaml", ".gherkio/tests/**/*.yml"]
  }
}
```text

---

### 3. Inline Alternative (Any Editor)

If you don't use VS Code or prefer localized configuration, add this directive comment as the **very first line** of your Gherkio YAML test files:

```yaml
# yaml-language-server: $schema=../../.gherkio-schema.json
```text

Tutorial: Build Your First Test

In this tutorial, you will write a complete Gherkio declarative integration test scenario from scratch.

We will build a multi-step user journey mimicking a content platform workflow:

  1. Fetch a post from an API endpoint.
  2. Assert the response status, check that the title is a non-empty string, and verify the post ID is correct.
  3. Save the userId from the post response.
  4. Fetch the user's profile using the saved variable and assert their username matches.

πŸ› οΈ Step 1: Create Your Test File

First, navigate to your Gherkio test directory (created during gherkio init) and create a new YAML file:

touch .gherkio/tests/example/auth/my-first-test.yaml
```text

Open this file in your favorite text editor.

---

## πŸ“ Step 2: Define Scenario Metadata

Every Gherkio test suite begins with a scenario declaration and optional tag lists for easy test suite filtering:

```yaml
scenario: Fetch Post and Verify Author Profile
tags:
  - regression
  - tutorial
```text

---

## πŸ”„ Step 3: Write the First Step (Fetch & Assert)

Let's append our first test step under the `steps` list. This step issues a standard HTTP `GET` request to fetch Post `#1`:

```yaml
steps:
  - request:
      method: GET
      url: https://jsonplaceholder.typicode.com/posts/1
    expect:
      status: 200
      body.id: 1
      body.title: string
      body.userId: integer
```text

### πŸ’‘ What's Happening Here?
- **`request.method` & `request.url`**: Define the HTTP command and fully-qualified target path.
- **`expect.status: 200`**: Ensures the server replies with a `200 OK` status.
- **`expect.body.id: 1`**: Asserts exact equality. The post ID must be exactly `1`.
- **`expect.body.title: string`**: Evaluates the data type. Asserts that the title field returned is a string.
- **`expect.body.userId: integer`**: Ensures the author reference is a valid number.

---

## πŸ’Ύ Step 4: Extract and Save Context Variables

Before fetching the user's profile, we need to know who wrote Post #1. We can extract the `userId` field dynamically and save it under a temporary variable name:

```yaml
    save:
      authorId: body.userId
```text

### πŸ’‘ What's Happening Here?
- **`save`**: The context extractor block.
- **`authorId: body.userId`**: Resolves the JSON body field `userId` (e.g. `1`), and binds its value to the session variable `authorId` for subsequent steps.

---

## πŸ”— Step 5: Write the Second Step (Variable Reuse)

Now, let's add a second step to retrieve the profile of the author we just saved. We will interpolate the `$authorId` variable directly in the request URL:

```yaml
  - request:
      method: GET
      url: https://jsonplaceholder.typicode.com/users/$authorId
    expect:
      status: 200
      body.id: integer
      body.username: string
```text

### πŸ’‘ What's Happening Here?
- **`url: .../users/$authorId`**: Gherkio automatically replaces `$authorId` with the value extracted in the previous step (e.g. `https://jsonplaceholder.typicode.com/users/1`).
- **`expect.body.username: string`**: Confirms that the profile contains a valid, non-empty username.

---

## πŸš€ Step 6: Review the Full Scenario File

Here is what your completed `.gherkio/tests/example/auth/my-first-test.yaml` file should look like:

```yaml
scenario: Fetch Post and Verify Author Profile
tags:
  - regression
  - tutorial

steps:
  - request:
      method: GET
      url: https://jsonplaceholder.typicode.com/posts/1
    expect:
      status: 200
      body.id: 1
      body.title: string
      body.userId: integer
    save:
      authorId: body.userId

  - request:
      method: GET
      url: https://jsonplaceholder.typicode.com/users/$authorId
    expect:
      status: 200
      body.id: integer
      body.username: string
```text

---

## πŸƒ Step 7: Run Your Test

Now run your new declarative scenario using the Gherkio CLI:

```bash
gherkio run example/auth/my-first-test.yaml --verbose
```text

### πŸŽ‰ The Output
Gherkio will execute both steps sequentially, resolving variables on the fly, and outputting beautiful execution metrics:

```text
βœ” Step 1: GET https://jsonplaceholder.typicode.com/posts/1 [200 OK] (95ms)
  βœ” Assertion: status == 200
  βœ” Assertion: body.id == 1
  βœ” Assertion: body.title matches type string
  βœ” Assertion: body.userId matches type integer
  β†’ Saved authorId: 1

βœ” Step 2: GET https://jsonplaceholder.typicode.com/users/1 [200 OK] (87ms)
  βœ” Assertion: status == 200
  βœ” Assertion: body.id matches type integer
  βœ” Assertion: body.username matches type string

=======================================================
SCENARIO RESULT: PASSED
Total Steps: 2 | Passed: 2 | Failed: 0 | Duration: 182ms
=======================================================
```text

You've successfully built, written, and verified your first multi-step integration test flow! Next, check out how to structure your custom local environment files to avoid hardcoding domain URLs under **[Folder & Project Setup](project-setup.md)**.

What Happens in a Test: Execution Lifecycle

Understanding how Gherkio executes a test scenario is crucial for writing reliable API integration tests and debugging failures. Unlike imperative test frameworks that execute arbitrary code, Gherkio follows a deterministic, multi-phase execution pipeline built in static Go.

This guide details every internal step Gherkio takes from the moment you run gherkio run until the final report is generated.


πŸ“Š Overview: The Execution Pipeline

When Gherkio runs a test scenario, it processes the YAML definition through 9 distinct lifecycle phases:

flowchart TD
    A["1. Init & Config Loading"] --> B["2. Setup Block Execution"]
    B --> C["3. Step Variable Resolution"]
    C --> D["4. Outbound Network & Security Sandbox"]
    D --> E["5. Execution & Latency Budgeting"]
    E --> F["6. Assertion & Schema Validation"]
    F --> G["7. State Persistence & Save Block"]
    G --> H{"More Steps?"}
    H -- Yes --> C
    H -- No --> I["8. Teardown Block Execution"]
    I --> J["9. Report Generation & Exit Code"]
```text

---

## πŸ” Phase-by-Phase Breakdown

### Phase 1: Initialization & Environment Context Resolution
Before executing any network calls, Gherkio loads workspace context from the `.gherkio/` directory:
1. **Reads Configuration (`.gherkio/config.yaml`)**: Loads project paths, report settings, masking, sandboxing, and multipart asset settings.
2. **Loads Environment Overrides (`.gherkio/environments/<env>.yaml`)**: Maps base URLs and service endpoints (e.g., `baseUrl: https://api.staging.example.com`).
3. **Injects Credentials (`.gherkio/credentials/<env>.yaml`)**: Injects user accounts and API keys into the `$accounts.<name>.<field>` variable scope. Sensitive values are marked for automatic console masking.
4. **Scenario Parsing**: Parses the selected YAML scenario. Run `gherkio validate` separately when you want full static validation before execution.

---

### Phase 2: Setup Block Execution (`setup`)
If your scenario defines a `setup` block, Gherkio executes these steps **first**, before the main `steps` sequence:
* **Purpose**: Seed test database, authenticate admin users, or create prerequisite resources.
* **Failure Rule**: If setup fails, the main `steps` block is skipped and execution proceeds to `teardown`.

```yaml
setup:
  - request:
      method: POST
      url: /admin/reset-db
    expect:
      status: 200
```text

---

### Phase 3: Step Compilation & Variable Resolution
For each step in the `steps` array, Gherkio dynamically compiles the HTTP request right before dispatching:
1. **Generator Evaluation**: Dynamic variables like `$randomEmail`, `$uuid`, or `$timestamp` are evaluated to produce fresh runtime values.
2. **Context Substitution**: Placeholders in URL paths, headers, query parameters, and request bodies are replaced:
   * `$accounts.admin.token` β†’ retrieved from credential store.
   * `$1-userId` β†’ retrieved when a variable with that explicit name exists.
3. **Headers & Body Serialization**: Formats payloads as JSON or multipart form data based on request configuration.

---

### Phase 4: Network Execution & Outbound Security Sandboxing
Gherkio passes the compiled HTTP request to its HTTP client engine:
* **SSRF Protection**: Evaluates destination IP addresses against local subnet blocklists to prevent unintended internal network scanning in CI environments.
* **HTTP Wire Dispatch**: Opens TCP connection and transmits payload over HTTP/1.1 or HTTP/2.

---

### Phase 5: Latency Budgeting & Timing Measurement
Simultaneously with wire execution, Gherkio's high-precision timing engine measures response performance:
* Records exact latency in milliseconds (`durationMs`).
* Evaluates configured timing budgets (e.g., `timing: { max: 200ms }`).
* If response latency exceeds budget, the step is flagged with a timing violation.

---

### Phase 6: Response Assertions & Validation
Once the HTTP response arrives, Gherkio evaluates all defined expectations in sequence:

1. **Status Code Assertion**: Compares actual status code against expected (`expect.status`).
2. **Header Assertions**: Verifies required headers (`Content-Type`, `Cache-Control`).
3. **Body Value Matchers**: Evaluates exact values and supported matchers such as `contains`, `startsWith`, `endsWith`, `regex`, `oneOf`, `in`, `gt`, `gte`, `lt`, `lte`, `exists`, and type/format matchers.
4. **JWT Assertions**: Auto-decodes a discovered JWT and exposes claims through paths such as `jwt.sub` and `jwt.role`.
5. **JSON Schema Matching**: Validates full response body against `.gherkio/schemas/<schema-name>.yaml`.
6. **Redis Assertions**: A separate `redis:` step exposes `redis.exists`, `redis.value`, and `redis.ttl` to the same assertion engine.

```yaml
expect:
  status: 200
  body.user.role: oneOf admin,manager
  body.token: exists
  schema: user-profile
```text

---

### Phase 7: State Persistence & Save Block
When assertions succeed, Gherkio extracts specified response values and stores them in the scenario runtime context:
* **Explicit Names**: The key written under `save:` is the variable name. Names such as `1-userId` are supported when explicit step-oriented naming is useful.
* **Cross-Step Propagation**: Subsequent steps can immediately reference saved values such as `$userId` in URLs, headers, or payloads.

```yaml
save:
  authToken: body.access_token
  createdUserId: body.user.id
```text

---

### Phase 8: Teardown Block Execution (`teardown`)
Whether the scenario passes or fails, Gherkio guarantees execution of the `teardown` block:
* **Purpose**: Delete temporary resources, revoke temporary tokens, or clear test data.
* **Resilience**: Runs even if an intermediate step threw an error, preventing test pollution in shared environments.

```yaml
teardown:
  - request:
      method: DELETE
      url: /users/$createdUserId
      headers:
        Authorization: Bearer $adminToken
```text

---

### Phase 9: Reporting & Output Generation
Finally, Gherkio aggregates all step timing, assertions, and payloads into output reports:
* **Console Terminal**: Outputs colored ANSI tree view with pass/fail badges, latency indicators, and masked sensitive data.
* **Report Artifacts**: Use `--report html`, `--report json`, or `--report html,json`. Output paths and retention are configured under `reports:` in `.gherkio/config.yaml`.

---

## 🎯 Example Execution Trace

Here is what happens step-by-step when executing a simple 2-step authentication scenario:

```text
[00:00.000] 🟒 INIT     Loading environment 'staging' from .gherkio/environments/staging.yaml
[00:00.005] 🟒 RESOLVE  Interpolated $accounts.user1.email -> "testuser@example.com"
[00:00.010] πŸš€ STEP 1   POST https://api.staging.example.com/v1/login
[00:00.145] πŸ“₯ RES 200  Received response in 135ms
[00:00.148] 🎯 ASSERT   status == 200 (PASS)
[00:00.150] 🎯 ASSERT   body.token exists (PASS)
[00:00.152] πŸ’Ύ SAVE     Saved $1-jwt = "eyJhbGciOi..."
[00:00.155] πŸš€ STEP 2   GET https://api.staging.example.com/v1/profile
                        Header 'Authorization' -> 'Bearer eyJhbGciOi...'
[00:00.240] πŸ“₯ RES 200  Received response in 85ms
[00:00.242] 🎯 ASSERT   body.email == "testuser@example.com" (PASS)
[00:00.245] βœ… SUMMARY  Scenario 'User Login Flow' PASSED (2 steps, 230ms total)
```text

---

## πŸ’‘ Summary of Execution Rules

| Feature | Behavior |
| :--- | :--- |
| **Failure behavior** | Remaining steps continue by default; a failed setup skips the main block. Teardown still runs. |
| **Variable Scoping** | Saved variables persist throughout the scenario run; reset on new scenario. |
| **Teardown Guarantee** | `teardown` block executes regardless of scenario pass/fail outcome. |
| **Credential Safety** | Values from `$accounts` are automatically sanitized and redacted in logs. |

Interactive Browser Playground

To lower Gherkio's learning curve to zero, the repository includes a self-contained, browser-based Interactive Playground and Documentation Hub located under docs/book/playground/index.html.

You can use the playground to experiment with test scenarios, visualize complex execution paths, and translate legacy commands in real-time.


πŸš€ How to Launch the Playground

Since the playground is a static, modern vanilla web application, you can run it instantly:

  • Online Sandbox: Access the hosted web workspace directly on GitHub Pages Playground.
  • Open via File Manager: Double-click docs/book/playground/index.html to launch it in your default web browser offline.
  • Open via Terminal:
    # Linux
    xdg-open docs/book/playground/index.html
    
    # macOS
    open docs/book/playground/index.html
    

πŸ› οΈ Main Features

+-------------------------------------------------------------------+
|  [ Read Chapters ]      [ Visual DSL Stepper ]    [ cURL Convert ] |
+-------------------------------------------------------------------+
```text

### 1. Visual DSL Stepper
- **What it does**: Type or edit Gherkio declarative YAML test steps on the left pane, and see a live graphical flowchart built on the right pane!
- **Interactive Cards**: Shows step-by-step HTTP methods (GET, POST, etc.), request URL routing, expected assertions, and saved context variables.
- **Timeline Connectors**: Displays step sequence borders with glowing accents.

### 2. cURL-to-YAML Step Translator
- **What it does**: Paste standard legacy cURL terminal logs (e.g., copied from Chrome Developer tools network inspector) and get back perfectly compiled Gherkio steps instantly.
- **Features**: Automatically extracts custom headers, handles multi-line request bodies, parses methods, and includes placeholder 200 expect blocks.
- **Copy Target**: Click "Copy" to drop the translated output straight into your `.gherkio/tests/` files.

DSL Reference

🟑 Advanced Guide β€” Gherkio features a fully declarative, human-readable YAML Domain Specific Language (DSL). It eliminates the need to write boilerplate code to test your REST, HTTP, or Redis-backed APIs.


🎨 Design Philosophy

Gherkio's DSL is built on four core tenets:

  • Declarative, Not Imperative: State what you want to request and what you expect to receive, rather than how to parse, compare, or loop.
  • AI-Native Compatibility: Structured as dense, well-defined YAML, making it exceptionally easy for Large Language Models (LLMs) to write, audit, and modify tests with 100% precision.
  • Zero Glue-Code: Native support for JWT assertions, Redis state verification, JSON Schema enforcement, and dynamic backoff retries.
  • Portability: Test suites execute inside any CI/CD pipeline, locally on a developer workstation, or loaded into AI agent workspaces via MCP.

πŸ—οΈ Scenario Lifecycle & Structure

A test scenario file is organized into three lifecycle sections (setup, steps, teardown):

# Categorization tags for filtering (e.g. gherkio run --tag smoke)
tags:
  - smoke
  - payments

# Pre-conditions: Executed first. If setup fails, steps are skipped.
setup:
  - request:
      method: POST
      url: /setup-db
    expect:
      status: 200

# Core Test Steps: Executed sequentially. Variables saved here propagate downstream.
steps:
  - request:
      method: GET
      url: /payments/methods
    expect:
      status: 200
      body.methods: array
    save:
      paymentMethodId: body.methods[0].id

  - request:
      method: POST
      url: /payments/charge
      body:
        methodId: $paymentMethodId
        amount: 1500
    expect:
      status: 201
      body.status: paid

# Post-conditions: Guaranteed to execute even if setup or steps fail.
teardown:
  - request:
      method: POST
      url: /teardown-db
```text

---

## πŸ“š Advanced DSL Capabilities

Explore specific features in the Advanced Practitioner path:

| Topic | Description |
| :--- | :--- |
| 🧩 **[Scenarios & Lifecycle](scenarios.md)** | Master scenario structure, tags, setup/teardown mechanics, and exit rules. |
| πŸ”„ **[Scenario Composition](composition.md)** | Reuse test files via `use: path/to/scenario.yaml` for modular auth and data setup. |
| 🎯 **[Assertions & Matchers](assertions.md)** | Learn value matchers (`contains`, `greaterThan`, `oneOf`, `in`, `matchesRegex`), JWT validation, and timing budgets. |
| πŸ“‹ **[Schema Validation](schemas.md)** | Enforce structural schema rules against response payloads using `.gherkio/schemas/`. |
| ⚑ **[Redis Cache State Checks](redis.md)** | Assert key-value states in Redis directly inside scenario test steps. |
| ✏️ **[Variable Assignment (`set`)](set.md)** | Assign, override, and freeze variables between requests β€” no extra HTTP call. |
| 🎭 **[Service Mocking & Virtualization](mocking.md)** | Intercept outbound HTTP calls with zero-dependency virtual responses and parameter reflection. |
| πŸ” **[Retries & Polling](retry.md)** | Handle asynchronous background jobs with exponential backoff and retry rules. |
| πŸ” **[Variables & Credentials](variables.md)** | Dynamic variable generators (`$randomEmail`, `$uuid`) and environment credential injection. |

Scenarios & Tags

Every Gherkio test file describes a single test scenario. The root of the YAML document contains the top-level keys for execution metadata, tagging, and execution phases.


πŸ—οΈ Top-Level Key Reference

All keys in the Gherkio DSL are case-sensitive and must be written in lowercase.

KeyTypeRequiredDescriptionExample
scenariostringYesHuman-readable name of the test scenarioscenario: Create new user
descriptionstringNoDetailed description of what this scenario tests. Shown in HTML report headerdescription: Verify user can login with valid credentials
tagsarrayNoList of categories/labels for execution filteringtags: [smoke, active]
setuparrayNoPre-condition HTTP requests or composed files(See Setup/Teardown chapter)
stepsarrayYesPrimary sequence of API request and assertion steps(See Steps chapter)
examplesarrayNoList of variable blocks for parametrized runs(See examples section below)
teardownarrayNoPost-condition cleanup steps (guaranteed to run)(See Setup/Teardown chapter)

🏷️ Tagging & Filtering Conventions

Tags are strings that allow you to segment and filter test executions. They are extremely valuable for executing specific subsets of a test suite (e.g. running only light tests on commit, and full suites nightly).

scenario: Authenticated checkout flow
description: Verifies a logged-in user can add items to cart and complete checkout with valid payment.
tags:
  - e2e
  - checkout
  - high-priority
```text

### Filtering test runs from CLI:

```bash
# Run tests containing the 'smoke' tag
gherkio run tests/ --tag smoke

# Run tests containing BOTH 'core' AND 'user' tags (Logical AND)
gherkio run tests/ --tag core --tag user
```text

---

## πŸ“Š Parametrized / Data-Driven Testing (`examples`)

You can execute a single scenario multiple times with different datasets using the `examples` block. Under the hood, the test runner runs the scenario for each set of variables defined in `examples`.

```yaml
scenario: Parametrized login validation
description: Verifies login fails with various bad inputs
examples:
  - username: "alice"
    password: "bad-password"
  - username: bob
    password: ""
steps:
  - name: Attempt login
    request:
      method: POST
      url: /api/login
      body:
        username: "{{username}}"
        password: "{{password}}"
    expect:
      status: 400
```text

Setup & Teardown

Managing database seeds, cleanups, and dynamic pre-conditions is critical for reliable integration tests. Gherkio provides native setup and teardown lifecycle blocks to isolate environment mutations.


πŸ” Lifecycle Execution Path

Gherkio follows a strict execution guarantee for test scenarios:

                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                  β”‚   Start Scenario   β”‚
                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚
                            β–Ό
                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                  β”‚ Execute SETUP Stepsβ”‚
                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚                           β”‚
      Success β”‚                   Failure β”‚
              β–Ό                           β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚   Execute Main   β”‚        β”‚    Skip Main     β”‚
    β”‚      STEPS       β”‚        β”‚      STEPS       β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              β”‚                           β”‚
              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚
                            β–Ό
                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                  β”‚  Execute TEARDOWN  β”‚
                  β”‚ Steps (Guaranteed) β”‚
                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚
                            β–Ό
                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                  β”‚    End Scenario    β”‚
                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```text

- **Setup Failures**: If any step in the `setup` block fails, Gherkio halts execution immediately, skips the primary `steps` block entirely (marking the scenario as failed), and jumps directly to the `teardown` block.
- **Teardown Guarantees**: The `teardown` block is **guaranteed to run** regardless of whether the `setup` block or the primary `steps` block succeeded or failed. This ensures database cleanups, session invalidations, or resource releases are always performed.

---

## πŸ“ Lifecycle Configuration Example

Here is a typical scenario writing a temporary resource, asserting against it, and then cleaning it up:

```yaml
scenario: Manage User Profile
setup:
  - use: shared/authenticate.yaml   # Get auth token
  - request:
      method: POST
      url: /profiles
      headers:
        Authorization: "Bearer ${token}"
      body:
        username: "temp_user"
    save:
      profileId: body.id

steps:
  - request:
      method: GET
      url: /profiles/$profileId
      headers:
        Authorization: "Bearer ${token}"
    expect:
      status: 200
      body.username: temp_user

teardown:
  - request:
      method: DELETE
      url: /profiles/$profileId     # Clean up target profile
      headers:
        Authorization: "Bearer ${token}"
    expect:
      status: 200
```text

Step Properties

Steps are the execution blocks inside Gherkio's setup, steps, and teardown lifecycle lists. They are evaluated sequentially, passing context variables, environment tokens, and parsed JSON fields down the scenario chain.


⚑ The Step Structure

A scenario step is defined as a YAML map containing structural blocks that configure the action, validate the response, extract variables, or control loops.

- name: Create checkout order        # Optional human-readable step label
  request:                       # 1. Action: Trigger HTTP request
    method: POST
    url: /v1/checkout
    timeout: 5s
  expect:                        # 2. Assert: Validate status & schema
    status: 201
    body.success: true
  save:                          # 3. Context: Store data for next steps
    orderId: body.id
  timing:                        # 4. Perform: Latency budget check
    max: 500ms
```text

---

## 🧭 Step Configuration Properties

Each step in a scenario sequence supports the following top-level keys:

| Property Key | Type | Required | Description | Example |
| :--- | :--- | :--- | :--- | :--- |
| `name` | `string` | No | Human-readable label for the step. Shown in test output and HTML report instead of `METHOD /url`. | `name: Create new order` |
| `if` | `string` | No | Conditional guard clause. Step is skipped if the expression evaluates to false. | `if: $responseCode == 200` |
| `request` | `object` | Conditional | HTTP request payload block. Mutually exclusive with other step operations. | (See Request Properties below) |
| `redis` | `object` | Conditional | Controlled read-only Redis operation. Mutually exclusive with other step operations. | `redis: { connection: local-cache, command: get, key: "product:42" }` |
| `use` | `string` | Conditional | Scenario composition. Imports and executes another scenario YAML file inline. Mutually exclusive with other step operations. | `use: shared/login.yaml` |
| `set` | `map[string]string`| Conditional | Inline variable assignment. Explicitly assigns or overrides variables without I/O. Mutually exclusive with other step operations. See [Variable Assignment (set)](set.md). | `set: { QUEUE_ID: "01KT4EBA37Y" }` |
| `export` | `object` | Conditional | Materializes a saved collection into an Excel (`.xlsx`) file. Mutually exclusive with other step operations. | `export: { file: fixtures/bulk.xlsx, from: "$items", columns: [...] }` |
| `import` | `object` | Conditional | Reads data from an Excel (`.xlsx`) file into runtime variables. Mutually exclusive with other step operations. | `import: { file: fixtures/users.xlsx, as: users }` |
| `repeat` | `object` | Conditional | Repeats a group of steps until a condition is true or the attempt limit is exhausted. Mutually exclusive with other step operations. | `repeat: { attempts: 20, until: "$count == 0", steps: [...] }` |
| `for_each` | `object` | Conditional | Executes nested steps sequentially once per item in a saved array. Mutually exclusive with other step operations. | `for_each: { from: "$items", as: item, steps: [...] }` |
| `with` | `map[string]string`| No | Variable overrides injected into a `use:` step. Values interpolated before injection; original values restored after completion. | `with: { PARENT_CLAIM_ISSUE_ID: $STATUS_APPROVED_ID }` |
| `expect` | `object` | No | Assertions mapping target dot-notation paths to expected formats or matchers. | `expect: { status: 200 }` |
| `save` | `map[string]string`| No | Context extraction map. Binds response parameters to dynamic variables. | `save: { token: body.accessToken }` |
| `retry` | `object` | No | Automated polling loop rules for testing eventually consistent resources. | (See Retry & Polling chapter) |
| `timing` | `object` | No | Latency budget validation. Asserts that request execution did not exceed duration limits. | `timing: { max: 300ms }` |

---

## 🌐 Request Configuration Properties (`request`)

The `request` block defines the HTTP action Gherkio will execute. It supports the following keys:

| Property Key | Type | Required | Description | Example |
| :--- | :--- | :--- | :--- | :--- |
| `method` | `string` | **Yes** | HTTP request verb. Sourced as `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `OPTIONS`, `HEAD`. | `method: POST` |
| `url` | `string` | **Yes** | Target URL. Supports relative paths (resolves to environment `baseUrl`) or absolute URLs. | `url: /v1/users` |
| `service` | `string` | No | Routes the request to a specific microservice defined in the active environment. | `service: auth` |
| `headers` | `map[string]string`| No | Key-value mapping of custom HTTP headers. Supports variable interpolation. | `headers: { Content-Type: "application/json" }` |
| `body` | `any` | No | Request body payload. Supports JSON maps, lists, raw strings, and variable injection. | `body: { role: "admin" }` |
| `query` | `map[string]string` | No | Query parameters appended to the URL. Supports variable interpolation in values. | `query: { status: available }` |
| `transform` | `object` | No | Declarative collection projections: filter, slice, and reshape arrays from saved variables into the request payload. | (See Requests chapter) |
| `multipart` | `object` | No | Multipart form-data wrapper used for sending form fields and binary file uploads. | (See Requests chapter) |
| `timeout` | `string` | No | HTTP socket timeout limit (parsed via standard Go duration strings like `5s`, `500ms`, `1m`). | `timeout: 10s` |

## Redis Steps (`redis`)

Redis steps expose only the read-only commands `get`, `exists`, `ttl`, and
`hgetall`. Arbitrary commands and Lua scripts are intentionally unsupported.

```yaml
- name: Verify product cache
  redis:
    connection: local-cache
    command: get
    key: "product:$productId"
  expect:
    redis.exists: true
    redis.value.id: "$productId"
  save:
    cachedName: redis.value.name
  retry:
    attempts: 5
    interval: 200
```text

`get` automatically decodes JSON values. Redis assertions and saved values use
the `redis.*` path: `redis.exists`, `redis.value`, `redis.value.<field>`, and
`redis.ttl`. Existing matchers, timing assertions, and retry strategies apply.

See [Redis Cache Checks](redis.md) for complete API-plus-cache scenarios,
command-specific result paths, TTL and hash examples, polling, and Sentinel use.

## Collection Request Loops (`for_each`)

Use `for_each` when an endpoint requires one request per item rather than a
single batch payload. `from` resolves a saved array, `as` names the scoped
current item (default: `item`), and nested `steps` execute sequentially.

```yaml
steps:
  - request:
      method: GET
      url: /api-a/items
    save:
      items: body.items

  - name: Send every item to API B
    for_each:
      from: $items
      as: item
      steps:
        - request:
            method: POST
            url: /api-b/items
            body:
              external_id: $item.id
              name: $item.name
          expect:
            status: 201
```text

The loop stops at the first failing nested step. Variables saved inside the
loop remain available afterward, while the item alias is restored when the
loop ends. An empty source array succeeds without executing nested steps; a
missing or non-array source fails the loop. Terminal and report output label
each nested execution as `for_each N/M`.

## Bounded Multi-Step Loops (`repeat`)

Use `repeat` when one polling attempt needs multiple operations. The nested
`steps` run sequentially, then `until` is evaluated against the updated
variables. A true condition preserves those variables for following steps and
ends the loop. An inner failure stops immediately; if the condition remains
false after every attempt, the repeat step fails.

```yaml
- name: Find an unused issue tag
  repeat:
    attempts: 20
    until: $existingTicketCount == 0
    steps:
      - name: Select candidate issue tag
        set:
          ISSUE_TAG_L3: ${randomItem(respIssueTagL3)}

      - name: Check tickets using candidate
        request:
          method: GET
          url: /v1/tickets
          query:
            issue_tag_id: $ISSUE_TAG_L3.id
        expect:
          status: 200
        save:
          existingTicketCount: count(body.data)
```text

This is a bounded loop, not unbounded recursion. Reports label every nested
execution as `repeat N/M`. A dry run previews the block once because no live
response exists for evaluating response-dependent exit conditions.

---

## πŸ“€ Excel Export (`export`)

The `export` step materializes a saved collection into an Excel (`.xlsx`) file.
It is a **sink**: it writes data to disk instead of making an HTTP request, so
it pairs naturally with `multipart.files` to generate fixtures for bulk-import
APIs.

```yaml
steps:
  - name: Fetch product catalog
    request:
      method: GET
      url: /products
    save:
      products: body.products

  - name: Generate bulk upload Excel fixture
    export:
      file: fixtures/bulk_upload.xlsx
      from: $products
      sheet: "BulkData"
      columns:
        - header: "product_title"
          value: "$string(item.title)"
        - header: "price"
          value: "$float(item.price)"
        - header: "priority"
          value: "$if(item.price > 500, 'priority', 'normal')"
        - header: "generated_at"
          value: "${dateNow(\"2006-01-02\")}"
```text

### Configuration Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `file` | `string` | **Yes** | Output path for the Excel file. Absolute paths are used as-is; relative paths resolve against the configured `export.path` (default: project root). |
| `from` | `string` | **Yes** | Source collection array variable (must start with `$`). |
| `sheet` | `string` | No | Worksheet name (default `Sheet1`). |
| `columns` | `list` | **Yes** | Ordered column definitions. |

Each column has:

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `header` | `string` | **Yes** | Column header text. |
| `value` | `string` | **Yes** | Per-cell expression. Supports interpolation, casting (`$string`, `$int`, `$float`, `$bool`), `$if(...)`, and references to saved variables. |

### Key Behaviors
- **Mutual Exclusion**: `export` cannot be combined with `request`, `redis`, `use`, `set`, `repeat`, `for_each`, `with`, `expect`, `save`, `timing`, or `retry`.
- **Row Scoping**: Each row is evaluated with the current item bound to `item` (plus all saved variables).
- **Conditional Values**: `$if(condition, then, else)` supports full boolean expressions with comparison operators (`==`, `!=`, `>`, `>=`, `<`, `<=`, `&&`, `||`, `!`).
- **No HTTP**: The step passes when the file writes successfully.
- **Configurable Output**: Set `export.path` in `.gherkio/config.yaml` to write relative `file` paths under a dedicated directory (e.g. `fixtures`).

---

## πŸ“₯ Excel Import (`import`)

The `import` step reads an Excel (`.xlsx`) spreadsheet from disk and parses its rows into an array of objects saved in a runtime variable (`as: <variableName>`). This enables test scenarios to ingest external Excel fixtures, iterate through records with `for_each`, or validate uploaded spreadsheet contents.

```yaml
steps:
  - name: Ingest customer dataset
    import:
      file: fixtures/customers.xlsx
      sheet: "Customers"          # Optional, defaults to active/first sheet
      as: CUSTOMER_LIST          # Target variable storing array of row maps
      header_row: 1              # Optional, 1-based header row index (default: 1)
      data_start_row: 2          # Optional, 1-based data start row (default: 2)

  - name: Create each customer via API
    for_each:
      from: $CUSTOMER_LIST
      as: customer
      steps:
        - name: Send creation request
          request:
            method: POST
            url: /v1/customers
            body:
              full_name: $customer.customer_name
              email: $customer.email_address
              initial_balance: $float(customer.balance)
```text

### Configuration Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `file` | `string` | **Yes** | Target `.xlsx` file path (absolute or relative to project root / export path). |
| `as` | `string` | **Yes** | Target variable name to store parsed rows (without leading `$`). |
| `sheet` | `string` | No | Worksheet name (default: active/first sheet). |
| `header_row` | `int` | No | 1-based row index containing column headers (default: `1`). |
| `data_start_row` | `int` | No | 1-based row index where data rows start (default: `2`). |
| `columns` | `list` | No | Optional list of explicit column alias mappings (`{ header, as }`). |

### Key Behaviors
- **Header Normalization**: When `columns` is omitted, headers containing spaces or punctuation are automatically converted to clean `snake_case` keys (e.g. `"Customer Name ($)"` becomes `customer_name`).
- **Explicit Aliasing**: Specify `columns` mappings to rename specific headers to custom field keys.
- **Mutual Exclusion**: `import` is a primary step operation and cannot be combined with `request`, `redis`, `use`, `set`, `repeat`, `for_each`, `export`, `with`, `expect`, `save`, `timing`, or `retry`.
- **Seamless Expressions**: The loaded variable is a standard array of maps, fully accessible via `$list[0].field`, `${randomItem(list)}`, and `for_each: { from: $list }`.

---

## πŸ”€ Conditional Execution (`if`)

Steps can be conditionally executed using the `if` guard property. If the expression evaluates to false, the step is skipped entirely (its HTTP request is not sent, assertions are ignored, and any variable extraction is bypassed). Skipped steps are tracked as `skipped` in test metrics, CLI logs, and HTML reports.

### Syntax and Comparison Operators
The `if` property expects a string expression consisting of variables, comparison operators, and literal values (strings, numbers, or booleans).

Supported operators:
*   `==` (Equal to)
*   `!=` (Not equal to)
*   `>` (Greater than)
*   `>=` (Greater than or equal to)
*   `<` (Less than)
*   `<=` (Less than or equal to)
*   `&&` (Logical AND)
*   `||` (Logical OR)
*   `!` (Logical negation)

`&&` has higher precedence than `||`. Use parentheses to group expressions.
Both operators short-circuit: a false left side skips the right side of `&&`,
and a true left side skips the right side of `||`.

### Examples

#### Basic Variable Comparison
```yaml
steps:
  - name: Generate Admin Invoice
    if: $USER_ROLE == admin
    request:
      method: POST
      url: /v1/invoices/admin
      body:
        amount: 150.00
    expect:
      status: 201
```text

#### Numeric Comparison
```yaml
steps:
  - name: Get Invoice Details
    if: $INVOICE_AMOUNT >= 1000
    request:
      method: GET
      url: /v1/audit/large-invoice/$INVOICE_ID
    expect:
      status: 200
```text

#### Truthiness Check (Check if variable exists and is not false/empty)
```yaml
steps:
  - name: Process Refund
    if: $REFUND_ENABLED
    request:
      method: POST
      url: /v1/refunds
      body:
        transaction_id: $TX_ID
```text

#### Compound Conditions

```yaml
steps:
  - name: Process complete customer record
    if: $item.ticket_name && $item.customer_name && $item.ticket_code
    request:
      method: POST
      url: /v1/webhooks/premature-tickets
```text

```yaml
# Parentheses and negation
if: ($enabled && $count > 0) || !($status == blocked)
```text

For backward compatibility, negating a comparison without parentheses (for
example `!$status == blocked`) is rejected as ambiguous. Write
`!($status == blocked)` instead.

---

## πŸ’Ύ Dynamic Variable Saving (`save`)

The `save` block allows steps to bind HTTP response parameters (body fields, headers, or decoded JWT claims) to variables that can be dynamically interpolated in all subsequent request URLs, headers, or bodies.

### Syntax Reference
```yaml
save:
  variable_name: response_source_path
```text

*   **`body.<path>`**: Extracts JSON fields. Supports dotted-paths, indexes, and collections.
    *   *Example*: `productId: body.items[0].id`
*   **`headers.<header-key>`**: Extracts HTTP response header values (case-insensitive).
    *   *Example*: `rateLimit: headers.X-Rate-Limit`
*   **`jwt.<claim>`**: Automatically decodes response JWT keys (looks for `token`, `accessToken`, `access_token`) and extracts claims.
    *   *Example*: `adminRole: jwt.role`
*   **`count(body.<path>)`**: Saves the length of a response array as an integer. An explicitly present `null` value saves `0`; an empty array also saves `0`. Missing paths and non-array values produce a save warning and are not stored.
    *   *Example*: `4-notesBeforeConflict: count(body.data)`

```yaml
save:
  notes: body.data
  notesCount: count(body.data)
```text

---

## ⏱️ Latency Budgets (`timing`)

In performance-critical applications, keeping endpoint response latency within a specific budget is a core contract. Gherkio allows developers to validate performance metrics directly inside test steps using the `timing` block:

### Configuration Syntax
```yaml
timing:
  max: duration_string # e.g. "200ms", "1s", "1.5s"
```text

If the combined execution time of the step exceeds the `max` threshold, Gherkio fails the step and reports a detailed latency budget violation error:

```text
❌ Step 2: GET /users/profile timing assertion failed
  - Expected latency: <= 200ms
  - Actual latency:   242ms
```text

---

## πŸ”Œ Scenario Composition & Context Bubble Up (`use`)

To keep test suites DRY, Gherkio steps can delegate execution to a shared modular test script using the `use:` tag.

### Execution Blueprint
```yaml
# Inside login-and-query.yaml
steps:
  - use: auth/login.yaml            # 1. Runs login sequence, saves $authToken
  - request:
      method: GET
      url: /profile
      headers:
        Authorization: "Bearer ${authToken}" # 2. Automatically inherits $authToken
```text

1.  **Monotonic Variables**: Any variable saved (via `save`) inside the composed YAML file is automatically merged and bubbles up to the parent execution context.
2.  **Context Inheritance**: Composed scenarios inherit all variables defined prior to their execution (e.g. host environments, active credential credentials).

---

## βš™οΈ Declarative Variable Assignment (`set`)

A `set` step explicitly assigns, updates, or overrides variables in the runtime context **without executing any request or composed scenario**. Use it to seed fixtures, override a saved value for one run, freeze a generated value so later steps agree on it, or hand state out of a loop/composed file.

```yaml
steps:
  - name: Seed the queue fixture
    set:
      QUEUE_ID: "01KT4EBA37Y"

  - name: Get Queue info
    request:
      method: GET
      url: /v1/queues/$QUEUE_ID
    expect:
      status: 200

  - name: Rotate the queue ID (separate step, see below)
    set:
      PREVIOUS_QUEUE_ID: "$QUEUE_ID"

  - name: Move to the next queue ID
    set:
      QUEUE_ID: "02HT5FCA38Z"
```text

### Key Behaviors
- **Mutual Exclusion**: A `set` step cannot be combined with `request`, `use`, `redis`, `repeat`, `for_each`, `export`, or `import` β€” only `name` and `if` may accompany it. `expect`, `save`, `timing`, `retry`, and `with` are *silently ignored* on a `set` step.
- **Interpolation**: Values are interpolated immediately at execution time against the active variable store, so `$QUEUE_ID`, credentials, and `save` results are all readable.
- **Keys are unordered within one block**: Derive a value from another variable in a *separate* `set` step.
- **Typed random selection**: An exact `${randomItem(array)}` value preserves the selected object, so later steps can access fields such as `$PARTNER_STATUS.id`. `${randomItem(array,id)}` stores only the selected field.
- **CLI Output**: In test logs, a `set` step is formatted to show which variables are being set (e.g., `set variables: QUEUE_ID`).

> πŸ“– **Full chapter**: [Variable Assignment (`set`)](set.md) covers value typing, precedence, guards, loops, and eleven worked scenarios.

Request Configuration

The request block defines the outgoing HTTP request. Gherkio supports full-featured HTTP client configurations including custom verbs, header maps, service targeting, and flexible request body encodings.


🌐 HTTP Request Key Reference

KeyTypeRequiredDescriptionExample
methodstringYesHTTP Method (GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD)method: GET
urlstringYesTarget path (relative to baseUrl) or fully qualified absolute URLurl: /v1/profile
querymap[string]stringNoQuery parameters appended to the URL. Supports variable interpolation in values.query: { status: available, limit: "10" }
servicestringNoTarget service key to override default baseUrlservice: payment
headersobjectNoMap of custom key-value HTTP headersheaders: { Content-Type: application/json }
bodyanyNoRequest payload. Can be JSON objects, lists, strings, or numbers(See below)
timeoutstringNoRequest-specific HTTP timeout overridetimeout: 45s

πŸ› οΈ Detailed Usage & Examples

Targeting Specific Services

If your system uses a microservice architecture, map service endpoints in your environment file (e.g. environments/staging.yaml):

baseUrl: https://staging.host.com
services:
  users: https://users-staging.host.com
  billing: https://billing-staging.host.com
```text

Invoke service routing in your YAML request:
```yaml
steps:
  - request:
      service: users            # Resolves to https://users-staging.host.com/v1/profile
      method: GET
      url: /v1/profile
```text

### JSON Request Payload

JSON bodies are defined natively in Gherkio YAML and fully support runtime variable interpolation:

```yaml
steps:
  - request:
      method: POST
      url: /register
      headers:
        Content-Type: application/json
      body:
        username: $randomEmail   # Interpolated using built-in generator
        profile:
          firstName: "John"
          lastName: "Doe"
          age: ${randomInt(18,65)}
```text

### Query Parameters (`query`)

Use the `query` block to append URL query parameters instead of embedding them directly in the URL string. This keeps URLs clean and allows variable interpolation for dynamic values:

```yaml
steps:
  - request:
      method: GET
      url: /pets/findByStatus
      query:
        status: available
        limit: "10"
    expect:
      status: 200
```text

Query parameter values support full variable interpolation:

```yaml
steps:
  - request:
      method: GET
      url: /users
      query:
        role: $userRole
        page: "${randomInt(1,5)}"
    expect:
      status: 200
```text

---

### Multipart Form Data (`multipart`)

To test form-based submissions or file uploads, Gherkio features an elegant `multipart` block. When `multipart` is defined, Gherkio automatically builds the boundary formatting and sets the proper standard HTTP header (`multipart/form-data; boundary=...`) for you.

You can mix raw fields and multiple file attachments inside a single payload:

```yaml
steps:
  - request:
      method: POST
      url: /v1/user/profile
      multipart:
        fields:
          username: "john_doe"
          role: $accounts.admin.role   # Support dynamic variables
        files:
          # 1. Simple path string syntax:
          avatar: "assets/john-avatar.png"
          
          # 2. Advanced schema mapping (with custom MIME types and filenames):
          document:
            path: "assets/resume.pdf"
            contentType: "application/pdf"
            filename: "john_doe_cv.pdf"
    expect:
      status: 200
      body.updated: true
```text

#### πŸ“ Assets Directory Location & Project Tree

File attachments specified under `multipart.files` are resolved relative to your project workspace root directory (the parent directory of `.gherkio/`). By convention, create an `assets/` directory at your project root to store test media files (images, avatars, PDFs):

You can configure that default directory in `.gherkio/config.yaml`:

```yaml
assets:
  path: assets
```text

The path is relative to the project root, although an absolute directory is also accepted. With this configuration, a multipart value such as `avatar: "john-avatar.png"` resolves to `<projectRoot>/assets/john-avatar.png`. If `assets.path` is omitted, the existing project-root and fixtures lookup behavior remains unchanged.

```text
my-api-tests/                      <-- Project Workspace Root
β”œβ”€β”€ .gherkio/                      <-- Gherkio Test Engine Config & Suites
β”‚   β”œβ”€β”€ config.yaml
β”‚   β”œβ”€β”€ environments/
β”‚   β”‚   └── local.yaml
β”‚   └── tests/
β”‚       └── user/
β”‚           └── upload-avatar.yaml
└── assets/                        <-- Assets Directory for Multipart File Uploads
    β”œβ”€β”€ john-avatar.png
    β”œβ”€β”€ sample-image.jpg
    └── resume.pdf
```text

#### Custom Assets Directory

To keep upload fixtures inside `.gherkio`, set a custom path relative to the project root:

```yaml
# .gherkio/config.yaml
assets:
  path: .gherkio/test-assets
```text

The corresponding project structure is:

```text
my-api-tests/
└── .gherkio/
    β”œβ”€β”€ config.yaml
    β”œβ”€β”€ test-assets/
    β”‚   β”œβ”€β”€ avatars/
    β”‚   β”‚   └── john.png
    β”‚   └── documents/
    β”‚       └── resume.pdf
    └── tests/
        └── upload.yaml
```text

Files are then referenced relative to the configured directory:

```yaml
multipart:
  files:
    avatar: "avatars/john.png"
    resume:
      path: "documents/resume.pdf"
      contentType: "application/pdf"
```text

The resolved paths are `.gherkio/test-assets/avatars/john.png` and `.gherkio/test-assets/documents/resume.pdf` under the project root.

An absolute assets directory is also supported when the files live outside the project:

```yaml
assets:
  path: /opt/gherkio/shared-assets
```text

In that case, `avatar: "avatars/john.png"` resolves to `/opt/gherkio/shared-assets/avatars/john.png`. Absolute paths make a configuration machine-specific, so relative paths are preferable for repositories and CI environments.

#### πŸ’‘ Key Details & File Path Resolution:

- **`multipart.fields`**: A flat map of key-value string pairs representing text form fields.
- **`multipart.files`**: A map of files. The field key is the form property name (e.g. `avatar`).
- **Flexible Directory Paths**: You can configure **any custom directory or path name** in your scenario step (e.g. `"assets/avatar.png"`, `"media/uploads/doc.pdf"`, or `"fixtures/test.csv"`).
- **Configured Assets Directory**: A bare relative file such as `"avatar.png"` is also looked up under the optional `assets.path` from `.gherkio/config.yaml`.
- **Search & Resolution Order**: When resolving a file, Gherkio automatically looks up the file in the following order:
  1. **Absolute Path**: If an absolute path is specified (e.g. `/tmp/test-image.png`)
  2. **Declared Path**: `<projectRoot>/<declared-path>` (e.g. `my-project/assets/john-avatar.png` or `my-project/custom-folder/file.jpg`)
  3. **Configured Assets Directory**: `<projectRoot>/<assets.path>/<declared-path>` when configured
  4. **Project Fixtures Fallback**: `<projectRoot>/fixtures/<filename>`
  5. **Gherkio Fixtures Fallback**: `<projectRoot>/.gherkio/fixtures/<filename>`
  6. **Current Working Directory**: Relative path from where `gherkio run` was executed.
- **MIME Type**: The simple string syntax uses `application/octet-stream`. Use the advanced map syntax (`path`, `contentType`, `filename`) when the receiving API requires a specific MIME type such as `image/png` or `application/pdf`.



---

### πŸ”„ Declarative Collection Projections (`transform`)

Gherkio allows you to dynamically filter, slice, project, and reshape array collections from your saved variables directly into your request payload target path before the HTTP request is dispatched.

#### Transform Block Key Reference:
| Key | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `from` | `string` | Yes | The source collection variable name (must start with `$`). |
| `as` | `string` | No | Variable alias to represent each element context during transformation (defaults to `"item"`). |
| `where` | `object` | No | Map of filters applying standard Gherkio matchers. |
| `limit` | `int` | No | Maximum count of matching projected elements to output. |
| `select` | `object` | Yes | Custom structural schema mapping. Supports recursive/nested projection maps. |

#### ⚑ Explicit Type Casting inside Projections
When mapping data from target APIs to your request payloads, different endpoints might expect different types. Gherkio provides explicit type-casting functions inside the projection's `select` block:
*   **`$string(var)`**: Converts a field to a string (e.g., `$string(item.id)` turns `1001` to `"1001"`).
*   **`$int(var)`**: Coerces/parses a field value into an integer.
*   **`$float(var)`**: Coerces/parses a field value into a float64.
*   **`$bool(var)`**: Coerces/parses truthy string or integer values into a boolean.

#### πŸŒ€ Conditional Value Selection (`$if`)
Values can vary conditionally using the **`$if(condition, thenValue, elseValue)`** function. This works inside transform `select` blocks and also directly in request `body` strings.

*   **`condition`**: A scoped variable path to evaluate for truthiness (e.g. `item.is_answered` or `is_active`).
*   **`thenValue`**: Value expression used when the condition is truthy.
*   **`elseValue`**: Value expression used when the condition is falsy (optional β€” if omitted, evaluates to `null`).

Truthiness rules: `nil`, `false`, `0`, and `""` are falsy; all other values are truthy.

**Inside transform `select` (scoped to item alias):**
```yaml
select:
  # Map different fields based on question type
  answer_value: "$if(q.is_answered, q.free_text_answer, q.default_answer)"
  
  # Use type casting inside conditionals
  quantity: "$if(item.in_stock, $int(item.qty), 0)"
  
  # No else clause β€” returns null when condition is falsy
  optional_field: "$if(item.has_value, item.value)"
```text

**Inside request `body` (use `$` prefix for variable references):**
```yaml
body:
  # Conditionally map based on a saved variable
  status: "$if(is_admin, $admin_endpoint, $user_endpoint)"
  
  # Use type casting with conditionals
  count: "$if(has_items, $int(item_count), 0)"
```text

#### Complete Example:
```yaml
steps:
  # Step 1: Fetch details
  - request:
      method: GET
      url: /surveys/132
    save:
      raw_questions: body.questions

  # Step 2: Reshape and submit survey answers
  - request:
      method: POST
      url: /surveys/132/submit
      body:
        survey_id: 132
      transform:
        # Dynamically inject the projected array under body.answers
        answers:
          from: $raw_questions
          as: q
          where:
            q.is_required: true
          limit: 10
          select:
            # Explicitly cast integer id from details response to string for submission!
            question_id: "$string(q.id)"
            
            # Coerce the sequence number to integer
            seq: "$int(q.seq)"
            
            # Static values and clean variable mapping
            is_answered: true
            free_text_answer: q.user_response
```text


---

### πŸ’‘ Type Preservation & Explicit Casting in Standard Bodies

Outside of the `transform` projection block, Gherkio automatically preserves the original type of standalone variables mapped inside the HTTP request `body`:
*   If you map a saved integer variable: `employee_id: $employee_id`, Gherkio keeps the value as a JSON number (`1234`) in the outgoing request payload.
*   If you explicitly want to coerce it to a string (or other types), wrap the variable in a casting operator inside the request body:
    ```yaml
    body:
      emp_id: "$string(employee_id)"   # Outgoing payload: "emp_id": "1234"
      status: "$bool(is_active)"       # Outgoing payload: "status": true
    ```

Redis Cache Checks

Gherkio can verify application cache state in the same scenario that exercises the HTTP API. Redis access is deliberately read-only: scenarios can inspect keys, but cannot create, update, or delete production data.

Define the connection once in your active environment file (.gherkio/environments/<env>.yaml), then reference its connection name from your redis scenario step.


πŸ”Œ Defining Redis Connections

Redis connection configurations belong in environment files (.gherkio/environments/local.yaml, staging.yaml, etc.) under the connections: block. This keeps host IPs, ports, and credentials out of your test scenario files.

1. Standalone Redis Connection

Add a named connection entry to .gherkio/environments/local.yaml:

baseUrl: http://localhost:8080

connections:
  application-cache:
    type: redis
    address: localhost:6379
    database: 0
    password: $GHERKIO_REDIS_PASSWORD   # Optional: Supports GHERKIO_ env vars
    timeout: 5s
```text

### 2. Redis Sentinel Connection (High Availability)

For Sentinel clusters, replace `address` with a `sentinel` block:

```yaml
connections:
  application-cache:
    type: redis
    sentinel:
      master: mymaster
      addresses:
        - sentinel-1:26379
        - sentinel-2:26379
        - sentinel-3:26379
      username: sentinel-user
      password: $GHERKIO_SENTINEL_PASSWORD
    username: redis-user
    password: $GHERKIO_REDIS_PASSWORD
    database: 0
    timeout: 5s
```text

---

## πŸ“ Complete API and Cache Example

Once your connection (`application-cache`) is declared in your environment file, reference it directly inside your scenario step:


```yaml
scenario: Product API populates Redis cache
steps:
  - name: Fetch product from the API
    request:
      method: GET
      url: /v1/products/42
    expect:
      status: 200
      body.data.id: exists
    save:
      productId: body.data.id
      productName: body.data.name

  - name: Verify the API cached the product
    redis:
      connection: application-cache
      command: get
      key: "product:$productId"
    expect:
      redis.exists: true
      redis.value.id: "$productId"
      redis.value.name: "$productName"
    save:
      cachedProduct: redis.value
      cachedProductName: redis.value.name
    timing:
      max: 500ms
    retry:
      attempts: 10
      interval: 200
      backoff: constant
      maxDuration: 5s
```text

The HTTP step saves values from its response. The Redis key and assertions can
use those values normally. If a `GET` value contains valid JSON, Gherkio decodes
it automatically, which makes nested paths such as `redis.value.id` available.
Non-JSON values remain strings.

## Redis Step Fields

| Field | Required | Description |
| :--- | :---: | :--- |
| `connection` | Yes | Named entry under `connections` in the active environment. Supports variable interpolation. |
| `command` | Yes | One of `get`, `exists`, `ttl`, or `hgetall`. |
| `key` | Yes | Redis key to inspect. Supports normal variables and runtime expressions. |

A `redis` step can also use the standard step fields `name`, `if`, `expect`,
`save`, `timing`, and `retry`. It is mutually exclusive with `request`, `set`,
and `use` in the same step.

## Commands and Result Paths

| Command | Purpose | Available assertion/save paths |
| :--- | :--- | :--- |
| `get` | Read a string value, automatically decoding JSON when possible. | `redis.exists`, `redis.value`, `redis.value.<path>` |
| `exists` | Check whether a key exists. | `redis.exists` |
| `ttl` | Read the remaining TTL in seconds using Redis TTL semantics. | `redis.ttl` |
| `hgetall` | Read all fields from a hash as an object. | `redis.exists`, `redis.value`, `redis.value.<field>` |

All normal Gherkio value matchers work on these paths.

### Check Existence

```yaml
- name: Session cache must exist
  redis:
    connection: application-cache
    command: exists
    key: "session:$sessionId"
  expect:
    redis.exists: true
```text

### Check TTL

```yaml
- name: Session must still have a useful lifetime
  redis:
    connection: application-cache
    command: ttl
    key: "session:$sessionId"
  expect:
    redis.ttl: gte 60
```text

Redis returns `-1` when a key exists without an expiry and `-2` when the key
does not exist. Gherkio preserves those values in `redis.ttl`.

### Check a Hash

```yaml
- name: Verify cached user hash
  redis:
    connection: application-cache
    command: hgetall
    key: "user:$userId"
  expect:
    redis.exists: true
    redis.value.id: "$userId"
    redis.value.status: active
  save:
    cachedStatus: redis.value.status
```text

Redis hash field values are returned as strings.

## Polling Eventually Consistent Caches

Cache population may happen asynchronously. Add `retry` to rerun the Redis
operation until every assertion passes or the retry limit is reached:

```yaml
- name: Wait for the worker to populate the cache
  redis:
    connection: application-cache
    command: get
    key: "ticket:$ticketId"
  expect:
    redis.exists: true
    redis.value.id: "$ticketId"
  retry:
    attempts: 20
    interval: 250
    backoff: exponential
    maxDuration: 10s
```text

Redis retries support `attempts`, `interval`, `backoff`, and `maxDuration`.
`onStatus` is HTTP-specific and does not apply to Redis operations.

## Sentinel Connections

Scenario syntax does not change when the environment uses Redis Sentinel:

```yaml
- name: Verify ticket cache through Sentinel
  redis:
    connection: application-cache
    command: get
    key: "ticket:$ticketId"
  expect:
    redis.exists: true
```text

For every attempt, Gherkio asks Sentinel for the current primary before opening
the Redis connection. A retry can therefore discover a new primary after a
failover. See [Redis Connections](../reference/environments.md#redis-connections)
for Sentinel authentication and TLS configuration.

## Safety Boundary

Only `GET`, `EXISTS`, `TTL`, and `HGETALL` are supported. Gherkio intentionally
does not expose arbitrary Redis commands, Lua scripts, writes, deletes, or key
scanning. This keeps cache verification declarative and safe for shared test
environments.

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.

Variable Assignment (set)

set: is one of Gherkio's eight primary step operations. It writes variables into the runtime context without performing any I/O β€” no HTTP request, no Redis command, no file access.

Reach for it when you need to:

  • Seed deterministic fixtures instead of hardcoding values in five different requests.
  • Override a saved or credential value for a single local/debug run.
  • Freeze a generated value (a correlation ID, an idempotency key) so several later steps agree on it.
  • Snapshot a decision β€” such as a random pick β€” once, so every later step reads the same object.
  • Hand state out of a composed scenario, or derive a value per iteration inside a loop.

🧱 Syntax

steps:
  - name: Seed the queue fixture
    set:
      QUEUE_ID: "01KT4EBA37Y"
      REGION: "ap-southeast-1"

  - name: Use the seeded value downstream
    request:
      method: GET
      url: /v1/queues/$QUEUE_ID
    expect:
      status: 200
```text

| Element | Rule |
| :--- | :--- |
| Step key | `set:` β€” a map of `VARIABLE_NAME: value` |
| Keys | Bare names, **no** `$` prefix. Write `QUEUE_ID`, then reference it as `$QUEUE_ID` |
| Values | **Scalars only** β€” text with optional interpolation. Nested maps or lists are rejected at load time (`cannot unmarshal !!map into string`) |
| Network | None. A `set` step never calls anything, and has no response to assert |
| Counted as | A normal step in terminal output, HTML reports, and pass/fail totals. A step skipped by `if:` counts as neither a pass nor a failure |

Quote values that look numeric or boolean (`"5"`, `"true"`) so their intent is obvious β€” see [Value Semantics](#-value-semantics) for how they are typed at run time.

---

## πŸ”’ Allowed & Forbidden Keys

Every step must define **exactly one** primary operation:

```text
request | redis | use | set | repeat | for_each | export | import
```text

Declaring two of them is rejected before execution:

```text
Step operations 'request', 'redis', 'use', 'set', 'repeat', 'for_each', 'export', and 'import' are mutually exclusive
```text

With `set`, only these side keys are read:

| Key | On a `set` step |
| :--- | :--- |
| `name` | βœ… Labels the step in terminal and HTML output |
| `if` | βœ… Full guard expression. When it evaluates to false the step is skipped and **nothing is set** |
| `request`, `use`, `redis`, `repeat`, `for_each`, `export`, `import` | ❌ Validation error (`mutually_exclusive`) |
| `expect`, `save`, `timing`, `retry`, `with` | ⚠️ Not rejected by the validator, but **silently ignored** β€” there is no response to assert or extract, and nothing to retry |

> [!IMPORTANT]
> Never pair `set:` with `expect:` or `save:`. The step still passes, which hides the mistake: your assertions never run. Split it into a `set` step followed by the request step that carries the assertions.

---

## πŸ”€ Value Semantics

- **Interpolation happens at execution time** against the live variable store, so a `set` value can read credentials, `save` results, built-in generators, and values from earlier `set` steps.
- **One `set` block is not ordered.** `set: { A: "1", B: "$A-2" }` is a race β€” the lookup of `$A` may run before `A` is written. Derive dependent values in a **separate** step.
- **Values are stored as text.** Use the casting helpers (`$int()`, `$float()`, `$bool()`, `$string()`) when a JSON body needs a real number or boolean, and remember that `if:` comparisons already coerce numeric strings, so `if: $COUNT > 3` works with `COUNT: "5"`.
- **An exact `${randomItem(...)}` keeps the runtime type.** When the *entire* trimmed value is `${randomItem(items)}` the selected object is stored (so `$PICK.id` works). `${randomItem(items,id)}` stores only that field. Any expression with surrounding text falls back to ordinary string interpolation.
- **Last write wins.** A later `set:`, a later `save:`, or a `with:` injection under the same name replaces the earlier value ([variable precedence](variables.md#-variable-precedence)).
- **Built-in generators refresh every step**, so `set` is the way to capture `$uuid` / `${randomInt(1,100)}` once and reuse it.

---

## 🍳 Examples

### 1. Seed a deterministic fixture

One place to change a fixture ID, instead of scattering it across requests.

```yaml
steps:
  - name: Seed the catalogue fixture
    set:
      PRODUCT_ID: "1001"
      CURRENCY: "SGD"

  - request:
      method: GET
      url: /products/$PRODUCT_ID
      query:
        currency: $CURRENCY
    expect:
      status: 200
```text

### 2. Override a saved or credential value for one run

Handy when a teammate's environment returns a different ID, or when you want to pin a test to a known record while debugging.

```yaml
steps:
  - request:
      method: GET
      url: /v1/users/me
    save:
      userId: body.data.id

  - name: Pin to the seeded account instead of the live one
    set:
      userId: "01KT4EBA37Y"

  - request:
      method: GET
      url: /v1/users/$userId
    expect:
      status: 200
```text

### 3. Rotate state between steps

Swap a "previous" and "current" pair so a later assertion can compare them. Use two steps β€” never one block.

```yaml
steps:
  - name: Keep the previous queue ID
    set:
      PREVIOUS_QUEUE_ID: "$QUEUE_ID"

  - name: Move to the next queue ID
    set:
      QUEUE_ID: "02HT5FCA38Z"

  - request:
      method: GET
      url: /v1/queues/$PREVIOUS_QUEUE_ID/events
    expect:
      body.migratedTo: $QUEUE_ID
```text

### 4. Pick one object once, then read many fields

`${randomItem(array)}` on its own line stores the whole object, which prevents the classic bug of picking a *different* record for each field.

```yaml
steps:
  - request:
      method: GET
      url: /v1/partner-statuses
    save:
      statuses: body.data

  - name: Choose a single partner status
    set:
      PICKED: ${randomItem(statuses)}

  - request:
      method: POST
      url: /v1/partners
      body:
        partner_status_id: $PICKED.id
        partner_status_value: $PICKED.value
    expect:
      status: 201
```text

### 5. Guard a default with `if:`

Branch on the current context β€” role, feature flag, or a value saved earlier β€” and skip the assignment entirely when the guard is false.

```yaml
steps:
  - request:
      method: POST
      url: /auth/login
    save:
      role: jwt.role

  - name: Give admins a wider page size
    if: $role == "admin"
    set:
      PAGE_SIZE: "100"

  - name: Default page size for everyone else
    if: $role != "admin"
    set:
      PAGE_SIZE: "25"

  - request:
      method: GET
      url: /v1/audit-logs
      query:
        limit: $PAGE_SIZE
    expect:
      status: 200
```text

### 6. Freeze a generated value that must stay identical

`$uuid` is regenerated for every step. If the same correlation ID has to appear in a create call and its follow-up lookup, capture it once.

```yaml
steps:
  - name: Freeze one correlation ID for the whole flow
    set:
      CORRELATION_ID: "$uuid"

  - request:
      method: POST
      url: /v1/orders
      headers:
        X-Correlation-ID: $CORRELATION_ID
      body:
        order_ref: $CORRELATION_ID
    expect:
      status: 201

  - request:
      method: GET
      url: /v1/orders
      query:
        correlation_id: $CORRELATION_ID
    expect:
      status: 200
      body.items: array
```text

### 7. Derive a value inside a `for_each` loop

A `set` nested in a loop body turns the current item into a reusable variable. Variables set inside the loop remain available after it, while the loop alias (`item`) does not.

```yaml
steps:
  - request:
      method: GET
      url: /v1/warehouses
    save:
      warehouses: body.data

  - name: Provision a shelf per warehouse
    for_each:
      from: $warehouses
      as: warehouse
      steps:
        - name: Build the shelf code for this warehouse
          set:
            SHELF_CODE: "${toUpper($warehouse.code)}-01"

        - request:
            method: POST
            url: /v1/warehouses/$warehouse.id/shelves
            body:
              code: "$SHELF_CODE"
          expect:
            status: 201
```text

### 8. Hand state out of a composed scenario

`use:` shares the parent variable store, so anything a composed scenario (or its `set` steps) writes is visible afterwards. `with:` is the opposite: its injected overrides are restored when the composed scenario finishes.

```yaml
# steps/auth/login.yaml
scenario: Login and tag the session
steps:
  - request:
      method: POST
      url: /auth/login
      body:
        username: "$username"
        password: "$password"
    save:
      token: body.accessToken

  - name: Tag the session so the parent can branch on it
    set:
      SESSION_TIER: "gold"
```text

```yaml
# steps/checkout.yaml
scenario: Compose login, then branch on the tier
steps:
  - use: auth/login.yaml
    with:
      username: $accounts.alpha.username

  - name: Only gold sessions get the promo
    if: $SESSION_TIER == "gold"
    set:
      PROMO_CODE: "GOLD-2026"
```text

### 9. Bookkeeping in `setup:` and `teardown:`

`teardown` is guaranteed to run even when earlier steps fail, which makes it a reliable place to set the flag that a cleanup request will read.

```yaml
scenario: Order cleanup bookkeeping

setup:
  - name: Assume nothing was created yet
    set:
      ORDER_ID: ""
      ORDER_CREATED: "false"

steps:
  - request:
      method: POST
      url: /v1/orders
    save:
      ORDER_ID: body.data.id

  - name: Remember there is something to clean up
    set:
      ORDER_CREATED: "true"

  - request:
      method: GET
      url: /v1/orders/$ORDER_ID
    expect:
      status: 200

teardown:
  - name: Delete the order only when it exists
    if: $ORDER_CREATED == "true"
    request:
      method: DELETE
      url: /v1/orders/$ORDER_ID
    expect:
      status: 204
```text

### 10. Compose a value from generators

Anything available to interpolation can be captured, mixed with literals, or nested.

```yaml
steps:
  - name: Seed the run constants
    set:
      REGION: "ap-southeast-1"

  - name: Build a synthetic registration payload once
    set:
      EMAIL: "qa_${randomString(8, \"numeric\")}@example.com"
      PASSWORD: "Pw-${randomInt(100000,999999)}"
      REFERRAL: "${toUpper($REGION)}-${base64(\"signup\")}"
      SIGNED_UP_AT: "${dateNow(\"2006-01-02 15:04:05\")}"
      TRIAL_ENDS: "${dateOffset(\"+14d\")}"

  - request:
      method: POST
      url: /v1/registrations
      body:
        email: "$EMAIL"
        password: "$PASSWORD"
        referral_code: "$REFERRAL"
        signed_up_at: "$SIGNED_UP_AT"
    expect:
      status: 201
```text

Note the two-step split: `REFERRAL` reads `$REGION`, and reading a variable from the *same* block would be a race (see [Value Semantics](#-value-semantics)).

### 11. Feed a `repeat` loop until the target state appears

The nested `set` re-picks a candidate on every attempt, and `until:` is evaluated after each successful block, so the loop exits as soon as the candidate is free.

```yaml
steps:
  - request:
      method: GET
      url: /v1/candidates
    save:
      candidates: body.data

  - name: Find an unused candidate
    repeat:
      attempts: 20
      until: $existingCount == 0
      steps:
        - set:
            CANDIDATE: ${randomItem(candidates)}

        - request:
            method: GET
            url: /v1/bookings
            query:
              candidate_id: $CANDIDATE.id
          expect:
            status: 200
          save:
            existingCount: count(body.data)

  - request:
      method: POST
      url: /v1/bookings
      body:
        candidate_id: $CANDIDATE.id
    expect:
      status: 201
```text

---

## πŸ–₯️ Observability & Dry-Run

- Terminal and report output labels the step with the keys it writes, sorted: `set variables: QUEUE_ID, REGION`.
- `gherkio run --dry-run` behaves identically here β€” there is no request to suppress, and later steps still resolve `$QUEUE_ID`.
- The written variables are reported as that step's saved variables, so `--verbose` traces show exactly what a `set` contributed.
- A failure to interpolate fails the step (and the run), with a precise message:

```text
Failed to interpolate set variable 'PARTNER': array variable "items" is not defined
```text

---

## ⚠️ Common Mistakes

| Symptom | Cause | Fix |
| :--- | :--- | :--- |
| `Step operations ... are mutually exclusive` | `set:` shares a step with `request:`, `use:`, `redis:`, `repeat:`, `for_each:`, `export:`, or `import:` | Split into two steps |
| `cannot unmarshal !!map into string` | A nested map or list was used as a value | Store scalars only; capture objects with `${randomItem(array)}` or `save:` |
| `undefined variable` on the next step, sometimes | A dependent value was referenced inside the same `set` block | Derive it in a following `set` step |
| Assertions silently pass | `expect:` / `save:` / `retry:` / `timing:` were added to a `set` step | Move them to the request step |
| A random pick returns inconsistent fields | `${randomItem(array,field)}` was used, storing only one field | Store the object with `${randomItem(array)}` and read `$PICK.field` |
| A body field arrives as a string | `set` stores text | Cast at the payload: `$int(COUNT)`, `$bool(FLAG)` |
| A different ID appears per step | `$uuid` / `${randomInt(...)}` regenerate each step | Freeze it once with `set` (example 6) |

---

## πŸ”— Related Reading

- [Steps & Actions](steps.md) β€” the full list of primary operations.
- [Variables & Context](variables.md) β€” generators, precedence, and interpolation rules.
- [Scenario Composition](composition.md) β€” what `use:` and `with:` share with the parent.
- [AI & Machine-Readable Reference](../reference/ai-reference.md) β€” dense form of the same rules for assistants.

🎭 Service Mocking & Virtualization

In modern microservice ecosystems, test suites frequently depend on third-party APIs (e.g. Stripe, Twilio, AWS S3) or external microservices that may be unstable, rate-limited, or costly to call during automated CI/CD runs.

Gherkio includes a native, in-process Service Virtualization & Request Interception Engine. It allows you to mock any HTTP endpoint directly within your environment configuration (.gherkio/environments/<env>.yaml) without launching external proxy servers like WireMock or Mountebank.


βš™οΈ How Mock Interception Works

When a test step executes an HTTP request, Gherkio's runner checks the target environment's mocks: array before dispatching a real network request over the wire:

sequenceDiagram
    autonumber
    participant Step as Gherkio Test Step
    participant Engine as Gherkio Interceptor
    participant Mock as Mock Engine (.yaml)
    participant Server as Real Target Server

    Step->>Engine: Dispatch HTTP Request
    Engine->>Mock: Match Method & URL against `mocks:` rules
    alt Match Found (Intercepted)
        Mock-->>Step: Return Virtual Response (status, headers, body)
        Note over Step: Console: βœ” [MOCK] Intercepted POST /v1/charges
    else No Match
        Engine->>Server: Dispatch Real HTTP Request over Network
        Server-->>Step: Return Real Server Response
    end
```text

---

## πŸ“ Defining Mocks in Environment YAML

Mocks are defined at the environment level inside `.gherkio/environments/<env>.yaml`. This ensures test scenarios remain clean and declarative while enabling different mock behaviors for `local`, `staging`, or `ci` environments.

```yaml
# .gherkio/environments/staging.yaml
baseUrl: "https://staging-api.company.com"

mocks:
  # -------------------------------------------------------------------
  # Rule 1: Mock Third-Party Payment Gateway (Stripe)
  # -------------------------------------------------------------------
  - request:
      method: POST
      url: "api.stripe.com/v1/charges"      # Matches URL substring or suffix
    response:
      status: 200
      headers:
        Content-Type: "application/json"
        X-Mocked-By: "Gherkio-Virtualization"
      body:
        id: "ch_mock_991823719"
        object: "charge"
        amount: "$request.body.amount"      # Dynamically echoes request body parameter!
        currency: "$request.body.currency"  # Dynamically echoes request body parameter!
        status: "succeeded"
        paid: true

  # -------------------------------------------------------------------
  # Rule 2: Mock SMS Verification Endpoint (Twilio)
  # -------------------------------------------------------------------
  - request:
      method: POST
      url: "/v1/sms/send"
    response:
      status: 201
      headers:
        Content-Type: "application/json"
      body:
        messageId: "SM-884192"
        to: "$request.body.phoneNumber"
        status: "queued"
```text

---

## πŸ” Request Matching Rules

Gherkio evaluates mock rules sequentially. A request is intercepted if **both** criteria match:

1. **HTTP Method**: Case-insensitive match (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`).
2. **URL Matching**: Evaluates target request URL without query parameters against the rule's `url` property:
   - **Exact Match**: Request URL equals `mock.request.url` (e.g. `https://api.stripe.com/v1/charges`).
   - **Suffix Match**: Request URL ends with `mock.request.url` (e.g. `/v1/charges`).
   - **Substring Match**: Request URL contains `mock.request.url` (e.g. `stripe.com`).

---

## ⚑ Dynamic Request Parameter Echoing

Mock response bodies are not limited to static payloads. Gherkio supports **Dynamic Echo Interpolation** to reflect parameters sent in the incoming request's body or headers directly into the virtual response:

| Expression Syntax | Source Location | Description | Example |
| :--- | :--- | :--- | :--- |
| `$request.body.<path>` | Request Body | Echoes a JSON property path from the incoming request body | `$request.body.user.email` |
| `$request.headers.<name>` | Request Header | Echoes an HTTP header value from the incoming request | `$request.headers.X-Correlation-ID` |

### Example: Dynamic Reflection Setup

```yaml
mocks:
  - request:
      method: POST
      url: "/v1/orders"
    response:
      status: 201
      headers:
        X-Correlation-ID: "$request.headers.x-correlation-id"
      body:
        orderId: "ORD-9918"
        customerEmail: "$request.body.customer.email"
        itemCount: "$request.body.items.length"
        totalAmount: "$request.body.total"
        processedAt: "2026-08-29T10:00:00Z"
```text

When a test step sends:

```yaml
- request:
    method: POST
    url: /v1/orders
    headers:
      X-Correlation-ID: "corr-xyz-123"
    body:
      customer:
        email: "alice@example.com"
      total: 149.99
```text

The mock engine intercepts the request and instantly responds with:

```json
{
  "orderId": "ORD-9918",
  "customerEmail": "alice@example.com",
  "totalAmount": 149.99,
  "processedAt": "2026-08-29T10:00:00Z"
}
```text

---

## πŸ“Š CLI Logging & Report Indicators

When Gherkio intercepts a request, it logs an explicit indicator to stdout so developers and QA engineers know the call was satisfied virtually:

```bash
βœ” [Attempt 1] [MOCK] Intercepted POST https://api.stripe.com/v1/charges
```text

In compiled HTML execution reports, intercepted requests are tagged with a **[MOCK]** badge alongside full request and virtual response inspection tabs.

Assertions & Dot-Paths

Assertions are the core validation engine of Gherkio. Placed inside the expect block of any step, assertions inspect, type-check, and validate the returned HTTP response status, headers, body structure, and even embedded JWT tokens.


πŸ—οΈ The Anatomy of expect

The expect block defines a set of strict contracts that the HTTP response must satisfy. Gherkio evaluates these assertions sequentially upon request completion.

⚑ Assertion Failure Behavior

Gherkio evaluates the assertions for a response and records every result in the report. A failed assertion marks the step and scenario as failed.

The CLI continues with remaining steps by default so one run can expose multiple failures. Teardown always runs. MCP callers can request fail-fast execution through the run_test tool when stopping after the first failed step is preferable.


🧭 Dot-Path Prefixes & Resolvers

Gherkio uses a declarative dot-notation system to target specific parts of the response. The prefix of the path determines which resolver is invoked:

Path PrefixTarget ComponentKey Case-SensitivityPurposeExample
statusHTTP Status CodeN/AValidates the exact status codestatus: 200
body.<path>JSON Response BodyCase-SensitiveTraverses response JSON structuresbody.user.email: email
headers.<key>HTTP Response HeadersCase-InsensitiveInspects response headersheaders.content-type: contains json
jwt.<claim>Decoded JWT payloadCase-SensitiveAutomatically decodes tokens and inspects claimsjwt.role: admin
schemaJSON Schema ContractN/AEnforces a full JSON schema contractschema: users/create
timing.durationRound-trip request timeN/AAsserts latency SLA/performancetiming.duration: lte 500ms

1. Response Body Traversal (body.<path>)

The body. prefix targets the response body payload (expected to be JSON). Gherkio traverses nested properties, list indices, and dictionary keys.

Key Syntax Features:

  • Nested Navigation: Use dots to descend into maps (e.g., body.user.profile.firstName).
  • Array Indexing: Use brackets to access specific list items (e.g., body.items[0].id).
  • Case Sensitivity: JSON keys are strictly case-sensitive. body.userId is different from body.userid.

Supported Matchers

Assertion ValueTypeExampleBehavior
Literal valueEqualitybody.role: adminExact string match
existsExistencebody.email: existsField must be present (any value)
not existsAbsencebody.error: not existsField must be absent
uuidFormatbody.id: uuidValid UUID v4 format
emailFormatbody.email: emailValid email format
datetimeFormatbody.createdAt: datetimeValid RFC3339 / ISO8601 datetime
uriFormatbody.avatar: uriValid URI format (e.g. https://...)
stringTypebody.name: stringValue is a string
numberTypebody.price: numberValue is numeric (int, float)
booleanTypebody.active: booleanValue is boolean
arrayTypebody.tags: arrayValue is an array
objectTypebody.meta: objectValue is an object/map
nullTypebody.deletedAt: nullValue is null
true / falseLiteralbody.active: trueValue is boolean true/false
contains <s>Substringbody.message: contains errorString contains substring
startsWith <s>Prefixbody.id: startsWith usr_String starts with prefix
endsWith <s>Suffixbody.email: endsWith @example.comString ends with suffix
regex <p>Patternbody.code: regex ^[A-Z]{3}\d{4}$String matches regex
oneOf <opts>Membershipbody.role: oneOf admin, editorValue matches any option
in <opts>Membershipbody.status: in active, pendingAlias for oneOf
schema: <name>Schemaschema: users/profileFull body validated against schema
schema: not <name>Negated schemaschema: not errors/validationBody must NOT match error schema

Negative Assertions with not exists

You can assert that a field is absent from the response body using not exists. This is useful for verifying that optional fields are omitted on purpose, or that error/cleanup fields are not present:

expect:
  body.deletedAt: not exists    # soft-delete field should not appear
  body.error: not exists        # no error on success path
  body.archived: not exists     # field completely absent from payload
```text

#### πŸ’‘ Intelligent Spelling Suggestion Engine
If you write an assertion for a path that does not exist in the response payload, Gherkio's smart diff engine doesn't just fail; it analyzes the keys that *were* actually present in the response and prints helpful spelling suggestions in your terminal:

```text
βœ— timing.duration = lte 500ms (actual: 312ms)
βœ— body.accessToken = exists
  Reason: field 'accessToken' not found in response body
  Suggestions:
    - message
    - statusCode
```text

---

### 1.1 JSONPath Assertions (`$.<path>`)

For complex payloads where dot-notation or array indexing are insufficient (e.g. querying elements dynamically based on filter criteria, wildcards, or deep scans), Gherkio supports standard JSONPath expressions.

Any assertion path starting with `$.` will automatically be evaluated as a JSONPath expression against the response body.

#### Syntax & Examples:

```yaml
expect:
  # Verify that the ID of the first book in the list is a number
  $.store.book[0].price: 8.95

  # Verify that all books under store have a category
  $.store.book[*].category: exists

  # Extract values matching filter conditions and assert
  $.store.book[?(@.price < 10)].title: contains "Sayings"
```text

---

### 2. HTTP Headers (`headers.<key>`)

The `headers.` prefix allows you to inspect HTTP response headers.

#### Key Syntax Features:
*   **Case-Insensitivity**: In accordance with the HTTP RFC, header lookups are fully case-insensitive. Gherkio normalizes these keys internally, meaning `headers.content-type`, `headers.Content-Type`, and `headers.CONTENT-TYPE` are completely equivalent.
*   **Multi-Value Headers**: If a header has multiple values, Gherkio normalizes them to a single comma-separated string for easy assertion matching (e.g. `headers.cache-control: contains no-cache`).

---

### 3. Automatic JWT Claim Decoding (`jwt.<claim>`)

One of Gherkio's most powerful native features is the automatic discovery and decoding of JSON Web Tokens.

#### How It Works:
1.  When a request completes, Gherkio scans the response body for standard JWT field names:
    *   `token`
    *   `accessToken`
    *   `access_token`
    *   `idToken`
    *   `id_token`
2.  If any of these fields are present and contain a valid base64-encoded, three-part JWT structure (`header.payload.signature`), Gherkio automatically decodes the payload section into memory.
3.  You can assert directly against claims in the payload using the `jwt.` prefix.

#### Example:
If the server returns:
```json
{
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4iLCJleHAiOjE3MTY4ODQwMDB9.sig"
}
```text
You can immediately assert:
```yaml
expect:
  jwt.sub: "123"
  jwt.role: "admin"
  jwt.exp: datetime  # Asserts that the expiration claim is present
```text

---

### 4. JSON Schema Contracts (`schema`)

For absolute validation of deep objects and API payloads, individual key assertions can become unwieldy. The `schema` directive enforces full, deep-object JSON Schema validation.

#### Syntax:
```yaml
expect:
  schema: path/to/schema
```text

#### How It Works:
*   **Path Resolution**: Gherkio looks for the schema file under your project's configured schemas directory (usually `.gherkio/schemas/`). It automatically appends `.yaml`, `.yml`, or `.json` to locate the file (e.g., `schema: users/profile` resolves to `.gherkio/schemas/users/profile.yaml`).
*   **Negation (`not`)**: You can assert that a response **does not** match a schema by prefixing the value with `not ` (e.g. `schema: not errors/validation`). This is ideal for testing error states where a validation error structure should *not* be returned.

---

### 5. Response Timing Assertions (`timing`)

To enforce service-level agreements (SLAs) or target performance constraints, Gherkio includes a native latency-checking block. This is configured at the **step level** (outside the `expect` block):

*   **Syntax**: `timing: { max: <duration> }`
*   **Duration Syntax**: A numeric value followed by `ms` (milliseconds) or `s` (seconds).
*   **Result**: It generates a `timing.duration = lte <duration>` assertion result in the test output.

```yaml
steps:
  - request:
      method: GET
      url: /v1/users
    timing:
      max: 500ms   # Step fails if request takes longer than 500ms
    expect:
      status: 200
```text

---

## πŸ”„ Dynamic Variables in Assertions

To avoid hardcoding values and to support end-to-end testing, Gherkio supports dynamic variable lookup directly in your assertion values.

### 1. Interpolating Saved & Global Variables (`$<variable>`)
Expected values inside the `expect` block are fully evaluated through the variable interpolation engine. This includes:
*   **Saved Variables**: Values extracted from previous steps using the `save` block (e.g. `$userId`).
*   **Environment & Credentials Variables**: Values loaded from the current environment YAML or credentials accounts (e.g. `$accounts.admin.email`).

```yaml
steps:
  - request:
      method: POST
      url: /v1/users
      body:
        email: "bob@example.com"
    save:
      newUserId: body.id

  - request:
      method: GET
      url: /v1/users/$newUserId
    expect:
      status: 200
      # Validates that the returned ID matches the ID saved from the previous step
      body.id: $newUserId
      # Validates against a global test account email from your environment config
      body.invitedBy: $accounts.admin.email
```text

### 2. Current Request References (`request.body.<field>`)
When verifying creation or update steps, you often want to assert that the response body contains the exact same data sent in the request. You can refer to the *current request's body* fields dynamically using the `request.body.` prefix.

```yaml
steps:
  - request:
      method: POST
      url: /v1/products
      body:
        title: "Mechanical Keyboard"
        price: 99.99
    expect:
      status: 201
      # Asserts that the response payload echoes back the sent request fields dynamically
      body.data.title: request.body.title
      body.data.price: request.body.price
```text

---

## πŸ—„οΈ Collection & Array Assertions

Gherkio provides advanced built-in collection resolvers to perform validations across lists without writing loops.

### 1. Array Size Validation (`count(<array-path>)`)

Asserts the exact length of a list returned in the response.

*   **Exact Count**: `count(body.items): 5`
*   **Comparators**: Append `.gt`, `.gte`, `.lt`, or `.lte` suffixes to the path to perform numeric bounds checks:
    *   `count(body.items).gte: 1` β€” Asserts that the array is not empty (has at least 1 item).
    *   `count(body.items).lt: 10` β€” Asserts that the array contains fewer than 10 items.

> [!NOTE]
> Unlike standard body paths, `count()` paths resolve directly against the parsed response body root. The `body.` prefix inside the parenthesis is optional, but supported.

---

### 2. Universal Collection Asserts (`all(<array-path>)`)

Asserts that **every single element** in a list satisfies a specific condition.

*   **Equality Check**: `all(body.items.status): active` β€” Asserts that every item in the `items` list has a `status` equal to `"active"`.
*   **Matcher Assertion**: `all(body.items.price): number` β€” Asserts that the `price` field in every element is a number.
*   **Negative Assertion**: `all(body.items.errors): not exists` β€” Asserts that the `errors` field is absent from every element.

| Condition | Example | Description |
|-----------|---------|-------------|
| Literal value | `all(body.items.role): admin` | Every element's field equals the value |
| Type matcher | `all(body.items.price): number` | Every element's field matches a type matcher |
| Format matcher | `all(body.items.email): email` | Every element's field matches a format matcher |
| Existence | `all(body.items.id): exists` | Every element has the field |
| **Absence** | `all(body.items.errors): not exists` | **No element has the field** |
| Collection matcher | `all(body.items.count): gte 1` | Every array element satisfies a count condition |

---

## 🌟 Comprehensive Real-World Example

Below is a complete, production-grade Gherkio test step demonstrating status codes, headers, nested body lookups, collection checks, automatic JWT assertions, and schema contract verification running side-by-side:

```yaml
scenario: Complete API contract and session validation
steps:
  - request:
      method: POST
      url: /v1/auth/session
      body:
        username: "admin"
        device: "laptop"
    expect:
      # 1. Enforce exact HTTP status code
      status: 201

      # 2. Case-insensitive header checks
      headers.Content-Type: contains json
      headers.x-transaction-id: exists

      # 3. Deep-path JSON body validations using matchers & dynamic variables
      body.session.id: uuid
      body.session.metadata.device: request.body.device     # Dynamic reference to current request payload
      body.session.metadata.tenant: $tenantId               # Interpolates variable saved from previous steps
      body.session.active: true

      # 4. Collection checks with comparison bounds
      count(body.session.permissions).gte: 3
      all(body.session.permissions): string

      # 5. Automatic JWT discovery and claims validation
      jwt.role: "administrator"
      jwt.sub: uuid

      # 6. Full API schema validation (located at .gherkio/schemas/auth/session-response.yaml)
      schema: auth/session-response
```text

Matcher Library

Gherkio includes a powerful, built-in library of 25+ assertion matchers. These matchers allow you to perform rich semantic validationsβ€”ranging from format verification (like UUIDs, email addresses, and datetimes) to mathematical range boundaries, string manipulations, and collection inspectionβ€”without writing any code or custom assertions.


πŸ“š Complete Matcher Quick-Reference

KeywordExampleTarget Data Must Be...
Existence
existsbody.id: existsPresent in the payload and not null.
not existsbody.deletedAt: not existsCompletely absent from the payload.
Type Verification
stringbody.name: stringA JSON string.
numberbody.price: numberAny integer or decimal/float value.
booleanbody.active: booleanA boolean value (true or false).
arraybody.tags: arrayA JSON array list.
objectbody.metadata: objectA JSON object map.
nullbody.archivedAt: nullStrictly a JSON null.
truebody.completed: trueStrictly boolean true.
falsebody.pending: falseStrictly boolean false.
String & Membership Operations
contains <value>body.msg: contains SuccessA string containing the exact <value>.
startsWith <val>body.sku: startsWith LAP-A string starting with prefix <val>.
endsWith <val>body.email: endsWith .comA string ending with suffix <val>.
regex <pattern>body.code: regex ^[A-Z]{3}$A string matching the Go regular expression.
oneOf <opts>body.role: oneOf admin, editorValue matches any of the comma-separated options.
in <opts>body.status: in active, pendingAlias for oneOf. Value matches any option.
Numeric Bounds
gt <value>body.rating: gt 4Greater than <value>.
gte <value>body.price: gte 9.99Greater than or equal to <value>.
lt <value>body.age: lt 18Less than <value>.
lte <value>body.limit: lte 100Less than or equal to <value>.
Format Validators
uuidbody.id: uuidA valid UUID v4 format string.
emailbody.email: emailA valid RFC-compliant email address.
datetimebody.createdAt: datetimeA valid RFC3339 / ISO8601 datetime string.
uribody.url: uriA valid absolute URI string.
ipv4body.ip: ipv4A valid IPv4 address (e.g. 192.168.1.1).
ipv6body.ip: ipv6A valid IPv6 address (supports compressed).
base64body.data: base64A valid Base64 or URL-Safe Base64 string.
macbody.address: macA valid MAC address format.
State Inspection
emptybody.items: emptyAn empty string "", empty list [], empty map {}, or null.

πŸ” Category Deep-Dive & Syntax Examples

1. Existence Checkers

These matchers verify whether a key is present or absent in the response payload structure.

  • exists: Evaluates to true if the field is present, regardless of its value (including empty values).
  • not exists: Evaluates to true only if the key is completely omitted from the JSON payload.
expect:
  body.id: exists
  body.deletedAt: not exists
```text

---

### 2. Type Enforcers & Booleans
Ensures response elements conform strictly to expected JSON data types.

*   **`string`**: Fails if the value is a number, boolean, array, or object.
*   **`number`**: Validates integers, floats, and scientific JSON numbers.
*   **`boolean`**: Matches either `true` or `false` booleans.
*   **`array`**: Verifies the field is a JSON list.
*   **`object`**: Verifies the field is a structured map/object.
*   **`null`**: Verifies the value is `null`.
*   **`true` / `false`**: Value-specific boolean enforcers.

```yaml
expect:
  body.profile.bio: string
  body.profile.age: number
  body.profile.isVerified: boolean
  body.tags: array
  body.preferences: object
  body.finished: true
  body.error: null
```text

---

### 3. String & Membership Operations (Argument-based)
Performs text and list membership assertions on target fields.

*   **`contains <value>`**: Checks if the target string contains the substring `<value>` (case-sensitive).
*   **`startsWith <value>`**: Checks if the target string begins with the prefix `<value>`.
*   **`endsWith <value>`**: Checks if the target string ends with the suffix `<value>`.
*   **`regex <pattern>`**: Matches the string against a standard Go regular expression.
*   **`oneOf <opts>`**: Checks if the value matches any of the comma-separated options.
*   **`in <opts>`**: Alias for `oneOf`. Checks if the value matches any of the comma-separated options.

```yaml
expect:
  body.description: contains Mechanical
  body.sku: startsWith KBD-
  body.supportEmail: endsWith @example.com
  body.productCode: regex ^[A-Z]{3}-\d{4}$
  body.role: oneOf admin, editor, moderator
  body.status: in active, pending
```text

---

### 4. Mathematical Bounds (Argument-based)
Used to enforce numeric restrictions. Gherkio automatically coerces both the target response field and the matcher argument into `float64` floating points for maximum comparison precision.

*   **`gt <value>`**: Greater than (`>`).
*   **`gte <value>`**: Greater than or equal to (`>=`).
*   **`lt <value>`**: Less than (`<`).
*   **`lte <value>`**: Less than or equal to (`<=`).

```yaml
expect:
  body.rating: gt 4.2
  body.stock: gte 0
  body.discount: lt 1.0
  body.itemsCount: lte 100
```text

---

### 5. Format & Protocol Validators
Performs regex-based and parser-based syntax verification on strings to enforce RFC protocols.

*   **`uuid`**: Asserts strict compliance with UUID v4 syntax.
*   **`email`**: Validates structural email address syntax (presence of `@`, valid domain blocks).
*   **`datetime`**: Parses string against standard RFC3339 / ISO8601 structures (e.g. `2026-05-29T10:12:15Z`).
*   **`uri`**: Verifies that the string is a valid absolute URI.
*   **`ipv4` / `ipv6`**: Enforces valid IP structures, ensuring IPv4 segments fall between `0-255` and IPv6 fields support compressed forms.
*   **`base64`**: Attempts standard base64 decoding and falls back to URL-safe raw base64 to ensure absolute compatibility.
*   **`mac`**: Validates standard colon-separated or hyphen-separated MAC addresses (e.g., `00:1a:2b:3c:4d:5e`).

```yaml
expect:
  body.userId: uuid
  body.contactEmail: email
  body.createdAt: datetime
  body.imageLink: uri
  body.serverIp: ipv4
  body.payloadHash: base64
  body.deviceMac: mac
```text

---

### 6. Emptiness Checker
The **`empty`** matcher is a unified helper designed to handle collections, strings, and missing records consistently:
*   Returns `true` if target is a string equal to `""`.
*   Returns `true` if target is a JSON list containing `0` items.
*   Returns `true` if target is a JSON object map containing `0` properties.
*   Returns `true` if target is `null`.

```yaml
expect:
  # Validates that list is empty
  body.items: empty
  # Validates that custom message string is blank
  body.alertMessage: empty
```text

---

## πŸ”„ Matchers inside Universal Collections (`all`)

Matchers are fully integrated into Gherkio's collection engine. When performing assertions on arrays, you can pass **any matcher keyword** as the target check inside an `all()` block:

```yaml
expect:
  # Enforces that every element in the permissions array is a valid UUID
  all(body.users.id): uuid

  # Enforces that every transaction amount is positive
  all(body.transactions.amount): gt 0

  # Enforces that every tags list is a valid array
  all(body.products.tags): array
```text

Schema Validation

Gherkio supports robust custom validation schemas. This allows you to enforce schema contracts for complex JSON APIs rather than writing dozens of line-by-line field assertions.


πŸ“ Schema Definitions Directory

All validation schemas live under .gherkio/schemas/ and are defined using standard YAML or JSON files.

Gherkio resolves referenced schemas relative to the .gherkio/schemas/ directory. For example, referencing users/profile matches:

  • .gherkio/schemas/users/profile.yaml (or .yml)
  • .gherkio/schemas/users/profile.json

⚑ Asserting a Schema in DSL

To validate that an API response matches your contract, reference the schema file relative to .gherkio/schemas/ inside the expect block:

steps:
  - request:
      method: GET
      url: /profile
    expect:
      status: 200
      schema: users/profile         # Matches users/profile.yaml or users/profile.json
```text

If the API response structure changes or omits a required field, Gherkio will output a clear error path highlighting precisely what failed the contract validation:

```text
❌ Step 1: GET /profile failed schema validation
  - body.id: Does not match pattern
  - body.email: Required field is missing
```text

---

## βš™οΈ Supported Schema Constraints Reference

Gherkio's schema engine is highly optimized and supports standard JSON Schema-like validation keywords. Below is the complete reference of all supported properties.

### 1. Structural & Core Keywords

| Keyword | Type | Description |
| :--- | :--- | :--- |
| `type` | `string` | Defines the expected data type. Allowed types: `object`, `array`, `string`, `number`, `integer`, `boolean`, `null`. |
| `nullable` | `boolean` | If set to `true`, the value is allowed to be `null` even if other type rules are violated. Defaults to `false`. |
| `enum` | `array` | A list of allowed values. The actual value must match one of the items exactly. |

### 2. Object Constraints

| Keyword | Type | Description |
| :--- | :--- | :--- |
| `properties` | `object` | A map of nested key-value schemas defining the structure of object fields. |
| `required` | `array` | A list of mandatory property keys that must exist and cannot be null. |

### 3. Array Constraints

| Keyword | Type | Description |
| :--- | :--- | :--- |
| `items` | `schema` | The validation schema applied to **every** item inside the array. |
| `minItems` | `integer` | Enforces the minimum number of items allowed in the array. |
| `maxItems` | `integer` | Enforces the maximum number of items allowed in the array. |

### 4. String Constraints

| Keyword | Type | Description |
| :--- | :--- | :--- |
| `pattern` | `string` | A standard Go-compliant regular expression pattern that the string must match. |
| `minLength` | `integer` | The minimum length (number of characters) required for the string. |
| `maxLength` | `integer` | The maximum length (number of characters) allowed for the string. |
| `format` | `string` | Pre-defined specialized string format matches. Supported formats are: |
| | | βž” `email`: Standard email format validation.<br>βž” `uuid`: Standard UUID v4 format validation.<br>βž” `datetime`: Valid RFC 3339/ISO 8601 timestamp formats.<br>βž” `uri`: Valid absolute URI/URL pathway validation. |

### 5. Numeric Constraints (Numbers & Integers)

| Keyword | Type | Description |
| :--- | :--- | :--- |
| `minimum` | `number` | Enforces that the numeric value is greater than or equal to this limit. |
| `maximum` | `number` | Enforces that the numeric value is less than or equal to this limit. |

---

## πŸ’‘ Complete Schema Validation Examples

### Example A: Comprehensive Array Schema (`.gherkio/schemas/posts/list.yaml`)
Enforces custom length limits, strict nullable attributes, item structure, and format matching for a list of blog posts.

```yaml
type: array
minItems: 1
maxItems: 50
items:
  type: object
  required:
    - id
    - title
    - views
    - tags
  properties:
    id:
      type: string
      format: uuid
    title:
      type: string
      minLength: 3
      maxLength: 100
    description:
      type: string
      nullable: true           # Description can be null or a string
    views:
      type: integer
      minimum: 0
    tags:
      type: array
      minItems: 1
      items:
        type: string
        pattern: "^[a-z0-9-]+$" # Tags must be lowercase alphanumeric-kebab
```text

### Example B: Nested Object Schema (`.gherkio/schemas/users/auth.yaml`)
Validates a standard login response structure.

```yaml
type: object
required:
  - user
  - token
properties:
  token:
    type: string
    minLength: 32
  user:
    type: object
    required:
      - id
      - email
      - role
    properties:
      id:
        type: string
        format: uuid
      email:
        type: string
        format: email
      role:
        type: string
        enum:
          - admin
          - user
          - editor
```text

Editor Autocomplete Schema

Gherkio ships a machine-readable JSON Schema that editors like VS Code and Neovim can use to provide inline autocomplete, real-time validation, and hover documentation when writing Gherkio YAML test files.


🎯 What Is This?

When you open a Gherkio test file (e.g. .gherkio/tests/login.yaml) in a compatible editor, the JSON Schema tells the editor:

  • Which top-level keys are valid (scenario, setup, steps, teardown, etc.)
  • What types each field expects (string, array, object, etc.)
  • Which keys are required in each block
  • What values are allowed for enum-like fields (HTTP methods, matcher names, etc.)
  • Descriptive comments shown as hover tooltips

This is powered by the gherkio schema CLI command, which introspects the Go model structs and emits a standard JSON Schema Draft-07 document.


πŸ—οΈ Schema Types

The gherkio schema command generates schemas for all first-class Gherkio file types. You can generate all of them at once, or target a specific type:

TypeFile PatternDescription
test.gherkio/tests/**/*.yamlFull scenario test files (setup + steps + teardown)
config.gherkio/config.yamlWorkspace configuration
environment.gherkio/environments/*.yamlEnvironment definitions (baseUrl, services)
credentials.gherkio/credentials/*.yamlAccount credentials per environment
schema-definition.gherkio/schemas/**/*.yamlJSON Schema Draft-07 contract files used in expect.schema assertions

πŸ“¦ Generating the Schema

Generate all schemas at once

gherkio schema > .gherkio-schema.json
```text

This produces a single JSON file keyed by type:

```json
{
  "test": { "$schema": "http://json-schema.org/draft-07/schema#", ... },
  "config": { ... },
  "environment": { ... },
  "credentials": { ... },
  "schema-definition": { ... }
}
```text

### Generate a specific schema type

```bash
gherkio schema --type test > gherkio-test-schema.json
gherkio schema --type environment > gherkio-env-schema.json
```text

### List available schema types

```bash
gherkio schema --list
```text

---

## πŸ–₯️ VS Code Integration

### 1. Install the YAML extension

Install the official [YAML Language Server (Red Hat)](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml) extension in VS Code.

### 2. Configure schema associations

Open or create `.vscode/settings.json` in your workspace root:

```json
{
  "yaml.schemas": {
    "./.gherkio-schema.json": [".gherkio/tests/**/*.yaml", ".gherkio/tests/**/*.yml"]
  }
}
```text

### 3. Per-file schema directive (alternative)

Add this comment as the **very first line** of any Gherkio YAML file to use a locally generated schema:

```yaml
# yaml-language-server: $schema=../../.gherkio-schema.json
scenario: Login flow
steps:
  ...
```text

---

## ⚑ Neovim Integration

### gherkio.nvim (recommended)

The official [gherkio.nvim](https://github.com/muhfaris/gherkio/tree/main/neovim/gherkio.nvim) Neovim plugin automatically downloads and caches the schema from the MCP server (`gherkio://dsl/schema.json`) on first use. No manual configuration required.

For manual schema configuration in Neovim, point your YAML LSP or `vim.schema` settings to the generated schema file:

```lua
-- Example: vim.schema (Neovim 0.10+)
vim.schema('.gherkio-schema.json', {
  filepattern = '.gherkio/tests/**/*.yaml',
})
```text

Or configure via `yaml.schemas` in `vim.lsp.config` if using `yaml-language-server` manually.

---

## πŸ“ Where to Place the Schema File

Place the generated `.gherkio-schema.json` in your **workspace root** (same level as `.gherkio/`):

```text
.
β”œβ”€β”€ .gherkio/
β”‚   β”œβ”€β”€ tests/
β”‚   β”‚   └── login.yaml
β”‚   β”œβ”€β”€ environments/
β”‚   β”‚   └── staging.yaml
β”‚   └── schemas/
β”‚       └── auth/token.yaml
└── .gherkio-schema.json    ← generated here
```text

Commit `.gherkio-schema.json` to version control so every teammate gets autocomplete without running any commands.

---

## πŸ”„ Keeping the Schema Up to Date

The schema is derived from the Go model structs in the `gherkio` binary. After upgrading `gherkio` to a new version, re-generate the schema:

```bash
gherkio schema > .gherkio-schema.json
```text

The MCP server always exposes the schema for the **currently running** binary version via the `gherkio://dsl/schema.json` resource URI.

---

## πŸ“‹ Schema Structure Overview

Below is a simplified view of the top-level structure the test schema validates:

```yaml
# Top-level keys (all optional except 'steps')
tags:           []string          # Filter tags for selective runs
setup:          []Step            # Pre-condition block; halts on failure
steps:          []Step            # Primary test block (required)
teardown:       []Step            # Cleanup block; always executes

# Each Step is either a 'use' (composition) or a 'request':
Step:
  name:     string               # Human-readable label
  use:      string               # Path to another scenario file (mutually exclusive with request)
  request:  Request              # HTTP request config (mutually exclusive with use)
  expect:   Expect               # Response assertions
  save:     map[string]string    # Extract variables from response
  timing:   TimingConfig        # Latency budget

# Request fields
Request:
  service:  string               # Named service override
  method:   string               # GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS
  url:      string               # URL path (appends to baseUrl)
  query:    map[string]string    # Query parameters
  headers:  map[string]string    # Custom headers
  body:     any                  # Request payload
  transform: map[string]ProjectionConfig  # Declarative array projections

# Expect fields
Expect:
  status:          integer       # Expected HTTP status code
  body.<path>:     MatcherValue  # Body dot-path assertions
  headers.<name>:  MatcherValue  # Header assertions
  jwt.<claim>:     MatcherValue  # JWT claim assertions
  schema:          string        # Schema contract name (from .gherkio/schemas/)
  timing.duration: MatcherValue  # Latency assertion
```text

For the full schema with all enum values, type constraints, and descriptions, run:

```bash
gherkio schema --type test | python3 -m json.tool | less
```text

Variables & Generators

Variables allow you to pass state dynamically, randomize inputs, and secure secrets without hardcoding values in test scenarios. Gherkio evaluates and injects variables dynamically per step.


⚑ Declaring & Using Variables

Variables are prefixed with $ (e.g., $accessToken).

For string boundary isolation inside header fields or request bodies, wrap them in curly braces:

  • Authorization: "Bearer ${accessToken}"

Default Fallbacks

If a variable might not be defined under the current context, you can specify a default fallback:

  • role: "${role:user}" βž” resolves to the value of $role if defined, otherwise defaults to "user".

Array Indexing (Bracket Notation)

If a saved variable contains an array, you can access individual elements by index using bracket notation:

  • $tags[0].id βž” accesses the id field of the first element in the $tags array
  • $tags[${randomInt(0,4)}].id βž” picks a random element each time (useful with retry)
  • ${randomItem(tags,id)} βž” picks an item using the actual array length and returns its id
  • ${randomItem(tags)} in an exact set value βž” stores the selected object, allowing $SELECTED_TAG.id and $SELECTED_TAG.name
  • $items[2].name βž” accesses the name field of the third element

This works in all variable interpolation contexts: request body, headers, URLs, save paths, and assertion values.

✏️ To assign or override a variable without issuing a request, use a set step. Variables written by set are interpolated the same way as saved ones.

When the response length is not fixed, prefer randomItem:

steps:
  - request:
      method: GET
      url: /users
    save:
      users: body.data

  - request:
      method: GET
      url: /users/${randomItem(users,id)}
```text

**Example** β€” randomly pick an issue tag from a saved array:

```yaml
steps:
  - request:
      method: GET
      url: /v1/issue-tags/l3
    save:
      issueTags: body.data          # save the full array

  - request:
      method: POST
      url: /v1/tickets
      body:
        issue_tag_id: $issueTags[${randomInt(0,4)}].id   # random tag each time
```text

Combined with `retry`, each attempt picks a different random index β€” useful for avoiding resource conflicts:

```yaml
  - request:
      method: POST
      url: /v1/tickets
      body:
        issue_tag_id: $issueTags[${randomInt(0,9)}].id
        name: "ticket-${randomInt}"
    retry:
      attempts: 5
      interval: 300
      backoff: constant
      onStatus: [409]
    expect:
      status: 200
```text

---

## 🎲 Built-in Single-Value Generators

Gherkio dynamically generates fresh values once per step for these standard single-value variables:

| Variable | Output Type | Example Output | Description |
| :--- | :--- | :--- | :--- |
| `$uuid` | `string` | `a1b2c3d4-e5f6-4789-abcd-ef1234567890` | Valid UUID v4 string |
| `$ulid` | `string` | `01ARZ3NDEKTSV4RRFFQ69G5FAV` | Monotonically increasing ULID |
| `$randomInt` | `integer` | `482910` | Random integer between `0` and `999999` |
| `$randomEmail` | `string` | `user_182938@example.com` | Random email address with a numeric suffix |
| `$randomPhone` | `string` | `+6281234567890` | Random Indonesian format phone number (default fallback) |

---

## πŸ› οΈ Dynamic Generator Functions

For more specialized payloads, Gherkio provides a collection of **parameterized generator functions**. These are declared using the `${functionName(args)}` syntax.

### 1. Numeric & String Randomizers

*   **`${randomInt(min, max)}`**: Generates a random integer within a custom inclusive range.
    *   *Example*: `${randomInt(1, 100)}` βž” `42`
*   **`${randomItem(array[, field])}`**: Selects from a saved response array using its runtime length.
    *   *Example*: `${randomItem(users,id)}` βž” selects a random user and returns its `id`.
    *   *Example*: `${randomItem(users,profile.email)}` βž” returns a nested field.
    *   *Example*: `${randomItem(ids)}` βž” returns a random primitive item.
    *   Empty arrays, missing variables, and missing fields produce clear interpolation errors.
*   **`${randomString(length, charset)}`**: Generates a random string of a specified length using a selected character set (`alpha`, `numeric`, `alphanumeric`).
    *   *Example*: `${randomString(12, "alphanumeric")}` βž” `a8F2k9Pq3xLm`
*   **`${randomPhone(countryOrPrefix)}`**: Generates a random phone number based on a country code or a raw dialing prefix.
    *   **Country Codes**: Supports mapping major ISO countries to standard mobile layouts. Valid codes include:
        *   `ID` (Indonesia): `+628...`
        *   `US` / `CA` (USA / Canada): `+1...`
        *   `GB` / `UK` (United Kingdom): `+447...`
        *   `SG` (Singapore): `+658...`
        *   `JP` (Japan): `+8190...`
        *   `DE` (Germany): `+491...`
        *   `FR` (France): `+336...`
        *   `AU` (Australia): `+614...`
        *   `IN` (India): `+919...`
        *   `CN` (China): `+861...`
        *   `BR` (Brazil): `+55119...`
        *   `RU` (Russia): `+79...`
        *   `ZA` (South Africa): `+277...`
        *   `KR` (South Korea): `+8210...`
        *   `NL` (Netherlands): `+316...`
        *   `ES` (Spain): `+346...`
        *   `IT` (Italy): `+393...`
        *   `MY` (Malaysia): `+601...`
        *   `PH` (Philippines): `+639...`
        *   `TH` (Thailand): `+668...`
        *   `VN` (Vietnam): `+849...`
    *   **Raw Dialing Prefix**: You can pass any standard dialing prefix directly (e.g. `+351` or `351`). Gherkio automatically ensures it has the `+` prefix and appends 9 random digits.
    *   **Default Fallback**: Empty/unrecognized inputs automatically fallback to the Indonesian mobile (`ID`) format.
    *   *Example (ISO Code)*: `${randomPhone("SG")}` βž” `+658591823`
    *   *Example (Raw Prefix)*: `${randomPhone("+351")}` βž” `+351982738192`

---

### 2. Time & Date Offset Functions

*   **`${timestamp()}`**: Returns the current Unix timestamp (seconds since epoch) as a string.
    *   *Example*: `${timestamp()}` βž” `1779958329`
*   **`${timestampMs()}`**: Returns the current Unix timestamp in milliseconds.
    *   *Example*: `${timestampMs()}` βž” `1779958329000`
*   **`${dateNow(format)}`**: Formats the current time using a custom layout. Defaults to Go layout `2006-01-02 15:04:05`.
    *   *Example*: `${dateNow("2006-01-02")}` βž” `2026-05-29`
*   **`${dateOffset(offset, format)}`**: Offsets the current date/time by days (`d`), hours (`h`), minutes (`m`), or seconds (`s`). The `format` argument is optional (defaults to `2006-01-02`).
    *   *Example (14 days forward)*: `${dateOffset("+14d")}` βž” `2026-06-12`
    *   *Example (2 hours back)*: `${dateOffset("-2h", "2006-01-02 15:04:05")}` βž” `2026-05-29 12:30:15`

> [!IMPORTANT]
> **Understanding Go's Date/Time Layouts**
>
> Gherkio uses Go's native reference-time formatting under the hood instead of traditional C-style `strftime` patterns (such as `%Y-%m-%d`). 
> 
> To define a layout, use values from Go's canonical reference date: **`January 2, 2006 at 3:04:05 PM MST`** (representing components `1 2 3 4 5 6 -0700`).
>
> | Desired Format | Go Layout Pattern | Example Output |
> | :--- | :--- | :--- |
> | **ISO 8601 / RFC 3339** | `2006-01-02T15:04:05Z07:00` | `2026-05-29T17:24:14Z` |
> | **YYYY-MM-DD** | `2006-01-02` | `2026-05-29` |
> | **DD/MM/YYYY** | `02/01/2006` | `29/05/2026` |
> | **MM-DD-YYYY** | `01-02-2006` | `05-29-2026` |
> | **24-Hour Time** | `15:04:05` | `17:24:14` |
> | **12-Hour Time AM/PM** | `03:04:05 PM` | `05:24:14 PM` |
> | **Full Month & Day** | `January 2, 2006` | `May 29, 2026` |
> | **Short Month & Year** | `Jan 02, 06` | `May 29, 26` |
>
> ⚠️ **Warning:** Traditional formats like `%Y-%m-%d` or `yyyy-MM-dd` **are not parsed** and will be returned as literal strings (e.g. `%Y-%m-%d`). Only use the reference date numbers above.
>
> ---
>
> ### πŸ› οΈ Creating Custom Layouts
> 
> You are not limited to the presets above! You can construct **any custom date/time format** by mixing your preferred punctuation (such as slashes `/`, dots `.`, hyphens `-`, colons `:`, or spaces) with Go's reference date tokens:
> 
> *   **Slash Separated**: `${dateNow("2006/01/02")}` βž” `2026/05/29`
> *   **Dotted Format**: `${dateNow("02.01.2006")}` βž” `29.05.2026`
> *   **Millisecond Precision**: `${dateNow("15:04:05.000")}` βž” `17:28:04.921`
> *   **Written Sentence**: `${dateNow("Monday, Jan 2, 2006")}` βž” `Friday, May 29, 2026`

---

### 3. Encoders & Text Formatters

*   **`${base64(data)}`**: Encodes the given string parameter to Base64.
    *   *Example*: `${base64("hello")}` βž” `aGVsbG8=`
*   **`${base64Decode(encoded)}`**: Decodes a Base64 string.
    *   *Example*: `${base64Decode("aGVsbG8=")}` βž” `"hello"`
*   **`${urlencode(data)}`**: Escapes special characters for use in URI query parameters.
    *   *Example*: `${urlencode("hello world!")}` βž” `hello+world%21`
*   **`${urldecode(encoded)}`**: Unescapes URL characters.
    *   *Example*: `${urldecode("hello+world%21")}` βž” `"hello world!"`
*   **`${toUpper(text)}`**: Converts the string to uppercase.
    *   *Example*: `${toUpper("admin")}` βž” `"ADMIN"`
*   **`${toLower(text)}`**: Converts the string to lowercase.
    *   *Example*: `${toLower("TOKEN")}` βž” `"token"`
*   **`${trim(text)}`**: Trims leading and trailing whitespaces.
    *   *Example*: `${trim("   secret   ")}` βž” `"secret"`
*   **`${trimPrefix(value, prefix)}`**: Removes the prefix when present. Arguments may be quoted literals or saved variable paths.
    *   *Example*: `${trimPrefix(PARTNER_STATUS.value,"Lainnya::")}` βž” `"Gramedia UT - Gramedia UT"`
*   **`${trimSuffix(value, suffix)}`**: Removes the suffix when present. Arguments may be quoted literals or saved variable paths.
    *   *Example*: `${trimSuffix(filename,".json")}` βž” removes the `.json` suffix.
*   **`${split(value, delimiter, index)}`**: Splits a string and returns the segment at the zero-based index. The value and delimiter may be quoted literals or saved variable paths.
    *   *Example*: `${split(PARTNER_STATUS.value,"::",1)}` turns `"abc::yyy"` into `"yyy"`.

---

### 4. Cryptographic Hashing & HMAC

*   **`${hash(algorithm, data)}`**: Generates a hexadecimal hash of the data string. Supported algorithms: `md5`, `sha1`, `sha256`.
    *   *Example*: `${hash("sha256", "gherkio")}` βž” `0f845d4c82c3c97d...`
*   **`${hmac(algorithm, key, message)}`**: Generates a hexadecimal keyed-hash message authentication code (HMAC). Supported algorithms: `md5`, `sha1`, `sha256`.
    *   *Example*: `${hmac("sha256", "my-secret", "message-data")}` βž” `a8d7c49f...`

---

## πŸ“Š Variable Precedence

When variable names overlap under the same test execution context, Gherkio resolves the values in the following order of precedence (highest overrides lowest):

```text
[1. Host Environment Variables]  <-- Sourced from host environment (GHERKIO_ prefix only)
               ↓
[2. Selected Account Credentials] <-- Loaded from active credential file
               ↓
[3. Step Saves]                  <-- Extracted dynamically from prior steps
               ↓
[4. Built-in Generators]         <-- Generated fresh per step
```text

> ⚠️ **Security Warning:** Only host environment variables prefixed with `GHERKIO_` are loaded. Normal environment variables like `PATH`, `USER`, or `SECRET_KEY` are ignored to prevent accidental leaks.

Credentials & Multi-Account Testing

Gherkio separates credential configurations from test scripts, enabling multi-role validation across local, staging, and production environments.


πŸ”‘ Directory & File Conventions

Credential maps live under .gherkio/credentials/<env>.yaml and match corresponding environments in .gherkio/environments/<env>.yaml.

Example: .gherkio/credentials/staging.yaml

accounts:
  admin:
    username: admin@host.com
    password: admin-secret-key
    role: super-admin
  viewer:
    username: viewer@host.com
    password: viewer-secret-key
    role: reader
```text

---

## ⚑ Direct Cross-Account References

You can reference specific account credentials directly inside your YAML request body or headers using the `$accounts.<name>.<field>` notation. This allows cross-account testing without specifying an active user at the command line.

```yaml
steps:
  - request:
      method: POST
      url: /auth/login
      body:
        username: $accounts.admin.username
        password: $accounts.admin.password
    expect:
      status: 200
```text

---

## πŸƒ Role-Based Command Execution

If you specify an active account flag via the CLI, Gherkio automatically injects all keys for that specific account into the root scope of the test scenario:

```bash
# Injects $username and $password automatically from the 'admin' map
gherkio run tests/my-test.yaml --env staging --account admin
```text

You can also run a test suite against all configured accounts sequentially using `--all-accounts`:

```bash
gherkio run tests/my-test.yaml --env staging --all-accounts
```text
 Gherkio executes the scenario once for every account defined in the credentials file, ensuring consistent role boundaries.

Retry & Polling

In modern microservice and serverless systems, APIs are frequently eventually consistent. Gherkio offers native, step-level retry configurations to pull and assert against resources over time without writing custom loops.


πŸ” Retry Key Reference

KeyTypeDefaultDescriptionExample
attemptsinteger1Maximum number of retry attempts to performattempts: 5
intervalinteger500Wait duration between retries, in millisecondsinterval: 1000
backoffstringconstantBackoff strategy: constant, linear, or exponentialbackoff: exponential
maxDurationstring(none)Hard time cap for the entire retry loop (e.g. 15s, 30s, 1m)maxDuration: 30s
onStatus[]integer(all)Only retry when the response has one of these HTTP status codesonStatus: [404, 202]

Backoff Strategies

Each strategy applies Β±25% random jitter to prevent thundering herd problems on your backend servers:

StrategyFormulaExample (interval: 1000)
constantinterval Γ— jitter1st: ~1000ms, 2nd: ~1000ms, 3rd: ~1000ms
linearinterval Γ— attempt Γ— jitter1st: ~1000ms, 2nd: ~2000ms, 3rd: ~3000ms
exponentialinterval Γ— 2^(attempt-1) Γ— jitter1st: ~1000ms, 2nd: ~2000ms, 3rd: ~4000ms

πŸ“Š Backoff Strategy Timelines (interval: 1000ms)

1. CONSTANT BACKOFF (Fixed interval between retries)
Attempt 1      Attempt 2      Attempt 3      Attempt 4      Attempt 5
   |--------------|--------------|--------------|--------------|--------------|
   [0s]          [1s]           [2s]           [3s]           [4s]           [5s]
                (1.0s)         (1.0s)         (1.0s)         (1.0s)

2. LINEAR BACKOFF (Interval increases linearly: 1s, 2s, 3s, 4s...)
Attempt 1      Attempt 2           Attempt 3                Attempt 4
   |--------------|-------------------|------------------------|-----------------------------|
   [0s]          [1s]                [3s]                     [6s]                          [10s]
                (1.0s)              (2.0s)                   (3.0s)

3. EXPONENTIAL BACKOFF (Interval doubles each attempt: 1s, 2s, 4s, 8s...)
Attempt 1      Attempt 2           Attempt 3                Attempt 4
   |--------------|-------------------|------------------------|-----------------------------------|
   [0s]          [1s]                [3s]                     [7s]                               [15s]
                (1.0s)              (2.0s)                   (4.0s)
```text

#### πŸ”„ Retry Loop Execution Sequence

```mermaid
sequenceDiagram
    autonumber
    participant Runner as Gherkio Runner
    participant API as Target API Endpoint

    Runner->>API: GET /jobs/job-123 (Attempt 1)
    API-->>Runner: 200 OK { status: "pending" }
    Note over Runner: Assertion Failed (status != "completed")<br/>Wait 1000ms (Interval Γ— 1)

    Runner->>API: GET /jobs/job-123 (Attempt 2)
    API-->>Runner: 200 OK { status: "processing" }
    Note over Runner: Assertion Failed (status != "completed")<br/>Wait 2000ms (Exponential Backoff)

    Runner->>API: GET /jobs/job-123 (Attempt 3)
    API-->>Runner: 200 OK { status: "completed" }
    Note over Runner: Assertions Passed! Step Succeeded.
```text

---


## ⚑ Eventually Consistent Polling Pattern

If a background job processes an asynchronous action (e.g. video processing or payment settlement), the status endpoint initially returns `pending`. We want to poll this endpoint until the status is `completed` or our retry limit is exceeded.

```yaml
steps:
  - request:
      method: POST
      url: /jobs
      body: { type: "compress-video" }
    save:
      jobId: body.id

  - request:
      method: GET
      url: /jobs/$jobId
    retry:
      attempts: 10
      interval: 1000              # milliseconds
      backoff: exponential        # 1s, 2s, 4s, 8s... with Β±25% jitter
      maxDuration: 30s            # hard stop after 30 seconds total
    expect:
      status: 200
      body.status: completed      # Retries until this assertion succeeds!
```text

### Retrying on Specific Status Codes

Use `onStatus` to only retry when the server returns a particular status. This avoids wasting retries on unrecoverable errors (like `400` or `403`):

```yaml
  - request:
      method: POST
      url: /v1/tickets
      body:
        issue_tag_id: $issueTags[${randomInt(0,9)}].id
    retry:
      attempts: 5
      interval: 300
      backoff: constant
      onStatus: [409]             # Only retry on conflict β€” other errors fail immediately
    expect:
      status: 201
```text

---

### How Retries Work Under the Hood:
1. Gherkio executes the HTTP request.
2. It validates the response against the `expect` block.
3. If all assertions succeed, the step completes immediately.
4. If **any** assertion fails, Gherkio checks `onStatus` (if set) β€” if the status code isn't in the list, it stops immediately without retrying.
5. Otherwise, it waits for `interval` ms (adjusted by the backoff strategy with jitter), re-injects fresh built-in variables (new `$uuid`, `$randomInt`, etc.), re-interpolates the request, and re-executes.
6. If `maxDuration` is set and the total elapsed time exceeds it, the retry loop aborts.
7. If the maximum `attempts` threshold is reached and assertions are still failing, the step fails, halting the scenario.

CLI Command & Reference

The Gherkio Command Line Interface (CLI) is the central orchestration tool for validating project settings, starting testing suites, generating reports, and exporting cURL workflows.


πŸƒ Execution Rules & File Resolution

When executing gherkio run <target>, Gherkio resolves the target pathway under the following rules:

  1. Directories: If <target> is a folder, Gherkio recursively scans all child files ending in .yaml or .yml under the folder, executing them sequentially or concurrently.
  2. Single Files: If <target> points directly to a YAML file, Gherkio executes only that single file.
  3. Workspace Discovery: Gherkio looks for a .gherkio/config.yaml file in the current working directory, then bubbles up parent directories until one is found. If no .gherkio workspace folder is detected, Gherkio errors and prompts you to run gherkio init.

🚦 Exit Status Codes

Gherkio follows standard POSIX process exit boundaries to ensure perfect compatibility with CI/CD platforms (GitHub Actions, GitLab CI, Jenkins):

  • 0: Success. All executed test scenarios passed, validation checks returned no warnings, or conversions completed successfully.
  • 1: Failure / Error. At least one step failed validation, an API returned a status/body mismatch, a syntax error was detected in your YAML files, or a microservice connection timed out.

πŸ–₯️ Print Output Modes

Modify how Gherkio logs information to terminal windows during execution using stdout flags:

  • Default (Clean): Prints structured step checks, colorized status ticks (βœ” / ❌), and execution durations per step.
  • Verbose (--verbose / -v): Outputs the exact request URL, method, serialized payload body, headers, along with the full response body/headers from the target host. Dynamic credentials and keys are automatically masked.
  • Quiet (--quiet / -q): Suppresses standard output entirely, returning only the final summary block and process exit status code.

πŸ“š Commands Map

CommandLinkDescription
gherkiogherkioRoot command showing general usage and flags.
gherkio initgherkio initScaffolds a new Gherkio workspace .gherkio/.
gherkio rungherkio runExecutes declarative test scenarios locally or in CI.
gherkio convertgherkio convertTranslates raw cURL statements ⇄ Gherkio YAML step formats.
gherkio validategherkio validatePerforms static analysis checking on syntax, credentials, and schemas.
gherkio schemagherkio schemaAuto-generates Draft-07 JSON Schema for editor auto-completions.
gherkio mcpgherkio mcpLaunches stdio Model Context Protocol (MCP) JSON-RPC communication.

gherkio

Gherkio is a testing and validation framework

Synopsis

Gherkio is a CLI tool for managing test execution, validation schemas, and generating reports.

Options

  -h, --help   help for gherkio

SEE ALSO

gherkio init

Initialize a new Gherkio project structure

Synopsis

Creates the .gherkio directory in the current working directory with the default project structure including config, environments, tests, reports, and schemas.

gherkio init [flags]

Options

  -h, --help   help for init

SEE ALSO

  • gherkio - Gherkio is a testing and validation framework

gherkio run

Execute a test scenario

Synopsis

Executes a Gherkio test YAML file and displays the results.

If no test file is provided, all tests in .gherkio/tests/ are executed. If a directory is provided, all tests in that directory are executed.

The test file path is resolved in the following order:

  1. As provided (relative to current working directory)
  2. Relative to .gherkio/tests/ directory

Example:

  gherkio run                    # Run all tests
  gherkio run tests/login.yaml
  gherkio run restful-api/       # Run all tests in restful-api/ directory
  gherkio run login.yaml --env staging
  gherkio run login.yaml --verbose
  gherkio run login.yaml --report html
  gherkio run login.yaml --env staging --account alpha
  gherkio run login.yaml --env staging --all-accounts
  gherkio run login.yaml --section steps        # Run all steps in 'steps' section only
  gherkio run login.yaml --section setup        # Run all setup steps only
  gherkio run login.yaml --section teardown     # Run all teardown steps only

gherkio run [test-file] [flags]

Options

      --account string            Account name from credentials file (e.g. alpha, beta)
      --all-accounts              Run tests against all accounts in the credentials file
      --dry-run                   Preview test execution without making HTTP requests
  -e, --env string                Environment to use (e.g. local, staging, production) (default "local")
  -h, --help                      help for run
      --iterations-per-user int   Number of sequential workflow executions performed by each virtual user (default 1)
      --line int                  Line number containing the step to run (default -1)
  -p, --parallel int              Number of tests to run in parallel (0 = auto-detect CPU count)
      --report string             Generate a report (format: html, json, or html,json)
      --report-raw                Skip sensitive data masking in JSON reports (cURL commands remain masked)
      --request-delay string      Wait before each HTTP request (defaults: 50ms/file, 100ms/directory; bare numbers are milliseconds)
      --section string            Section to run (setup, steps, teardown). When used without --step or --line, runs ALL steps in that section only.
      --step int                  Index of the step to run (0-indexed) (default -1)
  -t, --tag strings               Filter tests by tags (AND logic: test must have ALL specified tags)
  -u, --until string              Execute steps until a specific target, e.g. 'steps:1' or '2'
  -v, --verbose                   Show full request/response payloads
      --virtual-users int         Number of isolated users executing the same test concurrently (default 1)

SEE ALSO

  • gherkio - Gherkio is a testing and validation framework

gherkio convert

Convert cURL to Gherkio YAML or Gherkio YAML to cURL

Synopsis

Bidirectionally converts between standard cURL commands and Gherkio DSL YAML.

cURL to YAML (Default)

Provide the cURL command as an argument, pipe it via stdin, or read from a file using --file. Automatically resolves environment base URLs and converts JSON bodies into native YAML.

YAML to cURL (Reverse)

Use the --reverse (-r) flag and provide the Gherkio test YAML file path. Optionally extract a specific step with --step.

Examples:

  # Convert cURL from argument
  gherkio convert "curl -X POST https://api.com/login -d '{\"user\":\"x\"}'"
  
  # Convert cURL from file to a YAML file
  gherkio convert --file curl.txt --output tests/login.yaml --scenario "Login Flow"

  # Convert YAML step back to cURL
  gherkio convert --reverse tests/login.yaml --step 0 --env staging


gherkio convert [command/test-file] [flags]

Options

      --account string    Account name from credentials file to interpolate variables
  -e, --env string        Environment to resolve base URLs (e.g. local, staging) (default "local")
  -f, --file string       Read cURL command from a file
  -h, --help              help for convert
  -o, --output string     Write output directly to a file
  -r, --reverse           Convert Gherkio step/YAML to cURL command(s)
  -s, --scenario string   Custom scenario name for the generated YAML (default "untitled")
      --step int          Index of the step to convert in reverse mode (0-indexed) (default -1)
      --step-only         Output just the step block without scenario wrapper

SEE ALSO

  • gherkio - Gherkio is a testing and validation framework

gherkio validate

Validate a test file without executing HTTP requests

Synopsis

Performs static analysis on Gherkio test YAML files without making HTTP calls.

Validates:

  • YAML syntax and structure
  • Scenario structure (required fields)
  • Request method validity
  • Variable references ($var, $accounts.*, built-in generators)
  • Account references from credentials
  • Schema references from .gherkio/schemas/
  • use file existence
  • Retry configuration

Example:

  gherkio validate                           # Validate all test files
  gherkio validate login.yaml                # Validate single file
  gherkio validate --verbose                 # Show detailed results
  gherkio validate --env staging             # Validate with staging credentials

gherkio validate [test-file] [flags]

Options

  -e, --env string   Environment for credentials validation (default "local")
  -h, --help         help for validate
  -v, --verbose      Show detailed validation results

SEE ALSO

  • gherkio - Gherkio is a testing and validation framework

gherkio schema

Generate JSON Schema for Gherkio YAML files

Synopsis

Generates a JSON Schema for Gherkio YAML files. This schema can be used in editors (like VSCode, Neovim) for autocomplete and validation.

Usage: gherkio schema # Generate all schemas (default) gherkio schema --type test # Generate only test file schema gherkio schema --type config # Generate only config schema gherkio schema --type environment # Generate only environment schema gherkio schema --type credentials # Generate only credentials schema gherkio schema --type schema-definition # Generate only schema definition schema gherkio schema --list # List available schema types

Output: By default, outputs all schemas in a single JSON file keyed by type. Use --type to output a specific schema only.

gherkio schema [flags]

Options

  -h, --help          help for schema
      --list          List available schema types
      --type string   Schema type: test, config, environment, credentials, schema-definition

SEE ALSO

  • gherkio - Gherkio is a testing and validation framework

gherkio mcp

Start Gherkio Model Context Protocol (MCP) server over stdio

Synopsis

Starts the Gherkio Model Context Protocol (MCP) server over standard I/O (stdio). This allows Gherkio capabilities (globbing tests, reading scenarios, running tests, resolving environments) to be programmatically consumed by LLMs and agentic AI clients (e.g. Claude Desktop, VS Code extensions).

Stdout is strictly reserved for compliant JSON-RPC 2.0 frames. Diagnostic logs are output to Stderr.

gherkio mcp [flags]

Options

  -h, --help   help for mcp

SEE ALSO

  • gherkio - Gherkio is a testing and validation framework

Shell Completions

Accelerate your terminal workflow by enabling Gherkio autocomplete support. Gherkio automatically outputs autocomplete scripts for Bash, Zsh, Fish, and PowerShell shells.

[!NOTE] Secure UNIX Design: For security and transparency, Gherkio never mutates your shell profiles or system files automatically. The gherkio completion command strictly outputs the generated autocompletion script directly to standard output (stdout). Sourcing (source) or redirecting (>) the script gives you complete, explicit control over your terminal environment changes.


⚑ Generating Autocompletes

Generate the completion script matching your active shell using the gherkio completion command:

# Bash
gherkio completion bash

# Zsh
gherkio completion zsh

# Fish
gherkio completion fish

# PowerShell
gherkio completion powershell
```text

---

## πŸ”Œ Installing Completions Permanently

### 1. Zsh (macOS & Linux)

If shell completions are not already enabled in your environment, add this to your `~/.zshrc`:
```zsh
autoload -U compinit; compinit
```text

Generate the Gherkio completion script and place it inside a directory listed in your `$fpath`:
```bash
gherkio completion zsh > "${fpath[1]}/_gherkio"
```text

Alternatively, source it directly in your `~/.zshrc`:
```zsh
source <(gherkio completion zsh)
```text

---

### 2. Bash (Linux)

Ensure `bash-completion` is installed using your package manager (e.g. `apt-get install bash-completion` or `yum install bash-completion`).

Write the completion script to the system global configuration:
```bash
gherkio completion bash | sudo tee /etc/bash_completion.d/gherkio > /dev/null
```text

---

### 3. Fish

Write the completion script directly to your local user config folder:
```bash
gherkio completion fish > ~/.config/fish/completions/gherkio.fish
```text

---

## βš™οΈ Options

### Disable Descriptions
By default, Gherkio completions include flag and subcommand descriptions. If you prefer a faster, minimalistic autocomplete output, you can disable descriptions using the `--no-descriptions` flag:
```bash
gherkio completion zsh --no-descriptions > "${fpath[1]}/_gherkio"
```text

Integration Test Recipes

Welcome to the Gherkio Recipe Book! This section compiles production-grade design patterns and actionable code templates to solve complex, real-world integration testing challenges.

Rather than simple syntax references, these recipes show how to orchestrate high-performance, resilient, and secure test suites against live microservices.


πŸ—ΊοΈ Recipe Catalogue

Select a recipe below to dive into real-world code templates and design patterns:

RecipeCore Use CasePrimary Focus
πŸ”‘ Authentication & Token RecyclingDynamic login, JWT claim extraction, and recycling bearer tokens.Security & Identity
πŸ“¦ Bulk Upload & CollectionsSubmitting JSON sequences and asserting conditions across collections (all(), count()).Data Pipelines & Lists
❌ Negative & Error ValidationVerifying business constraints, bad payloads, and detailed API error shapes.Contract Resilience
πŸ”„ Async Polling & ConsistencyHandling eventual consistency and long-running tasks using dynamic backoff retries.Event-Driven Systems
πŸ›‘οΈ Multi-Account Role BoundariesAsserting RBAC limitations and security boundaries across different users.Authorization Security
πŸ“ Contract Verification with SchemasValidating complex JSON bodies against centralized reusable schema rules.Structural Testing
⚑ Parallel Execution SafetyOrchestrating concurrent test runs securely using dynamic thread-safe parameters.Performance & Speed
πŸ—οΈ Database Seeding & Data StrategiesSetting up and tearing down transient data states to guarantee test isolation.State Management

πŸ’‘ How to Use These Recipes

  1. Copy & Adapt: Each recipe includes a fully valid Gherkio DSL YAML snippet. Copy it into your .gherkio/tests/ directory and replace the mock URLs with your system's endpoints.
  2. Utilize Environments: Combine these recipes with Gherkio's environment configurations (.gherkio/environments/) to run the exact same tests across local, staging, and production targets without code modifications.
  3. Integrate with CI/CD: Combine Parallel Execution and Data Management strategies to execute hundreds of validation scenarios inside your GitHub Actions, GitLab CI, or Jenkins pipelines in seconds.

Authentication & JWT Token Recycling Flow

This recipe demonstrates how to authenticate a user dynamically, extract their access token, inspect decoded JWT claims, and inject the token into all subsequent request headers.


πŸ“ The Scenario

scenario: Authenticated User Flow
tags:
  - security
  - authentication

steps:
  # Step 1: Login & obtain access token
  - request:
      method: POST
      url: https://api.my-domain.com/v1/auth/login
      headers:
        Content-Type: application/json
      body:
        username: $accounts.admin.username
        password: $accounts.admin.password
    expect:
      status: 200
      body.token: exists
      body.email: email
      
      # Assert decoded claims from response.token (accessToken)
      jwt.role: super-admin
      jwt.sub: uuid
      jwt.isActive: true
    save:
      # Extract token to $authToken variable
      authToken: body.token

  # Step 2: Use the dynamic token to query secure endpoints
  - request:
      method: GET
      url: https://api.my-domain.com/v1/secure/profile
      headers:
        Authorization: "Bearer ${authToken}"
    expect:
      status: 200
      body.role: super-admin
```text

---

## πŸ’‘ Key Design Patterns Used

1. **Direct Credentials Extraction**: Sourced securely from environmental configurations via `$accounts.admin.username` to keep secrets out of version control.
2. **Automatic JWT Assertion**: The `jwt.<claim>` keyword triggers standard JWT claim extraction under the hood on response fields matching `token` or `accessToken`.
3. **Dynamic Variable Recycling**: Using the `save:` block to extract the token, then injecting it cleanly as `Authorization: "Bearer ${authToken}"` in next step headers.

Bulk Upload & Collection Validation

This recipe demonstrates how to submit a list collection in a single POST request and assert properties across the returned array of created items.


πŸ“ The Scenario

scenario: Bulk Item Upload Validation
tags:
  - bulk
  - catalog

steps:
  # Step 1: Submit bulk items list
  - request:
      method: POST
      url: /v1/products/bulk
      headers:
        Content-Type: application/json
      body:
        - sku: "LAP-CORE-I5"
          name: "Developer Laptop v1"
          price: 1200.00
        - sku: "LAP-CORE-I7"
          name: "Developer Laptop v2"
          price: 1800.00
    expect:
      status: 201
      body: array
      
      # Enforce array length is exactly 2
      count(body): 2
      
      # Validate every item in response matches criteria
      all(body.status): "active"
      all(body.sku): startsWith "LAP-"
      
      # Assert specific items in the list using indices
      body[0].sku: "LAP-CORE-I5"
      body[1].price: 1800.00
```text

---

## πŸ’‘ Key Design Patterns Used

1. **Declarative Arrays**: JSON arrays are mapped directly as standard YAML sequences under the `body` field.
2. **Dynamic Collection Assertions**: `count(body): 2` evaluates array boundaries without looping scripts.
3. **High-Level Array Matchers**: The `all(<path>): <matcher>` syntax validates fields on every member of an array.

Negative Testing & Error Validation

API contracts should explicitly enforce business constraints and output clean error bodies. Negative tests assert that requests containing bad payloads, invalid formatting, or missing authentication are successfully rejected by the server.


πŸ“ The Scenario

scenario: Negative Profile Creation Checks
tags:
  - active
  - negative

steps:
  # Step 1: Attempt to register with a duplicate email
  - request:
      method: POST
      url: /v1/users
      body:
        email: "existing-user@my-domain.com"
        password: "securepassword"
    expect:
      status: 409
      body.error: "DuplicateEmail"
      body.message: contains "already exists"

  # Step 2: Attempt to submit a weak password
  - request:
      method: POST
      url: /v1/users
      body:
        email: $randomEmail
        password: "123"
    expect:
      status: 400
      body.code: "InvalidPassword"
      body.details.password[0]: contains "too short"

  # Step 3: Attempt to fetch billing without credentials
  - request:
      method: GET
      url: /v1/billing
    expect:
      status: 401
      body.error: "Unauthorized"
```text

---

## πŸ’‘ Key Design Patterns Used

1. **Assert Non-2xx Responses**: In Gherkio, steps expecting `4xx` or `5xx` statuses are fully expected and marked as green when the response matches.
2. **Detailed Error Inspections**: Using `contains` assertions on `body.details` arrays to ensure validation packages output helpful user guidance.

Async Polling & Eventual Consistency

Verify asynchronous operations (such as data pipeline synchronization or long-running audio processing jobs) that require time to complete.


πŸ“ The Scenario

scenario: Async Order Sync Validation
tags:
  - async
  - orders

steps:
  # Step 1: Submit long-running order sync task
  - request:
      method: POST
      url: /v1/orders/sync
    expect:
      status: 202
      body.jobId: exists
    save:
      syncJobId: body.jobId

  # Step 2: Poll status endpoint with backoff until completed
  - request:
      method: GET
      url: /v1/orders/sync/$syncJobId
    retry:
      attempts: 8
      interval: 1500                 # Interval in milliseconds between retries
      backoff: exponential           # Backoff strategy: "linear" or "exponential"
      maxDuration: 10s               # Maximum total duration allowed for the retry loop
    expect:
      status: 200
      body.status: "success"
      body.processedCount: gt 0
      body.errors: empty
```text

---

## πŸ“Š Polling & Backoff Execution Visual Illustration

When Gherkio executes Step 2 with `interval: 1500` and `backoff: exponential`, the runner dynamically adjusts the wait duration between consecutive requests:

```text
Attempt 1 (0s) ──[ Wait 1.5s ]──> Attempt 2 (1.5s) ──[ Wait 3.0s ]──> Attempt 3 (4.5s) ──[ Wait 6.0s ]──> (Passes or hits maxDuration 10s)
```text

```mermaid
sequenceDiagram
    autonumber
    participant Runner as Gherkio Test Engine
    participant API as Async Order API

    Runner->>API: POST /v1/orders/sync (Submit Task)
    API-->>Runner: 202 Accepted { jobId: "sync-8841" }
    Note over Runner: Saves syncJobId = "sync-8841"

    loop Retry Polling Loop (Attempts: 8, maxDuration: 10s)
        Runner->>API: GET /v1/orders/sync/sync-8841 (Attempt 1)
        API-->>Runner: 200 OK { status: "pending", processedCount: 0 }
        Note over Runner: Assertion Failed (status != "success")<br/>Wait 1.5s (Base Interval)

        Runner->>API: GET /v1/orders/sync/sync-8841 (Attempt 2)
        API-->>Runner: 200 OK { status: "processing", processedCount: 15 }
        Note over Runner: Assertion Failed (status != "success")<br/>Wait 3.0s (Exponential Backoff: 1.5s Γ— 2)

        Runner->>API: GET /v1/orders/sync/sync-8841 (Attempt 3)
        API-->>Runner: 200 OK { status: "success", processedCount: 150, errors: [] }
        Note over Runner: All Assertions Passed! Step Succeeded.
    end
```text

---

## πŸ’‘ Key Design Patterns Used

1. **Wait State Separation**: Spares resources by avoiding hardcoded sleep scripts, utilizing declarative interval timers.
2. **Backoff Escalation**: Exponentially increases interval duration (1.5s βž” 3.0s βž” 6.0s), giving slow-settling background tasks wider windows while finishing fast for quick jobs.
3. **Step Halting**: If the status remains non-successful after 8 attempts (or 10 seconds total), Gherkio halts the scenario and records a failure diagnostic snapshot.

Multi-Account Role Boundary Testing

Ensure your APIs isolate resources according to client roles. This recipe shows how to write a scenario validating role limits across multiple accounts.


πŸ“ The Scenario

scenario: Role Boundary Validation
tags:
  - security
  - permissions

steps:
  # Step 1: Login as administrator to create resource
  - request:
      method: POST
      url: /v1/restricted-resource
      headers:
        Authorization: "Bearer ${adminToken}"   # adminToken saved in setup block
      body:
        title: "Confidential Strategy Document"
    expect:
      status: 201
    save:
      resourceId: body.id

  # Step 2: Attempt to read resource using viewer token
  - request:
      method: GET
      url: /v1/restricted-resource/$resourceId
      headers:
        Authorization: "Bearer ${viewerToken}"  # viewerToken saved in setup block
    expect:
      status: 200
      body.title: exists

  # Step 3: Attempt to delete resource as viewer (should fail)
  - request:
      method: DELETE
      url: /v1/restricted-resource/$resourceId
      headers:
        Authorization: "Bearer ${viewerToken}"
    expect:
      status: 403
      body.error: "PermissionDenied"
```text

---

## πŸ’‘ Key Design Patterns Used

1. **Setup Token Caching**: Authenticates once inside a `setup` block using credentials defined under `.gherkio/credentials/staging.yaml` for both `admin` and `viewer` accounts.
2. **Access Isolation Assertion**: Direct verification of resource endpoints returning `403 Forbidden` for restricted action blocks while successfully returning `200 OK` for authorized actions.

Contract Verification with Schemas

Validate complex microservice structures against strict JSON Schema contracts with a single assertion line.


πŸ“ The Scenario

scenario: Verify User List Contract
tags:
  - contract
  - users

steps:
  - request:
      method: GET
      url: /v1/users
      headers:
        Authorization: "Bearer ${adminToken}"
    expect:
      status: 200
      
      # Validates the response body against the target schema
      schema: users/list-response
```text

---

## πŸ“ The Schema File: `.gherkio/schemas/users/list-response.yaml`

Define your structural contracts inside `.gherkio/schemas/`:

```yaml
type: object
required:
  - users
  - pageInfo
properties:
  users:
    type: array
    items:
      type: object
      required:
        - id
        - name
        - email
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        email:
          type: string
          format: email
  pageInfo:
    type: object
    required:
      - page
      - totalPages
    properties:
      page:
        type: integer
      totalPages:
        type: integer
```text

---

## πŸ’‘ Key Design Patterns Used

1. **Zero-Boilerplate Checks**: Validating deep collection trees, formats, and required field lists with one simple `schema:` declaration.
2. **Schema Reuse**: The schema file can be referenced by multiple scenarios, ensuring changes to contracts are updated in one central location.

Parallel Execution Best Practices

To accelerate CI feedback loops, Gherkio supports multi-threaded, parallel test executions. However, running integration tests concurrently requires careful state separation.


⚑ Running Parallel Tests from the CLI

Run a directory of tests with custom concurrency levels using the --parallel (or -p) flag:

# Run tests with 4 parallel execution threads
gherkio run .gherkio/tests/ -p 4
```text

`--parallel 4` means up to four **different test files** run at once. It is intended to speed up
a directory or suite run; it is not a virtual-user load model.

To repeat the **same workflow** with concurrent virtual users, use:

```bash
gherkio run .gherkio/tests/checkout.yaml \
  --virtual-users 2 \
  --iterations-per-user 3 \
  --report html
```text

Here, two users run concurrently and each user performs three sequential iterations, for six
workflow executions in total. Each user's variables are isolated from the other user. The HTML
report preserves the VU/iteration hierarchy and lets you expand or collapse each workflow.

---

## ⚠️ The Concurrent Mutation Problem

If multiple tests manipulate the exact same shared DB entity, race conditions will occur:

```mermaid
sequenceDiagram
    participant T1 as Test 1 (Edit Laptop Price)
    participant DB as Shared Database
    participant T2 as Test 2 (Assert Laptop Details)

    T1->>DB: UPDATE laptop WHERE id = 1 SET price = 10.00
    T2->>DB: GET laptop WHERE id = 1
    Note over T2: Asserts price == 12.00 (expected original)
    DB-->>T2: returns price = 10.00
    Note over T2: ❌ Test Failed!
```text

---

## πŸ›‘οΈ Key Safety Strategies

### 1. Isolated Dynamic Data
Avoid hardcoding entity identifiers or emails. Utilize Gherkio's dynamic generators to create unique resources per test thread:

```yaml
steps:
  - request:
      method: POST
      url: /v1/users
      body:
        email: $randomEmail         # Unique email per thread!
        name: "Test User"
```text

### 2. Microservice Routing Isolation
If your microservices support multi-tenant isolation, inject tenancy headers:

```yaml
steps:
  - request:
      method: GET
      url: /v1/products
      headers:
        X-Tenant-ID: "tenant-${uuid}"  # Dynamic tenant separation
```text

Database Seeding & Data Strategies

Ensure tests run in complete isolation and leave zero residue by designing robust setup and teardown architectures.


In this model, every scenario creates its own resource during setup and deletes it during teardown.

scenario: Transient Resource Lifecycle
setup:
  - request:
      method: POST
      url: /items
      body: { name: "Temp Book" }
    save:
      bookId: body.id

steps:
  - request:
      method: PUT
      url: /items/$bookId
      body: { name: "Updated Book" }
    expect:
      status: 200

teardown:
  - request:
      method: DELETE
      url: /items/$bookId
    expect:
      status: 200
```text
- **Pros**: Zero DB pollution, highly predictable, immune to concurrent execution conflicts.
- **Cons**: Adds additional HTTP calls per test scenario.

---

## πŸ—οΈ Strategy B: Bulk Environmental Seeding

If tests require extensive reference lists (e.g. currency maps, country codes, permissions catalogues), create an environmental seeding script.

Map this initializer test at the very beginning of your pipeline:

```yaml
# tests/00-seed.yaml
scenario: Seed Database
steps:
  - request:
      method: POST
      url: /admin/seed
      headers:
        X-Seed-Key: $accounts.admin.password
    expect:
      status: 200
```text

Configure your test orchestrator to execute this scenario sequentially first, before running the remaining tests in parallel:

```bash
# Seed sequentially
gherkio run .gherkio/tests/00-seed.yaml

# Run tests in parallel
gherkio run .gherkio/tests/ -p 4
```text
- **Pros**: Keeps individual scenario scripts small and extremely fast.
- **Cons**: Shared state must be read-only during parallel steps to prevent test interference.

Model Context Protocol (MCP) Server

πŸ”΄ Expert & Platform Engineering Guide β€” Gherkio features native, built-in support for the Model Context Protocol (MCP), a standard JSON-RPC protocol initiated by Anthropic that allows AI models (like Gemini, Claude, or GPT) to interact securely and productively with local tools and files.

By running an MCP server, Gherkio enables AI agents to plan, write, validate, execute, and debug integration tests entirely in the background, transforming Gherkio from a simple CLI tool into an autonomous AI QA coworker.


🧭 Why Gherkio Integrates MCP

Without MCP, an AI assistant operates blindly inside your project:

  • It doesn't know Gherkio's exact DSL grammar, available matchers, or configuration layouts, leading to syntax hallucinations.
  • It cannot execute HTTP calls to verify if a test passes or fails.
  • It cannot read environment and credential configurations to perform role-based checking.

With Gherkio's MCP server active, the AI assistant receives:

  1. Dynamic Tools: Can initialize workspaces, create/update test files, run tests in dry-run or real execution mode, and perform static analysis checks.
  2. Native Resources: Read-only access to Gherkio's DSL specification, autocomplete JSON schemas, and canonical syntax examples.

πŸ”Œ Architecture & Connection Transport

Gherkio's MCP server uses a standardized stdio (standard input/output) connection transport.

When your editor (e.g. Cursor, VS Code, Zed) or AI desktop client (Claude Desktop) starts Gherkio, it launches a persistent background process:

graph TD
    subgraph HostClient ["AI Agent Client (Claude Desktop / Cursor IDE / Zed)"]
        A[AI Agent Engine]
    end
    subgraph GherkioMCPServer ["Gherkio MCP Process"]
        B[gherkio mcp daemon]
        C[Workspace Controller]
        D[Runner Engine]
    end
    subgraph ProjectWorkspace ["Filesystem & Network"]
        E[".gherkio/tests/ & .gherkio/schemas/"]
        F[Live HTTP API Endpoints]
    end

    A <-->|stdio JSON-RPC| B
    B --> C
    B --> D
    C <-->|Read / Write| E
    D <-->|Execute Tests| F
```text

---

## πŸ› οΈ MCP Tools & Capabilities

The Gherkio MCP server exposes 25+ structured tools to the AI assistant:

| Tool Category | Key Tools | Purpose |
| :--- | :--- | :--- |
| **Workspace & Config** | `mcp_gherkio_init_project`, `get_config`, `get_environment_context` | Discover active project structure, target environments, and credentials. |
| **Scenario Management** | `create_test`, `read_test`, `update_test`, `delete_test`, `list_tests` | Author and edit YAML test files in `.gherkio/tests/`. |
| **cURL Conversion** | `convert_curl_to_yaml`, `convert_yaml_to_curl` | Convert raw cURL terminal commands into Gherkio DSL scenarios. |
| **Validation & Dry-Run** | `validate_test`, `validate_workspace` | Perform static analysis to detect invalid matchers or missing variables. |
| **Execution Engine** | `run_test` (with `dryRun=true` or isolated `step` execution) | Execute scenarios in real-time and return detailed step traces. |

---

## ⚑ Client Integrations & Setup Guides

Connect Gherkio directly to your preferred AI developer tool:
* πŸ› οΈ **[MCP Setup Guide](setup.md)**: Configuration snippets for Claude Desktop, Cursor IDE, VS Code, and Zed.
* 🧰 **[Tools Reference](tools.md)**: Exhaustive JSON-RPC payload and tool definitions.
* πŸ“š **[Resources Reference](resources.md)**: Accessing static DSL specs and autocomplete schemas.
* πŸ€– **[LLM & AI Workflows](llm-integration.md)**: Prompting patterns for automated test generation and self-healing test runs.

MCP Server Setup & Editor Integrations

Learn how to connect Gherkio's Model Context Protocol (MCP) server to your favorite AI assistant clients, IDEs, and environments.


πŸš€ Connecting to Clients

You can connect Gherkio's Model Context Protocol (MCP) server to your client using two execution paths:

  1. Binary Mode (Standard): Invokes the locally installed gherkio mcp executable.
  2. Zero-Install Mode (Go Run): Dynamically fetches and runs the server using standard Go remote module execution:
    go run github.com/muhfaris/gherkio@v0.1.0-alpha.9 mcp
    

πŸ› οΈ Configuration Blocks

1. Claude Desktop Setup

To enable Claude Desktop to invoke Gherkio's testing suite, add the server command to your local Claude configuration file:

  • Mac Path: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows Path: %APPDATA%\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "gherkio": {
      "command": "gherkio",
      "args": ["mcp"],
      "env": {
        "PATH": "/usr/local/bin:/opt/homebrew/bin:/usr/bin"
      }
    }
  }
}
```text

#### Option B: Zero-Install (Go Run)
```json
{
  "mcpServers": {
    "gherkio": {
      "command": "go",
      "args": ["run", "github.com/muhfaris/gherkio@v0.1.0-alpha.9", "mcp"],
      "env": {
        "PATH": "/usr/local/bin:/opt/homebrew/bin:/usr/bin"
      }
    }
  }
}
```text

---

### 2. Cursor IDE Integration
To use Gherkio dynamically within Cursor's Composer or Chat:

1. Open **Cursor Settings** βž” **Features** βž” **MCP**.
2. Click **+ Add New MCP Server**.
3. Configure the following fields:

#### Option A: Local Binary (Recommended for speed)
- **Name**: `gherkio`
- **Type**: `stdio`
- **Command**: `/absolute/path/to/gherkio mcp` (e.g. `/home/username/go/bin/gherkio mcp`)

#### Option B: Zero-Install (Go Run)
- **Name**: `gherkio`
- **Type**: `stdio`
- **Command**: `go run github.com/muhfaris/gherkio@v0.1.0-alpha.9 mcp`

Click **Save**. The status dot should immediately turn green.

---

### 3. VS Code Integration (Roo Code / Continue)
If you use **Roo Code (Roo Cline)** or **Continue** extensions in VS Code, add Gherkio to your custom MCP servers list:

#### Option A: Local Binary (Recommended for speed)
```json
{
  "mcpServers": {
    "gherkio": {
      "command": "gherkio",
      "args": ["mcp"]
    }
  }
}
```text

#### Option B: Zero-Install (Go Run)
```json
{
  "mcpServers": {
    "gherkio": {
      "command": "go",
      "args": ["run", "github.com/muhfaris/gherkio@v0.1.0-alpha.9", "mcp"]
    }
  }
}
```text

---

### 4. Zed Editor Configuration
To activate Gherkio's MCP capabilities within the Zed editor's built-in assistant, edit your local `~/.config/zed/settings.json` file:

#### Option A: Local Binary (Recommended for speed)
```json
{
  "context_servers": {
    "gherkio": {
      "command": "gherkio",
      "args": ["mcp"]
    }
  }
}
```text

#### Option B: Zero-Install (Go Run)
```json
{
  "context_servers": {
    "gherkio": {
      "command": "go",
      "args": ["run", "github.com/muhfaris/gherkio@v0.1.0-alpha.9", "mcp"]
    }
  }
}
```text


---

## βœ… Troubleshooting

### Red / Offline Connection State
If your client fails to connect or Gherkio's status dot is red:
1. **Check PATH**: Ensure the directory where `gherkio` is installed (e.g. `/home/username/go/bin` or `/usr/local/bin`) is present in the `PATH` environment variable defined in the configuration block.
2. **Execute manually**: Run `gherkio mcp` in your system shell. It should start silently and wait for stdin. Press `Ctrl+C` to terminate it. If it fails with `command not found`, re-run the `go install` procedure.

MCP Tools Reference

Gherkio's Model Context Protocol (MCP) server exposes a rich suite of 21 tools that allow AI models (such as Gemini, Claude, or GPT) to interact natively with Gherkio test suites, config files, environments, credentials, schemas, and runners.


πŸ› οΈ Project & Workspace Tools

1. init_project

Initialize a new Gherkio project structure with default configuration, schemas, environments, and template examples in the specified directory.

  • Arguments:
    • path (string, optional): Subdirectory or absolute path to initialize the Gherkio project. Defaults to the current working directory.
  • Example call:
    {
      "name": "init_project",
      "arguments": { "path": "my-api-tests" }
    }
    

2. get_project_info

Retrieve active Gherkio project metadata, workspace root, and resolved directory structures.

  • Arguments: None (returns project directory paths and version).

πŸ§ͺ Scenario Management

3. list_tests

Scan and retrieve a list of all Gherkio test scenarios (.yaml files) inside .gherkio/tests/ with their step counts and scenario names.

  • Arguments: None

4. read_test

Read the full raw Gherkio YAML test scenario contents of a given test file path.

  • Arguments:
    • path (string, required): Absolute or relative path to the Gherkio scenario file.

5. create_test

Create a new Gherkio test scenario file in the project workspace under .gherkio/tests/. Statically validates syntax and schema references before writing to disk.

  • Arguments:
    • path (string, required): Relative path under .gherkio/tests/ to save the file (e.g. auth/login.yaml).
    • scenarioYaml (string, required): Raw YAML string of the Gherkio test scenario. Rule: Always prefix saved/referenced dynamic variables with the sequential step number (e.g., 1-authToken for step 1, 2-resourceId for step 2) for strict step-level traceability.

6. update_test

Overwrite an existing Gherkio test file scenario in the project workspace, writing a backup first.

  • Arguments:
    • path (string, required): Absolute or relative path to the Gherkio scenario file to update.
    • scenarioYaml (string, required): Updated Gherkio test scenario YAML content. Rule: Always prefix saved/referenced dynamic variables with the sequential step number (e.g., 1-authToken for step 1, 2-resourceId for step 2) for strict step-level traceability.

7. delete_test

Permanently delete a Gherkio test file from the workspace.

  • Arguments:
    • path (string, required): Absolute or relative path to the Gherkio scenario file to delete.

πŸƒ Execution & Validation

8. run_test

Execute a Gherkio test scenario (fully or step-isolated) and receive highly detailed, structured JSON results containing request headers/bodies, response details, timing budgets, and assertion outcomes.

  • Arguments:
    • path (string, required): Absolute or relative path to the test scenario to run.
    • env (string, optional): Environment to execute against (defaults to project config default or local).
    • account (string, optional): Optional account name from environments credentials to use for dynamic variable injection.
    • step (integer, optional): Step index to execute in isolation (0-indexed). Defaults to -1 (run all).
    • section (string, optional): Section to run (setup, steps, teardown).
    • dryRun (boolean, optional): Preview test execution without making HTTP requests.
    • verbose (boolean, optional): Show full request/response payloads. Defaults to true.
    • failFast (boolean, optional): Stop executing remaining steps when a step fails. Defaults to false.

9. validate_test

Validate a Gherkio scenario's syntax, structure, request methods, and schema/composition references without executing it.

  • Arguments:
    • scenarioYaml (string, required): Gherkio YAML test scenario string to validate.

10. validate_workspace

Perform full, multi-file static analysis on all workspace tests, validating syntax, variable scopes, credentials, schema, and use: scenario references.

  • Arguments:
    • path (string, optional): Optional path to a specific test file.
    • env (string, optional): Optional environment (e.g. staging) to validate credential credentials schemas.

πŸ”‘ Environment & Credentials Management

11. list_environments

List all configured Gherkio test environments along with their baseUrl and overriding services.

  • Arguments: None

12. create_environment

Create a new environment config file in .gherkio/environments/<name>.yaml.

  • Arguments:
    • name (string, required): Environment name (e.g., staging, production).
    • yaml (string, required): Environment YAML content containing baseUrl and services.

13. update_environment

Update an existing environment config file in .gherkio/environments/<name>.yaml.

  • Arguments:
    • name (string, required): Environment name to update.
    • yaml (string, required): Updated environment YAML content.

14. read_credential

Read credentials for a specific environment.

  • Arguments:
    • env (string, required): Environment name.

15. create_credential

Create a new credentials file for an environment under .gherkio/credentials/<env>.yaml.

  • Arguments:
    • env (string, required): Environment name.
    • yaml (string, required): Credentials YAML content with accounts mapping.

16. update_credential

Update an existing credentials file for an environment.

  • Arguments:
    • env (string, required): Environment name.
    • yaml (string, required): Updated credentials YAML content.

πŸ“ Schema Validation

17. list_schemas

List all custom assertion schemas configured under .gherkio/schemas/ in the workspace.

  • Arguments: None

18. create_schema

Create a new schema definition file in .gherkio/schemas/<name>.yaml.

  • Arguments:
    • name (string, required): Schema name (e.g. user-profile).
    • yaml (string, required): Schema YAML content defining expected structure.

19. update_schema

Update an existing schema definition file.

  • Arguments:
    • name (string, required): Schema name to update.
    • yaml (string, required): Updated schema YAML content.

πŸ” cURL & DSL Conversion

20. convert_curl_to_yaml

Convert a raw cURL command string into a formatted Gherkio DSL YAML test scenario or step.

  • Arguments:
    • curl (string, required): Raw cURL command string to parse and convert.
    • scenarioName (string, optional): Optional custom scenario name wrapping the conversion. Defaults to Converted curl.
    • stepOnly (boolean, optional): If true, returns only the individual step's YAML content rather than the full scenario wrapper.
    • env (string, optional): Optional environment to interpolate and format base URL references.

21. convert_yaml_to_curl

Convert a Gherkio test scenario step (or all steps) into reproducible standard cURL commands.

  • Arguments:
    • path (string, required): Relative or absolute path to the Gherkio test YAML file.
    • step (integer, optional): Optional step index (0-indexed) to convert.
    • env (string, optional): Optional environment name to load credentials.
    • account (string, optional): Optional account name to interpolate credential variables.

MCP Resources Reference

Gherkio's Model Context Protocol (MCP) server exposes a set of 7 read-only resources containing the active DSL specifications, schema autocomplete files, canonical YAML test examples, available assertion matchers, variables, and project structures.

These resources are exposed directly to the AI model on connection, allowing it to quickly grasp the syntax and structure of the Gherkio test system without relying on outdated external training data.


πŸ“‚ Available Resources

1. gherkio://dsl/spec

  • Name: Gherkio DSL Grammar Spec
  • MimeType: text/markdown
  • Description: Contains the exact structural keys, step properties, request configurations, and variable interpolation guidelines for writing Gherkio tests.

2. gherkio://dsl/schema.json

  • Name: Gherkio Autocomplete JSON Schema
  • MimeType: application/json
  • Description: The automatically generated Draft-07 JSON Schema mapping internal models to DSL fields. Excellent for AI code autocomplete.

3. gherkio://dsl/examples

  • Name: Gherkio DSL Canonical Examples
  • MimeType: text/yaml
  • Description: Working code snippets illustrating requests, saves, assertions, compositions, and collection validation in action.

4. gherkio://dsl/matchers

  • Name: Available Matchers
  • MimeType: application/json
  • Description: A comprehensive JSON map of all 25+ assertion matchers, detailing their expected syntax, type requirements, and behavior.

5. gherkio://dsl/variables

  • Name: Built-in Variables
  • MimeType: application/json
  • Description: Documented built-in generator variables ($uuid, $ulid, $randomInt, $randomEmail, $randomPhone) available natively in Gherkio test runs.

6. gherkio://dsl/paths

  • Name: Canonical Paths
  • MimeType: application/json
  • Description: Explanation and examples of canonical dot-notation paths for assertions and saves (body.*, headers.*, jwt.*).

7. gherkio://project/structure

  • Name: Project Directory Structure
  • MimeType: text/markdown
  • Description: A breakdown of the Gherkio workspace configuration folder (.gherkio/), explaining where configuration, environments, credentials, tests, reports, and schemas reside.

⚑ Reading a Resource

AI clients can read any resource by calling the resources/read protocol method:

{
  "method": "resources/read",
  "params": {
    "uri": "gherkio://dsl/spec"
  }
}
```text
 Gherkio returns the raw Markdown or JSON content directly, ensuring perfect, real-time sync with the codebase.

LLM Integration & AI Agents

Gherkio was built from the ground up to be AI-native. Its flat, descriptive, YAML-based Domain Specific Language (DSL) and standard Model Context Protocol (MCP) server make it exceptionally easy for LLMs (such as Gemini, Claude, or GPT) to write, parse, and execute integration tests.

This chapter outlines best practices for prompting AI models to build tests, and integrating Gherkio with agentic workflows.


πŸ€– Prompting LLMs for Test Generation

When asking an AI model to write or modify Gherkio tests, feed the model Gherkio's core grammar rules. Below is a highly optimized prompt template you can use:

You are an expert QA engineer specializing in Gherkio's declarative API testing framework.
Write a Gherkio YAML test scenario based on the following requirements:
- Target endpoint: POST /v1/checkout
- Request JSON Body: { "itemId": <uuid>, "quantity": <integer between 1 and 5> }
- Expected output: Status 201, return body.orderId as a UUID.
- Save body.orderId for subsequent steps.

Rules:
1. Use Gherkio's built-in generator $uuid for the itemId.
2. Use Gherkio's dynamic generator ${randomInt(1,5)} for the quantity.
3. Validate response status code and response type structures.
4. Always prefix any saved dynamic variables in the 'save' block with the step number (e.g. '1-createdOrderId' for step 1) to ensure strict cross-step traceability.
```text

The AI model will output a flawless declarative schema matching Gherkio requirements:

```yaml
scenario: Complete Item Checkout
steps:
  - request:
      method: POST
      url: /v1/checkout
      body:
        itemId: $uuid
        quantity: ${randomInt(1,5)}
    expect:
      status: 201
      body.orderId: uuid
    save:
      1-createdOrderId: body.orderId
```text

---

## πŸ”Œ Using MCP Tools Effectively in Agent Workflows

In agentic environments (such as Claude Desktop, Cursor, or specialized coder loops), Gherkio's MCP server empowers the model to run a complete, sandboxed test execution loop:

```mermaid
graph TD
    A[Agent receives QA Task] --> B[Agent calls: create_test]
    B --> C[Agent calls: run_test]
    C --> D{Tests Pass?}
    D -- No --> E[Agent calls: update_test to fix code/assertions]
    E --> C
    D -- Yes --> F[Agent reports success to human]
```text

### ⚑ Recommended Agent Operations

1. **Static Analysis Pre-check**: Prior to writing a test to disk, agents should utilize the `validate_test` tool. This ensures the YAML syntax is valid and all referenced custom assertion schemas are properly mapped, preventing run-time parser crashes.
2. **Step-Isolated Executions**: For large end-to-end user journeys (e.g. 10 steps), running the entire scenario takes time. Agents can debug issues by isolating a single step using the `step` parameter in `run_test`:
   ```json
   {
     "name": "run_test",
     "arguments": {
       "path": "tests/e2e/checkout.yaml",
       "step": 4
     }
   }
  1. Automatic cURL Conversion: When importing external manual tests, agents can translate curl logs directly into Gherkio step structures by calling convert_curl_to_yaml.

Engine Architecture & System Design

πŸ”΄ Expert & Platform Engineering Guide β€” A deep-dive into Gherkio's static Go engine, package hierarchy, thread-safe execution pipeline, security sandboxing, and reporting subsystems.


πŸ“ Core Package Hierarchy

Gherkio's codebase is designed in Go with strict decoupling, zero external runtime dependencies, and high execution speed:

  • cmd/: CLI command interfaces powered by Cobra. Defines subcommands (run, init, validate, convert, schema, mcp).
  • internal/parser/: Reads YAML Gherkio DSL files, performs syntactic structural parsing, checks schema validity, and compiles raw strings into Go AST structs.
  • internal/runner/: The core execution state machine. Manages HTTP client connections, dynamic variable scopes ($accounts, $env, saved variables), timing budgets, and assertion evaluation.
  • internal/matcher/: Evaluation engine for value matchers (equal, contains, greaterThan, lessThan, oneOf, in, matchesRegex, jwt, schema).
  • internal/mcp/: Native JSON-RPC Model Context Protocol (MCP) server running on stdio, exposing 25+ tools and resources for AI assistant workflows.
  • internal/report/: Compiles execution traces into console ANSI logs, JUnit XML, HTML interactive dashboards, and JSON files.

⚑ Step Execution & State Machine

The runner evaluates each scenario step using an isolated state machine:

graph TD
    A[Cobra CLI invocation] --> B[Parser: Load & Validate DSL YAML]
    B --> C[Workspace: Resolve Environment & Credentials]
    C --> D[Runner: Initialize Scenario Context]
    D --> E[Execute SETUP Steps]
    E -- Success --> F[Execute STEPS Sequence]
    E -- Failure --> G[Skip STEPS Sequence]
    F --> H[Execute TEARDOWN Steps]
    G --> H
    H --> I[Report Subsystem: Render Logs & Files]
    I --> J[Exit Code: 0 = PASS, 1 = FAIL]
```text

---

## πŸ›‘οΈ Outbound Network Security & SSRF Sandboxing

Gherkio features built-in Server-Side Request Forgery (SSRF) prevention sandboxing for safe execution in untrusted CI/CD pipelines:
* **IP Whitelisting / Blacklisting**: Inspects resolved IP addresses prior to TCP connection handshake.
* **Subnet Isolation**: Prevents test steps from probing internal cloud metadata endpoints (e.g. `169.254.169.254` or `127.0.0.1/8`) unless explicitly authorized in `.gherkio/config.yaml`.
* **Credential Redaction**: Intercepts terminal logging buffers and automatically sanitizes values from `$accounts` and authorization headers.

---

## 🏎️ Parallel Execution & Thread Safety

When running multiple scenarios concurrently via `gherkio run --parallel=4`:
* **Isolated Scenario Scopes**: Each scenario run maintains its own isolated memory map for saved variables (`save` block) and response buffers.
* **Stateless HTTP Client Pooling**: Shares TCP connections safely across worker goroutines while isolating TLS session states.
* **Thread-Safe Reporting**: Synchronizes stdout ANSI log printing and file output writers via mutex locking to guarantee clean, non-interleaved terminal logs.

Test Reports & Run Results

Gherkio produces structured output in two forms: console output (immediate, ephemeral) and saved reports (persistent, shareable).


Console Output

When you run gherkio run, each step is printed to stdout with its result:

βœ“ POST https://dummyjson.com/auth/login
  βœ“ status = 200
  βœ“ body.exists = true
  βœ“ body.username = emilys
  Duration: 312ms

βœ“ GET https://dummyjson.com/users/1
  βœ“ status = 200
  βœ“ body.id = 1
  Duration: 89ms

βœ“ PASS
2 passed, 0 failed, 2 total
Duration: 401ms
```text

### Pass/Fail Indicators

| Symbol | Meaning |
|--------|---------|
| `βœ“` | Step or assertion passed |
| `βœ—` | Step or assertion failed |
| `β†’` | Redirected (HTTP 3xx) |

### Failure Output

When a step fails, Gherkio prints the failed assertion details plus the full response:

```text
βœ— POST https://petstore.swagger.io/v2/pet/1
  βœ— status = 200 (expected), got 404
  βœ— body.id exists (path not found)

Available fields:
  - message
  - statusCode

Response:
Status: 404

Body:
{
  "message": "Pet not found"
}
```text

Gherkio also extracts available JSON fields when a path is not found, so you can quickly correct your assertion path.

### Verbose Output

Use `--verbose` to show full request and response bodies:

```bash
gherkio run tests/login.yaml --verbose
```text

Sensitive fields (password, token, secret, creditCard, etc.) are always masked in console output.

---

## Saved Reports

Reports are generated only when you add the `--report` flag:

```bash
gherkio run tests/login.yaml --report html          # HTML report
gherkio run tests/login.yaml --report json          # JSON report
gherkio run tests/login.yaml --report html,json     # Both formats
```text

### Report Directory Structure

Reports are saved under `.gherkio/reports/`:

```text
.gherkio/reports/
β”œβ”€β”€ latest/                           # most recent run (overwritten each time)
β”‚   β”œβ”€β”€ report.html
β”‚   └── report.json
β”œβ”€β”€ 20260610_143052/                 # timestamped copy
β”‚   β”œβ”€β”€ report.html
β”‚   └── report.json
└── failures/                         # individual failed step snapshots
    └── failure-<Scenario>-<StepName>-step<N>-<timestamp>.json
```text

The `latest/` directory is always overwritten with the most recent run. Timestamp directories preserve historical runs indefinitely (no automatic cleanup by default).

### HTML Report

The HTML report is a self-contained file with **no external dependencies** - all CSS and JavaScript are inlined. Open it in any browser.

**Example:** An actual HTML report from the petstore test suite looks like this (open `.gherkio/reports/latest/report.html` in your browser to explore it interactively):

![HTML report preview](../assets/images/report-example.png)

**Features:**
- Dark/light mode via CSS media query
- Expandable step sections (collapsed by default, failed steps auto-expand)
- Pass/fail badges per step with HTTP status code coloring
- Response time badge on each step (turns red if a timing.max assertion failed)
- Copyable cURL command per step (reproduce the exact request in your terminal)
- Copyable request ID badge (if the server returns x-request-id or similar headers)
- Expand All / Collapse All toolbar toggle
- Sensitive values masked automatically

### JSON Report

The JSON report is machine-readable and ideal for CI/CD pipelines, historical analysis, or custom dashboards.

**Report structure:**

```json
{
  "metadata": {
    "version": "1.0.0",
    "timestamp": "2026-06-10T14:30:00Z",
    "duration": 401,
    "scenario": "login example",
    "testFile": "tests/auth/login.yaml",
    "environment": "local"
  },
  "summary": {
    "total": 2,
    "passed": 2,
    "failed": 0,
    "passedPercent": 100
  },
  "steps": [
    {
      "index": 0,
      "label": "POST https://dummyjson.com/auth/login",
      "duration": 312,
      "durationLabel": "312ms",
      "passed": true,
      "curl": "curl -X POST 'https://dummyjson.com/auth/login' -H 'Content-Type: application/json' -d '{"username":"emilys","password":"***masked***"}'",
      "request": {
        "method": "POST",
        "url": "https://dummyjson.com/auth/login",
        "headers": { "Content-Type": "application/json" },
        "body": { "username": "emilys", "password": "***masked***" }
      },
      "response": {
        "status": 200,
        "headers": { "Content-Type": "application/json", "x-request-id": "req_abc123" },
        "body": { "accessToken": "***masked***", "username": "emilys", "id": 1 }
      },
      "requestId": "req_abc123",
      "assertions": [
        { "path": "status", "expected": "200", "actual": "200", "passed": true },
        { "path": "body.accessToken", "expected": "exists", "actual": "***masked***", "passed": true }
      ]
    }
  ],
  "errors": []
}
```text

**Key fields per step:**

| Field | Description |
|-------|-------------|
| `duration` / `durationLabel` | Step duration in milliseconds and human-readable form |
| `curl` | Copy-pasteable cURL command with sensitive values masked |
| `requestId` | Tracing ID extracted from response headers (x-request-id, x-trace-id, request-id, etc.) |
| `assertions` | Each assertion with path, expected, actual, and pass/fail status |

### Raw Reports (Unmasked)

By default, sensitive values are masked in JSON reports. To generate an unmasked report:

```bash
gherkio run tests/login.yaml --report json --report-raw
```text

The `--report-raw` flag only affects JSON reports. HTML reports and cURL commands always mask sensitive values regardless of this flag.

### Failure Snapshots

When a step fails, Gherkio also saves an individual JSON snapshot in `.gherkio/reports/failures/`:

```json
{
  "timestamp": "2026-05-31T03:17:41.744Z",
  "scenario": "Delete a Pet (DELETE)",
  "testFile": "tests/petstore/01-pet/05-delete-pet.yaml",
  "failedStepIndex": 0,
  "failedStepLine": 3,
  "role": "steps",
  "error": "assertion status failed: expected 200, got 404",
  "diagnostics": {
    "request": { "method": "DELETE", "url": "https://petstore.swagger.io/v2/pet/1", "headers": {}, "body": null },
    "response": { "statusCode": 404, "durationMs": 260, "headers": {...}, "body": null },
    "variableStoreAtFailure": { "randomEmail": "user_552580@example.com", ... }
  }
}
```text

These snapshots capture the full request/response context at failure time, including the variable store state, making it easy to debug flaky or environment-specific failures.

---

## Configuration

Reports can be configured in `.gherkio/config.yaml`:

```yaml
reports:
  format: html        # default format when --report is omitted
  directory: reports  # directory under .gherkio/ (default: reports)
```text

The `--report` CLI flag always overrides the config setting for a given run.

---

## CI/CD Integration

Use the JSON report for CI/CD ingestion:

```bash
gherkio run --report json --env ci

# Exit code 0 = all passed, exit code 1 = one or more failed
```text

Parse the summary object for pass/fail counts:

```bash
cat .gherkio/reports/latest/report.json | jq .summary
# { "total": 4, "passed": 4, "failed": 0, "passedPercent": 100 }
```text

Frequently Asked Questions

General

What is Gherkio?

Gherkio is a declarative integration testing platform that lets teams write API integration tests in pure YAML. It compiles to a single static Go binary with zero external runtime dependencies. Tests describe multi-step HTTP request sequences, extract variables, define rich assertions, and enforce security policies β€” all in a self-documenting YAML DSL.

What makes Gherkio different from Postman or Bruno?

Postman and Bruno are GUI-centric API clients. Gherkio is a CLI-first integration testing platform designed for CI/CD pipelines. Tests are plain YAML files that live in your repository, execute deterministically, and produce structured reports β€” no GUI required, no vendor lock-in.

Does Gherkio require Node.js, Python, or a JVM?

No. Gherkio is a single static Go binary with zero external runtime dependencies. It runs anywhere Go compiles β€” Linux, macOS, Windows, Docker, and even air-gapped environments.

Is Gherkio free and open source?

Yes. Gherkio is released under the MIT license and is fully open source. You can use it for personal projects, internal tools, and commercial products without restrictions.

Technical

What API styles does Gherkio support?

Gherkio's HTTP engine supports REST APIs, GraphQL endpoints (via JSON POST), and any HTTP-based API. Native gRPC support is on the roadmap.

How does Gherkio handle authentication?

Gherkio supports multi-account credentials, environment variable injection, automatic bearer token injection, cookie persistence, and sensitive field masking. You can run the same test against admin, user, and read-only accounts using gherkio run --all-accounts.

Can Gherkio validate response schemas?

Yes. Gherkio includes native JSON schema validation. Define your expected schema in .gherkio/schemas/ and assert against it inline:

expect:
  schema: user-response
```text

### Does Gherkio support retries and polling?
Yes. Gherkio includes a configurable retry engine with exponential backoff, fixed intervals, and status-based exit conditions for eventual consistency testing.

### Can I reuse scenarios across test files?
Yes. Gherkio supports scenario composition with the `use:` keyword, letting you compose complex test flows from reusable building blocks.

## CI/CD & Automation

### Can Gherkio integrate with my CI/CD pipeline?
Yes. Gherkio produces POSIX exit codes, structured JSON reports, and machine-readable output. It integrates with GitHub Actions, GitLab CI, Jenkins, CircleCI, and any CI platform that can run a Go binary.

### Does Gherkio support parallel test execution?
Yes. Use the `-p` or `--parallel` flag to control concurrency: `gherkio run . -p 4`.

### Can Gherkio run tests against multiple environments?
Yes. Gherkio supports environment profiles defined in `.gherkio/environments/`. Switch between local, staging, and production configurations without modifying test files.

## AI Integration

### Can AI coding assistants write Gherkio tests?
Yes. Gherkio ships with a native Model Context Protocol (MCP) server that exposes your testing sandbox to AI assistants. Tools like Cursor, Claude Desktop, Cline, and Copilot can read specifications, generate scenarios, validate structures, and run tests using natural language.

### How do I set up the MCP server?
See the [MCP Setup Guide](../mcp/setup.md) for configuration blocks for Claude Desktop, Cursor, VS Code, Zed, and Neovim.

## Troubleshooting

### How do I convert existing cURL commands to Gherkio YAML?
Use the CLI: `gherkio convert --curl "curl -X POST https://api.example.com/login"`. Or use the [Interactive Playground](../getting-started/playground.md) for a visual cURL-to-YAML translator.

### How do I debug a failing test?
Run your test with the `-v` (verbose) flag to see full request/response payloads, assertion results, and execution traces. Gherkio also generates structured HTML and JSON reports under `.gherkio/reports/`.

### Where can I get help?
- Open an issue on [GitHub](https://github.com/muhfaris/gherkio/issues)
- Check the [Changelog](../changelog.md) for recent changes and known issues
- Review the [Contributing Guide](../contributing/overview.md) for development questions

AI and Machine-Readable Reference

This page is the compact reference for generating valid Gherkio tests. The detailed DSL chapters remain canonical for explanations and edge cases. Historical RFCs are not current syntax references.

Minimal Scenario

scenario: Fetch user
tags: [smoke, users]
steps:
  - name: Fetch users
    request:
      method: GET
      url: /users
      query:
        page: 1
    expect:
      status: 200
      body.data: array
    save:
      users: body.data

  - name: Select a user
    set:
      SELECTED_USER: ${randomItem(users)}

  - name: Fetch selected user
    if: $SELECTED_USER.id
    request:
      method: GET
      url: /users/$SELECTED_USER.id
    expect:
      status: 200
```text

Top-level fields are `scenario`, `description`, `tags`, `setup`, `steps`, `teardown`, and `examples`. `steps` is required. Setup runs before the main workflow; failed setup skips the main workflow; teardown always runs.

## Step Operations

A step performs one primary operation:

- `request:` sends HTTP.
- `redis:` runs one controlled read-only Redis command.
- `set:` assigns variables without I/O; only `name` and `if` may accompany it.
- `use:` composes another scenario; `with:` supplies local overrides.
- `repeat:` runs nested `steps` up to `attempts` times and checks `until` after each successful block.
- `for_each:` runs nested `steps` once per item in a source collection array.
- `export:` materializes a saved collection into an Excel (`.xlsx`) file.
- `import:` reads an Excel (`.xlsx`) file from disk into runtime variables.

All operation types support `name` and `if`. Request and Redis steps also support `expect`, `save`, `timing`, and `retry` where applicable.

For a multi-step polling cycle, use a bounded repeat block:

```yaml
- name: Find unused candidate
  repeat:
    attempts: 20
    until: $existingCount == 0
    steps:
      - set:
          candidate: ${randomItem(candidates)}
      - request:
          method: GET
          url: /items
          query: { candidate_id: $candidate.id }
        expect: { status: 200 }
        save: { existingCount: count(body.data) }
```text

Variables written by nested steps remain available after success. Any nested
failure stops the loop, and unmet `until` after the final attempt fails it.

Negate a simple condition by quoting it:

```yaml
- name: Authenticate when token is absent
  if: "!$accessToken"
  use: shared/auth/auth.yaml
```text

Combine conditions with `&&` and `||`; `&&` binds more tightly. Parentheses
control grouping, and `!` negates a truthiness check or parenthesized expression:

```yaml
- name: Process complete ticket
  if: $item.ticket_name && $item.customer_name && $item.ticket_code
  request:
    method: POST
    url: /tickets
```text

## Excel Export (`export`)

The `export` step writes a saved collection directly into an Excel (`.xlsx`) file on disk without making HTTP requests:

```yaml
- name: Export products to Excel
  export:
    file: fixtures/products.xlsx    # Target file path (relative to export.path or project root)
    from: $products                 # Source collection variable (must start with $)
    sheet: "Products"               # Optional worksheet name (defaults to Sheet1)
    columns:
      - header: "ID"
        value: "$string(item.id)"
      - header: "Title"
        value: "$string(item.title)"
      - header: "Price"
        value: "$float(item.price)"
      - header: "Tier"
        value: "$if(item.price > 100, 'Premium', 'Standard')"
```text

Each column expression supports casting (`$string`, `$int`, `$float`, `$bool`), `$if(condition, then, else)`, and access to `item.<field>` or runtime variables.

## Excel Import (`import`)

The `import` step reads an Excel (`.xlsx`) file from disk into a runtime array variable without making HTTP requests:

```yaml
- name: Ingest customers from spreadsheet
  import:
    file: fixtures/customers.xlsx   # Path to .xlsx file
    sheet: "Sheet1"                 # Optional worksheet name
    as: customers                   # Variable name storing array of row objects
    columns:                        # Optional explicit aliases; headers default to snake_case
      - header: "Customer Name"
        as: full_name
```text

Headers with spaces are auto-normalized to `snake_case` (e.g. `$customer.customer_name`).

## Loops (`for_each`)

```yaml
- name: Process each created item
  for_each:
    from: $createdItems
    as: item
    steps:
      - name: Verify item status
        request:
          method: GET
          url: /items/$item.id
        expect:
          status: 200
```text

## HTTP Request

```yaml
request:
  service: billing             # optional named environment service
  method: POST                 # GET, POST, PUT, DELETE, PATCH
  url: /v1/orders
  query:
    include: [customer, items] # arrays create repeated query keys
  headers:
    Authorization: Bearer $accessToken
  body:
    user_id: $SELECTED_USER.id
  timeout: 30s
```text

Use `multipart` instead of `body` for uploads:

```yaml
request:
  method: POST
  url: /attachments
  multipart:
    fields:
      title: evidence
    files:
      attachment:
        path: documents/evidence.pdf
        contentType: application/pdf
        filename: evidence.pdf
```text

Relative file names are checked against `assets.path` after the project-relative path. When `contentType` is omitted, the multipart writer uses `application/octet-stream`.

## Variables and Functions

- `$name`, `${name}`: variable reference.
- `${name:default}`: fallback when missing.
- `$accounts.<name>.<field>`: credential value.
- `$value.path`, `$items[0].id`: nested paths.
- `$string(value)`, `$int(value)`, `$float(value)`, `$bool(value)`: request-body casts.
- `$uuid`, `$ulid`, `$randomInt`, `$randomEmail`, `$randomPhone`, `$timestamp`, `$timestampMs`.
- `${randomInt(min,max)}`.
- `${randomItem(array[,field])}`; an exact `${randomItem(array)}` in `set:` preserves the selected object.
- `${dateNow(format)}`, `${dateOffset(duration,format)}`.
- `${base64(string)}`, `${base64Decode(encoded)}`, `${urlencode(string)}`, `${urldecode(encoded)}`.
- `${hash(algo,data)}`, `${hmac(algo,key,message)}`.
- `${randomString(length,charset)}`, `${toUpper(string)}`, `${toLower(string)}`, `${trim(string)}`.
- `${trimPrefix(value,prefix)}`, `${trimSuffix(value,suffix)}`, `${split(value,delimiter,index)}`.
- `$if(condition,thenValue,elseValue)` inside transform selections.

## Assertions and Saved Values

```yaml
expect:
  status: 200
  body.id: uuid
  body.email: email
  body.name: contains Gramedia
  body.role: oneOf admin,manager
  body.deleted_at: not exists
  count(body.items): 3
  all(body.items): object
timing:
  max: 500ms
save:
  userId: body.id
  itemCount: count(body.items)
```text

Canonical paths include `body.<field>`, `headers.<name>`, `jwt.<claim>`, and `redis.<field>`. Supported matchers include `exists`, `not exists`, `uuid`, `email`, `datetime`, `uri`, `string`, `number`, `boolean`, `array`, `object`, `null`, `true`, `false`, `contains`, `startsWith`, `endsWith`, `regex`, `oneOf`, `in`, `gt`, `gte`, `lt`, `lte`, `empty`, `ipv4`, `ipv6`, `base64`, and `mac`.

In `save:`, `count(body.<path>)` stores an array length. Empty arrays and explicit `null` values store `0`; missing paths and non-array values warn and are not stored.

## Retry

```yaml
retry:
  attempts: 5
  interval: 500
  backoff: exponential          # constant, linear, exponential
  maxDuration: 15s
  onStatus: [404, 409]
```text

## Redis

Environment:

```yaml
connections:
  cache:
    type: redis
    sentinel:
      master: mymaster
      addresses: [sentinel-1:26379, sentinel-2:26379]
    username: $REDIS_USERNAME
    password: $REDIS_PASSWORD
    database: 0
```text

Step:

```yaml
- name: Verify cached user
  redis:
    connection: cache
    command: get               # get, exists, ttl, hgetall
    key: user:$userId
  expect:
    redis.exists: true
    redis.value.id: $userId
```text

## Execution Models

```bash
# Different test files concurrently
gherkio run .gherkio/tests --parallel 4

# Same workflow: two isolated users, three sequential iterations each
gherkio run workflow.yaml --virtual-users 2 --iterations-per-user 3

# Reports
gherkio run workflow.yaml --report html,json
```text

`examples:` is data-driven scenario iteration. Virtual-user mode cannot be combined with `examples`, directory runs, `--parallel`, `--all-accounts`, or partial-step selection.

## Common Invalid Syntax

- Do not use unsupported report flags such as `--report-html`, `--report-json`, or `--report-junit`; use `--report`.
- Do not use a fixed random index when the array length comes from a response; use `randomItem`.
- Do not select an object twice to obtain related fields; store one `${randomItem(array)}` object and access its fields.
- Do not configure both `address` and `sentinel` for one Redis connection.
- Do not use Redis mutation commands; Redis steps are intentionally read-only.
- Do not combine `export` or `import` with `request`, `redis`, `use`, `set`, `repeat`, `for_each`, `expect`, `save`, `retry`, or `timing` in the same step.
- Do not combine `set` with `request`, `redis`, `use`, `repeat`, `for_each`, `export`, or `import` in the same step (validation error `mutually_exclusive`); `expect`, `save`, `retry`, `timing`, and `with` on a `set` step do not error but are ignored.

Global Configuration (config.yaml)

The config.yaml file located in .gherkio/config.yaml defines project metadata, execution targets, schema paths, auto-masking configurations, security sandboxing, and reporting defaults.


πŸ“ Complete Configuration Reference (.gherkio/config.yaml)

Below is the exhaustive, production-ready config.yaml reference containing all available configuration properties and inline documentation:

gherkio_version: 0.1.0

# ----------------------------------------------------------------------
# 1. Project Metadata
# ----------------------------------------------------------------------
project:
  name: "billing-system"
  version: "1.0.0"

# ----------------------------------------------------------------------
# 2. Environments Directory Configuration
# ----------------------------------------------------------------------
environments:
  default: local                    # Sourced if --env flag is omitted
  path: .gherkio/environments       # Path to environment configurations

# ----------------------------------------------------------------------
# 3. Tests & Scenarios Configuration
# ----------------------------------------------------------------------
tests:
  path: .gherkio/tests              # Directory where test scenarios reside

# ----------------------------------------------------------------------
# 4. Validation Schemas Configuration
# ----------------------------------------------------------------------
schemas:
  path: .gherkio/schemas            # Directory containing JSON/YAML validation schemas

# ----------------------------------------------------------------------
# 5. Multipart File Assets Directory
# ----------------------------------------------------------------------
assets:
  path: assets                      # Default workspace directory for multipart file uploads

# ----------------------------------------------------------------------
# 5b. Excel Export Output Directory
# ----------------------------------------------------------------------
export:
  path: fixtures                    # Default output directory for the export step's Excel files

# ----------------------------------------------------------------------
# 6. Security, Masking & Outbound Sandboxing
# ----------------------------------------------------------------------
security:
  mask:
    enabled: true                   # Enable dynamic log sanitization
    fields:                         # Case-insensitive field keys to mask in output/reports
      - token
      - password
      - secret
      - Authorization
      - creditCard
  sandboxing:
    enabled: true                   # Enforce outbound network security boundaries
    allowedDomains:                 # Allowed target domains (supports wildcards)
      - "*.company.com"
      - "api.stripe.com"
    blockedDomains:                 # Explicitly blocked target domains
      - "analytics.untrusted.com"
    blockPrivateSubnets: true       # Prevent loopback (127.0.0.1) & private IP SSRF attacks

# ----------------------------------------------------------------------
# 7. Reports & Failure Snapshot Dumps
# ----------------------------------------------------------------------
reports:
  path: .gherkio/reports            # Output path for test run summaries
  format: html                      # Report format ("html", "json", or "html,json")
  archive: true                     # Keep a history of past reports
  retention: 10                     # Keep up to 10 historical reports
  maskSensitive: true               # Mask credentials in HTML report pages
  failures:
    enabled: true                   # Write detailed JSON debug snapshots on test failures
    path: .gherkio/reports/failures # Directory for failure snapshots
    maskSensitive: true             # Mask credentials in failure dumps
    retainCount: 50                 # Retain up to 50 failure snapshots

# ----------------------------------------------------------------------
# 8. Authentication Defaults (Optional)
# ----------------------------------------------------------------------
jwt_token_path: "body.token"        # JSON path for auto-extracting JWT tokens
```text

---

## βš™οΈ Detailed Configuration Section Reference

### 1. `project`
Defines the name and version of your API testing suite. Used when generating test run reports and HTML dashboards.

### 2. `environments`
* `default`: The default target environment name (e.g. `local`) if the CLI `--env` flag is omitted.
* `path`: Relative path pointing to target environment host definition files (e.g. `.gherkio/environments/`).

### 3. `tests`, `schemas`, & `assets`
* `tests.path`: Directory where `.yaml` scenario suites are stored.
* `schemas.path`: Directory where reusable JSON/YAML schema validation files are stored.
* `assets.path`: Default folder for storing multipart file attachments (images, PDFs, documents).

### 3b. `export`
* `export.path`: Default output directory for the `export` step's generated Excel files. Relative paths resolve from the project root; absolute paths are used as-is. When set, an `export.file` relative path is written under this directory.

### 4. `security.mask` (Credential Redaction)
Scans all stdout console logs, raw HTTP tracebacks, and generated report files. If any field matches `security.mask.fields`, Gherkio automatically replaces its value with `[MASKED]`.

### 5. `security.sandboxing` (Network Protection Engine)
Prevents SSRF and unauthorized network egress:
* `allowedDomains`: List of target domain patterns permitted for HTTP requests.
* `blockedDomains`: List of explicitly forbidden target domains.
* `blockPrivateSubnets`: Resolves domain IPs prior to HTTP calls. Rejects requests targeting private or loopback subnets (`127.0.0.1`, `10.0.0.0/8`, `192.168.0.0/16`).

### 6. `reports` & `reports.failures`
Controls test result artifact generation:
* `format`: Report output format (`html` or `json`).
* `archive` & `retention`: Maintains historical report versions up to `retention` runs.
* `failures`: Creates diagnostic JSON snapshots in `.gherkio/reports/failures/` whenever an assertion fails, preserving full request/response context for debugging.

Environments Configuration

Target different deployment environments dynamically. All configurations are stored as standard YAML files inside the .gherkio/environments/ directory.


πŸ“ Directory Resolution & Naming

Gherkio matches the environment filename with the CLI --env (or -e) execution flag:

  • --env staging βž” loads .gherkio/environments/staging.yaml
  • --env local βž” loads .gherkio/environments/local.yaml

βš™οΈ Environment YAML Properties

KeyTypeRequiredDescriptionExample
baseUrlstringYesTarget domain for relative requestsbaseUrl: https://api.staging.net
servicesobjectNoMicroservice override pathways(See below)
connectionsobjectNoNamed Redis connections used by redis steps(See below)

Redis Connections

Redis connection details belong to the active environment rather than scenario files:

baseUrl: http://localhost:8080

connections:
  local-cache:
    type: redis
    address: localhost:6379
    database: 0
    timeout: 5s
```text

Optional fields are `username`, `password`, `database`, `tls`, and `timeout`.
Connection strings support normal Gherkio interpolation, so secrets can be supplied
through `GHERKIO_` environment variables instead of committed to source control.

For Redis Sentinel, replace the direct `address` with a `sentinel` block:

```yaml
connections:
  application-cache:
    type: redis
    sentinel:
      master: mymaster
      addresses:
        - sentinel-1:26379
        - sentinel-2:26379
        - sentinel-3:26379
      username: sentinel-user
      password: $GHERKIO_SENTINEL_PASSWORD
      tls: false
    username: redis-user
    password: $GHERKIO_REDIS_PASSWORD
    database: 0
    tls: false
    timeout: 5s
```text

Sentinel endpoints are tried in order. Each Redis operation discovers the current
primary using `SENTINEL get-master-addr-by-name`, so a retried step discovers the
new primary after failover. Credentials and TLS inside `sentinel` apply only to
Sentinel; the connection-level credentials and TLS apply to the Redis primary.

See [Redis Cache Checks](../dsl/redis.md) for using the named connection in a
test scenario with assertions, saved values, timing limits, and retry polling.

---

## ⚑ Microservice Endpoint Overrides

In multi-service networks, different features (identity, checkout, inventory) are hosted on independent servers. Use the `services` map to cleanly route specific API requests:

```yaml
# .gherkio/environments/staging.yaml
baseUrl: https://staging.my-company.com

services:
  identity:
    baseUrl: https://auth-staging.my-company.net
  checkout:
    baseUrl: https://checkout-staging.my-company.net
```text

When writing a step, reference the service key:
```yaml
steps:
  # Routes to https://auth-staging.my-company.net/v1/token
  - request:
      service: identity
      method: POST
      url: /v1/token
```text
 This allows you to migrate identical testing logic between `local` (where all services run on local ports) and `production` with zero script modifications.

---

## 🎭 Service Mocking / Virtualization

Gherkio supports service virtualization directly within your environment file. You can define outbound mock intercepts to stub third-party API dependencies or simulate specific response conditions (e.g. failure states) without hitting real external endpoints.

```yaml
# .gherkio/environments/local.yaml
baseUrl: http://localhost:8080

mocks:
  - request:
      method: GET
      url: /api/external-service/status
    response:
      status: 200
      headers:
        Content-Type: application/json
      body:
        status: "healthy"
        service: "virtualized-dependency"
```text

When the test runner encounters a step requesting a URL matching the defined mock request (`method` and `url`), it intercepts the request and instantly returns the configured `response` status, headers, and body.

### Intercept Variable Interpolation
Mock responses support dynamic variable interpolation using the context variables from the active test runner session, allowing you to return customized responses:
```yaml
mocks:
  - request:
      method: POST
      url: /api/echo
    response:
      status: 201
      body:
        message: "Hello {{username}}"
```text

Credentials File Layout

Manage test account usernames, passwords, API keys, and client secrets securely. Credentials map to target environments and are stored inside .gherkio/credentials/.


πŸ”’ Directory Convention & Security

Credentials files are mapped exactly to environments:

  • --env staging loads .gherkio/credentials/staging.yaml if it exists.

[!CAUTION] Add .gherkio/credentials/*.yaml to your project's .gitignore file to ensure sensitive client secrets are never committed to version control.


πŸ“ Credentials YAML Layout

# .gherkio/credentials/staging.yaml
accounts:
  # Account Role key
  admin:
    username: admin@company.com
    password: super-secret-key-1
    clientId: cid_12389
    
  # Additional Account Roles
  manager:
    username: manager@company.com
    password: manager-secret-key
    clientId: cid_45678
```text

---

## ⚑ Referencing Credentials in DSL

Gherkio makes all variables nested inside an account map immediately available inside test scenarios via two pathways:

### 1. Direct Explicit Variable Binding
Use `$accounts.<role>.<field>` to reference any credential property explicitly:

```yaml
steps:
  - request:
      method: POST
      url: /login
      body:
        email: $accounts.admin.username
        pass: $accounts.admin.password
```text

### 2. Contextual Active Role Auto-Injection
If you execute the test run specifying a selected account, Gherkio dynamically flattens the account keys directly into the root context:

```bash
gherkio run my-test.yaml --env staging --account manager
```text

Now, inside the test, you can directly reference `$username` and `$password` without specifying the account role prefix:

```yaml
steps:
  - request:
      method: POST
      url: /login
      body:
        email: $username            # Dynamically binds to manager@company.com
        pass: $password            # Dynamically binds to manager-secret-key
```text
This is the ultimate pattern for verifying role isolation boundaries with identical test scripts.

Schema Definitions Layout

Define structural response validation schemas under .gherkio/schemas/ to enforce robust API contracts.


πŸ“ Specification Standards

Gherkio natively parses schemas conforming to the JSON Schema Draft-07 specification. You can define schemas as standard JSON (.json) or YAML (.yaml/.yml).


πŸ“ Canonical Schema Layout Example

Example: .gherkio/schemas/auth/token-payload.yaml

$schema: "http://json-schema.org/draft-07/schema#"
type: object
required:
  - token
  - expiresAt
  - user
properties:
  token:
    type: string
    pattern: "^eyJ[A-Za-z0-9-_=]+\\.[A-Za-z0-9-_=]+\\.?[A-Za-z0-9-_.+/=]*$" # Simple JWT format regex
  expiresAt:
    type: string
    format: date-time
  user:
    type: object
    required:
      - id
      - email
    properties:
      id:
        type: string
        format: uuid
      email:
        type: string
        format: email
```text

---

## ⚑ Asserting the Schema inside steps

Refer to the relative path of the schema file (omitting the extension) under the `expect` block:

```yaml
steps:
  - request:
      method: POST
      url: /v1/auth/token
    expect:
      status: 200
      schema: auth/token-payload     # References .gherkio/schemas/auth/token-payload.yaml
```text

If the server response contains a different structure (e.g. `expiresAt` is a number, or `user.id` is not a valid UUID), the test run halts with high-precision validation tracebacks.

Contributing Overview

Thank you for helping build Gherkio! This guide helps you get up to speed with setting up your local Go development environment, building the CLI, and running the test suite.


πŸ› οΈ 1. Local Development Setup

Prerequisites

  • Go: Version 1.25+ installed on your system.

Scaffold & Run

Build the CLI tool locally and run tests directly:

# Build the binary
go build -o gherkio .

# Initialize a default playground project
./gherkio init

# Run a test scenario
./gherkio run example/login.yaml -v
```text

---

## πŸ§ͺ 2. Test Suite & Validation

Gherkio takes testing seriously. Please ensure all tests pass before submitting any pull requests.

### Running Go Tests
Run the entire Go test suite:
```bash
go test ./...
```text

Run tests on a specific package (e.g., the runner engine):
```bash
go test -v ./internal/runner/
```text

### Golden File Snapshot Tests
Printer rendering tests inside `/internal/runner/` use **golden snapshot files** (stored under `internal/runner/testdata/`) to compare exact terminal printer bytes.

If you intentionally alter terminal output formatting, regenerate these snapshot files using the `-update` flag:
```bash
go test ./internal/runner/ -update
```text

---

## πŸ“ 3. Git Commit Standards

We use the [Conventional Commits](https://www.conventionalcommits.org/) specification to keep the Git history clean and readable:

- `feat: add between matcher for numeric bounds`
- `fix: resolve memory leak on retry exponential backoff`
- `docs: update recipes with JWT token authentication`
- `test: expand coverage on built-in generators`

Adding a Custom Matcher

Gherkio's assertion engine is designed to be easily extensible. All matchers live under internal/runner/matchers.go.


πŸ› οΈ Step-by-Step Walkthrough

Follow these three simple steps to implement a custom matcher (for example, a between matcher to check if a numeric response is inside bounds):

Step 1: Register the Matcher Keyword

Open internal/runner/matchers.go and add the keyword to the slice of known matchers inside GetAvailableMatchers():

func GetAvailableMatchers() []string {
	return []string{
		"exists", "not exists", "uuid", "email", // ...
		"between", // <-- Register your matcher keyword here
	}
}
```text

### Step 2: Implement the Evaluation Logic

Add the evaluation logic inside `evaluateAssertion` or helper dispatcher functions in the same file. Parse arguments if your matcher takes inputs (e.g. `between 1 10`):

```go
if strings.HasPrefix(expectedStr, "between ") {
	args := strings.TrimPrefix(expectedStr, "between ")
	parts := strings.Fields(args)
	if len(parts) != 2 {
		return AssertionResult{Passed: false, Reason: "between requires 2 arguments (min, max)"}
	}
	
	// Parse min and max as float64, compare against actual numeric value, and return:
	// return AssertionResult{Passed: true}
}
```text

### Step 3: Add Unit Tests

Open [internal/runner/matchers_test.go](file:///home/muhfaris/Documents/projects/sourcecode/src/github.com/muhfaris/gherkio/internal/runner/matchers_test.go) and append test cases validating both passing and failing assertions:

```go
func TestMatchers(t *testing.T) {
	// Add test cases for your new matcher:
	// AssertPass(t, "between 1 10", 5)
	// AssertFail(t, "between 1 10", 12)
}
```text
 Ensure your tests pass perfectly by running `go test -v ./internal/runner/`.

Deploying to GitHub Pages

Gherkio's developer documentation is built as a static HTML site using mdBook. Because of this static, zero-runtime architecture, hosting and serving your documentation via GitHub Pages is fully supported, completely free, and takes under 2 minutes.


The most robust way to host your documentation is by using GitHub Actions. This automates the build process: every time you push edits to the main branch, the pipeline regenerates Cobra CLI subcommand manual pages, compiles the static HTML files, and deploys them to GitHub Pages.

1. Add the Workflow File

Create a new workflow file at .github/workflows/deploy-docs.yml inside your repository:

name: Deploy Developer Documentation

on:
  push:
    branches:
      - main
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: false

jobs:
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4

      - name: Setup Go Environment
        uses: actions/setup-go@v5
        with:
          go-version: '1.25'
          cache: true

      - name: Install mdBook
        run: |
          MDBOOK_VERSION="v0.4.43"
          curl -sSL "https://github.com/rust-lang/mdBook/releases/download/${MDBOOK_VERSION}/mdbook-${MDBOOK_VERSION}-x86_64-unknown-linux-gnu.tar.gz" | tar -xz
          sudo mv mdbook /usr/local/bin/
          mdbook --version

      - name: Build Documentation
        run: |
          make docs-build

      - name: Setup GitHub Pages
        uses: actions/configure-pages@v4

      - name: Upload Static Assets Artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: 'docs/book/book'

      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4
```text

### 2. Configure GitHub Repository Settings
1. Go to your repository on GitHub.
2. Navigate to **Settings** βž” **Pages** (under the "Code and automation" section).
3. Under **Build and deployment**, set the **Source** dropdown to **GitHub Actions**.
4. Push your workflow file to `main`. The deployment action will fire, and your live URL will be active within seconds!

---

## πŸ—οΈ Method 2: Manual Local Build & Push

If you prefer to compile the book locally and push the static bundle directly to a dedicated `gh-pages` branch without using GitHub Actions:

### 1. Build the Static HTML
Run the local compilation tool to generate output files under `docs/book/book/`:
```bash
make docs-build
```text

### 2. Push to `gh-pages`
You can use standard Git commands or a deployment utility (such as the `gh-pages` npm package or simple scripting) to push the contents of `docs/book/book/` to your `gh-pages` branch:

```bash
# Example quick-script to publish static output
cd docs/book/book
git init
git checkout -b gh-pages
git add .
git commit -m "docs: deploy static developer book manually"
git remote add origin https://github.com/muhfaris/gherkio.git
git push -f origin gh-pages
```text

---

## 🎨 Premium Offline Features Compatibility

All custom static page features implemented in Gherkio's mdBook layout β€” including offline visual **Mermaid Flowcharts** and local assets β€” work flawlessly when hosted on GitHub Pages:

*   **No External CDNs**: Mermaid rendering relies strictly on the pre-compiled `mermaid.min.js` and `mermaid-init.js` assets bundled inside the workspace. GitHub Pages serves these local files securely under standard HTTPS parameters.
*   **SEO-Friendly Output**: mdBook generates semantic, indexing-friendly static HTML files. Search engines (like Google and DuckDuckGo) can crawl and index your DSL specifications instantly out-of-the-box.

Changelog

All notable changes to Gherkio will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.


[Unreleased]

[0.1.0-alpha.9] - 2026-08-30

Added

  • Redis Cache Assertions: Added controlled, read-only Redis steps for GET, EXISTS, TTL, and HGETALL, including redis.* assertions, saved values, retries, timing checks, authentication, TLS, database selection, and Redis Sentinel primary discovery.
  • Virtual-User Load Runs: Added --virtual-users and --iterations-per-user for concurrent isolated users running complete workflows sequentially, with virtual-user and iteration metadata included in reports.
  • Response-Aware Random Selection: Added ${randomItem(array[,field])} using the runtime collection length, including typed object preservation when the expression is used as an exact set: value.
  • String Helpers: Added ${trimPrefix(...)}, ${trimSuffix(...)}, and ${split(...,delimiter,index)} interpolation helpers.
  • Configurable Multipart Assets: Added assets.path to .gherkio/config.yaml while retaining the existing project-root and fixtures fallbacks when it is omitted.
  • Bounded Multi-Step Loops: Added repeat.attempts, repeat.until, and nested repeat.steps for workflows that must reselect data and refetch an API until a response-derived condition succeeds, with attempt labels in terminal and HTML reports.
  • Executable Feature Catalog: Expanded the built-in .gherkio project into consolidated, self-contained mocked workflows covering composition, multipart uploads, collections, repeat loops, runtime helpers, matchers, and load execution, plus opt-in direct Redis and Sentinel examples.

Changed

  • Request Pacing: Added --request-delay with sensible defaults for individual files and directory runs.
  • Step-Prefixed Variables: Saved names such as 1-ticketId and 2-userId now validate, interpolate, and convert consistently.
  • Load-Test Reporting: HTML reports now present load-run summaries and virtual-user workflow executions with clearer expandable details.
  • Documentation Theme: Set Navy as the default mdBook theme and placed Mermaid diagrams on a light canvas for readable labels across Light, Navy, Ayu, and Coal.
  • AI Documentation Reliability: Added a compact machine-readable reference, refreshed llms.txt, corrected stale runtime claims, and introduced consistency tests for CLI flags, book links, MCP request fields, matchers, and built-in functions.
  • Saved Collection Counts: save: now accepts count(body.<path>); arrays save their length and explicit null values save 0 without hiding missing or incorrectly typed paths.
  • Variable Snapshot Reporting: Reports now distinguish variables available before execution from final variables preserved after repeat blocks; generated cURL commands mask authorization headers, and authToken variables use built-in masking.

Fixed

  • Transport Failure Accounting: Request construction and transport errors now fail the scenario and contribute to failure totals instead of allowing a false-green result.
  • Composed Variable Validation: Project validation now recognizes example variables and outputs saved or set by nested use workflows, and accepts negated schema assertions.
  • Strict MCP Schemas: MCP tool input schemas now emit explicit object properties and strict validation metadata required by stricter clients.
  • Deterministic Query Parameters: Request URLs now serialize query parameters in a stable order.

[0.1.0-alpha.8] - 2026-08-08

Added

  • Payload-Aware MCP Authoring: MCP scenario creation prompts can derive useful request bodies, queries, headers, expectations, and saved values from supplied API payload context.

Fixed

  • MCP Step Isolation: Running step index 0 through run_test now executes only that step instead of falling back to the complete scenario.

[0.1.0-alpha.7] - 2026-07-23

Added

  • Collection Assertions: Added any(path) and expanded all(path) collection matching, including nested collection fields.
  • Session Persistence: Saved scenario values can persist between runs and are available to reverse cURL conversion and subsequent workflows.
  • Collection Diagnostics: HTML reports show collection assertion details and clearer matching failures.

Changed

  • Showcase Suite: Standardized the example scenarios, added third-party Stripe virtualization, and integrated the showcase into CI.

[0.1.0-alpha.6] - 2026-07-15

Added

  • Data-Driven Testing (Parametrization): Scenario-level execution iteration using the examples block.
  • Composed Traceability: Visually grouped composed scenario blocks, dynamic nesting indentation by depth, and local context variable snapshots in HTML reports.
  • Outbound Service Mocking & Interception: Direct request interception and virtualized responses defined in environment files, with full variable interpolation support.
  • JSONPath Assertions: Support for standard JSONPath queries ($.) inside response body expectation assertions.
  • Step Locator & Reverse Conversion Enhancements: Integrated session variables and robust parsing of nested list structures when converting steps back to cURL.
  • Rich Schema Hover Documentation: Enhanced autocomplete JSON schemas with detailed description strings for editor tooltips.
  • Declarative Variable Assignment (set:): Direct inline variable assignment step style.
  • Environment Context Command (env context): Interactive auto-selection hints command.

[0.1.0-alpha.5] - 2026-06-22

Added

  • Fail-Fast Mode: Added runner and MCP support for stopping after the first failure while still guaranteeing teardown execution.
  • Composition Overrides: Added with: variables for use: steps, with interpolation and restoration of the caller's values after composition completes.
  • Save-Path Warnings: Missing response paths referenced by save: now produce non-fatal warnings in terminal, JSON, and HTML output.
  • Resolved Variables in Reports: Reports now expose resolved workflow variables for debugging and traceability.
  • MCP Documentation Improvements: Expanded DSL resources with multipart, query, bracket-path, assertion, and granular execution guidance.

Changed

  • Documentation Discoverability: Added AI-crawler metadata, FAQs, and denser answer-first project documentation.

[0.1.0-alpha.4] - 2026-06-10

Added

  • Conditional Steps: Added value-based conditional execution for declarative workflows.
  • Scenario and Step Metadata: Added scenario description, step name, and request query fields.
  • Structured Request Reports: HTML and JSON reports now preserve step names and expose request method, URL, headers, query parameters, and body details.
  • Editor and Report Documentation: Added dedicated references for JSON Schema editor integration and generated test reports.

Fixed

  • MCP Authoring Feedback: Improved MCP test creation and execution behavior based on early user feedback.

[0.1.0-alpha.3] - 2026-05-31

Added

  • Declarative Collection Transforms: Added filtering, slicing, projection, and explicit type casting for response collections used in later requests.
  • Partial Execution with --until: Added execution through a selected step boundary.
  • Swagger Petstore Showcase: Added a complete public API example suite.

Fixed

  • JSON Number Precision: Preserved large integer values without lossy floating-point conversion.
  • Report Error Handling: Corrected failures encountered while producing reports.
  • MCP DSL Resources: Exposed variable and DSL guidance through MCP resources.

[0.1.0-alpha.2] - 2026-05-29

Added

  • Gherkio Developer Book: Introduced a fully-fledged developer guide containing 23 chapters built with mdBook covering CLI tools, DSL syntax, and advanced recipes.
  • Interactive Browser Playground: Added a self-contained, offline-first visual interface supporting:
    • Visual DSL Stepper: Dynamic execution flowchart rendering.
    • cURL ↔ YAML Step Translator: Real-time legacy terminal logs conversion.
  • Hosted GitHub Pages Support: Configured static deploy workflows to serve both the developer book and the playground in a unified workspace.
  • Stunning System Architecture Visual: Embedded a premium, dark-mode glassmorphic diagram explaining Gherkio's MCP daemon and execution flow.
  • Expanded Phone Number Support: Added broader localized randomized phone number formats supporting international validations.

Fixed

  • Fixed playground asset bundling path mapping inside the docs-build compilation targets.
  • Corrected search index generation schemas for mdBook chapter queries.

[0.1.0-alpha.1] - 2026-05-28

Added

  • RFC-18 Automated Failure Debug Snapshots: Auto-capturing raw request/response payload snapshots during test failures.
  • RFC-19 Domain Sandboxing & SSRF Prevention: Outbound network sandboxing policies with user-configurable allowlists and blocklists.
  • RFC-21 Multipart Form-Data & File Upload: Streaming of multipart files via Go io.Pipe.
  • Dynamic Parameterized Generators: Parameterized variable generators supporting offset dates and randomized values.
  • Expanded MCP Server Tools: Exposing gherkio init, gherkio validate, and gherkio convert directly as discoverable tools in the MCP layer.
  • Cobra CLI Doc Generator: Automatically building Cobra CLI manual pages to markdown.

[0.1.0-alpha] - 2026-05-26

Added

  • Core Declarative Engine: Sequential HTTP requests, responses, headers, and parameter binding.
  • Value Matchers & Negative Assertions: Core validations (exists, equals, contains, matches, not exists, schema: not <name>).
  • Setup & Teardown Blocks: Scenario setup and error-tolerant teardown block executions.
  • Retry & Polling Loops: Configurable attempts, interval timers, and linear backoff algorithms.
  • Multi-Account Credentials Management: Namespaced credentials loader with safe YAML integrations.