docs/step_definitions.md

# Step Definitions

Step definitions connect the Gherkin steps in your feature files to actual code. They're the glue between your natural language specifications and the implementation that tests your application.

## Creating Step Definition Files

Step definitions should be placed in `test/features/step_definitions/` with a `.exs` extension:

```elixir
# test/features/step_definitions/authentication_steps.exs
defmodule AuthenticationSteps do
  use Cucumber.StepDefinition
  import ExUnit.Assertions
  
  # Step definitions go here
end
```

## Basic Step Definition

Step definitions are created using the `step` macro:

```elixir
step "I am logged in as a customer", context do
  # Authentication logic here
  Map.put(context, :user, create_and_login_customer())
end
```

## Steps with Parameters

Cucumber supports several parameter types that can be used in step patterns. Parameters are accessed through pattern matching on the `args` field of the context:

### String Parameters

```elixir
step "I am on the product page for {string}", %{args: [product_name]} do
  # Navigate to product page
  %{current_page: :product, product_name: product_name}
end
```

### Integer Parameters

```elixir
step "I should have {int} items in my wishlist", %{args: [expected_count]} = context do
  # Assertion for wishlist count
  assert get_wishlist_count(context) == expected_count
  context
end
```

### Float Parameters

```elixir
step "the total price should be {float}", %{args: [expected_total]} = context do
  # Assertion for price
  assert_in_delta get_cart_total(context), expected_total, 0.01
  context
end
```

### Word Parameters

```elixir
step "I should see the {word} dashboard", %{args: [dashboard_type]} = context do
  # Assertion for dashboard type
  assert get_current_dashboard(context) == dashboard_type
  context
end
```

### Multiple Parameters

When a step has multiple parameters, you can pattern match on all of them:

```elixir
step "I transfer {float} from {string} to {string}", %{args: [amount, from_account, to_account]} do
  # Transfer logic
  %{transfer: %{amount: amount, from: from_account, to: to_account}}
end
```

## Working with Data Tables

In your feature file:
```gherkin
Given I have the following items in my cart:
  | Product Name    | Quantity | Price |
  | Smartphone      | 1        | 699.99|
  | Protection Plan | 1        | 79.99 |
```

In your step definitions:
```elixir
step "I have the following items in my cart:", context do
  # Access the datatable
  datatable = context.datatable

  # Access headers
  headers = datatable.headers  # ["Product Name", "Quantity", "Price"]
  
  # Access rows as maps
  items = datatable.maps
  # [
  #   %{"Product Name" => "Smartphone", "Quantity" => "1", "Price" => "699.99"},
  #   %{"Product Name" => "Protection Plan", "Quantity" => "1", "Price" => "79.99"}
  # ]
  
  # Process the items
  Map.put(context, :cart_items, items)
end
```

## Working with DocStrings

DocStrings allow you to pass multi-line text to a step:

In your feature file:
```gherkin
When I submit the following JSON:
  """
  {
    "name": "Test Product",
    "price": 29.99,
    "available": true
  }
  """
```

In your step definitions:
```elixir
step "I submit the following JSON:", context do
  # The docstring is available in context.docstring
  json_data = Jason.decode!(context.docstring)
  
  # Process the JSON
  Map.put(context, :submitted_data, json_data)
end
```

## Return Values

Step definitions must return one of the following values (matching ExUnit's setup behavior):

- `:ok` - Keeps the context unchanged
- A map - Merged into the existing context
- A keyword list - Merged into the existing context
- `{:ok, map_or_keyword_list}` - Merged into the existing context
- `{:error, reason}` - Fails the step with the given reason

## Reusable Step Definitions

You can create reusable step definitions that can be shared across multiple features:

```elixir
# test/features/step_definitions/common_steps.exs
defmodule CommonSteps do
  use Cucumber.StepDefinition
  import ExUnit.Assertions
  
  step "I wait {int} seconds", %{args: [seconds]} = context do
    Process.sleep(seconds * 1000)
    context
  end
  
  step "I should see {string}", %{args: [text]} = context do
    assert page_contains_text?(context, text)
    context
  end
end
```

## Best Practices

1. **Keep steps focused**: Each step should do one thing well
2. **Use descriptive step patterns**: Make your steps readable and self-documenting
3. **Share common steps**: Create reusable step definitions for common actions
4. **Handle errors gracefully**: Return `{:error, reason}` for expected failures
5. **Maintain context**: Always return the context (or `:ok`) to maintain state between steps