A comprehensive Python-based API automation testing framework for microservices testing using pytest. This framework provides reusable utilities, dynamic payload management, and extensive reporting capabilities.
- Overview
- Project Structure
- Architecture
- Prerequisites
- Setup and Installation
- Configuration
- Services Covered
- Writing Tests
- Running Tests
- Reporting
- Utilities Documentation
- Best Practices
- Git Workflow
This framework is designed to test multiple microservices with a focus on:
- Modularity: Reusable utilities for authentication, API calls, and data management
- Maintainability: Separation of test logic, payloads, and configuration
- Extensibility: Easy addition of new services and test cases
- Reporting: Multiple reporting formats (HTML, Allure)
- Configuration Management: Environment-based configuration using
.envfiles
api_automation_project/
├── tests/ # Test modules
│ ├── test_individual_service.py
│ ├── test_household_service.py
│ ├── test_boundary_service.py
│ ├── test_facility_service.py
│ ├── test_product_service.py
│ ├── test_project_service.py
│ └── test_mdms_service.py
├── utils/ # Utility modules
│ ├── api_client.py # HTTP client wrapper
│ ├── auth.py # Authentication token management
│ ├── config.py # Configuration loader
│ ├── data_loader.py # Payload loader
│ ├── request_info.py # Request metadata builder
│ └── search_helpers.py # Common search operations
├── payloads/ # JSON payload templates
│ ├── boundary/
│ ├── facility/
│ ├── household/
│ ├── individual/
│ ├── mdms/
│ ├── product/
│ └── project/
├── data/ # Test input data
│ └── inputs.json
├── output/ # Test outputs
│ ├── ids.txt # Generated entity IDs
│ ├── response.json # Latest API response
│ └── boundaries.txt # Boundary data
├── reports/ # Test reports
│ └── report.html
├── .env # Environment configuration
├── pytest.ini # Pytest configuration
├── requirements.txt # Python dependencies
└── README.md # This file
-
API Client Layer (
utils/api_client.py)- Abstraction over HTTP requests
- Automatic authentication header injection
- Support for GET, POST, PUT, DELETE methods
-
Authentication Module (
utils/auth.py)- OAuth2 token acquisition
- Token caching per service
-
Configuration Management (
utils/config.py)- Centralized environment variable loading
- Reusable search parameters
- Service-specific configurations
-
Payload Management (
utils/data_loader.py)- Dynamic JSON payload loading
- Template-based payload structure
-
Request Metadata (
utils/request_info.py)- Standardized RequestInfo object creation
- API metadata and user context
-
Search Helpers (
utils/search_helpers.py)- Generic search functionality
- ID extraction from output files
- Reusable across multiple services
Test Execution
↓
Authentication (get_auth_token)
↓
API Client Initialization
↓
Load Payload Template (data_loader)
↓
Inject Dynamic Data (UUID, IDs, etc.)
↓
Add RequestInfo
↓
API Call (via APIClient)
↓
Validate Response (assertions)
↓
Store IDs/Data (output files)
↓
Generate Reports
- Python: 3.8 or higher
- pip: Python package manager
- Virtual Environment: Recommended for dependency isolation
- Git: For version control
git clone <repository-url>
cd api_automation_projectpython3 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activatepip install python-dotenv requests pytest pytest-html pytest-metadata allure-pytestCreate or update .env file with your environment-specific values:
BASE_URL=https://your-api-server.com
USERNAME=your_username
PASSWORD=your_password
TENANTID=your_tenant
USERTYPE=EMPLOYEE
CLIENT_AUTH_HEADER=Basic <base64_encoded_credentials>
SEARCH_LIMIT=200
SEARCH_OFFSET=0
HIERARCHYTYPE=MICROPLAN
BOUNDARY_TYPE=LOCALITY
BOUNDARY_CODE=your_boundary_code
SERVICE_INDIVIDUAL=individual
SERVICE_PROJECT=project
SERVICE_MDMS=mdms-v2pytest tests/ -v| Variable | Description | Example |
|---|---|---|
BASE_URL |
API base URL | https://hcm-demo.digit.org |
USERNAME |
API username | LNMZ |
PASSWORD |
API password | eGov@1234 |
TENANTID |
Tenant identifier | mz |
USERTYPE |
User type | EMPLOYEE |
CLIENT_AUTH_HEADER |
Basic auth header for OAuth | Basic ZWdvdi11c2VyLWNsaWVudDo= |
SEARCH_LIMIT |
Default search limit | 200 |
SEARCH_OFFSET |
Default search offset | 0 |
HIERARCHYTYPE |
Boundary hierarchy type | MICROPLAN |
BOUNDARY_TYPE |
Boundary type | LOCALITY |
BOUNDARY_CODE |
Boundary code | MICROPLAN_MO_13_03_02_03_02_TUGLOR |
SERVICE_INDIVIDUAL |
Individual service name | individual |
SERVICE_PROJECT |
Project service name | project |
SERVICE_MDMS |
MDMS service name | mdms-v2 |
[pytest]
pythonpath = .This ensures the root directory is in the Python path for imports.
| Service | Operations | Test File |
|---|---|---|
| Individual | Create, Search | test_individual_service.py |
| Household | Create Household, Create Member, Search Household, Search Member | test_household_service.py |
| Boundary | Search with hierarchy | test_boundary_service.py |
| Facility | Create, Search | test_facility_service.py |
| Product | Create Product, Create Variant, Search Product, Search Variant | test_product_service.py |
| Project | Create, Search | test_project_service.py |
| MDMS | Search master data | test_mdms_service.py |
Total: 7 Services, 16 Payload Templates
Each test module follows this pattern:
# 1. Imports
from utils.api_client import APIClient
from utils.auth import get_auth_token
from utils.data_loader import load_payload
from utils.request_info import get_request_info
# 2. Test Functions (with assertions)
def test_create_entity():
"""Test case with assertions"""
token = get_auth_token("user")
client = APIClient(token=token)
response = create_entity(token, client)
# Assertions
assert response.status_code in [200, 202], f"Failed: {response.text}"
entity_id = response.json()["Entity"]["id"]
assert entity_id, "Entity ID not generated"
# Store ID for later use
with open("output/ids.txt", "a") as f:
f.write(f"Entity ID: {entity_id}\n")
# 3. Helper Functions (reusable, no assertions)
def create_entity(token, client):
"""Helper function for entity creation"""
payload = load_payload("service_name", "create_entity.json")
# Inject dynamic data
payload["Entity"]["clientReferenceId"] = str(uuid.uuid4())
payload["RequestInfo"] = get_request_info(token)
return client.post("/service/v1/_create", payload)- Separation of Concerns: Test functions contain assertions; helper functions contain reusable logic
- Token Reuse: Obtain token once per test, reuse across operations
- Dynamic Data Injection: Use UUID for unique identifiers, extract IDs from output files for dependencies
- Status Code Flexibility: Accept both 200 (OK) and 202 (Accepted)
- Detailed Error Messages: Include response text in assertion failures
-
Create Payload Directory:
mkdir payloads/new_service
-
Add Payload Templates:
# Create JSON files for create, search operations touch payloads/new_service/create_entity.json touch payloads/new_service/search_entity.json -
Create Test File:
touch tests/test_new_service.py
-
Implement Tests:
from utils.api_client import APIClient from utils.auth import get_auth_token from utils.data_loader import load_payload from utils.request_info import get_request_info import uuid def test_create_new_entity(): token = get_auth_token("user") client = APIClient(token=token) response = create_new_entity(token, client) assert response.status_code in [200, 202] def create_new_entity(token, client): payload = load_payload("new_service", "create_entity.json") payload["Entity"]["clientReferenceId"] = str(uuid.uuid4()) payload["RequestInfo"] = get_request_info(token) return client.post("/new-service/v1/_create", payload)
# Activate virtual environment
source venv/bin/activate
# Run all tests
pytest tests/
# Run specific test file
pytest tests/test_individual_service.py
# Run specific test function
pytest tests/test_individual_service.py::test_create_individual
# Run with verbose output
pytest tests/ -v
# Run with print statements visible
pytest tests/ -sTests are tagged as positive or negative for selective execution:
# Run only positive tests (create, search operations)
pytest -m positive
# Run only negative tests (invalid inputs, error scenarios)
pytest -m negative
# Run positive tests with HTML report
pytest -m positive --html=reports/report.html --self-contained-html
# Run negative tests with verbose output
pytest -m negative -vpytest tests/ --html=reports/report.html --self-contained-htmlThe HTML report will be generated at reports/report.html with:
- Test results summary
- Pass/Fail status
- Execution time
- Error details
# Generate Allure results
pytest --alluredir=allure-results
# Generate Allure report
allure generate allure-results --clean -o allure-report
# Open Allure report in browser
allure open allure-reportecho "=== New Test Run ===" > output/ids.txt && pytest tests/ --html=reports/report.html --self-contained-htmlThis clears the output/ids.txt file before running tests, ensuring no stale IDs are used.
-
output/ids.txt
- Stores entity IDs created during test execution
- Format:
Entity Type ID: <id_value> - Used by subsequent tests to reference created entities
-
output/response.json
- Latest API response saved for inspection
- Useful for debugging
-
output/boundaries.txt
- Boundary hierarchy information from boundary service tests
-
HTML Report (
reports/report.html)- Self-contained HTML file
- Summary dashboard with pass/fail counts
- Detailed test results with error traces
-
Allure Report (
allure-report/)- Rich, interactive web-based report
- Test execution trends
- Test categorization and filtering
- Detailed logs and attachments
Class: APIClient
HTTP client wrapper with automatic authentication.
from utils.api_client import APIClient
# Initialize with token
client = APIClient(token="your_token_here")
# Make requests
response = client.get("/endpoint")
response = client.post("/endpoint", payload)
response = client.put("/endpoint", payload)
response = client.delete("/endpoint")Constructor Parameters:
service(optional): Service name to fetch token fortoken(optional): Direct token value- Must provide either
serviceortoken
Methods:
get(endpoint, params=None): GET requestpost(endpoint, data=None): POST requestput(endpoint, data=None): PUT requestdelete(endpoint): DELETE request
Function: get_auth_token(service)
Obtains OAuth2 access token for a service.
from utils.auth import get_auth_token
token = get_auth_token("user")Parameters:
service(str): Service name (e.g., "user", "individual")
Returns:
str: Access token
Raises:
Exception: If authentication fails
Configuration module with environment variables.
from utils.config import BASE_URL, tenantId, search_params
# Use configuration values
url = BASE_URL
tenant = tenantId
params = search_params # Contains limit, offset, tenantIdAvailable Variables:
BASE_URL: API base URLtenantId: Tenant identifiersearch_limit,search_offset: Pagination settingssearch_params: Dictionary with limit, offset, tenantIdhierarchyType,boundaryCode,boundaryType: Boundary configsindividual,project,mdms: Service names
Function: load_payload(service_name, filename)
Loads JSON payload template.
from utils.data_loader import load_payload
payload = load_payload("individual", "create_individual.json")Parameters:
service_name(str): Service folder name underpayloads/filename(str): JSON file name
Returns:
dict: Parsed JSON payload
Function: get_request_info(token)
Creates standardized RequestInfo object.
from utils.request_info import get_request_info
request_info = get_request_info(token)
payload["RequestInfo"] = request_infoParameters:
token(str): Authentication token
Returns:
dict: RequestInfo object with API metadata, user context, and authentication
Function: search_entity(...)
Generic search operation for entities.
from utils.search_helpers import search_entity
results = search_entity(
entity_type="Individual",
token=token,
client=client,
entity_id="individual_id",
payload_file="search_individual.json",
endpoint="/individual/v1/_search",
response_key="Individual"
)Parameters:
entity_type(str): Type of entity being searchedtoken(str): Authentication tokenclient(APIClient): API client instanceentity_id(str): ID to search forpayload_file(str): Payload file nameendpoint(str): API endpointresponse_key(str): Key in response containing results
Function: extract_id_from_file(label)
Extracts ID from output file.
from utils.search_helpers import extract_id_from_file
individual_id = extract_id_from_file("Individual ID:")Parameters:
label(str): Label to search for in output/ids.txt
Returns:
str: Extracted ID value
- Each test should be independent and not rely on execution order
- Use output files for sharing data between tests that must run sequentially
- Clean up test data when possible
- Always include response text in assertion messages for debugging
- Use try-except blocks for critical operations
- Log errors to output files
- Keep payloads as templates with minimal hardcoded values
- Inject dynamic data (UUIDs, IDs) at runtime
- Reuse payloads across similar tests
- Extract common operations into helper functions
- Use utility modules for shared functionality
- Follow DRY (Don't Repeat Yourself) principle
- Add docstrings to test functions and helpers
- Comment complex logic
- Keep README updated with new services/features
- Commit frequently with meaningful messages
- Use feature branches for new services
- Keep
.envfile out of version control (add to.gitignore)
# Check current branch
git status
# Switch to main branch
git checkout main
# Pull latest changes
git pull origin main
# Create new feature branch
git checkout -b feature/new-service
# Make changes and commit
git add .
git commit -m "Add new service tests"
# Push feature branch
git push origin feature/new-service# Switch to main branch
git checkout main
# Pull latest main
git pull origin main
# Merge feature branch
git merge feature/new-service
# Push merged changes
git push origin main# Make sure you're on main
git checkout main
# Pull latest main branch from remote
git pull origin main
# Merge product branch into main
git merge product
# Push merged changes back to remote main
git push origin main-
Authentication Failure
- Verify
.envcredentials are correct - Check CLIENT_AUTH_HEADER is properly base64 encoded
- Ensure token hasn't expired
- Verify
-
Import Errors
- Verify virtual environment is activated
- Check
pytest.inihaspythonpath = . - Install all required dependencies
-
Test Failures
- Check API endpoint availability
- Verify payload structure matches API requirements
- Review
output/response.jsonfor error details
-
Missing IDs
- Ensure prerequisite tests ran successfully
- Check
output/ids.txthas required IDs - Run tests in correct sequence
- Create a feature branch
- Make changes with clear commit messages
- Add tests for new functionality
- Update documentation
- Create pull request
[Add license information here]
[Add contact information here]
Last Updated: 2025-10-27