# Data-Driven Testing

> Run a test once per row of data—from an internal table, a saved dataset, a data source, or a generator—binding columns to request fields with {{row.COLUMN}} tokens.

Source: https://totalshiftleft.ai/help-center/product-documentation/data-driven-testing

## Overview

**Data-driven testing** runs a single test once for every row of data. You attach data to a test, bind columns to request fields, and Shift Left Studio iterates the test across the selected rows. This guide walks the **Data-driven testing** panel in the test editor exactly as it appears in the UI. Explore the capability at [data-driven testing](/features/test-data/data-driven-testing).

This panel uses `{{row.COLUMN}}` tokens (and generator `{{ … }}` tokens). It is a different system from **Data Sources**, which use `[[variable]]` tokens — never mix the two. For reusable external connections, see [Data sources](/help-center/product-documentation/data-sources).

## Before you begin

- Open the test you want to iterate (a test inside a project → feature → endpoint).
- Scroll the test editor to the **Data-driven testing** panel. It shows a heading **Data-driven testing** with an **On / Off** toggle on the right and the subtitle *"Run this test once for every row of data — a CSV/Excel file, a saved dataset, an internal table, or generated data."*
- To use a **Saved dataset**, you first need a dataset in this project (see [Datasets](#step-6--manage-datasets) below). To use a **Saved data source**, create one first — see [Data sources](/help-center/product-documentation/data-sources).

## Step 1 — Turn on data-driven testing

1. Click the toggle at the top-right of the panel. It flips from **Off** to **On** and the configuration area expands beneath a divider.
2. Leaving it **Off** runs the test normally (a single execution).

## Step 2 — Choose a data source

Under **Data source**, pick one of four options from the dropdown:

| Option | What it does |
|--------|--------------|
| **Internal data table** | Type columns inline and add rows by hand — no file or saved object needed. |
| **Saved data source (CSV / Excel / DB / API)** | Reuse a saved [data source](/help-center/product-documentation/data-sources). Columns load automatically. |
| **Saved dataset** | Reuse a versioned, project-scoped dataset created in the Datasets area. |
| **Random generator** | Generate synthetic rows on the fly from generator field types. |

Switching the source clears the others so a leftover empty source can't conflict with your selection.

### Internal data table

1. In the **Columns (comma separated)** field, type your column names, for example `name, age, token`.
2. A grid appears with one column per header. Click **+ Add row** to add rows, and fill each cell. Use the **✕** at the end of a row to remove it.

### Saved data source

1. Choose a source from the **Select a data source…** dropdown (each entry shows its name and type, e.g. `Customers (CSV)`).
2. Columns and a preview load automatically. The panel reminds you: *"Bind columns with `column → target`, or reference them as `{{row.COLUMN}}` in any field. Tip: enable 'has headers' on the data source so columns are named."*

### Saved dataset

1. Choose a dataset from the **Select a dataset…** dropdown.
2. Use **Refresh** to reload the list (for example, after creating a dataset in another tab), or **Manage datasets ↗** to open the Datasets area in a new tab.
3. If the project has none, the panel shows *"No saved datasets in this project yet."* with a **Create one ↗** link.

### Random generator

1. Set **How many rows** (the row count, default 5) and an optional **Seed (for repeatable data)** — the same seed always produces the same rows.
2. Click **+ Add field** for each column. Give it a **column name** (e.g. `email`) and pick a generator **type** from the dropdown.
3. Available generator types include: `uuid`, `sequence`, `int`, `float`, `bool`, `firstName`, `lastName`, `fullName`, `email`, `phone`, `city`, `country`, `address`, `company`, `postalCode`, `creditCard`, `cvv`, `iban`, `time`, `date`, `pastDate`, `futureDate`, `word`, `words`, `lorem`, and `pattern`.

## Step 3 — Choose which rows run (iteration mode)

Set the **Iteration mode** dropdown to control which of the source rows are used:

| Mode | Extra input | Runs |
|------|-------------|------|
| **every** | — | every row |
| **firstN** | **Number of rows** | the first N rows |
| **lastN** | **Number of rows** | the last N rows |
| **range** | **From row (0-based)** and **To row (inclusive)** | a contiguous slice |
| **specific** | **Row index (0-based)** | one specific row |
| **random** | **How many random rows** | a random sample |
| **unique** | — | de-duplicated rows |
| **filtered** | a **Filter JSON** field | rows matching the filter |

For **filtered**, enter a small JSON filter, for example `[{"field":"status","op":"eq","value":"active"}]`.

## Step 4 — Bind columns to the request

Map each column to a place in the outgoing request. Under **Bind columns to the request**:

1. Click **+ Add binding** to add a mapping row, or **Auto-map columns** to create one binding per detected column at once (this appears once a preview has loaded columns).
2. Each binding row reads **column → target · target path · transform**:

| Field | What it does |
|-------|--------------|
| **column** | The source column name to read from each row. |
| **target** | Where to place the value: **query**, **path**, **header**, **cookie**, **body**, or **form**. |
| **target (key or body.path)** | The parameter name or a dotted body path (e.g. `user.id`). |
| **transform** | Optional value transform: **uppercase**, **lowercase**, **trim**, **base64**, **number**, or **no transform**. |

3. Use the **✕** at the end of a row to remove a binding.

Alternatively (or in addition), reference any column directly inside any parameter, header, or body field as `{{row.COLUMN}}` — for example `{{row.token}}`. Typing `{{` in a request field opens autocomplete listing your data columns and the generator tokens; use ↑↓ to navigate and Enter or Tab to insert. Unknown tokens are left untouched.

## Step 5 — Set execution options and preview

At the bottom of the panel:

| Control | What it does |
|---------|--------------|
| **Parallel threads** | How many rows run concurrently (default 1). |
| **On row failure** | What happens when a row fails: **continue**, **stop**, or **skip**. |
| **Preview iterations** | Runs a dry preview without sending real requests. |

Click **Preview iterations**. The panel reports how many rows are selected — for example *"5 of 12 rows selected → 5 iteration(s)"* — and shows a sample table of the first rows (up to 10). Errors appear in red; a *"Loading data…"* note shows while it fetches.

Expand **Dynamic values you can use ({{ … }})** to see a cheat-sheet listing `{{row.COLUMN}}` for each of your columns plus the generator tokens.

## Step 6 — Manage datasets

Datasets are reusable, project-scoped, versioned data tables. Open the **Datasets** area (the **Manage datasets ↗** link from the panel, or `#/datasets`). There you can:

- **Create an internal data table** — enter a **Dataset name**, **Columns (comma separated)**, and rows (one row per line, comma-separated values), then click **Create dataset**.
- **Import a dataset** — enter a **Dataset name**, choose a format (**CSV**, **JSON**, or **YAML**), paste the file content into the **Paste file content** box, and click **Import**.
- **Export CSV** / **Export JSON** — download a dataset in either format.
- **Archive** — archive a dataset you no longer need (confirmation required).

Each dataset row shows its source type, version (e.g. `v3`), and cache policy. Back in the test, select the dataset under **Saved dataset** and click **Refresh** if it isn't listed yet.

## Step 7 — Review the results

After a data-driven run, the **Data-driven iterations** grid shows one row per iteration, with a header count such as *"4/5 passed"*. Columns are:

| Column | Shows |
|--------|-------|
| **#** | Iteration number (1-based). |
| **Status** | **PASSED**, **FAILED**, **ERROR**, or **SKIPPED**. |
| **Row** | A short label of the row that was used. |
| **HTTP** | The HTTP status code returned. |
| **Failed field** | The first failing assertion's field (blank when the row passed). |
| **Time** | Response time in milliseconds. |

## Troubleshooting

- **No columns to bind** — for a data source, make sure **Has Headers** is enabled so columns are named; otherwise click **Preview iterations** to detect columns.
- **A column shows blank in the request** — confirm the binding **column** name matches the header exactly, or that your `{{row.COLUMN}}` token spells the column exactly.
- **Wrong number of iterations** — check the **Iteration mode** and its arguments (for example a **firstN** value or a **range** that is out of bounds).
- **Preview error** — the source may be unreachable or misconfigured; open the underlying dataset or data source and verify it loads.

## Related articles

- [Data sources](/help-center/product-documentation/data-sources)
- [Test Configuration](/help-center/product-documentation/test-config)
- [Test case](/help-center/product-documentation/test-case)
- [Test run](/help-center/product-documentation/test-run)

