Skip to content

Scenario Outlines

Scenario Outlines (also called Scenario Templates) allow you to run the same scenario multiple times with different data. They're perfect for data-driven testing.

Basic Syntax

Use Scenario Outline with an Examples table:

gherkin
Feature: Calculator

  Scenario Outline: Addition with various inputs
    Given I have number <start>
    When I add <add>
    Then the result should be <result>

    Examples:
      | start | add | result |
      | 5     | 3   | 8      |
      | 10    | 5   | 15     |
      | 0     | 0   | 0      |
      | -5    | 10  | 5      |

This generates 4 separate test cases, one for each row in the Examples table.

How It Works

Placeholder Substitution

Placeholders in angle brackets <name> are replaced with values from the Examples table:

gherkin
Given I have number <start>
# Row 1: Given I have number 5
# Row 2: Given I have number 10
# Row 3: Given I have number 0
# Row 4: Given I have number -5

Generated Tests

Each Examples row creates an independent test:

   PASS  Feature: Calculator
   ✓ Scenario Outline: Addition with various inputs (Example 1)
     ✓ Given I have number 5
     ✓ When I add 3
     ✓ Then the result should be 8
   ✓ Scenario Outline: Addition with various inputs (Example 2)
     ✓ Given I have number 10
     ✓ When I add 5
     ✓ Then the result should be 15
   ...

Step Definitions

Step definitions work exactly the same—the placeholders are replaced before matching:

php
#[Given('I have number {n}')]
public function haveNumber(int $n): void
{
    $this->number = $n;
}

#[When('I add {n}')]
public function add(int $n): void
{
    $this->number += $n;
}

#[Then('the result should be {expected}')]
public function resultShouldBe(int $expected): void
{
    expect($this->number)->toBe($expected);
}

String Values in Examples

For string values, you can include or exclude quotes:

gherkin
Scenario Outline: User greeting
  Given a user "<name>" exists
  Then the greeting should be "<greeting>"

  Examples:
    | name  | greeting      |
    | John  | Hello, John!  |
    | Jane  | Hello, Jane!  |

The quotes in the step are part of the pattern, not the placeholder.

Multiple Examples Tables

You can have multiple Examples tables, often to group related data:

gherkin
Scenario Outline: User permissions
  Given a user with role "<role>"
  When they try to access "<resource>"
  Then access should be "<result>"

  Examples: Admin permissions
    | role  | resource   | result  |
    | admin | dashboard  | granted |
    | admin | settings   | granted |
    | admin | users      | granted |

  Examples: Regular user permissions
    | role | resource   | result  |
    | user | dashboard  | granted |
    | user | settings   | denied  |
    | user | users      | denied  |

Each Examples table can have a name for documentation.

Tags on Examples

Apply tags to specific Examples tables:

gherkin
Scenario Outline: Payment processing
  Given I have <amount> in my account
  When I purchase an item for <price>
  Then my balance should be <balance>

  @smoke
  Examples: Basic transactions
    | amount | price | balance |
    | 100    | 50    | 50      |
    | 200    | 75    | 125     |

  @slow @integration
  Examples: Edge cases
    | amount | price | balance |
    | 0      | 0     | 0       |
    | 1000   | 1000  | 0       |

Run only smoke examples:

bash
pest --bdd --tags="@smoke"

Complex Data Types

Boolean Values

gherkin
Scenario Outline: Feature flags
  Given feature "<feature>" is <enabled>
  Then the feature should be <status>

  Examples:
    | feature     | enabled | status   |
    | dark_mode   | true    | visible  |
    | dark_mode   | false   | hidden   |
    | beta_panel  | yes     | visible  |
    | beta_panel  | no      | hidden   |

Float Values

gherkin
Scenario Outline: Discount calculation
  Given a product priced at <price>
  When I apply a <discount>% discount
  Then the final price should be <final>

  Examples:
    | price  | discount | final  |
    | 100.00 | 10       | 90.00  |
    | 50.00  | 25       | 37.50  |
    | 75.50  | 20       | 60.40  |

Best Practices

Meaningful Example Names

Use descriptive Examples table names:

gherkin
Scenario Outline: Login validation
  Given I enter email "<email>"
  And I enter password "<password>"
  When I submit the login form
  Then I should see "<message>"

  Examples: Valid credentials
    | email            | password | message         |
    | user@example.com | secret   | Welcome back!   |

  Examples: Invalid email formats
    | email          | password | message         |
    | not-an-email   | secret   | Invalid email   |
    | @missing.com   | secret   | Invalid email   |

  Examples: Password requirements
    | email            | password | message              |
    | user@example.com |          | Password required    |
    | user@example.com | short    | Password too short   |

Keep Tables Focused

Each Examples table should test related variations:

Good:

gherkin
Examples: Positive numbers
  | a  | b  | sum |
  | 1  | 2  | 3   |
  | 5  | 5  | 10  |

Examples: Negative numbers
  | a   | b   | sum  |
  | -1  | -2  | -3   |
  | -5  | 10  | 5    |

Avoid: One giant table mixing unrelated test cases.

Use When Data Varies, Not Logic

Scenario Outlines are for varying data, not behavior:

Good use case:

gherkin
Scenario Outline: Currency conversion
  Given I have <amount> <from_currency>
  When I convert to <to_currency>
  Then I should have approximately <result> <to_currency>

  Examples:
    | amount | from_currency | to_currency | result |
    | 100    | USD           | EUR         | 92     |
    | 100    | EUR           | GBP         | 86     |

Better as separate scenarios:

gherkin
# Different logic paths shouldn't be in outlines
Scenario: Login with valid credentials
  Given a valid user
  When I login
  Then I should see dashboard

Scenario: Login with invalid credentials
  Given an invalid user
  When I login
  Then I should see error  # Different outcome, different logic

Complete Example

gherkin
Feature: E-commerce Pricing
  As a customer
  I want to see accurate pricing
  So I can make informed purchases

  Scenario Outline: Product pricing with tax
    Given a product "<product>" priced at $<base_price>
    And the tax rate is <tax_rate>%
    When I view the product
    Then the displayed price should be $<total>

    Examples: US Tax Rates
      | product        | base_price | tax_rate | total  |
      | Wireless Mouse | 29.99      | 8.25     | 32.46  |
      | USB Cable      | 9.99       | 8.25     | 10.81  |
      | Keyboard       | 79.99      | 8.25     | 86.59  |

    Examples: EU Tax Rates
      | product        | base_price | tax_rate | total  |
      | Wireless Mouse | 29.99      | 20       | 35.99  |
      | USB Cable      | 9.99       | 20       | 11.99  |
      | Keyboard       | 79.99      | 20       | 95.99  |

  Scenario Outline: Quantity discounts
    Given I add <quantity> of "<product>" to my cart
    When the discount is calculated
    Then I should receive <discount>% off

    Examples:
      | quantity | product        | discount |
      | 1        | Wireless Mouse | 0        |
      | 5        | Wireless Mouse | 5        |
      | 10       | Wireless Mouse | 10       |
      | 25       | Wireless Mouse | 15       |
php
class PricingSteps
{
    private Product $product;
    private float $taxRate;
    private Cart $cart;

    #[Given('a product {name} priced at ${price}')]
    public function productWithPrice(string $name, float $price): Product
    {
        return $this->product = Product::factory()->create([
            'name' => $name,
            'price' => $price,
        ]);
    }

    #[Given('the tax rate is {rate}%')]
    public function taxRate(float $rate): void
    {
        $this->taxRate = $rate;
    }

    #[Then('the displayed price should be ${expected}')]
    public function displayedPriceShouldBe(float $expected): void
    {
        $calculated = $this->product->price * (1 + $this->taxRate / 100);
        expect(round($calculated, 2))->toBe($expected);
    }

    #[Given('I add {quantity} of {product} to my cart')]
    public function addToCart(int $quantity, string $product): void
    {
        $this->cart = new Cart();
        $item = Product::where('name', $product)->first();
        $this->cart->add($item, $quantity);
    }

    #[Then('I should receive {discount}% off')]
    public function shouldReceiveDiscount(int $discount): void
    {
        expect($this->cart->discountPercentage())->toBe($discount);
    }
}

Released under the MIT License.