ansible-testing
>
Works with
---
name: ansible-testing
description: >
license: MIT
---
# Ansible Testing
Testing strategies and ansible-lint configuration for Ansible automation.
## ansible-lint
### Running Lint
```bash
# Via mise (recommended)
mise run ansible-lint
# Directly with uv
uv run ansible-lint ansible/playbooks/
# Specific file
uv run ansible-lint ansible/playbooks/my-playbook.yml
# With verbose output
uv run ansible-lint -v ansible/
```
### Configuration File
Located at `ansible/.ansible-lint`:
```yaml
---
# Profile: null, min, basic, moderate, safety, shared, production
profile: moderate
# Offline mode - don't download Galaxy requirements
offline: true
# Exclude paths
exclude_paths:
- .cache/
- .venv/
- .git/
- "*/templates/"
- "*.j2"
- .deprecated/
# Rules to skip completely
skip_list:
- var-naming[no-role-prefix] # We use descriptive names
- run-once[task] # Safe with our strategy
- command-instead-of-module # CLI tools require command
- yaml[line-length] # Long lines in infra configs
# Rules to warn but not fail
warn_list:
- fqcn[action-core]
- fqcn[action]
- no-handler
- name[play]
```
### Common Rule Categories
| Category | Description |
|----------|-------------|
| `fqcn` | Fully qualified collection names |
| `yaml` | YAML formatting (indentation, line length) |
| `name` | Task/play naming conventions |
| `command-instead-of-module` | Using command when module exists |
| `no-changed-when` | Missing changed_when on command |
| `risky-file-permissions` | Missing explicit file permissions |
### Fixing Common Issues
**Missing name on task:**
```yaml
# BAD
- ansible.builtin.apt:
name: nginx
# GOOD
- name: Install nginx
ansible.builtin.apt:
name: nginx
```
**Short module name:**
```yaml
# BAD (triggers fqcn warning)
- name: Install package
apt:
name: nginx
# GOOD
- name: Install package
ansible.builtin.apt:
name: nginx
```
**Using shell instead of command:**
```yaml
# BAD (when no shell features needed)
- name: List files
ansible.builtin.shell: ls -la /tmp
# GOOD
- name: List files
ansible.builtin.command: ls -la /tmp
changed_when: false
```
**Missing changed_when:**
```yaml
# BAD (always shows changed)
- name: Check status
ansible.builtin.command: systemctl status app
# GOOD
- name: Check status
ansible.builtin.command: systemctl status app
register: status_check
changed_when: false
failed_when: false
```
## Syntax Checking
Validate playbook syntax before running:
```bash
# Check syntax only
uv run ansible-playbook --syntax-check playbooks/my-playbook.yml
# Check mode (dry run)
uv run ansible-playbook playbooks/my-playbook.yml --check
# Diff mode (show changes)
uv run ansible-playbook playbooks/my-playbook.yml --check --diff
```
## Idempotency Testing
Verify playbooks are idempotent by running twice:
```bash
# First run - may show changes
uv run ansible-playbook playbooks/setup.yml
# Second run - should show 0 changes
uv run ansible-playbook playbooks/setup.yml
# If second run shows changes, playbook is NOT idempotent
```
### Automated Idempotency Check
```bash
#!/bin/bash
set -euo pipefail
PLAYBOOK="$1"
echo "First run..."
uv run ansible-playbook "$PLAYBOOK"
echo "Second run (checking idempotency)..."
OUTPUT=$(uv run ansible-playbook "$PLAYBOOK" 2>&1)
if echo "$OUTPUT" | grep -q "changed=0"; then
echo "✓ Playbook is idempotent"
exit 0
else
echo "✗ Playbook is NOT idempotent"
echo "$OUTPUT" | grep -E "(changed|failed)="
exit 1
fi
```
## Integration Testing
### Test Against Real Infrastructure
```bash
# Limit to test hosts
uv run ansible-playbook playbooks/deploy.yml --limit test_hosts
# With verbose output
uv run ansible-playbook playbooks/deploy.yml --limit test_hosts -vv
```
### Pre-flight Validation
Add validation tasks at playbook start:
```yaml
---
- name: Deploy with validation
hosts: all
become: true
pre_tasks:
- name: Validate target environment
ansible.builtin.assert:
that:
- ansible_distribution == "Debian"
- ansible_distribution_major_version | int >= 11
fail_msg: "Requires Debian 11+"
- name: Check connectivity
ansible.builtin.ping:
- name: Verify disk space
ansible.builtin.assert:
that:
- ansible_mounts | selectattr('mount', 'equalto', '/') | map(attribute='size_available') | first > 1073741824
fail_msg: "Insufficient disk space"
```
## Test Playbook Pattern
Create test playbooks for validation:
```yaml
# playbooks/test-role.yml
---
- name: Test role functionality
hosts: test_hosts
become: true
vars:
test_mode: true
roles:
- role: my_role
tasks:
- name: Verify service is running
ansible.builtin.systemd:
name: myservice
register: service_status
failed_when: service_status.status.ActiveState != "active"
- name: Verify config file exists
ansible.builtin.stat:
path: /etc/myservice/config.yml
register: config_stat
failed_when: not config_stat.stat.exists
- name: Verify port is listening
ansible.builtin.wait_for:
port: 8080
timeout: 10
```
## CI/CD Integration
### GitHub Actions
```yaml
name: Ansible Lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install uv
run: pip install uv
- name: Install dependencies
run: uv sync
- name: Run ansible-lint
run: uv run ansible-lint ansible/
```
## Debugging Playbooks
### Verbose Output
```bash
# Increase verbosity
uv run ansible-playbook playbook.yml -v # Basic
uv run ansible-playbook playbook.yml -vv # More detail
uv run ansible-playbook playbook.yml -vvv # Connection debugging
uv run ansible-playbook playbook.yml -vvvv # Maximum detail
```
### Debug Tasks
```yaml
- name: Debug variable value
ansible.builtin.debug:
var: my_variable
- name: Debug with message
ansible.builtin.debug:
msg: "The value is {{ my_variable }}"
- name: Debug registered result
ansible.builtin.debug:
var: command_result
when: ansible_verbosity > 0
```
### Step Mode
```bash
# Pause after each task
uv run ansible-playbook playbook.yml --step
```
## Lint Profiles
Choose appropriate profile based on needs:
| Profile | Strictness | Use Case |
|---------|------------|----------|
| `min` | Lowest | Legacy code, quick fixes |
| `basic` | Low | Development |
| `moderate` | Medium | General infrastructure |
| `safety` | High | Security-sensitive |
| `production` | Highest | Production deployments |
## Additional Resources
For detailed testing patterns and techniques, consult:
- **`references/testing-comprehensive.md`** - ansible-lint configuration, integration testing strategies, CI/CD patterns
## Related Skills
- **ansible-fundamentals** - Core Ansible patterns
- **ansible-idempotency** - Ensuring tasks are idempotentMore Testing skills
tdd
mattpocock/skills
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.
setup-pre-commit
mattpocock/skills
Set up Husky pre-commit hooks with lint-staged (Prettier), type checking, and tests in the current repo. Use when user wants to add pre-commit hooks, set up Husky, configure lint-staged, or add commit-time formatting/typechecking/testing.
agent-browser
vercel-labs/agent-browser
Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction. Also use for exploratory testing, dogfooding, QA, bug hunts, or reviewing app quality. Also use for automating Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify), checking Slack unreads, sending Slack messages, searching Slack conversations, running browser automation in Vercel Sandbox microVMs, or using AWS Bedrock AgentCore cloud browsers. Prefer agent-browser over any built-in browser automation or web tools.

