Skip to content

GManjunathRavana/Spotify_API_testing

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Spotify API Test Framework - Developer Guide

Version: 2.4.0 Last Updated: 2025-10-12 Maintained By: QA Engineering Team


Table of Contents

  1. Overview
  2. Getting Started
  3. Architecture
  4. Development Guidelines
  5. Testing Strategy
  6. Configuration Management
  7. Authentication System
  8. Writing Tests
  9. Troubleshooting Guide
  10. Best Practices
  11. Contributing

Overview

This is a production-grade API test automation framework for the Spotify Web API. The framework provides comprehensive test coverage across functional, integration, and end-to-end scenarios, with support for both client credentials and OAuth authentication flows.

Key Features

  • Dual Authentication Support: Seamlessly handles both Client Credentials and OAuth 2.0 Authorization Code flows
  • Multi-Environment Configuration: QA, Staging, and Production environment support with hierarchical configuration
  • Intelligent Test Organization: Marker-based test categorization for flexible execution
  • Automatic Token Management: Token refresh and expiration handling built-in
  • Comprehensive Error Handling: Retry logic with exponential backoff and rate limiting support
  • Production-Ready Reporting: Allure integration with detailed test reports

Technology Stack

  • Language: Python 3.9+
  • Test Framework: Pytest 8.3.2
  • HTTP Client: Requests with retry mechanisms
  • Configuration: YAML-based hierarchical config
  • Reporting: Allure, HTML, JUnit XML
  • Security: Environment variable-based credential management

Getting Started

Prerequisites

# Python 3.9 or higher
python --version

# pip package manager
pip --version

Installation

# Clone the repository
git clone <repository-url>
cd spotify_api_automation_enhanced_2_4

# Create virtual environment (recommended)
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install dependencies
pip install -r requirements.txt

Initial Configuration

  1. Copy environment template:
cp .env.example .env
  1. Configure credentials in .env:
SPOTIFY_CLIENT_ID=your_client_id_here
SPOTIFY_CLIENT_SECRET=your_client_secret_here
SPOTIFY_REDIRECT_URI=http://127.0.0.1:8080/callback
ENVIRONMENT=qa
  1. Verify setup:
python verify_config.py

You should see all configuration checks passing.

Running Your First Test

# Run functional tests (no OAuth required)
pytest tests/functional/albums/test_album_retrieval.py -v

# Expected output:
# βœ“ test_get_album_by_id_success PASSED
# βœ“ test_get_multiple_albums_success PASSED

Architecture

High-Level Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                 Test Layer                          β”‚
β”‚  (tests/functional, integration, e2e)               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            Pytest Fixtures (conftest.py)            β”‚
β”‚  - spotify_client (auth flow selection)             β”‚
β”‚  - test_config (environment configuration)          β”‚
β”‚  - test_user_data (dynamic user data)               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         SpotifyAPIClient (orchestrator)             β”‚
β”‚  libs/spotify_client.py                             β”‚
β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”˜
     β”‚          β”‚          β”‚          β”‚           β”‚
β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β” β”Œβ”€β”€β–Όβ”€β”€β”€β” β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β” β”Œβ”€β”€β–Όβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”€β”
β”‚ Albums  β”‚ β”‚Tracksβ”‚ β”‚Playlistsβ”‚ β”‚Search β”‚ β”‚ Users  β”‚
β”‚ Client  β”‚ β”‚Clientβ”‚ β”‚  Client β”‚ β”‚Client β”‚ β”‚Client  β”‚
β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”¬β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
     β”‚          β”‚          β”‚           β”‚         β”‚
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                         β”‚
           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
           β”‚   SpotifyBaseClient       β”‚
           β”‚  libs/base_client.py      β”‚
           β”‚  (HTTP operations, retry) β”‚
           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                         β”‚
           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
           β”‚  SpotifyAuthManager       β”‚
           β”‚     libs/auth.py          β”‚
           β”‚  (authentication bridge)  β”‚
           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                         β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚                                 β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Client          β”‚          β”‚ OAuth Handler        β”‚
β”‚ Credentials     β”‚          β”‚ (authorization_code) β”‚
β”‚ Flow            β”‚          β”‚ + Token Refresh      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Core Components

1. SpotifyAPIClient (libs/spotify_client.py)

The main entry point for all API interactions. Orchestrates authentication and provides organized access to endpoint-specific clients.

# Initialization
client = SpotifyAPIClient(config, auth_flow='client_credentials')
client.authenticate()

# Access endpoint clients
album = client.albums.get_album("album_id")
tracks = client.search.search("query", ["track"])
profile = client.users.get_current_user_profile()

2. Endpoint Clients (libs/endpoints/)

Domain-specific clients that encapsulate endpoint logic:

  • AlbumsClient: Album retrieval, multiple albums
  • TracksClient: Track details, audio features
  • PlaylistsClient: Create, update, delete playlists
  • SearchClient: Search across different types
  • UsersClient: User profile, playlists

3. SpotifyBaseClient (libs/base_client.py)

Handles HTTP operations with built-in:

  • Retry logic (exponential backoff for 5xx errors)
  • Rate limiting (respects Retry-After header)
  • Error handling (custom exceptions)
  • Request/response logging

4. Authentication System (libs/auth.py, libs/advanced_auth.py)

Dual authentication architecture:

# SpotifyAuthManager (libs/auth.py)
# - Orchestrates authentication flows
# - Authentication bridge pattern
# - Token validation and refresh

# SpotifyAdvancedAuth (libs/advanced_auth.py)
# - OAuth 2.0 implementation
# - Browser-based authorization
# - Token storage in .auth_cache/

Key Innovation: Authentication Bridge

# libs/auth.py:397-439
def _load_tokens_from_advanced_auth(self):
    """Load OAuth tokens from advanced_auth storage."""
    # Reads tokens from .auth_cache/tokens_{hash}.json
    # Transfers to SpotifyOAuthHandler for unified token management

5. Configuration System (utils/config_loader.py)

Hierarchical configuration with three layers:

base.yaml (defaults)
    ↓
environment.yaml (qa/staging/prod overrides)
    ↓
Environment variables (runtime overrides)

Development Guidelines

Code Style

We follow PEP 8 with these specific conventions:

# Function naming: snake_case
def get_album_by_id(album_id: str) -> Dict[str, Any]:
    pass

# Class naming: PascalCase
class SpotifyAPIClient:
    pass

# Constants: UPPER_SNAKE_CASE
MAX_RETRIES = 3
DEFAULT_TIMEOUT = 30

# Private methods: _leading_underscore
def _handle_error(self, response):
    pass

Type Hints

All public functions must include type hints:

from typing import Dict, List, Optional, Any

def search(
    self,
    query: str,
    types: List[str],
    limit: int = 20,
    offset: int = 0
) -> Dict[str, Any]:
    """Search for items on Spotify.

    Args:
        query: Search query string
        types: List of item types to search
        limit: Maximum number of results (1-50)
        offset: Index of first result to return

    Returns:
        Dict containing search results

    Raises:
        SpotifyAPIError: If search request fails
    """
    pass

Documentation Standards

Docstring Format

We use Google-style docstrings:

def authenticate(self, flow_type: str = 'client_credentials') -> bool:
    """Authenticate with Spotify API.

    This method handles both client credentials and OAuth flows.
    It automatically refreshes expired tokens when refresh_token
    is available.

    Args:
        flow_type: Authentication flow type.
            - 'client_credentials': Server-to-server auth
            - 'authorization_code': User-specific auth with OAuth

    Returns:
        True if authentication successful, False otherwise

    Raises:
        SpotifyAuthError: If authentication fails and cannot recover

    Example:
        >>> client = SpotifyAPIClient(config)
        >>> client.authenticate(flow_type='client_credentials')
        True
    """
    pass

Inline Comments

Use comments to explain "why", not "what":

# Good: Explains reasoning
# Wait for rate limit to reset before retrying
# Respecting Retry-After header prevents further 429 errors
retry_after = int(response.headers.get('Retry-After', 5))
time.sleep(retry_after)

# Bad: States the obvious
# Get retry after header
retry_after = int(response.headers.get('Retry-After', 5))

Error Handling

Custom Exceptions

# libs/exceptions.py (if not exists, create)
class SpotifyFrameworkError(Exception):
    """Base exception for framework errors."""
    pass

class SpotifyAPIError(SpotifyFrameworkError):
    """API request failed."""
    def __init__(self, message: str, status_code: int = None):
        self.status_code = status_code
        super().__init__(message)

class SpotifyAuthError(SpotifyFrameworkError):
    """Authentication failed."""
    pass

class SpotifyConfigError(SpotifyFrameworkError):
    """Configuration invalid or missing."""
    pass

Error Handling Pattern

def make_api_request(self):
    """Standard error handling pattern."""
    try:
        response = requests.get(url, timeout=30)
        response.raise_for_status()
        return response.json()

    except requests.exceptions.Timeout:
        logger.error(f"Request timeout after 30s: {url}")
        raise SpotifyAPIError("Request timed out")

    except requests.exceptions.HTTPError as e:
        if e.response.status_code == 401:
            raise SpotifyAuthError("Invalid or expired token")
        elif e.response.status_code == 429:
            retry_after = e.response.headers.get('Retry-After', 60)
            raise SpotifyAPIError(f"Rate limited. Retry after {retry_after}s")
        else:
            raise SpotifyAPIError(f"HTTP {e.response.status_code}: {e}")

    except Exception as e:
        logger.exception("Unexpected error in API request")
        raise SpotifyAPIError(f"Unexpected error: {e}")

Logging

Using the Logger

from utils.logger import get_logger

logger = get_logger(__name__)  # Use module name

# Log levels
logger.debug("Token validation successful")        # Verbose, development
logger.info("Authenticating with client_credentials")  # General info
logger.warning("Token expires in 60s, consider refresh")  # Warnings
logger.error("Authentication failed: invalid credentials")  # Errors
logger.critical("Cannot load configuration, exiting")  # Critical failures

Log Message Guidelines

# Good: Structured, actionable information
logger.info(
    f"API request: {method} {endpoint} "
    f"(status={response.status_code}, duration={duration:.2f}s)"
)

# Good: Include context for debugging
logger.error(
    f"Failed to retrieve album: {album_id}",
    extra={'user_id': user_id, 'flow': auth_flow}
)

# Bad: Not enough context
logger.info("Request sent")

# Bad: Too verbose, clutters logs
logger.debug(f"Response: {json.dumps(response.json(), indent=2)}")

Testing Strategy

Test Organization

tests/
β”œβ”€β”€ functional/          # Public API tests (Client Credentials)
β”‚   β”œβ”€β”€ albums/
β”‚   β”‚   β”œβ”€β”€ test_album_retrieval.py
β”‚   β”‚   └── test_multiple_albums.py
β”‚   β”œβ”€β”€ search/
β”‚   β”‚   └── test_search_tracks.py
β”‚   └── tracks/
β”‚       └── test_track_retrieval.py
β”‚
β”œβ”€β”€ integration/         # User-specific tests (OAuth)
β”‚   β”œβ”€β”€ test_user_playlists.py
β”‚   └── test_user_profile.py
β”‚
└── e2e/                # Complete workflows (OAuth)
    └── test_playlist_workflow.py

Test Markers

Markers control test execution and authentication:

# pytest.ini
[pytest]
markers =
    functional: Functional tests for individual endpoints
    integration: Integration tests for workflows
    e2e: End-to-end tests for complete scenarios
    requires_user_auth: Tests requiring OAuth authentication
    client_credentials_only: Tests using client credentials
    smoke: Quick smoke tests
    slow: Long-running tests

Usage Examples

# Run all functional tests
pytest -m functional -v

# Run tests that need OAuth
pytest -m requires_user_auth -v

# Run smoke tests only
pytest -m smoke -v

# Combine markers (AND)
pytest -m "functional and not slow" -v

# Combine markers (OR)
pytest -m "integration or e2e" -v

# Exclude marker
pytest -m "not slow" -v

Fixture Scopes

Understanding fixture scopes is critical for performance:

# Session scope: Loaded once per test session
@pytest.fixture(scope="session")
def test_config():
    """Configuration loaded once and reused."""
    return load_config()

# Function scope: Created for each test
@pytest.fixture(scope="function")
def spotify_client(request, test_config):
    """Fresh client per test for isolation."""
    # Authentication happens for EVERY test
    # Good for isolation, slower execution
    return SpotifyAPIClient(test_config)

# Module scope: Created once per test file
@pytest.fixture(scope="module")
def shared_test_data():
    """Loaded once per test module."""
    return load_test_data("albums.json")

Performance Note: Current implementation uses function-scoped spotify_client, causing ~30-95s per test due to repeated authentication. Consider session-scoped fixtures for read-only tests.

Test Data Management

Static Test Data

# TestData/test_albums.json
{
  "albums": [
    {
      "id": "4LH4d3cOWNNsVw41Gqt2kv",
      "name": "The Dark Side of the Moon",
      "artist": "Pink Floyd"
    }
  ]
}

# Load in tests
def test_get_album(spotify_client):
    test_data = FileHelper.load_json_file("TestData/test_albums.json")
    album_id = test_data['albums'][0]['id']
    album = spotify_client.albums.get_album(album_id)
    assert album['name'] == test_data['albums'][0]['name']

Dynamic Test Data

@pytest.fixture
def test_user_data(spotify_client):
    """Get real authenticated user data."""
    if spotify_client.auth_flow == 'authorization_code':
        profile = spotify_client.users.get_current_user_profile()
        return {'user_id': profile['id']}
    return {'user_id': None}

Configuration Management

Configuration Hierarchy

The ConfigLoader implements a three-layer configuration system:

# Layer 1: Base configuration (config/base.yaml)
api:
  base_url: "https://api.spotify.com/v1"
  timeout: 30
  max_retries: 3

# Layer 2: Environment configuration (config/qa.yaml)
api:
  timeout: 45  # Override for QA

# Layer 3: Environment variables
SPOTIFY_API__TIMEOUT=60  # Highest priority

Environment Variable Pattern

Format: SPOTIFY_<SECTION>__<KEY>

# Set API base URL
SPOTIFY_API__BASE_URL=https://api.spotify.com/v2

# Set authentication credentials
SPOTIFY_AUTH__CLIENT_ID=your_client_id
SPOTIFY_AUTH__CLIENT_SECRET=your_client_secret

# Set nested values (double underscore for nesting)
SPOTIFY_TEST__CLEANUP_AFTER_TESTS=false

Configuration Files

base.yaml

# config/base.yaml - Common settings
api:
  base_url: "https://api.spotify.com/v1"
  timeout: 30
  max_retries: 3
  retry_backoff_factor: 1.0

auth:
  client_id: ${SPOTIFY_CLIENT_ID}
  client_secret: ${SPOTIFY_CLIENT_SECRET}
  redirect_uri: ${SPOTIFY_REDIRECT_URI}

test:
  cleanup_after_tests: true
  parallel_execution: false

logging:
  level: INFO
  format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"

Environment-Specific (qa.yaml, staging.yaml, prod.yaml)

# config/qa.yaml - QA overrides
api:
  timeout: 45  # Longer timeout in QA
  max_retries: 5  # More retries in QA

logging:
  level: DEBUG  # Verbose logging in QA

test:
  cleanup_after_tests: true
# config/prod.yaml - Production overrides
api:
  timeout: 20  # Shorter timeout in prod
  max_retries: 1  # Fail fast in prod

logging:
  level: WARNING  # Minimal logging in prod

test:
  cleanup_after_tests: false  # Keep data for investigation

Switching Environments

# Method 1: Set in .env file
echo "ENVIRONMENT=staging" >> .env
pytest tests/

# Method 2: Environment variable
ENVIRONMENT=prod pytest tests/

# Method 3: In code
config = load_config(environment='staging')

Authentication System

Overview

The framework implements a sophisticated dual authentication system that seamlessly handles both Client Credentials and OAuth flows.

Authentication Flows

Client Credentials Flow

Use Case: Server-to-server authentication, public API access

Flow:

  1. Send client_id + client_secret to token endpoint
  2. Receive access_token (valid for ~3600s)
  3. Use token for API requests
  4. Get new token when expired (no refresh_token)

Implementation:

# Automatic in conftest.py
@pytest.fixture
def spotify_client(test_config):
    # Default flow if no @pytest.mark.requires_user_auth
    client = SpotifyAPIClient(test_config, auth_flow='client_credentials')
    client.authenticate()
    return client

OAuth Authorization Code Flow

Use Case: User-specific operations requiring permissions

Flow:

  1. Redirect user to Spotify authorization page
  2. User grants permissions
  3. Receive authorization code
  4. Exchange code for access_token + refresh_token
  5. Use access_token for API requests
  6. Refresh token when expired (automatic)

Implementation:

# Triggered by marker
@pytest.mark.requires_user_auth
def test_user_profile(spotify_client):
    # Automatically uses authorization_code flow
    profile = spotify_client.users.get_current_user_profile()
    assert 'id' in profile

Authentication Bridge Architecture

Problem Solved: How to integrate external OAuth token generation with framework authentication?

Solution: Authentication Bridge Pattern

# libs/auth.py:397-439
class SpotifyAuthManager:
    def _load_tokens_from_advanced_auth(self):
        """Bridge to load OAuth tokens from external storage.

        This method reads tokens stored by manual_token_setup.py
        or oauth_helper.py (using AdvancedSpotifyAuth) and loads
        them into SpotifyOAuthHandler for unified token management.
        """
        # Find token file in .auth_cache/
        token_files = Path('.auth_cache').glob('tokens_*.json')

        for token_file in token_files:
            token_data = json.loads(token_file.read_text())

            # Extract tokens
            tokens = token_data.get('tokens', {})

            # Transfer to OAuth handler
            self.oauth_handler._store_token_data(tokens)

            logger.info("Loaded OAuth tokens from advanced_auth")
            return True

        return False

Flow Diagram:

Manual Token Setup / OAuth Helper
         ↓
    advanced_auth.py
         ↓
  .auth_cache/tokens_{hash}.json
         ↓
  SpotifyAuthManager._load_tokens_from_advanced_auth()
         ↓
  SpotifyOAuthHandler (unified token management)
         ↓
    Tests (transparent token access)

Token Management

Automatic Token Refresh

# libs/auth.py - SpotifyOAuthHandler
def get_valid_token(self) -> str:
    """Get valid access token, refreshing if necessary."""

    # Step 1: Check current token
    if self.is_token_valid():
        return self.access_token

    # Step 2: Token expired, attempt refresh
    if self.refresh_token:
        logger.info("Token expired, refreshing...")
        self.refresh_access_token()
        return self.access_token

    # Step 3: No refresh token available
    raise SpotifyAuthError(
        "Token expired and no refresh_token available. "
        "Run manual_token_setup.py to re-authenticate."
    )

def is_token_valid(self) -> bool:
    """Check if token is still valid (with 60s buffer)."""
    if not self.access_token or not self.token_expires_at:
        return False

    # 60-second buffer prevents mid-request expiration
    return time.time() < (self.token_expires_at - 60)

Token Storage Format

{
  "tokens": {
    "access_token": "BQC8X9y...",
    "refresh_token": "AQBz...",
    "token_type": "Bearer",
    "expires_in": 3600,
    "token_expires_at": 1728750000.0
  },
  "metadata": {
    "client_id": "7b5dcaac78ea4358b7b9a6f982551df3",
    "scopes": ["user-read-private", "user-read-email"],
    "created_at": 1728746400.0
  },
  "stored_at": 1728746400.0,
  "client_id_hash": "e4f2a1b..."
}

Setting Up OAuth Tokens

Method 1: Manual Token Setup

python manual_token_setup.py

Interactive prompts:

Enter access_token: [paste token]
Enter refresh_token (optional): [paste token]
Enter token_type (default: Bearer): [press enter]
Enter expires_in seconds (default: 3600): [press enter]

Token sources:

  • Postman OAuth 2.0 flow
  • Spotify Developer Dashboard
  • curl command with OAuth flow
  • Any OAuth 2.0 client

Method 2: OAuth Helper (Browser-based)

python oauth_helper.py

Automated flow:

  1. Opens browser to Spotify authorization page
  2. User logs in and grants permissions
  3. Callback handled by local server (port 8080)
  4. Tokens automatically saved to .auth_cache/

Writing Tests

Test Template: Functional Test (Client Credentials)

"""
tests/functional/albums/test_album_operations.py

Functional tests for album retrieval operations.
These tests use client_credentials flow (no OAuth required).
"""
import pytest
from typing import Dict, Any


class TestAlbumRetrieval:
    """Test suite for album retrieval operations."""

    def test_get_single_album_success(self, spotify_client):
        """Verify successful retrieval of a single album.

        Test validates:
        - Album can be retrieved by valid ID
        - Response contains all required fields
        - Data types are correct
        """
        # Arrange
        album_id = "4LH4d3cOWNNsVw41Gqt2kv"  # Pink Floyd - Dark Side

        # Act
        album = spotify_client.albums.get_album(album_id)

        # Assert
        assert album['id'] == album_id
        assert 'name' in album
        assert 'artists' in album
        assert isinstance(album['artists'], list)
        assert len(album['artists']) > 0

    def test_get_album_invalid_id_returns_404(self, spotify_client):
        """Verify 404 error for invalid album ID.

        Negative test to ensure proper error handling
        for non-existent resources.
        """
        # Arrange
        invalid_id = "INVALID_ID_12345"

        # Act & Assert
        with pytest.raises(SpotifyAPIError) as exc_info:
            spotify_client.albums.get_album(invalid_id)

        assert exc_info.value.status_code == 404

    @pytest.mark.parametrize("album_id,expected_name", [
        ("4LH4d3cOWNNsVw41Gqt2kv", "The Dark Side of the Moon"),
        ("1DFixLWuPkv3KT3TnV35m3", "Thriller"),
        ("6DEjYFkNZh67HP7R9PSZvv", "Abbey Road"),
    ])
    def test_get_album_multiple_ids(
        self,
        spotify_client,
        album_id: str,
        expected_name: str
    ):
        """Verify album retrieval for multiple known albums.

        Parametrized test to validate consistent behavior
        across different albums.
        """
        # Act
        album = spotify_client.albums.get_album(album_id)

        # Assert
        assert album['name'] == expected_name
        assert album['type'] == 'album'

Test Template: Integration Test (OAuth)

"""
tests/integration/test_user_playlists.py

Integration tests for user playlist operations.
These tests require OAuth authentication with user context.
"""
import pytest
import time


class TestUserPlaylists:
    """Test suite for user playlist operations."""

    @pytest.mark.requires_user_auth  # Triggers OAuth flow
    def test_create_playlist_success(self, spotify_client, test_user_data):
        """Verify user can create a new playlist.

        Prerequisites:
        - Valid OAuth token with playlist-modify-public scope
        - Authenticated user context

        Test validates:
        - Playlist creation succeeds
        - Created playlist has correct properties
        - Playlist is owned by authenticated user
        """
        # Arrange
        user_id = test_user_data['user_id']
        playlist_name = f"Test_Playlist_{int(time.time())}"

        # Act
        playlist = spotify_client.playlists.create_playlist(
            user_id=user_id,
            name=playlist_name,
            public=False,
            description="Created by automated test"
        )

        # Assert
        assert playlist['name'] == playlist_name
        assert playlist['public'] is False
        assert playlist['owner']['id'] == user_id

        # Cleanup
        spotify_client.playlists.unfollow_playlist(playlist['id'])

    @pytest.mark.requires_user_auth
    def test_get_user_playlists(self, spotify_client, test_user_data):
        """Verify user can retrieve their playlists.

        Test validates:
        - User playlists can be retrieved
        - Response contains pagination metadata
        - Each playlist has required fields
        """
        # Arrange
        user_id = test_user_data['user_id']

        # Act
        response = spotify_client.playlists.get_user_playlists(
            user_id=user_id,
            limit=20
        )

        # Assert
        assert 'items' in response
        assert 'total' in response
        assert 'limit' in response
        assert response['limit'] == 20

        # Validate each playlist
        for playlist in response['items']:
            assert 'id' in playlist
            assert 'name' in playlist
            assert 'owner' in playlist

Test Template: E2E Test

"""
tests/e2e/test_playlist_workflow.py

End-to-end test for complete playlist management workflow.
"""
import pytest
import time


@pytest.mark.requires_user_auth
class TestPlaylistWorkflow:
    """Complete workflow tests for playlist operations."""

    def test_complete_playlist_lifecycle(self, spotify_client, test_user_data):
        """Test complete playlist lifecycle: create β†’ update β†’ add tracks β†’ delete.

        This E2E test validates the entire user workflow for
        managing playlists, ensuring all operations work together.
        """
        user_id = test_user_data['user_id']

        # Step 1: Create playlist
        playlist_name = f"E2E_Test_{int(time.time())}"
        playlist = spotify_client.playlists.create_playlist(
            user_id=user_id,
            name=playlist_name,
            public=False
        )
        playlist_id = playlist['id']
        assert playlist['name'] == playlist_name

        # Step 2: Update playlist details
        updated_name = f"{playlist_name}_Updated"
        spotify_client.playlists.update_playlist(
            playlist_id=playlist_id,
            name=updated_name,
            description="Updated description"
        )

        updated_playlist = spotify_client.playlists.get_playlist(playlist_id)
        assert updated_playlist['name'] == updated_name
        assert updated_playlist['description'] == "Updated description"

        # Step 3: Add tracks to playlist
        track_uris = [
            "spotify:track:3n3Ppam7vgaVa1iaRUc9Lp",  # Example track
            "spotify:track:0VjIjW4GlUZAMYd2vXMi3b"
        ]
        spotify_client.playlists.add_tracks(
            playlist_id=playlist_id,
            track_uris=track_uris
        )

        # Verify tracks added
        tracks = spotify_client.playlists.get_playlist_tracks(playlist_id)
        assert len(tracks['items']) == 2

        # Step 4: Delete playlist (cleanup)
        spotify_client.playlists.unfollow_playlist(playlist_id)

        # Verify deletion (should return 404)
        with pytest.raises(SpotifyAPIError) as exc:
            spotify_client.playlists.get_playlist(playlist_id)
        assert exc.value.status_code == 404

Fixture Examples

Custom Fixture for Test Data

# conftest.py or test file
@pytest.fixture
def sample_album_ids():
    """Provide sample album IDs for testing."""
    return [
        "4LH4d3cOWNNsVw41Gqt2kv",  # Pink Floyd
        "1DFixLWuPkv3KT3TnV35m3",  # Michael Jackson
        "6DEjYFkNZh67HP7R9PSZvv",  # The Beatles
    ]

def test_get_multiple_albums(spotify_client, sample_album_ids):
    """Test using fixture data."""
    albums = spotify_client.albums.get_multiple_albums(sample_album_ids)
    assert len(albums['albums']) == 3

Cleanup Fixture

@pytest.fixture
def cleanup_playlists(spotify_client):
    """Automatically cleanup created playlists after test."""
    created_playlists = []

    # Return function to register playlists for cleanup
    def register_playlist(playlist_id: str):
        created_playlists.append(playlist_id)

    yield register_playlist

    # Cleanup after test
    for playlist_id in created_playlists:
        try:
            spotify_client.playlists.unfollow_playlist(playlist_id)
            print(f"Cleaned up playlist: {playlist_id}")
        except Exception as e:
            print(f"Cleanup failed: {e}")

# Usage
@pytest.mark.requires_user_auth
def test_with_cleanup(spotify_client, test_user_data, cleanup_playlists):
    playlist = spotify_client.playlists.create_playlist(
        test_user_data['user_id'], "Test"
    )
    cleanup_playlists(playlist['id'])  # Register for cleanup

    # Test logic...
    # Playlist automatically deleted after test

Troubleshooting Guide

Common Issues

1. "No valid OAuth tokens found"

Symptom: Integration/E2E tests fail with authentication error

Cause: Missing OAuth tokens for authorization_code flow

Solution:

# Run manual token setup
python manual_token_setup.py

# Or use OAuth helper
python oauth_helper.py

Verify: Check .auth_cache/ directory has tokens_*.json file with refresh_token field


2. "Token expired and no refresh_token available"

Symptom: Tests fail after some time with token expiration error

Cause: Token file missing refresh_token or token_expires_at field

Solution:

# Re-run token setup with complete token data
python manual_token_setup.py

# Ensure you provide:
# - access_token (required)
# - refresh_token (required for auto-refresh)
# - expires_in (default: 3600)

Verify:

# Check token file format
import json
with open('.auth_cache/tokens_<hash>.json') as f:
    data = json.load(f)
    assert 'refresh_token' in data['tokens']
    assert 'token_expires_at' in data['tokens']

3. "Configuration file not found"

Symptom: ConfigurationError: Environment config file not found: config/qa.yaml

Cause: Missing environment configuration file

Solution:

# Check ENVIRONMENT setting
echo $ENVIRONMENT  # Should be qa, staging, or prod

# Verify config file exists
ls config/qa.yaml

# Create from template if missing
cp config/base.yaml config/qa.yaml

4. Tests are very slow (30-95s per test)

Symptom: Each test takes 30+ seconds to run

Cause: Per-test authentication overhead (function-scoped fixture)

Temporary Workaround:

# Run tests in parallel
pytest tests/functional/ -n 4  # 4 workers

Permanent Solution (requires code change):

# conftest.py - Add session-scoped fixture
@pytest.fixture(scope="session")
def shared_client(test_config):
    """Shared client for read-only tests."""
    client = SpotifyAPIClient(test_config, auth_flow='client_credentials')
    client.authenticate()
    return client

# Use in read-only tests
def test_get_album(shared_client):
    album = shared_client.albums.get_album("album_id")

5. "429 Too Many Requests" errors

Symptom: Tests fail with rate limiting errors

Cause: Exceeded Spotify API rate limits

Solution:

# Run tests with delay
pytest tests/ --dist loadscope -n 2  # Reduce workers

# Add delay between requests (in code)
import time
time.sleep(1)  # Add 1s delay between tests

Framework Handles This: Automatic retry with Retry-After header


6. Windows encoding errors (Unicode characters)

Symptom: UnicodeEncodeError: 'charmap' codec can't encode character

Cause: Windows console doesn't support Unicode by default

Solution: Already fixed in verify_config.py with UTF-8 wrapper

For other scripts:

import sys
import io

if sys.platform == 'win32':
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
    sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')

Debugging Techniques

Enable Debug Logging

# Method 1: Set log level in config
# config/qa.yaml
logging:
  level: DEBUG

# Method 2: Environment variable
SPOTIFY_LOG_LEVEL=DEBUG pytest tests/

# Method 3: Pytest flag
pytest tests/ --log-cli-level=DEBUG

Inspect Token Data

# Quick script to inspect tokens
import json
from pathlib import Path

token_files = Path('.auth_cache').glob('tokens_*.json')
for token_file in token_files:
    print(f"\n{token_file.name}:")
    data = json.loads(token_file.read_text())
    print(f"  Access Token: {'*' * 20} (present)")
    print(f"  Refresh Token: {data['tokens'].get('refresh_token', 'MISSING')}")
    print(f"  Expires At: {data['tokens'].get('token_expires_at', 'MISSING')}")

Test Single Endpoint

# Quick test script
from libs.spotify_client import SpotifyAPIClient
from utils.config_loader import load_config

config = load_config('qa')
client = SpotifyAPIClient(config, auth_flow='client_credentials')
client.authenticate()

# Test specific endpoint
album = client.albums.get_album("4LH4d3cOWNNsVw41Gqt2kv")
print(album['name'])  # Should print album name

Best Practices

Testing Best Practices

  1. Use Descriptive Test Names

    # Good
    def test_get_album_with_valid_id_returns_album_data(spotify_client):
        pass
    
    # Bad
    def test_album(spotify_client):
        pass
  2. Follow AAA Pattern (Arrange, Act, Assert)

    def test_search_tracks(spotify_client):
        # Arrange - Setup test data
        query = "Pink Floyd"
        search_types = ["track"]
    
        # Act - Execute the operation
        results = spotify_client.search.search(query, search_types)
    
        # Assert - Verify the outcome
        assert 'tracks' in results
        assert len(results['tracks']['items']) > 0
  3. Test One Thing Per Test

    # Good - Single responsibility
    def test_album_has_id(spotify_client):
        album = spotify_client.albums.get_album("album_id")
        assert 'id' in album
    
    def test_album_has_name(spotify_client):
        album = spotify_client.albums.get_album("album_id")
        assert 'name' in album
    
    # Bad - Multiple assertions for different concerns
    def test_album(spotify_client):
        album = spotify_client.albums.get_album("album_id")
        assert 'id' in album
        assert 'name' in album
        assert 'type' in album  # If this fails, we don't know about the others
  4. Use Fixtures for Common Setup

    @pytest.fixture
    def created_playlist(spotify_client, test_user_data):
        """Create and return a test playlist."""
        playlist = spotify_client.playlists.create_playlist(
            test_user_data['user_id'], "Test Playlist"
        )
        yield playlist
        # Cleanup
        spotify_client.playlists.unfollow_playlist(playlist['id'])
  5. Parametrize for Multiple Scenarios

    @pytest.mark.parametrize("status_code,exception_type", [
        (401, SpotifyAuthError),
        (403, SpotifyAuthError),
        (404, SpotifyAPIError),
        (500, SpotifyAPIError),
    ])
    def test_error_handling(spotify_client, mocker, status_code, exception_type):
        mocker.patch('requests.request', return_value=Mock(status_code=status_code))
        with pytest.raises(exception_type):
            spotify_client.albums.get_album("test_id")

Code Organization Best Practices

  1. Keep endpoint clients focused

    # Good - AlbumsClient only handles album operations
    class AlbumsClient:
        def get_album(self, album_id: str) -> Dict:
            pass
    
        def get_multiple_albums(self, album_ids: List[str]) -> Dict:
            pass
    
    # Bad - Mixed responsibilities
    class AlbumsClient:
        def get_album(self, album_id: str) -> Dict:
            pass
    
        def search_albums(self, query: str) -> Dict:  # Should be in SearchClient
            pass
  2. Use constants for magic strings

    # constants.py
    class SpotifyEndpoints:
        ALBUMS = "/albums"
        TRACKS = "/tracks"
        SEARCH = "/search"
    
    class HTTPMethods:
        GET = "GET"
        POST = "POST"
        PUT = "PUT"
        DELETE = "DELETE"
    
    # Usage
    response = self.make_request(HTTPMethods.GET, SpotifyEndpoints.ALBUMS)
  3. Keep configuration DRY

    # Good - Base config with environment overrides
    # base.yaml - Common settings
    api:
        timeout: 30
    
    # qa.yaml - QA-specific only
    api:
        timeout: 45
    
    # Bad - Duplicate everything
    # qa.yaml
    api:
        timeout: 45
        max_retries: 3  # Duplicated from base
        retry_backoff_factor: 1.0  # Duplicated from base

Security Best Practices

  1. Never commit credentials

    # Verify .gitignore
    .env
    .auth_cache/
    *.key
    *.pem
    credentials.json
  2. Use environment-specific credentials

    # QA credentials
    SPOTIFY_CLIENT_ID=qa_client_id
    
    # Prod credentials (different)
    SPOTIFY_CLIENT_ID=prod_client_id
  3. Mask sensitive data in logs

    # Good
    logger.info(f"Authenticated with client_id: {client_id[:8]}***")
    
    # Bad
    logger.info(f"Authenticated with token: {access_token}")
  4. Set file permissions for token cache

    # Restrict .auth_cache permissions
    import os
    from pathlib import Path
    
    cache_dir = Path(".auth_cache")
    cache_dir.mkdir(mode=0o700, exist_ok=True)  # Owner-only access
    
    token_file = cache_dir / "tokens.json"
    os.chmod(token_file, 0o600)  # Read/write by owner only

Contributing

Development Workflow

  1. Create feature branch

    git checkout -b feature/add-artist-endpoint
  2. Make changes

    • Write code following style guide
    • Add tests for new functionality
    • Update documentation
  3. Run tests locally

    # Run all tests
    pytest tests/ -v
    
    # Run specific tests
    pytest tests/functional/artists/ -v
    
    # Check code quality
    black .
    flake8 .
    mypy libs/
  4. Commit changes

    git add .
    git commit -m "feat: add artist endpoint client"
  5. Push and create PR

    git push origin feature/add-artist-endpoint
    # Create pull request on GitHub

Commit Message Format

Follow conventional commits format:

<type>(<scope>): <subject>

<body>

<footer>

Types:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation changes
  • style: Code style changes (formatting, etc.)
  • refactor: Code refactoring
  • test: Adding or updating tests
  • chore: Maintenance tasks

Examples:

feat(albums): add support for album tracks pagination

fix(auth): correct token expiry calculation
Updated token validation to include 60-second buffer

docs(readme): update installation instructions

test(search): add negative tests for invalid queries

refactor(config): simplify configuration loading logic

Code Review Checklist

For Reviewers:

  • Code follows style guide (PEP 8)
  • All functions have type hints
  • Tests added for new functionality
  • Tests pass locally
  • Documentation updated (if needed)
  • No hardcoded credentials or secrets
  • Error handling is comprehensive
  • Logging is appropriate (not too verbose)

For Contributors:

  • Ran black . for formatting
  • Ran flake8 . with no errors
  • All tests pass: pytest tests/ -v
  • Added docstrings to new functions
  • Updated CHANGELOG.md (if applicable)
  • No merge conflicts with main branch

Adding New Endpoint Clients

Step 1: Create endpoint client file

# libs/endpoints/artists.py
from libs.base_client import SpotifyBaseClient
from typing import Dict, List, Optional

class ArtistsClient:
    """Client for Spotify Artists API endpoints."""

    def __init__(self, base_client: SpotifyBaseClient):
        """Initialize Artists client.

        Args:
            base_client: Base client for HTTP operations
        """
        self.base_client = base_client

    def get_artist(self, artist_id: str) -> Dict:
        """Get Spotify artist by ID.

        Args:
            artist_id: Spotify artist ID

        Returns:
            Dict containing artist data

        Raises:
            SpotifyAPIError: If request fails
        """
        endpoint = f"/artists/{artist_id}"
        return self.base_client.make_request("GET", endpoint)

    def get_artist_top_tracks(
        self,
        artist_id: str,
        market: str = "US"
    ) -> Dict:
        """Get artist's top tracks.

        Args:
            artist_id: Spotify artist ID
            market: Market code (e.g., "US", "GB")

        Returns:
            Dict containing top tracks

        Raises:
            SpotifyAPIError: If request fails
        """
        endpoint = f"/artists/{artist_id}/top-tracks"
        params = {"market": market}
        return self.base_client.make_request("GET", endpoint, params=params)

Step 2: Add to SpotifyAPIClient

# libs/spotify_client.py
from libs.endpoints.artists import ArtistsClient

class SpotifyAPIClient:
    def __init__(self, config, auth_flow='client_credentials'):
        # ... existing initialization ...

        # Add artist client
        self.artists = ArtistsClient(self.base_client)

Step 3: Create tests

# tests/functional/artists/test_artist_retrieval.py
import pytest

class TestArtistRetrieval:
    """Test suite for artist retrieval operations."""

    def test_get_artist_by_id(self, spotify_client):
        """Verify artist can be retrieved by ID."""
        artist_id = "0TnOYISbd1XYRBk9myaseg"  # Pitbull
        artist = spotify_client.artists.get_artist(artist_id)

        assert artist['id'] == artist_id
        assert 'name' in artist
        assert artist['type'] == 'artist'

    def test_get_artist_top_tracks(self, spotify_client):
        """Verify artist top tracks retrieval."""
        artist_id = "0TnOYISbd1XYRBk9myaseg"
        tracks = spotify_client.artists.get_artist_top_tracks(artist_id)

        assert 'tracks' in tracks
        assert len(tracks['tracks']) > 0

Step 4: Update documentation

# README.md 

### Artist Operations

```python
# Get artist
artist = client.artists.get_artist("artist_id")

# Get artist top tracks
top_tracks = client.artists.get_artist_top_tracks("artist_id", market="US")

### Pre-commit Hooks (Optional)

```bash
# Install pre-commit
pip install pre-commit

# Create .pre-commit-config.yaml
repos:
  - repo: https://github.com/psf/black
    rev: 23.12.1
    hooks:
      - id: black

  - repo: https://github.com/pycqa/flake8
    rev: 7.0.0
    hooks:
      - id: flake8
        args: ['--max-line-length=100']

# Install hooks
pre-commit install

File Organization & Root Folder Guide

Overview

This section explains the purpose of all files and folders in the root directory, which files are essential vs. optional, and what can be safely removed.


Root Folder Files - Detailed Guide

πŸ“‹ Essential Configuration Files (DO NOT REMOVE)

File Purpose Why Essential
.env Environment variables for credentials Contains your Spotify API credentials (client_id, client_secret). Required for authentication.
.env.example Template for .env file Used to create .env file. Keep for reference when setting up on new machines.
pytest.ini Pytest configuration Defines test markers, Python paths, and pytest behavior. Required for tests to run correctly.
requirements.txt Python dependencies Lists all required packages. Essential for installation (pip install -r requirements.txt).
conftest.py Pytest fixtures Contains global fixtures (spotify_client, test_config, etc.). Tests won't run without this.

πŸ”§ Essential Scripts (KEEP - Frequently Used)

Script Purpose When to Use
verify_config.py Configuration verification tool Run after setup or when troubleshooting. Checks all configuration, dependencies, and authentication.
manual_token_setup.py Manual OAuth token entry Use when you have OAuth tokens from Postman/other sources and want to manually enter them.
oauth_helper.py Browser-based OAuth setup Use for automatic OAuth token generation via browser authorization flow.
setup.py Python package setup Used for installing framework as package (pip install -e .). Keep for development setup.

πŸš€ Test Execution Scripts (OPTIONAL - Convenience scripts)

Script Purpose Recommendation
run_tests.bat Windows batch script for running tests Optional - Convenience script. You can run pytest directly instead.
run_tests.ps1 PowerShell script for running tests Optional - Convenience script. You can run pytest directly instead.

πŸ“ Essential Directories (DO NOT REMOVE)

Directory Purpose Why Essential
config/ Environment configurations (qa.yaml, auth.yaml, base.yaml) Required for multi-environment support and configuration loading.
libs/ Core framework code (auth, clients, endpoints) Contains all framework implementation. Cannot run tests without this.
tests/ Test suites (functional, integration, e2e) Contains all test cases. Primary purpose of the framework.
utils/ Utility modules (config_loader, logger, helpers) Shared utilities used throughout framework.
TestData/ Test data files (JSON files with test data) Test data for data-driven tests.

πŸ“ Generated/Runtime Directories (Auto-created, not in git)

Directory Purpose Can Remove?
.auth_cache/ OAuth token storage Yes, but will lose saved tokens. Tokens will need to be re-generated.
logs/ Test execution logs Yes - Auto-regenerated on test runs.
reports/ Test reports (HTML, Allure, JUnit) Yes - Auto-regenerated on test runs.
.pytest_cache/ Pytest cache for faster reruns Yes - Auto-regenerated by pytest.
pycache/ Python bytecode cache Yes - Auto-regenerated by Python.
venv/ Python virtual environment Yes, but you'll need to recreate it (python -m venv venv).

πŸ› οΈ Tool-Specific Directories (Safe to remove)

Directory Purpose
.idea/ PyCharm/IntelliJ IDE settings

πŸ”„ Other Configuration Files

File Purpose Recommendation
.gitignore Git ignore rules Keep if using git. Prevents committing sensitive files (.env, .auth_cache, etc.).
pyproject.toml Python project metadata and build config Keep for modern Python packaging. Used by tools like black, pytest, etc.
MANIFEST.in Package manifest for distribution Keep if you plan to distribute framework as package. Otherwise optional.

Essential "Must Keep" Files

Core Configuration:

  • .env
  • .env.example
  • pytest.ini
  • requirements.txt
  • conftest.py
  • pyproject.toml

Core Scripts:

  • verify_config.py
  • manual_token_setup.py
  • oauth_helper.py
  • setup.py

Core Directories:

  • config/
  • libs/
  • tests/
  • utils/
  • TestData/

Primary Documentation:

  • README.md (this file)
  • WORKFLOW_DOCUMENTATION.md (interview prep)
  • INTERVIEW_QUESTIONS.md (interview prep)
  • FRAMEWORK_EVALUATION.md (stakeholder presentations)

File Organization Commands

Clean Generated Files (Regenerate when needed)

# Clean cache and generated files
rm -rf .pytest_cache/ __pycache__/ logs/ reports/

# These will be auto-regenerated on next test run

Verify Core Files Exist

python verify_config.py

# Should show:
# βœ… All essential files present
# βœ… Configuration valid
# βœ… Framework ready

Minimal Framework Setup

If you want the absolute minimum files to run tests:

Files (13 essential):

.env
pytest.ini
requirements.txt
conftest.py
verify_config.py
manual_token_setup.py
oauth_helper.py
setup.py
pyproject.toml
README.md

Directories (4 essential):

config/
libs/
tests/
utils/

Everything else is optional, documentation, or auto-generated.


Appendix

Useful Commands Reference

# Testing
pytest tests/ -v                              # Run all tests
pytest tests/functional/ -v                   # Functional tests only
pytest -m requires_user_auth -v               # OAuth tests only
pytest -k "test_album" -v                     # Tests matching pattern
pytest tests/ -x                              # Stop on first failure
pytest tests/ --maxfail=3                     # Stop after 3 failures
pytest tests/ -n 4                            # Parallel execution (4 workers)
pytest tests/ --lf                            # Run last failed tests
pytest tests/ --ff                            # Run failures first

# Reporting
pytest tests/ --html=reports/report.html     # HTML report
pytest tests/ --junitxml=reports/junit.xml   # JUnit XML
pytest tests/ --alluredir=reports/allure-results  # Allure
allure serve reports/allure-results          # View Allure report

# Code Quality
black .                                       # Format code
flake8 .                                      # Lint code
mypy libs/ utils/                             # Type checking
pylint libs/ utils/                           # Static analysis

# Configuration
python verify_config.py                       # Verify setup
python manual_token_setup.py                  # Setup OAuth
python oauth_helper.py                        # Browser OAuth

# Environment
export ENVIRONMENT=qa                         # Set environment
echo $ENVIRONMENT                             # Check environment

File Structure Reference

spotify_api_automation_enhanced_2_4/
β”œβ”€β”€ .auth_cache/                 # OAuth token storage (not in git)
β”‚   └── tokens_*.json
β”œβ”€β”€ .github/                     # GitHub workflows (if CI/CD setup)
β”‚   └── workflows/
β”‚       └── tests.yml
β”œβ”€β”€ config/                      # Configuration files
β”‚   β”œβ”€β”€ base.yaml               # Base configuration
β”‚   β”œβ”€β”€ qa.yaml                 # QA environment
β”‚   β”œβ”€β”€ staging.yaml            # Staging environment
β”‚   └── prod.yaml               # Production environment
β”œβ”€β”€ libs/                        # Core framework code
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ auth.py                 # Authentication manager
β”‚   β”œβ”€β”€ advanced_auth.py        # OAuth implementation
β”‚   β”œβ”€β”€ spotify_client.py       # Main API client
β”‚   β”œβ”€β”€ base_client.py          # HTTP client
β”‚   └── endpoints/              # Endpoint clients
β”‚       β”œβ”€β”€ __init__.py
β”‚       β”œβ”€β”€ albums.py
β”‚       β”œβ”€β”€ tracks.py
β”‚       β”œβ”€β”€ playlists.py
β”‚       β”œβ”€β”€ search.py
β”‚       └── users.py
β”œβ”€β”€ tests/                       # Test suites
β”‚   β”œβ”€β”€ conftest.py             # Pytest fixtures
β”‚   β”œβ”€β”€ functional/             # Functional tests
β”‚   β”‚   β”œβ”€β”€ albums/
β”‚   β”‚   β”œβ”€β”€ search/
β”‚   β”‚   └── tracks/
β”‚   β”œβ”€β”€ integration/            # Integration tests
β”‚   β”‚   β”œβ”€β”€ test_user_playlists.py
β”‚   β”‚   └── test_user_profile.py
β”‚   └── e2e/                    # E2E tests
β”‚       └── test_playlist_workflow.py
β”œβ”€β”€ utils/                       # Utility modules
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ config_loader.py        # Configuration management
β”‚   β”œβ”€β”€ logger.py               # Logging setup
β”‚   └── helpers.py              # Helper functions
β”œβ”€β”€ TestData/                    # Test data files
β”‚   β”œβ”€β”€ test_albums.json
β”‚   β”œβ”€β”€ test_playlists.json
β”‚   └── test_users.json
β”œβ”€β”€ reports/                     # Test reports (generated)
β”‚   β”œβ”€β”€ allure-results/
β”‚   └── pytest_report.html
β”œβ”€β”€ logs/                        # Log files (generated)
β”œβ”€β”€ .env                         # Environment variables (not in git)
β”œβ”€β”€ .env.example                # Environment template
β”œβ”€β”€ .gitignore                  # Git ignore rules
β”œβ”€β”€ pytest.ini                  # Pytest configuration
β”œβ”€β”€ requirements.txt            # Python dependencies
β”œβ”€β”€ setup.py                    # Package setup
β”œβ”€β”€ MANIFEST.in                 # Package manifest
β”œβ”€β”€ README.md                   # Project overview
β”œβ”€β”€ WORKFLOW_DOCUMENTATION.md   # Workflow guide
β”œβ”€β”€ INTERVIEW_QUESTIONS.md      # Interview Q&A


Contact & Support

Internal Team Contacts:

Resources:

Issue Tracking:

  • Report bugs: [Issue Tracker URL]
  • Feature requests: [Issue Tracker URL]
  • Documentation improvements: [Wiki/Confluence URL]

Last Updated: 2025-10-12 Framework Version: 2.4.0 Document Version: 1.0

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages