AnsiblePlayTest is a powerful framework for testing Ansible playbooks using scenario-based approaches. This guide will help you get started with creating and running your first tests.
- Prerequisites
- Installation
- Quick Start
- Basic Concepts
- Your First Test
- Project Structure
- Running Tests
- Command Line Interface
- Next Steps
Before getting started, ensure you have:
- Python 3.10 or higher
- Ansible 2.9.0 or higher
- Basic understanding of Ansible playbooks and YAML
pip install ansible-playtestgit clone <repository-url>
cd ansible-playtest
pip install -e .Here's a minimal example to get you started quickly:
import pytest
@pytest.mark.playbooks_dir("playbooks")
@pytest.mark.inventory_path("tests/inventory/hosts.ini")
@pytest.mark.scenarios_dir("tests/scenarios/my_scenario.yml")
def test_my_playbook(playbook_path, scenario_path, playbook_runner):
"""Test that demonstrates basic playbook execution."""
assert playbook_runner.success, f"Playbook failed: {playbook_runner.execution_details}"Scenarios are YAML files that define:
- Mock responses for external services
- Verification criteria to validate playbook behavior
- Expected outcomes (success or failure)
Replace Ansible modules with mock implementations to:
- Avoid side effects during testing
- Control module behavior and responses
- Test error conditions
Verify different aspects of playbook execution:
- Module call counts
- Parameter validation
- Call sequences
- Error handling
Let's create a complete example from scratch.
Create the following directory structure:
my_ansible_project/
├── playbooks/
│ └── hello_world.yml
├── tests/
│ ├── test_hello_world.py
│ ├── inventory/
│ │ └── hosts.ini
│ └── scenarios/
│ └── hello_world_scenario.yml
└── pytest.ini
playbooks/hello_world.yml:
---
- name: Hello World Playbook
hosts: localhost
gather_facts: false
tasks:
- name: Ping the host
ansible.builtin.ping:
register: ping_result
- name: Display greeting
ansible.builtin.debug:
msg: "Hello, World! Ping was successful: {{ ping_result.ping }}"
- name: Create a file
ansible.builtin.copy:
content: "Hello from Ansible!"
dest: "/tmp/hello.txt"tests/inventory/hosts.ini:
[test_hosts]
localhost ansible_connection=localtests/scenarios/hello_world_scenario.yml:
---
name: "Hello World Test Scenario"
description: "Tests basic playbook functionality with mocked modules"
playbook: "hello_world.yml"
# Mock responses for modules
service_mocks:
"ansible.builtin.copy":
changed: true
dest: "/tmp/hello.txt"
mode: "0644"
# Verification criteria
verify:
# Verify expected module calls
expected_calls:
"ansible.builtin.ping": 1
"ansible.builtin.debug": 1
"ansible.builtin.copy": 1
# Verify parameters passed to modules
parameter_validation:
ansible.builtin.copy:
- content: "Hello from Ansible!"
dest: "/tmp/hello.txt"tests/test_hello_world.py:
import pytest
@pytest.mark.playbooks_dir("playbooks")
@pytest.mark.inventory_path("tests/inventory/hosts.ini")
class TestHelloWorld:
"""Test class for Hello World playbook."""
@pytest.mark.scenarios_dir("tests/scenarios/hello_world_scenario.yml")
def test_hello_world_playbook(self, playbook_path, scenario_path, playbook_runner):
"""Test the Hello World playbook execution."""
# The playbook_runner fixture automatically runs the playbook with the scenario
assert playbook_runner.success, (
f"Playbook execution failed: {playbook_runner.execution_details}"
)
# Access execution details
print(f"Playbook success: {playbook_runner.execution_details['playbook_success']}")
print(f"Verification passed: {playbook_runner.execution_details['verification_passed']}")pytest.ini:
[tool:pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -v --tb=shortA typical AnsiblePlayTest project follows this structure:
project/
├── playbooks/ # Your Ansible playbooks
│ ├── main_playbook.yml
│ └── library/ # Custom modules (optional)
│ └── custom_module.py
├── tests/ # Test files and data
│ ├── test_main.py # Test implementations
│ ├── inventory/ # Test inventories
│ │ └── hosts.ini
│ ├── scenarios/ # Test scenarios
│ │ ├── scenario_01.yml
│ │ └── scenario_02.yml
│ └── mocks/ # Module mocks (optional)
│ ├── ansible.builtin.uri.py
│ └── ansible.builtin.copy.py
├── ansible.cfg # Ansible configuration
├── pytest.ini # pytest configuration
└── requirements.txt # Python dependencies
Run all tests:
pytestRun specific test:
pytest tests/test_hello_world.py::TestHelloWorld::test_hello_world_playbookRun with verbose output:
pytest -v -sCreate custom mock implementations for specific modules:
tests/mocks/ansible.builtin.uri.py:
#!/usr/bin/env python3
def main():
return {
"changed": False,
"status": 200,
"json": {"message": "Mocked API response"},
"content": '{"message": "Mocked API response"}'
}
if __name__ == "__main__":
print(main())Test email functionality with the built-in SMTP mock:
@pytest.mark.smtp_mock_server(port=1025)
def test_email_notification(self, playbook_path, scenario_path, smtp_mock_server, playbook_runner):
assert playbook_runner.success
# smtp_mock_server provides access to sent emailsIsolate your tests with virtual environments:
@pytest.mark.use_virtualenv
@pytest.mark.requirements_file("requirements-test.txt")
def test_with_isolated_environment(self, playbook_path, scenario_path, playbook_runner):
assert playbook_runner.success# scenario.yml
---
name: "Error Test Scenario"
expected_failure: true # Expect the playbook to fail
verify:
expected_calls:
"ansible.builtin.fail": 1@pytest.mark.parametrize("scenario_file", [
"scenario_dev.yml",
"scenario_prod.yml",
"scenario_test.yml"
])
def test_multiple_environments(self, scenario_file, playbook_runner):
assert playbook_runner.success# scenario.yml
verify:
sequence_validation:
- "ansible.builtin.debug"
- "ansible.builtin.uri"
- "ansible.builtin.copy"- Module not found: Ensure your collections path is set correctly
- Scenario not found: Check the relative path to your scenario files
- Verification failures: Review the verification criteria in your scenario
- Permission errors: Ensure proper file permissions for temporary directories
Increase verbosity to see more details:
pytest -v -s # For pytestNow that you've created your first test, explore these advanced topics:
- Scenario Configuration - Learn about advanced scenario options
- Verification Strategies - Understand different verification methods
- Mock Collections - Create reusable mock collections
- SMTP Server Mocking - Test email functionality
- Using Markers - Leverage pytest markers for configuration
- Keep scenarios focused on specific functionality
- Use descriptive names for tests and scenarios
- Mock external dependencies to avoid side effects
- Verify both positive and negative test cases
- Use virtual environments for isolated testing
- Document your test scenarios clearly
Happy testing with AnsiblePlayTest! 🚀